aboutsummaryrefslogblamecommitdiffstats
path: root/bugzillaBugTriage.js
blob: 8c3959009880ad54199638b7e2e219d8ee8d167b (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                                                                                                                                                                   
                   

                                                     
             

                                        
                                   
                                   
 


                                                       

                                                                            

                                                                                  





                                                                                   

                      
                       
                       
                                                         
                                                            
                                       
                                                                             
 
                                                                 
                                                    
                











                                                                                                        

                                          


                                              
 

                                                                               
                                                                        

                                                                                                    
 


                   

                                       
                

                                                                                                        
                                                                     


                                                                            

                                      












                                                                                       
                                  
                               


                              

                              
                               

                                                                        
                                
                                 
                                    
                       


                          

                   

                              
















                                                                         

  
                                             





















                                                                       
                                                                   
                                                     

                                                                      



                                                             

                             

                                        

   





















                                                                          
  











                                                                               
 




                                                         
 




                                                          
 




                                                       
 
                                          
 
                                 
 

                                          
 



                                                           
 



                                                       
 


                                                                                           
 

                                    
 

                               
 

                              
 






















                                        
 



                                                              
 






































































                                                                                              

 




                                                         
 
 
                                                                 

                                                  
   


                                         






                                                                        
 
 
   






                                                                               

                                
                                              










                                                                              
  
 
   





                                                                            
   
                               







                                                                  
  

   






                                                                            

                                                                   
                                














                                                                   

 





                                                                               









                                 
 
 



                                            


                                     






                                     
  
 
                                       



                                                             


   










                                                                               
   


























                                                                    


   










                                                                           
   
































                                                              


   










                                                                           
   



























                                                             


   










                                                                           
   










































                                              


   
          

                                         





                                                    

  
                                                                                       
                   
 
   



















                                                                                       
                                      


                                     
                  
   

                                                      
  
 
   
                                           


                                     

               











                                                                        
  
 

                                  















                                                                                
   


























































                                                                                                                                                         
  
 
                                                 













                                                                                                
  

   

   
                                                      

                           















































                                                                                                   

 
                         


                                                    
   

                 


                                                                                                             

  
   


                                                                              

                                                                        








                                                                  


   
                                            


                                         

               
                                                



                                                                                  
  

   






                                                              
               
   

















                                                                 
  
 
   



                                         
               

                                    
   

                                               
  
 





                                                                                  

  
   
                                                                               
   
   








                                                                           

  





                                                                              
  

   
                          
   

                                           
                                      
                                                               

  

                                                                
   

                                                          
                                         





                                                                                           
  

   
                                                                          






                                                    
               
   
   































































                                                                                            
  
 
   






                                                                        

               








































































                                                                                          


   




















                                                                                
               
   


































                                                                                      
  

   



                                                                              

               


















































                                                                                     


   
                                                                            






                                                                   
               
   
   




































                                                                                                   
  

   

                                                                             
   
                                                 






                                                         
  

   














                                                                                   
   

                                               
                                                              












                                                                                         


   
                                    





                                                                           
   


                                                         
 



                                                                      
 

                                                                             
 





                                                             
 




                                                                                              
 
                                                       


   
                                                                        
   
                                                                       
   



               











                                                                                    
  


                                      


                                     
                  
   
   
                                             

                                                                   
  


                                          















                                                                                 
  

   



                                                   

               
                                                      
















                                                                                    
  


                                
   
                    
   

               
                                                   

                                                             
  

   



                                                                             

                                                            
                                                          






                                                           
  


                                             




                                                                           

                              







                                                                      
  

   
                                           



                                                                              
   



                                                           
  

   






















                                                                              
   

































                                                                                     
  
 

                                                        
                                                     


                                         
  
 

                                                  


                                           

                                            
                                                      


                             








                                                   
  
 
   
                                                  


                                                
               
   







                                                         
  
 
   
                                                                   



                                             

               
                                                  
























































                                                                                        
               
   





                                                                                      
  


                                       







                                                                                

               




































































                                                                                         

                                             


















                                                                                
  

   
   
   





























                                                                               

   
                                                                           




                                                                                

               



























                                                                                       






                                                                                                  































































































                                                                                                                
                      














































































                                                                                             

                                                                    

















































































































































                                                                                                         

 

                                      
  
 
                 
                                                                 
                                        
/*jslint 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 */
/*global jetpack */
// Released under the MIT/X11 license
// http://www.opensource.org/licenses/mit-license.php
"use strict";
jetpack.future.import("pageMods");
jetpack.future.import("storage.simple");
jetpack.future.import("selection");
jetpack.future.import("clipboard");

// http://en.wikipedia.org/wiki/HSL_color_space
// when only the value of S is changed
// stupido!!! the string is value in hex for each color
const RHColor = new Color(158, 41, 43); // RGB 158, 41, 43; HSL 359, 1, 39
const FedoraColor = new Color(0, 40, 103); // RGB 0, 40, 103; HSL 359, 1, 39
const RawhideColor = new Color(0, 119, 0); // or "green", or RGB 0, 119, 0, or HSL
// 120, 0, 23
const RHITColor = new Color(102, 0, 102); // RGB 102, 0, 102; HSL 300, 0, 20
const SalmonPink = new Color(255, 224, 176); // RGB 255, 224, 176; HSL 36, 2, 85
const ReporterColor = new Color(255, 255, 166); // RGB 255, 255, 166; HSL 60, 2, 83
const EmptyLogsColor = new Color(0, 255, 0);
const FullLogsColor = FedoraColor;

var Luminosity = 0.85;
var Desaturated = 0.4;
var TriagedDistro = 13;
var NumberOfFrames = 7;
var XMLRPCurl = "https://bugzilla.redhat.com/xmlrpc.cgi";
var bugURL = "https://bugzilla.redhat.com/show_bug.cgi?id=";
var myStorage = jetpack.storage.simple;
var badMIMEArray = [ "application/octet-stream", "text/x-log", "undefined" ];

// ==============================================================
// TODO https://wiki.mozilla.org/Labs/Jetpack/JEP/24
var manifest = {
	settings : [
			{
				name : "BZpassword",
				type : "password",
				label : "Bugzilla password"
			},
			{
				name : "JSONURL",
				type : "text",
				label : "Configuration file URL",
				"default" : "http://mcepl.fedorapeople.org/scripts/BugZappers_data.json"
			} ]
};
jetpack.future.import("storage.settings");
// if (!jetpack.storage.settings.BZpassword) {
// jetpack.settings.open();
// }

var jsonDataURL = myStorage.JSONURL ? myStorage.JSONURL
		: "http://mcepl.fedorapeople.org/scripts/BugZappers_data.json";
var PCIIDsURL = "http://mcepl.fedorapeople.org/scripts/drm_pciids.json";
var abrtQueryURL = "https://bugzilla.redhat.com/buglist.cgi?"
		+ "cmdtype=dorem&remaction=run&namedcmd=all%20NEW%20abrt%20crashes&sharer_id=74116";

var reqCounter = 0;
var msgStrs = {};

var CommentRe = new RegExp("^\\s*#");
var BlankLineRe = new RegExp("^\\s*$");
// nová řádka
// [    65.631] (--) intel(0): Chipset: "845G"
var ChipsetRE = new RegExp("^\\s*\\[?[ 0-9.]*\\]?\\s*\\(--\\) ([A-Za-z]+)\\([0-9]?\\): Chipset: (.*)$");
var ATIgetIDRE = new RegExp("^.*\\(ChipID = 0x([0-9a-fA-F]+)\\).*$");
var AbrtRE = new RegExp("^\\s*\\[abrt\\]");
var signalHandlerRE = new RegExp("^\\s*#[0-9]*\\s*<signal handler called>");
var frameNoRE = new RegExp("^\\s*#([0-9]*)\\s");

// For identification of graphics card
var manuChipStrs = [ [ "ATI Radeon", "ATI", "1002" ],
		[ "ATI Mobility Radeon", "ATI", "1002" ],
		[ "Intel Corporation", "INTEL", "8086" ], [ "NVIDIA", "NV", "10de" ] ];
var backTranslateManufacturerPCIID = [ {
	regexp : "ATI Technologies Inc",
	addr : "1002"
}, {
	regexp : "Intel Corporation",
	addr : "8086"
}, {
	regexp : "nVidia Corporation",
	addr : "10de"
} ];
// Initialize data from remote URL
var XMLHttpRequestDone = false;
var hashBugzillaName = [];
var hashBugzillaWholeURL = [];
var defAssigneeList = [];
var suspiciousComponents = [];

var signatureFedoraString = "";
// TODO we should have an array SpecialFlags instead of multiple Boolean
// variables
var parseAbrtBacktraces = false;
var queryButtonAvailable = false;
var upstreamButtonAvailable = false;
var logSubmits = false;
var chipIDsGroupings = [];
var AddrArray = [];
var PCI_ID_Array = [];
var topRow = {};
var bottomRow = {};

// Get JSON configuration data
loadText = function(URL, cb_function, what) {
	if (what === undefined) { // missing optional argument
		what = this;
	}

	var req = new XMLHttpRequest();
	req.open("GET", URL, true);
	req.onreadystatechange = function(aEvt) {
		if (req.readyState == 4) {
			if (req.status == 200) {
				cb_function.call(what, req.responseText);
			} else {
				throw "Getting " + URL + "failed!";
			}
		}
	};
	req.send("");
};

loadJSON = function(URL, cb_function, what) {
	if (what === undefined) { // missing optional argument
		what = this;
	}

	loadText(URL, function(text) {
		var data = JSON.parse(text);
		cb_function.call(what, data);
	}, what);
};

loadJSON(jsonDataURL, function(response) {
	msgStrs = response.strings;
	signatureFedoraString = response.signature;
	suspiciousComponents = response.suspiciousComponents;
	hashBugzillaName = response.bugzillalabelNames;
	hashBugzillaWholeURL = response.bugzillaIDURLs;
	// [{'regexp to match component':'email address of an universal
		// maintainer'}, ...]
		AddrArray = response.CCmaintainer;
		defAssigneeList = response.defaultAssignee;
		queryButtonAvailable = response.queryButton;
		upstreamButtonAvailable = response.upstreamButton;
		parseAbrtBacktraces = response.parseAbrtBacktraces;
		logSubmits = response.submitsLogging;
		newUpstreamBugsURLArray = response.newUpstreamBug;
		queryUpstreamBugsURLArray = response.queryUpstreamBug;
		chipIDsGroupings = response.chipIDsGroupings;
		topRow = response.topRow;
		bottomRow = response.bottomRow;
	});

// Get card translation table
loadJSON(PCIIDsURL, function(response) {
	PCI_ID_Array = response;
});

// ======== load external library ===============================

// var XMLRPCMessage = {};
// var req = new XMLHttpRequest();
// req.open("GET","http://mcepl.fedorapeople.org/scripts/xmlrpc.js",true);
// req.onreadystatechange = function (aEvt) {
// if (req.readyState == 4) {
// if (req.status == 200) {
// var thisDoc = jetpack.tabs.focused.contentDocument;
// var script = thisDoc.createElement("script");
// script.setAttribute("type","text/javascript");
// script.innerHTML = req.responseText;
// thisDoc.getElementsByTagName("head")[0].appendChild(script);
// XMLRPCMessage =
// jetpack.tabs.focused.contentWindow.wrappedJSObject.XMLRPCMessage;
// console.log("XMLHTTPRequest should be loaded.");
// console.log(XMLRPCMessage);
// }
// }
// };
// console.log("Now we are calling XMLHTTPRequest to load xmlrpc.js");
// req.send("");
/*
 * 
 * xmlrpc.js beta version 1 Tool for creating XML-RPC formatted requests in
 * JavaScript
 * 
 * Copyright 2001 Scott Andrew LePera scott@scottandrew.com
 * http://www.scottandrew.com/xml-rpc
 * 
 * License: You are granted the right to use and/or redistribute this code only
 * if this license and the copyright notice are included and you accept that no
 * warranty of any kind is made or implied by the author.
 * 
 */

function XMLRPCMessage(methodname) {
	this.method = methodname || "system.listMethods";
	this.params = [];
	return this;
}

XMLRPCMessage.prototype.setMethod = function(methodName) {
	if (!methodName)
		return;
	this.method = methodName;
};

XMLRPCMessage.prototype.addParameter = function(data) {
	if (arguments.length == 0)
		return;
	this.params[this.params.length] = data;
};

XMLRPCMessage.prototype.xml = function() {

	var method = this.method;

	// assemble the XML message header
	var xml = "";

	xml += "<?xml version=\"1.0\"?>\n";
	xml += "<methodCall>\n";
	xml += "<methodName>" + method + "</methodName>\n";
	xml += "<params>\n";

	// do individual parameters
	for ( var i = 0; i < this.params.length; i++) {
		var data = this.params[i];
		xml += "<param>\n";

		xml += "<value>"
				+ XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data),
						data) + "</value>\n";

		xml += "</param>\n";
	}

	xml += "</params>\n";
	xml += "</methodCall>";

	return xml; // for now
};

XMLRPCMessage.dataTypeOf = function(o) {
	// identifies the data type
	var type = typeof (o);
	type = type.toLowerCase();
	switch (type) {
	case "number":
		if (Math.round(o) == o)
			type = "i4";
		else
			type = "double";
		break;
	case "object":
		var con = o.constructor;
		if (con == Date)
			type = "date";
		else if (con == Array)
			type = "array";
		else
			type = "struct";
		break;
	}
	return type;
};

XMLRPCMessage.doValueXML = function(type, data) {
	var xml = "<" + type + ">" + data + "</" + type + ">";
	return xml;
};

XMLRPCMessage.doBooleanXML = function(data) {
	var value = (data == true) ? 1 : 0;
	var xml = "<boolean>" + value + "</boolean>";
	return xml;
};

XMLRPCMessage.doDateXML = function(data) {
	var xml = "<dateTime.iso8601>";
	xml += dateToISO8601(data);
	xml += "</dateTime.iso8601>";
	return xml;
};

XMLRPCMessage.doArrayXML = function(data) {
	var xml = "<array><data>\n";
	for ( var i = 0; i < data.length; i++) {
		xml += "<value>"
				+ XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]),
						data[i]) + "</value>\n";
	}
	xml += "</data></array>\n";
	return xml;
};

XMLRPCMessage.doStructXML = function(data) {
	var xml = "<struct>\n";
	for ( var i in data) {
		xml += "<member>\n";
		xml += "<name>" + i + "</name>\n";
		xml += "<value>"
				+ XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]),
						data[i]) + "</value>\n";
		xml += "</member>\n";
	}
	xml += "</struct>\n";
	return xml;
};

XMLRPCMessage.getParamXML = function(type, data) {
	var xml;
	switch (type) {
	case "date":
		xml = XMLRPCMessage.doDateXML(data);
		break;
	case "array":
		xml = XMLRPCMessage.doArrayXML(data);
		break;
	case "struct":
		xml = XMLRPCMessage.doStructXML(data);
		break;
	case "boolean":
		xml = XMLRPCMessage.doBooleanXML(data);
		break;
	default:
		xml = XMLRPCMessage.doValueXML(type, data);
		break;
	}
	return xml;
};

function dateToISO8601(date) {
	// wow I hate working with the Date object
	var year = new String(date.getYear());
	var month = leadingZero(new String(date.getMonth()));
	var day = leadingZero(new String(date.getDate()));
	var time = leadingZero(new String(date.getHours())) + ":"
			+ leadingZero(new String(date.getMinutes())) + ":"
			+ leadingZero(new String(date.getSeconds()));

	var converted = year + month + day + "T" + time;
	return converted;
}

function leadingZero(n) {
	// pads a single number with a leading zero. Heh.
	if (n.length == 1)
		n = "0" + n;
	return n;
}

// ==============================================================
/**
 * format date to be in ISO format (just day part)
 * 
 * @param date
 * @return string with the formatted date
 */
function getISODate(dateStr) {
	function pad(n) {
		return n < 10 ? '0' + n : n;
	}
	var date = new Date(dateStr);
	return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-'
			+ pad(date.getDate());
}

/**
 * select element of the array where regexp in the first element matches second
 * parameter of this function
 * 
 * @param list
 *            array with regexps and return values
 * @param chosingMark
 *            string by which the element of array is to be matched
 * @return string chosen element
 */
filterByRegexp = function(list, chosingMark) {
	var chosenPair = [];
	if (list.length > 0) {
		chosenPair = list.filter(function(pair) {
			return new RegExp(pair.regexp, "i").test(chosingMark);
		});
	}
	if (chosenPair.length > 0) {
		return chosenPair[0].addr.trim();
	} else {
		return "";
	}
};

/**
 * Converts attributes value of the given list of elements to the Javascript
 * list.
 * 
 * @param list
 *            array of elements
 * @return array of values
 */
valuesToList = function(list) {
	var outL = [];

	list.forEach(function(e, i, a) {
		if (e.hasAttribute("value")) {
			outL.push(e.getAttribute("value").trim());
		}
	});
	return outL;
};

/**
 * Check whether an item is member of the list. Idea is just to make long if
 * commands slightly more readable.
 * 
 * @param mbr
 *            string to be searched in the list
 * @param list
 *            list
 * @return position of the string in the list, or -1 if none found.
 */
isInList = function(mbr, list) {
	return (list.indexOf(mbr) !== -1);
};

function createBlankPage(ttl, bodyBuildCB) {
	var title = ttl || "Yet another untitled page";
	var that = this;

	var logTab = jetpack.tabs.open("about:blank");
	jetpack.tabs.onReady(function() {
		var otherDoc = logTab.contentDocument;
		otherDoc.title = title;
		otherDoc.body.innerHTML = "<h1>" + title + "</h1>";
		bodyBuildCB.call(that, otherDoc.body);
		logTab.focus();
	});
}

// ============================================================================
// Color management methods
// originally from
// http://www.mjijackson.com/2008/02\
// /rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript

function Color(r, g, b) {
	if (r instanceof Array) {
		this.r = r[0];
		this.g = r[1];
		this.b = r[2];
	} else {
		this.r = r;
		this.g = g;
		this.b = b;
	}
}

Color.prototype.update = function(r, g, b) {
	this.r = r;
	this.g = g;
	this.b = b;
};

Color.prototype.hs = function(nStr) {
	if (Number(nStr) === 0) {
		return "00";
	} else if (nStr.length < 2) {
		return "0" + nStr;
	} else {
		return nStr;
	}
};

Color.prototype.toString = function() {
	var rH = Number(this.r.toFixed()).toString(16);
	var gH = Number(this.g.toFixed()).toString(16);
	var bH = Number(this.b.toFixed()).toString(16);
	return "#" + this.hs(rH) + this.hs(gH) + this.hs(bH);
};

/**
 * Converts an RGB color value to HSL. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSL_color_space. Assumes r, g, and b are
 * contained in the set [0, 255] and returns h, s, and l in the set [0, 1].4343
 * 
 * @param Number
 *            r The red color value
 * @param Number
 *            g The green color value
 * @param Number
 *            b The blue color value
 * @return Array The HSL representation
 */
Color.prototype.hsl = function() {
	var r = this.r / 255;
	var g = this.g / 255;
	var b = this.b / 255;
	var max = Math.max(r, g, b), min = Math.min(r, g, b);
	var h, s, l = (max + min) / 2;

	if (max === min) {
		h = s = 0; // achromatic
	} else {
		var d = max - min;
		s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
		switch (max) {
		case r:
			h = (g - b) / d + (g < b ? 6 : 0);
			break;
		case g:
			h = (b - r) / d + 2;
			break;
		case b:
			h = (r - g) / d + 4;
			break;
		}
		h /= 6;
	}

	return [ h, s, l ];
};

/**
 * Converts an HSL color value to RGB. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSL_color_space. Assumes h, s, and l are
 * contained in the set [0, 1] and returns r, g, and b in the set [0, 255].
 * 
 * @param Number
 *            h The hue
 * @param Number
 *            s The saturation
 * @param Number
 *            l The lightness
 * @return Array The RGB representation
 */
Color.prototype.hslToRgb = function(h, s, l) {
	function hue2rgb(p, q, t) {
		if (t < 0) {
			t += 1;
		}
		if (t > 1) {
			t -= 1;
		}
		if (t < 1 / 6) {
			return p + (q - p) * 6 * t;
		}
		if (t < 1 / 2) {
			return q;
		}
		if (t < 2 / 3) {
			return p + (q - p) * (2 / 3 - t) * 6;
		}
		return p;
	}

	var r, g, b;

	if (s === 0) {
		r = g = b = l; // achromatic
	} else {
		var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
		var p = 2 * l - q;
		r = hue2rgb(p, q, h + 1 / 3);
		g = hue2rgb(p, q, h);
		b = hue2rgb(p, q, h - 1 / 3);
	}

	return [ r * 255, g * 255, b * 255 ];
};

/**
 * Converts an RGB color value to HSV. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSV_color_space. Assumes r, g, and b are
 * contained in the set [0, 255] and returns h, s, and v in the set [0, 1].
 * 
 * @param Number
 *            r The red color value
 * @param Number
 *            g The green color value
 * @param Number
 *            b The blue color value
 * @return Array The HSV representation
 */
Color.prototype.hsv = function() {
	var r = this.r / 255;
	var g = this.g / 255;
	var b = this.b / 255;
	var max = Math.max(r, g, b), min = Math.min(r, g, b);
	var h, s, v = max;

	var d = max - min;
	s = max === 0 ? 0 : d / max;

	if (max === min) {
		h = 0; // achromatic
	} else {
		switch (max) {
		case r:
			h = (g - b) / d + (g < b ? 6 : 0);
			break;
		case g:
			h = (b - r) / d + 2;
			break;
		case b:
			h = (r - g) / d + 4;
			break;
		}
		h /= 6;
	}

	return [ h, s, v ];
};

/**
 * Converts an HSV color value to RGB. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSV_color_space. Assumes h, s, and v are
 * contained in the set [0, 1] and returns r, g, and b in the set [0, 255].
 * 
 * @param Number
 *            h The hue
 * @param Number
 *            s The saturation
 * @param Number
 *            v The value
 * @return Array The RGB representation
 */
Color.prototype.hsvToRgb = function(h, s, v) {
	var r, g, b;

	var i = Math.floor(h * 6);
	var f = h * 6 - i;
	var p = v * (1 - s);
	var q = v * (1 - f * s);
	var t = v * (1 - (1 - f) * s);

	switch (i % 6) {
	case 0:
		r = v;
		g = t;
		b = p;
		break;
	case 1:
		r = q;
		g = v;
		b = p;
		break;
	case 2:
		r = p;
		g = v;
		b = t;
		break;
	case 3:
		r = p;
		g = q;
		b = v;
		break;
	case 4:
		r = t;
		g = p;
		b = v;
		break;
	case 5:
		r = v;
		g = p;
		b = q;
		break;
	}

	return [ r * 255, g * 255, b * 255 ];
};

/**
 * Provide
 */
Color.prototype.lightColor = function() {
	var hslArray = this.hsl();
	var h = Number(hslArray[0]);
	var s = Number(hslArray[1]) * Desaturated;
	var l = Luminosity;
	var desA = this.hslToRgb(h, s, l);
	return new Color(desA[0], desA[1], desA[2]);
};

// ====================================================================================
// BzPage's methods

/**
 * 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(id, str) {
	try {
		var kwd = this.dok.getElementById(id).value;
	} catch (e) {
		// For those who don't have particular element at all or if it is empty
		return false;
	}
	console.log("id = " + id + ", kwd = " + kwd.trim());
	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(str) {
	return (this.idContainsWord('keywords', str));
};

/**
 * Set additional keyword if it isn't there
 * 
 * @param str
 *            string with the keyword
 * @return none
 */
BzPage.prototype.setKeyword = function(str) {
	this.addTextToTextBox('keywords', str);
};

BzPage.prototype.getOptionValue = function(id) {
	// Some special bugs don't have version for example
	try {
		return this.dok.getElementById(id).value;
	} catch (e) {
		console.error("Failed to find element with id = " + id);
		return "#NA";
	}
};

/* Offline supporting functions */
/**
 * 
 * @todo FIXME this probably makes a closure and a memory leak name='changeform'
 *       investigate
 *       https://developer.mozilla.org/en/How_to_Turn_Off_Form_Autocompletion
 * 
 * <form method="post" action="process_bug.cgi" autocomplete="off">
 * 
 * Reading
 * http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13
 * random notes: - 17.13.3 provides all steps necessary - enctype !=
 * application/x-www-form-urlencoded => SHOULD fails (no further questions
 * needed) - http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1. is
 * nice explanation (albeit quite dated) - on multiple values
 * http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.6.1 -
 * příliš jednoduché
 * http://www.innovation.ch/java/HTTPClient/emulating_forms.html -
 */
BzPage.prototype.serializeForm = function(form) {
	var serialForm = {
		dataOut : "",
		name : form.name,
		method : form.method,
		acceptCharset : form.acceptCharset,
		action : form.action, // TODO shouldn't we get a non-relative URL?
		enctype : form.enctype,
		cookie : this.dok.cookie,
		autocomplete : form.getAttribute("autocomplete"),
		bugNo : this.bugNo
	};

	function genURIElement(sName, sValue) {
		return encodeURIComponent(sName) + "=" + encodeURIComponent(sValue);
	}

	/**
	 * @param o
	 *            control to be serialized
	 * @return String with the serialized control
	 */
	function serializeControl(element) {
		var val = element.value;
		// console.log("val.toSource() = " + val.toSource());
		/*
		 * on HTMLSelectElement we have an attribute 'type' of type DOMString,
		 * readonly The type of this form control. This is the string
		 * "select-multiple" when the multiple attribute is true and the string
		 * "select-one" when false.
		 */
		if ((val == null) || (val == undefined) || (val == "")) {
			return;
		} else if (val instanceof Array) {
			return val.map(function(x) {
				return genURIElement(element.name, x.value);
			}).join("&");
		} else if (val instanceof String) {
			return genURIElement(element.name, val);
		} else { // assume HTMLCollection
			return Array.map(val, function(x) {
				return genURIElement(element.name, x.value);
			}).join("&");
		}
	}

	serialForm.dataOut = Array
			.filter(
					form.elements,
					function(el) {
						return !el.disabled
								&& el.name
								&&
								// FIXME shouldn't I just add && el.value here?
								(el.checked
										|| /select|textarea/i.test(el.nodeName) || /text|hidden|password|search/i
										.test(el.type));
					}).map(serializeControl).join("&");
	return serialForm;
};

BzPage.prototype.submitCallback = function(evt) {
	console.log("Submit Callback!");
	if (jetpack.__parent__.navigator.onLine) {
		var serForm = this
				.serializeForm(jetpack.tabs.focused.contentWindow.document.forms
						.namedItem("changeform"));
		console.log("serForm:\n" + serForm.toSource());
	} else {
		var serForm = this
				.serializeForm(jetpack.tabs.focused.contentWindow.document.forms
						.namedItem("changeform"));
		myStorage.forms[this.bugNo] = serForm;
		evt.stopPropagation();
		evt.preventDefault();
	}
};

/**
 * 
 * 
 * Yes, this is correct, this is NOT method of bzPage!
 */
function onlineCallback() {
	function deserializeAndSend(formData) {
		// FIXME notImplemented
		// is it enough to just
		// run XMLHttpRequest? Probably yes, this is just a form
		// and this is just a HTTP request
		// it is probably better to get already processed
		// application/x-www-form-urlencoded
		// see http://htmlhelp.com/reference/html40/forms/form.html for details
		// and also https://developer.mozilla.org/en/AJAX/Getting_Started
		// what's?
		// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\
		// /Global_Functions/encodeURI & co.
		// this seems to be also interesting
		// https://developer.mozilla.org/en/Code_snippets/Post_data_to_window
		console.error("Sending bugs not implemented yet!");
		return ""; // FIXME check other HTTP headers to be set

		var bugID = formData.bugNo;
		var req = new XMLHttpRequest();
		req.open("POST", formData.action, true);
		// FIXME co očekávám za odpověď? req.overrideMimeType("text/xml");
		// * Accept-Encoding
		// * Accept-Language
		// * Accept (MIME types)
		req.setRequestHeader("Connection", "keep-alive");
		req.setRequestHeader("Keep-Alive", 300);
		req.setRequestHeader("Content-Type", formData.enctype);
		req.setRequestHeader("Referer", bugURL + bugID);
		req.setRequestHeader("Accept-Charset", formData.acceptCharset);
		req.setRequestHeader("Cookie", formData.cookie);
		req.onreadystatechange = function(aEvt) {
			if (req.readyState == 4) {
				if (req.status == 200) {
					console.log("Sent form for bug " + bugID);
					delete myStorage.forms[bugID];
				} else {
					console.error("Sending form for bug " + bugID + "failed!");
				}
			}
		};
		req.send(formData.data);
	}

	if (myStorage.forms.length > 0) {
		myStorage.forms.forEach(function(x) {
			deserializeAndSend(x);
		});
	}
}

/* Bugzilla functions. */

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

/**
 * 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() {
	var verStr = this.getOptionValue("version").toLowerCase();
	var verNo = 0;
	if (/rawhide/.test(verStr)) {
		verNo = 999;
	} else {
		verNo = Number(verStr);
	}
	return verNo;
};

/**
 * Send mouse click to the specified element
 * 
 * @param element
 *            where to send mouseclick to
 * @return None
 */
BzPage.prototype.clickMouse = function(target) {
	var localEvent = this.dok.createEvent("MouseEvents");
	localEvent.initMouseEvent("click", true, true, this.dok.defaultView, 0, 0,
			0, 0, 0, false, false, false, false, 0, null);
	target.dispatchEvent(localEvent);
};

/**
 * Add text to the text box (comment box or status whiteboard)
 * 
 * @param id
 *            string with the id of the element
 * @param string2BAdded
 *            string to be added to the comment box
 * 
 * @return none
 */
BzPage.prototype.addTextToTextBox = function(id, string2BAdded) {
	var textBox = this.dok.getElementById(id);
	var separator = ", ";
	if (textBox.tagName.toLowerCase() === "textarea") {
		separator = "\n\n";
	} else {
		// don't add string if it is already there
		if (textBox.value.indexOf(string2BAdded) != -1) {
			return;
		}
	}

	// don't remove the current content of the comment box,
	// just behave accordingly
	if (textBox.value.length > 0) {
		textBox.value = textBox.value.trim() + separator;
	}
	textBox.value = textBox.value + string2BAdded;
};

/**
 * Add new keyword among the keywords.
 * 
 * @param str
 *            string with the new keyword
 * @return none
 * 
 * Checks for the existing keywords.
 */
BzPage.prototype.addKeyword = function(str) {
	this.addTextToTextBox("keywords", str);
};

BzPage.prototype.commentsWalker = function(fce) {
	var comments = this.dok.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() {
	var that = this;
	this.commentsWalker(function(x) {
		var email = x.getElementsByClassName("vcard")[0]
				.getElementsByTagName("a")[0].textContent;
		if (new RegExp(that.reporter).test(email)) {
			x.style.backgroundColor = ReporterColor.toString();
		}
	});
};

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

/**
 * Is this bug a RHEL bug?
 * 
 * @return Boolean true if it is a RHEL bug
 */
BzPage.prototype.isRHEL = function() {
	return (/Red Hat Enterprise Linux/).test(this.product);
};

/**
 * Find out whether the bug is needed an attention of bugZappers
 * 
 * @return Boolean whether the bug has been triaged or not
 */
BzPage.prototype.isTriaged = function() {
	// First excceptions
	if (this.version > 7 && this.version < 13) {
		return this.dok.getElementById("bug_status").value.toUpperCase() !== "NEW";
	} else { // and then the rule
		return this.hasKeyword("Triaged");
	}
};

/**
 * Set branding colours to easily distinguish between Fedora and RHEL bugs
 * 
 * @param brand
 *            string with product of the current bug
 * @param version
 *            string with the version of the bug
 * @param its
 *            string with the IsueTracker numbers
 * @return none
 * 
 */
BzPage.prototype.setBranding = function() {
	var brandColor = {};
	var TriagedColor = {};

	if (this.isRHEL()) {
		if (this.its && (this.its.length > 0)) {
			brandColor = RHITColor;
		} else {
			brandColor = RHColor;
		}
	} else if (new RegExp("Fedora").test(this.product)) {
		if (this.version == 999) {
			brandColor = RawhideColor;
		} else {
			brandColor = FedoraColor;
		}
	}

	// Comment each of the following lines to get only partial branding
	this.dok.getElementsByTagName("body")[0].style.background = brandColor
			.toString()
			+ " none";
	this.dok.getElementById("titles").style.background = brandColor.toString()
			+ " none";

	// Remove "Bug" from the title of the bug page, so we have more space with
	// plenty of tabs
	var titleElem = this.dok.getElementsByTagName("title")[0];
	titleElem.textContent = titleElem.textContent.slice(4);
	var bodyTitleParent = this.dok.getElementById("summary_alias_container").parentNode;
	var bodyTitleElem = bodyTitleParent.getElementsByTagName("b")[0];
	bodyTitleElem.textContent = bodyTitleElem.textContent.slice(4);

	// Make background-color of the body of bug salmon pink
	// for security bugs.
	if (this.hasKeyword("Security")) {
		this.dok.getElementById("bugzilla-body").style.background = SalmonPink
				.toString() + ' none';
	}

	// Make it visible whether the bug has been triaged
	if (this.isTriaged()) {
		this.dok.getElementById("bz_field_status").style.background = brandColor
				.lightColor().toString()
				+ " none";
	}

	// we should make visible whether maintCCAddr is in CCList
	if (isInList(this.maintCCAddr, this.CCList)) {
		var ccEditBoxElem = this.dok.getElementById("cc_edit_area_showhide");
		// ccEditBoxElem.textContent = "*"+ccEditBoxElem.textContent;
		ccEditBoxElem.style.color = "navy";
		ccEditBoxElem.style.fontWeight = "bolder";
		ccEditBoxElem.style.textDecoration = "underline";
	}

	// mark suspicious components
	var compElems;
	if (suspiciousComponents
			&& isInList(this.component, suspiciousComponents)
			&& (compElems = this.dok
					.getElementById("bz_component_edit_container"))) {
		compElems.style.background = "red none";
	}
};

/**
 * Given line to be parsed, find out which chipset it is and fill in the
 * whiteboard
 * 
 * @param iLine
 *            string with the whole unparsed "interesting line"
 * @param driverStr
 *            string with the driver name
 * @return None
 */
BzPage.prototype.fillInWhiteBoard = function(iLine, driverStr) {
	function groupIDs(manStr, cardStrID) {
		var outStr = filterByRegexp(chipIDsGroupings, manStr + "," + cardStrID);
		if (outStr.length === 0) {
			outStr = "UNGROUPED_" + manStr + "/" + cardStrID;
		}
		return outStr;
	}
	;

	/**
	 * Given PCI IDs for manufacturer and card ID return chipset string
	 * 
	 * @param manufacturerNo
	 *            string with manufacturer PCI ID
	 * @param cardNo
	 *            string with card PCI ID
	 * 
	 * @return array with chip string and optinoal variants
	 */
	function checkChipStringFromID(manufacturerNo, cardNo) {
		var soughtID = (manufacturerNo + "," + cardNo).toUpperCase();
		var outList = PCI_ID_Array[soughtID];
		if (outList) {
			return outList;
		} else {
			return "";
		}
	}
	;

	var outStr = "";
	var cardIDStr = "";
	var cardIDArr = [];

	chipSwitchboard: if (driverStr === "RADEON") {
		var cardID = iLine.replace(ATIgetIDRE, "$1");
		cardIDArr = checkChipStringFromID("1002", cardID);
		if (cardIDArr.length > 0) {
			cardIDStr = cardIDArr[0];
			if (cardIDArr[1]) {
				optionStr = cardIDArr[1];
				outStr = groupIDs(driverStr, cardIDStr) + "/" + optionStr;
			} else {
				outStr = groupIDs(driverStr, cardIDStr);
				optionStr = "";
			}
		} else {
			outStr = "**** FULLSTRING: " + iLine;
		}
	} else {
		// Intel Corporation, NVIDIA
		cardIDArr = manuChipStrs.filter(function(el, ind, arr) {
			return new RegExp(el[0], "i").test(iLine);
		});
		if (cardIDArr && (cardIDArr.length > 0)) {
			cardIDArr = cardIDArr[0];
		} else {
			outStr = iLine;
			break chipSwitchboard;
		}
		// cardIDArr [0] = RE, [1] = ("RADEON","INTEL","NOUVEAU"), [2] = manu
		// PCIID
		iLine = iLine.replace(new RegExp(cardIDArr[0], "i")).trim();
		// nVidia developers opted-out from grouping
		if (driverStr === "INTEL") {
			outStr = groupIDs(cardIDArr[1], iLine);
		} else {
			outStr = iLine;
		}
	}
	this.addTextToTextBox("status_whiteboard", ("card_" + outStr).trim());
	this.dok.getElementById("chipmagic").style.display = "none";
};

/**
 * 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 originalLocation
 *            object after which the new button will be added
 * @param newId
 *            string with the id of the new button; has to be unique in whole
 *            page
 * @param newLabel
 *            string with the label which will be shown to user
 * @param commentString
 *            string with comment to be added to the comment box
 * @param nState
 *            string with the new state bug should switch to (see
 *            generalPurposeCureForAllDisease function for details)
 * @param secPar
 *            string with second parameter for generalPurposeForAllDisease
 * @param doSubmit
 *            bool optional whether the button should submit whole page (default
 *            true)
 * 
 * @return none
 */
BzPage.prototype.addNewButton = function(originalLocation, newId, newLabel,
		commentString, nState, secPar, doSubmit, after) {
	var that = this;
	var commStr = "";
	if (doSubmit === undefined) { // missing optional argument
		doSubmit = false;
	}
	if (after === undefined) { // missing optional argument
		after = false;
	}
	if (msgStrs[commentString]) {
		commStr = msgStrs[commentString];
	}
	var newButton = this.dok.createElement("input");
	newButton.setAttribute("id", newId);
	if (doSubmit) {
		newButton.setAttribute("type", "submit");
	} else {
		newButton.setAttribute("type", "button");
	}
	newButton.value = newLabel;
	newButton.addEventListener("click", function(evt) {
		that.generalPurposeCureForAllDisease(commStr, nState, secPar);
	}, false);

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

/**
 * Get attached Xorg.0.log, parse it and find the value of chip. Does not fill
 * the whiteboard itself, just adds button to do so,paramList so that slow
 * XMLHttpRequest is done in advance.
 * 
 * @return None
 */
BzPage.prototype.fillInChipMagic = function () {
    var XorgLogURL = "";
    var XorgLogAttID = "";
    var XorgLogFound = false;
    var attURL = "", interestingLine = "";
    var interestingArray = [];


    // Find out Xorg.0.log attachment URL
    this.XorgLogAttList = this.attachments.filter(function (value, index, array) {
        // Xorg.0.log must be text, otherwise we cannot parse it
        return (/[xX].*log/.test(value[0]) && /text/.test(value[2]));
    });
    if (this.XorgLogAttList.length === 0) {
        return;
    }

    XorgLogAttID = this.XorgLogAttList[this.XorgLogAttListIndex][1];
    attURL = "https://bugzilla.redhat.com/attachment.cgi?id="+XorgLogAttID;
    that = this;

    var req = new XMLHttpRequest();
    req.open("GET",attURL,true);
    req.onreadystatechange = function (aEvt) {
        if (req.readyState == 4) {
            if (req.status == 200) {
                var ret = req.responseText;
                var interestingLineArr = ret.split("\n").
                    filter(function (v,i,a) {
                        return ChipsetRE.test(v);
                });
                console.log("interestingLineArr = " + interestingLineArr.toSource());
                if (interestingLineArr.length >0) {
                    interestingArray = ChipsetRE.exec(interestingLineArr[0]);
                    interestingLine = interestingArray[2].
                        replace(/[\s"]+/g," ").trim();
                    var whiteboardInput = that.dok.
                        getElementById("status_whiteboard");
                    that.addNewButton(whiteboardInput,"chipmagic","Fill In",
                        "","CHIPMAGIC",
                        interestingLine+"\t"+interestingArray[1].toUpperCase(),
                        false,true);
                }
            } else {
                throw "Getting attachment " + attURL + "failed!";
            }
        }
    };
    req.send("");

    this.XorgLogAttListIndex++;
};

/**
 * 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
 * @return None
 * 
 */
BzPage.prototype.queryInNewTab = function(text, component, product) {
	// Optional parameter
	if (product === undefined) {
		product = this.product;
	}
	var url = "https://bugzilla.redhat.com/buglist.cgi?query_format=advanced";
	if (product) {
		url += "&product=" + product.trim();
	}
	if (component) {
		url += "&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;
		url += searchText;
		jetpack.tabs.open(url);
		// Don't do it ... b.m.o is apparently not powerful enough to sustain
		// the weight
		// of the search
		if (false) {
			url = "https://bugzilla.mozilla.org/buglist.cgi?query_format=advanced"
					+ "field0-0-0=product;type0-0-0=regexp;"
					+ "value0-0-0=thunderbird|firefox|xulrunner"
					+ searchText.replace("&", ";");
			jetpack.tabs.open(url);
		}
	}
};

/**
 * 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 = jetpack.selection.text;
	if (!text) {
		text = jetpack.clipboard.get();
	}
	if (text) {
		this.queryInNewTab(text, this.component);
	}
};

/**
 * Search simple query in the upstream bugzilla appropriate for the component.
 */
BzPage.prototype.queryUpstream = function() {
	var text = jetpack.selection.text;
	if (!text) {
		text = jetpack.clipboard.get();
	}
	if (text) {
        var text = encodeURIComponent(text.trim());
		var url = filterByRegexp(queryUpstreamBugsURLArray,this.component);
        jetpack.tabs.open(url+text);
	}
}

/**
 * 
 */
BzPage.prototype.sendBugUpstream = function() {
	var url = filterByRegexp(newUpstreamBugsURLArray, this
			.getOptionValue("component"));

	var ret = jetpack.tabs.open(url);
	var that = this;
	jetpack.tabs.onReady(function() {
		var otherDoc = ret.contentDocument;
		var otherElems = otherDoc.forms.namedItem("Create").elements;
		otherElems.namedItem("short_desc").value = that.dok
				.getElementById("short_desc_nonedit_display").textContent
				.trim();
		otherElems.namedItem("comment").value = that.collectComments();
		ret.focused();
	});
};

/**
 * Parse the row with the attachment
 * 
 * @param
 * <tr> DOM element to be parsed
 * @return array with 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.parseAttachmentLine = function(inElem) {
	var MIMEtype = "";
	var size = 0;

	// Skip over obsolete attachments
	if (inElem.getElementsByClassName("bz_obsolete").length > 0) {
		return ( []);
	}

	// getting name of the attachment
	var attName = inElem.getElementsByTagName("b")[0].textContent.trim();

	var aHrefsArr = inElem.getElementsByTagName("a");
	var aHref = Array.filter(aHrefsArr, function(x) {
		return x.textContent.trim() == "Details";
	})[0];
	var id = parseInt(aHref.getAttribute("href").replace(
			/^.*attachment.cgi\?id=/, ""), 10);

	// getting MIME type and size
	var stringArray = inElem.getElementsByClassName("bz_attach_extra_info")[0].textContent
			.replace(/[\n ()]+/g, " ").trim().split(", ");
	size = parseInt(stringArray[0], 10);
	MIMEtype = stringArray[1].split(" ")[0];

	return [ attName, id, MIMEtype, size, inElem ];
};

/**
 * Select option with given label 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(id, label) {
	var sel = this.dok.getElementById(id);
	var options = Array.filter(sel.getElementsByTagName("option"), function(x) {
		return x.textContent.trim() == label;
	});
	theOption = options.length ? options[0] : [];
	if (theOption) {
		theOption.selected = true;
		var intEvent = this.dok.createEvent("HTMLEvents");
		intEvent.initEvent("change", true, true);
		theOption.dispatchEvent(intEvent);
	}
};

/**
 * Check for the presence of a keyword
 * 
 * @param str
 *            string with the keyword
 * @return Boolean
 * 
 */
BzPage.prototype.hasKeyword = function(str) {
	var kwd = this.dok.getElementById('keywords').value.trim();
	return (new RegExp(str).test(kwd));
};

/**
 * Add accesskey to the particular element
 * 
 * @param rootElement
 *            element to which the new text object will be attached
 * @param beforeText
 *            text before the accesskey character
 * @param accKey
 *            what will be the accesskey itself
 * @param afterText
 *            text after the accesskey character
 * @return modified element with the fixed accesskey FIXME isn't this closure
 *         and possible memleak?
 */
BzPage.prototype.fixElement = function(elem, beforeText, accKey, afterText) {
	elem.setAttribute("accesskey", accKey.toLowerCase());
	elem.innerHTML = beforeText + "<b><u>" + accKey + "</u></b>" + afterText;
	return elem;
};

/**
 * Add XGL to the CC list
 * 
 * @param evt
 *            event which made this function active
 * @return none
 */
BzPage.prototype.changeOwner = function(newAssignee) {
	/**
	 * Take care that when changing assignment of the bug, current owner is
	 * added to CC list. Switch off setting to the default assignee
	 */
	var defAssigneeButton;
	if (!isInList(newAssignee, this.CCList)) {
		this.dok.getElementById("newcc").textContent = newAssignee;
	}
	if (newAssignee) {
		this.clickMouse(this.dok.getElementById("bz_assignee_edit_action"));
		this.dok.getElementById("assigned_to").value = newAssignee;
		this.dok.getElementById("set_default_assignee").checked = false;
		if (defAssigneeButton = this.dok
				.getElementById("setdefaultassigneebutton")) {
			defAssigneeButton.style.display = "none";
		}
	}
};

/**
 * Set the bug to NEEDINFO state
 * 
 * Working function.
 * 
 * @return none
 */
BzPage.prototype.setNeedinfoReporter = function() {
	this.clickMouse(this.dok.getElementById("needinfo"));
	this.selectOption("needinfo_role", "reporter");
};

/**
 * Return string with the ID for the external_id SELECT for external bugzilla
 * 
 * @param URLhostname
 *            string hostname of the external bugzilla
 * @return string with the string for the external_id SELECT
 */
BzPage.prototype.getBugzillaName = function(URLhostname) {
	var bugzillaID = "";
	if (hashBugzillaName[URLhostname]) {
		bugzillaID = hashBugzillaName[URLhostname];
	} else {
		bugzillaID = "";
	}
	return bugzillaID;
};

/**
 * Generate URL of the bug on remote bugzilla
 * 
 * @param selectValue
 *            Number which is index of the bugzilla in hashBugzillaWholeURL
 * @param bugID
 *            Number which is bug ID
 * @return string with the URL
 */
BzPage.prototype.getWholeURL = function(selectValue, bugID) {
	var returnURL = "";
	if (hashBugzillaWholeURL[selectValue]) {
		returnURL = hashBugzillaWholeURL[selectValue] + bugID;
	} else {
		returnURL = "";
	}
	return returnURL;
};

/**
 * Callback function for the XMLRPC request
 * 
 * @param ret
 *            object with xmlhttprequest response with attributes: + status --
 *            int return code + statusText + responseHeaders + responseText
 */
BzPage.prototype.callBack = function(data, textStatus) {
	if (--this.reqCounter <= 0) {
		setTimeout(this.dok.location.reload, 1000);
	}
};

/**
 * The worker function -- call XMLRPC to fix MIME type of the particular
 * attachment
 * 
 * @param id
 *            integer with the attachment id to be fixed
 * @param type
 *            string with the new MIME type, optional defaults to "text/plain"
 * @param email
 *            Boolean whether email should be sent to appropriate person;
 *            option, defaults to false
 * 
 * updateAttachMimeType($data_ref, $username, $password)
 * 
 * Update the attachment mime type of an attachment. The first argument is a
 * data hash containing information on the new MIME type and the attachment id
 * that you want to act on.
 * 
 * $data_ref = { "attach_id" => "<Attachment ID>", # Attachment ID to perform
 * MIME type change on. "mime_type" => "<New MIME Type Value>", # Legal MIME
 * type value that you want to change the attachment to. "nomail" => 0, #
 * OPTIONAL Flag that is either 1 or 0 if you want email to be sent or not for
 * this change };
 * 
 */
BzPage.prototype.fixAttachById = function(id, type, email) {
	if (type === undefined) {
		type = "text/plain";
	}
	if (email === undefined) {
		email = false;
	}

	var msg = new XMLRPCMessage("bugzilla.updateAttachMimeType");
	msg.addParameter( {
		'attach_id' : id,
		'mime_type' : type,
		'nomail' : !email
	});
	msg.addParameter(this.login);
	msg.addParameter(this.password);

	var req = new XMLHttpRequest();
	var that = this;
	req.open("POST", XMLRPCurl, true);
	req.overrideMimeType("text/xml");
	req.setRequestHeader("Content-type", "text/xml");
	req.onreadystatechange = function(aEvt) {
		if (req.readyState == 4) {
			if (req.status == 200) {
				console.log("Fixing attachment MIME type success!");
				that.callBack();
			} else {
				console.error("Fixing MIME type attachment failed!");
			}
		}
	};
	req.send(msg.xml());
	this.reqCounter++;
};

// FIXME possibly eliminate this function altogether and
// make it inline?
BzPage.prototype.fixAllAttachments = function(list) {
	Array.forEach(list, function(x) {
		this.fixAttachById(x[1]);
	}, this);
};

/**
 * Create a button for fixing all bad attachments.
 * 
 * @param list
 *            Array of all bad attachmentss
 * @return button fixing all bad Attachments
 */
BzPage.prototype.createFixAllButton = function(list) {
	if (!XMLRPCMessage) {
		return;
	}
	var that = this;
	var elem = this.dok.createElement("a");
	elem.setAttribute("href", "");
	elem.setAttribute("accesskey", "f");
	elem.innerHTML = "<b>F</b>ix all";
	elem.addEventListener("click", function() {
		that.fixAllAttachments(list);
	}, false);
	return elem;
};

/**
 * Add a link to the bad attachment for fixing it.
 * 
 * @param
 * <TR> DOM jQuery element with a bad attachment
 * @return none
 */
BzPage.prototype.addTextLink = function(row) {
	var that = this;
	var elemS = row[4].getElementsByTagName("td");
	var elem = elemS[elemS.length - 1];
	elem.innerHTML += "<br/><a href=''>Text</a>";
	elem.addEventListener("click", function(x) {
		that.fixAttachById(row[1], "text/plain");
	}, false);
};

/**
 * Add information about the upstream bug upstream, and closing it.
 * 
 * @param evt
 *            event which called this handler
 * 
 * @return none
 */
BzPage.prototype.addClosingUpstream = function() {
	var refs = this.dok.getElementById("external_bugs_table")
			.getElementsByTagName("tr");
	// that's a bad id, if there is a one. :)
	var inputBox = this.dok.getElementById("inputbox");
	var externalBugID = 0;
	var wholeURL = "";

	// Fix missing ID on the external_id SELECT
	this.dok.getElementsByName("external_id")[0].setAttribute("id",
			"external_id");

	if (inputBox.value.match(/^http.*/)) {
		var helpAElem = this.dok.createElement("a");
		wholeURL = inputBox.value;
		helpAElem.setAttribute("href", wholeURL);
		var paramsArr = helpAElem.search.replace(/^\?/, '').split('&');
		// get ID#
		var params = {}, s = [];
		paramsArr.forEach(function(par, idx, arr) {
			s = par.split('=');
			params[s[0]] = s[1];
		});
		if (params.id) {
			externalBugID = parseInt(params.id, 10);
			inputBox.value = externalBugID;
		}
		// get host and bugzillaName
		var bugzillaName = this.getBugzillaName(helpAElem.hostname);
		this.selectOption("external_id", bugzillaName);
	} else if (!isNaN(inputBox.value)) {
		externalBugID = parseInt(inputBox.value, 10);
		var bugzillaID = this.dok.getElementById("external_id").value;
		wholeURL = this.getWholeURL(bugzillaID, externalBugID);
	} else {
		// no inputBox.value -- maybe there is an external bug from
		// the previous commit?
	}

	// It is not good to close bug as UPSTREAM, if there is no reference
	// to the upstream bug.
	if ((externalBugID > 0) || (refs.length > 2)) {
		this.addTextToTextBox("comment", msgStrs.sentUpstreamString.replace(
				"§§§", wholeURL));
		this.selectOption("bug_status", "CLOSED");
		this.selectOption("resolution", "UPSTREAM");
	} else {
		console.log("No external bug specified among the External References!");
	}
};

/**
 * Insert a row of buttons before the marked element
 * 
 * @param anchor
 *            element before which the row of buttons will be inserted
 * @param array
 *            array of data for buttons to be generated
 * @return none
 */
BzPage.prototype.generateToolBar = function(anchor, array) {
	for ( var i = 0; i < array.length; i++) {
		var butt = array[i];
		this.addNewButton(anchor, butt.idx, butt.msg, butt.string, butt.state,
				butt.parameter, butt.submit);
	}
};

/**
 * Generalized function for all actions
 * 
 * @param addString
 *            string to be added as new comment
 * @param nextState
 *            string signifying next state of the bug (whatever is in Bugzilla +
 *            "NEEDINFO" meaning NEEDINFO(Reporter))
 * @param secondParameter
 *            string with label on the subbutton for reason of closing the bug
 * @return none
 */
BzPage.prototype.generalPurposeCureForAllDisease = function(addString,
		nextState, secondParameter) {
	var verNo = this.getVersion();

	if (addString.length > 0) {
		this.addTextToTextBox("comment", addString);
	}

	if (nextState === "CLOSED") {
		if (secondParameter === "UPSTREAM") {
			this.addClosingUpstream();
		} else if (secondParameter === "SOMERELEASE") {
			// for RAWHIDE close as RAWHIDE,
			// if active selection -> CURRENTRELEASE
			// and put the release version to
			// "Fixed in Version" textbox
			// otherwise -> NEXTRELEASE
			this.selectOption("bug_status", nextState);
			var text = "";
			if (jetpack.selection.text) {
				text = jetpack.select.text.trim();
			}
			if (text.length > 0) {
				this.selectOption("resolution", "CURRENTRELEASE");
				this.dok.getElementById("cf_fixed_in").value = text;
			} else if (verNo === 999) {
				this.selectOption("resolution", "RAWHIDE");
			} else {
				this.selectOption("resolution", "NEXTRELEASE");
			}
		} else if (secondParameter.length > 0) {
			this.selectOption("bug_status", nextState);
			this.selectOption("resolution", secondParameter);
			return 0;
		} else {
			throw "Missing resolution for CLOSED status.";
		}
	}

	// Now closing bugs is done, what about the rest?
	if (nextState === "NEEDINFO") {
		this.setNeedinfoReporter();
	} else if (nextState === "ADDKEYWORD") {
		if (secondParameter.length === 0) {
			throw "Keyword has to be defined";
		}
		this.addKeyword(secondParameter);
	} else if (nextState === "ASSIGNED") {
		// Now we lie completely, we just set keyword Triaged,
		// this is not just plain ASSIGNED, but
		// modified according to
		// https://fedoraproject.org/wiki/BugZappers/Meetings/Minutes-2009-Oct-27
		// and
		// http://meetbot.fedoraproject.org/fedora-meeting/2009-11-24\
		// /fedora-meeting.2009-11-24-15.11.log.html
		// and
		// http://meetbot.fedoraproject.org/fedora-meeting/2009-11-24\
		// /fedora-meeting.2009-11-24-15.11.log.html
		// for F13 and later, ASSIGNED is "add Triaged keyword" (as well)
		// for <F13 it is "add both" (ASSIGNED status and Triaged keyword)
		if (!isInList(this.maintCCAddr, this.CCList)) {
			this.dok.getElementById("newcc").textContent = this.maintCCAddr;
		}
		if ((!this.isRHEL()) && (verNo < TriagedDistro)) {
			this.selectOption("bug_status", nextState);
		}
		this.setKeyword("Triaged");
	} else if (nextState === "QUERYSEL") {
		this.queryForSelection();
	} else if (nextState === "QUERYUP") {
		this.queryUpstream();
	} else if (nextState === "SENDUPSTREAM") {
		this.sendBugUpstream();
	} else if (nextState === "SETDEFASS") {
		if (secondParameter.length > 0) {
			this.changeOwner(secondParameter);
		}
	} else if (nextState === "CHIPMAGIC") {
		var splitArr = secondParameter.split("\t");
		this.fillInWhiteBoard(splitArr[0], splitArr[1]);
	} else if (nextState.length > 0) {
		this.selectOption("bug_status", nextState);
	}

	if (secondParameter === "ADDSELFCC") {
		this.dok.getElementById("addselfcc").checked = true;
	} else if (secondParameter === "NODEFAULTASSIGNEE") {
		this.dok.getElementById("set_default_assignee").removeAttribute(
				"checked");
	}
};

/**
 * 
 */
BzPage.prototype.parseBacktrace = function(ret) {
	var splitArray = ret.split("\n");
	var i = 0, ii = splitArray.length;
	var outStr = "", curLine = "", numStr = "";
	var lineCounter = 0, endLineNo = 0;

	while (i < ii) {
		if (signalHandlerRE.test(splitArray[i])) {
			break;
		}
		i++;
	}

	if (i < ii) {
		lineCounter = parseInt(frameNoRE.exec(splitArray[i])[1], 10);
		endLineNo = lineCounter + NumberOfFrames;
		curLine = splitArray[i];
		while ((lineCounter < endLineNo) && (curLine.trim().length > 0)
				&& (i < ii)) {
			outStr += curLine + '\n';
			numStr = frameNoRE.exec(curLine);
			if (numStr) {
				lineCounter = parseInt(numStr[1], 10);
			}
			i++;
			curLine = splitArray[i];
		}
	}
	return outStr;
};

/**
 * Main executable functioning actually building all buttons on the page --
 * separated into function, so that it could be called from onload method of the
 * XMLHttpRequest.
 * 
 * @param jsonList
 *            Array created from JSON
 * @return none
 */
BzPage.prototype.buildButtons = function(above, below) {
	// Generate a list of <input> elements in the page
	this.addNewButton(this.dok.getElementById("commit_middle"),
			"changeOwnerbtn", "Mark Triaged", "", "ASSIGNED",
			"NODEFAULTASSIGNEE");

	// THE MAIN BUTTON ROWS
	var commentBox = this.dok.getElementById("comment");
	var brElement = this.dok.createElement("br");
	commentBox.parentNode.normalize();
	commentBox.parentNode.insertBefore(brElement, commentBox);
	// this.generateToolBar(commentBox.previousSibling,above);
	this.generateToolBar(brElement, above);
	this.generateToolBar(this.originalButton, below);

	var commentArea = this.dok.getElementById("comment_status_commit");
	var brElementPlacer = commentArea.getElementsByTagName("br")[0];

	if (queryButtonAvailable || upstreamButtonAvailable) {
		brElementPlacer.parentNode.insertBefore(this.dok.createElement("br"),
				brElementPlacer);
	}

	if (queryButtonAvailable) {
		// Add query search button
		this.addNewButton(brElementPlacer, "newqueryintab", "Query for string",
				"", "QUERYSEL", "", false);
	}
	if (queryUpstreamBugsURLArray) {
		console.log("Adding upstream query search box");
		if (filterByRegexp(queryUpstreamBugsURLArray,this.component)) {
			this.addNewButton(brElementPlacer, "upstreamqueryintab", "Query upstream",
					"", "QUERYUP", "", false);
		}
	}
	// Button for upstreaming
	if (upstreamButtonAvailable) {
		this.addNewButton(brElementPlacer, "sendupstream", "Send upstream", "",
				"SENDUPSTREAM", "", false);
	}

	// var brElement2BMoved = this.dok.querySelector("#comment_status_commit
	// br:last-of-type");
	// var brWhereMove = this.dok.getElementsByClassName("status")[0];
	// brWhereMove.parentNode.insertBefore(brElement2BMoved.cloneNode(true),
	// brWhereMove);
	// brElement2BMoved.parentNode.removeChild(brElement2BMoved);

	// TODO Get compiz bugs as well
	if ((chipIDsGroupings.length > 0)
			&& this.maintCCAddr === "xgl-maint@redhat.com") {
		// Add find chip magic button
		var whiteboard_string = this.dok.getElementById("status_whiteboard").value;
		if (!/card_/.test(whiteboard_string)) {
			this.fillInChipMagic();
		}
	}

	// Add setting default assignee
	if ((this.defaultAssignee.length > 0)
			&& (this.defaultAssignee !== this.owner)) {
		this.addNewButton(
				this.dok.getElementById("bz_assignee_edit_container"),
				"setdefaultassigneebutton", "Def. Assignee", "", "SETDEFASS",
				this.defaultAssignee, false, true);
	}
};

BzPage.prototype.addLogRecord = function() {
	var rec = {};
	rec.date = new Date();
	rec.bugId = this.bugNo;
	rec.title = this.title;
	var comment = jetpack.tabs.focused.contentWindow.prompt(
			"Enter comments for this comment").trim();
	if (comment.length > 0) {
		rec.comment = comment;
		var recKey = getISODate(rec.date) + "+" + rec.bugId;
		var clearLogAElem = this.dok.getElementById("clearLogs");
		clearLogAElem.style.color = FullLogsColor;
		clearLogAElem.style.fontWeight = "bolder";
		if (myStorage.logs[recKey]) {
			myStorage.logs[recKey].comment += "<br/>\n" + comment;
		} else {
			myStorage.logs[recKey] = rec;
		}
		jetpack.storage.simple.sync();
	} else if (comment === null) {
		return null;
	}
};

BzPage.prototype.timeSheetRecordsPrinter = function(body, records) {
	// sort the records into temporary array
	var tmpArr = [];

	for ( var i in records) {
		if (records.hasOwnProperty(i)) {
			tmpArr.push( [ i, records[i] ]);
		}
	}
	tmpArr.sort(function(a, b) {
		return a[0] > b[0] ? 1 : -1;
	});

	var currentDay = "";
	// now print the array
	tmpArr
			.forEach(function(rec) {
				var x = rec[1];
				var dayStr = getISODate(x.date);
				if (dayStr != currentDay) {
					currentDay = dayStr;
					body.innerHTML += "<hr/><p><strong>" + currentDay
							+ "</strong></p>";
				}
				body.innerHTML += "<p><em><a href='https://bugzilla.redhat.com/show_bug.cgi?id="
						+ x.bugId
						+ "'>Bug "
						+ x.title
						+ "</a>"
						+ " </em>\n<br/>" + x.comment + "</p>";
			});
};

BzPage.prototype.generateTimeSheet = function(body) {
	var doc = body.ownerDocument;
	this.timeSheetRecordsPrinter(body, myStorage.logs);
};

// /////////////////////////////////////////////////////////////////////////////
function BzPage(doc) {
	this.dok = doc;
	var that = this;
	this.originalButton = this.dok.getElementById("commit");

	var loginArr = this.dok.querySelector("#header ul.links li:last-of-type").textContent
			.split("\n");
	this.login = loginArr[loginArr.length - 1].trim();

	if (myStorage.BZpassword) {
		this.password = myStorage.BZpassword;
	} else {
		var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
				.getService(Components.interfaces.nsIPromptService);
		var password = {
			value : ""
		}; // default the password to pass
		var check = {
			value : true
		}; // default the checkbox to true
		var result = prompts.promptPassword(null, "Title", "Enter password:",
				password, null, check);
		// result is true if OK was pressed, false if cancel was pressed.
		// password.value is
		// set if OK was pressed. The checkbox is not displayed.
		if (result) {
			this.password = password.value;
			myStorage.BZpassword = this.password;
			jetpack.storage.simple.sync();
		}
	}

	var bugNoTitle = this.dok.querySelector("#title > p").textContent.trim();
	this.bugNo = new RegExp("[0-9]+").exec(bugNoTitle)[0];

	this.reporter = this.getReporter();
	this.product = this.getOptionValue("product");
	this.component = this.getOptionValue("component");
	this.version = this.getVersion();
	this.title = this.dok.getElementById("short_desc_nonedit_display").textContent;
	var ITbutton = this.dok.getElementById("cf_issuetracker");
	this.its = ITbutton ? ITbutton.value.trim() : "";
	this.CCList = Array.map(this.dok.getElementById("cc"), function(item) {
		return item.value;
	});
	// TODO be careful about this, seems breaking for non-RH BugZappers,
	// but I cannot see why
	this.owner = this.dok.getElementById("bz_assignee_edit_container")
			.getElementsByClassName("fn")[0].textContent;
	this.defaultAssignee = filterByRegexp(defAssigneeList, this.component)
			.toLowerCase();
	this.maintCCAddr = filterByRegexp(AddrArray, this.component).toLowerCase();

	this.XorgLogAttList = [];
	this.XorgLogAttListIndex = 0;
	this.attachments = [];
	this.reqCounter = 0;
	var atts = this.dok.getElementById("attachment_table")
			.getElementsByTagName("tr");
	for ( var i = 1, ii = atts.length - 1; i < ii; i++) {
		this.attachments.push(this.parseAttachmentLine(atts[i]));
	}

	var badAttachments = this.attachments.filter(function(att, idx, arr) {
		return (isInList(att[2], badMIMEArray));
	});

	if (badAttachments.length > 0) {
		var titleElement = this.dok
				.getElementsByClassName("bz_alias_short_desc_container")[0];
		titleElement.style.backgroundColor = "olive";
		titleElement.appendChild(this.createFixAllButton(badAttachments));
		badAttachments.forEach(function(x, i, a) {
			this.addTextLink(x);
		}, this);
	}

	// Dig out backtrace
	this.btSnippet = "";

	console.log("parseAbrtBacktraces = " + parseAbrtBacktraces);
	if (parseAbrtBacktraces && AbrtRE.test(this.title)) {

		var notedLabel = this.dok.querySelector("label[for='newcc']");
		while (notedLabel.firstChild) {
			var node = notedLabel.removeChild(notedLabel.firstChild);
			notedLabel.parentNode.insertBefore(node, notedLabel);
		}
		notedLabel.parentNode.removeChild(notedLabel);

		var mainTitle = this.dok
				.getElementsByClassName("bz_alias_short_desc_container")[0];
		var abrtButton = this.dok.createElement("a");
		abrtButton.setAttribute("accesskey", "a");
		abrtButton.setAttribute("href", abrtQueryURL);
		abrtButton.textContent = "Abrt bugs";
		mainTitle.appendChild(abrtButton);

		if (this.idContainsWord("cf_devel_whiteboard", 'btparsed')) {
			this.addTextToTextBox('status_whiteboard', 'btparsed');
		}

		if (!(this.isTriaged() || this.idContainsWord("status_whiteboard",
				'btparsed'))) {
			var btAttachments = this.attachments
					.filter(function(att, idx, arr) {
						return (/backtrace/.test(att[0]));
					});
			// TODO we need to go through all backtrace attachments, but
			// just the first one will do for now, we would need to do async
			// parsing
			btAttachments.forEach(function(x) {
				attURL = "https://bugzilla.redhat.com/attachment.cgi?id="
						+ x[1];
				console.log("attURL = " + attURL);
				console.log("btSnippet = " + this.btSnippet);
				if (!this.btSnippet) {
					var btRaw = loadText(attURL, function(ret) {
						this.btSnippet = this.parseBacktrace(ret);
						if (this.btSnippet) {
							this.addTextToTextBox("comment", this.btSnippet);
							this.addTextToTextBox("status_whiteboard",
									"btparsed");
						}
					}, this);
				}
			}, this);
		}
	}

	// Take care of signature for Fedora bugzappers
	if (signatureFedoraString.length > 0) {
		this.dok.forms.namedItem("changeform").addEventListener("submit",
				function() {
					that.addTextToTextBox("comment", signatureFedoraString);
				}, false);
	}

	this.setBranding();
	this.checkComments();
	this.buildButtons(topRow, bottomRow);

	// UI for the customization JSON URL
	var additionalButtons = this.dok.getElementById("bugzilla-body")
			.getElementsByClassName("related_actions")[0];
	var customJSONURLUI = this.dok.createElement("li");
	customJSONURLUI.innerHTML = "\u00A0-\u00A0<a href='#' id='customJSONbutton'>"
			+ "BugZap config</a>";
	additionalButtons.appendChild(customJSONURLUI);
	this.dok.getElementById("customJSONbutton").addEventListener(
			"click",
			function() {
				var newURL = jetpack.tabs.focused.contentWindow
						.prompt("URL for your JSON customization file");
				if (newURL) {
					myStorage.JSONURL = newURL;
					jetpack.storage.simple.sync();
					jetpack.tabs.focused.contentWindow.location.reload();
				}
			}, false);

	// set default assignee on change of the component
	this.dok.getElementById("component").addEventListener(
			"change",
			function() {
				that.component = that.getOptionValue("component");
				that
						.changeOwner(filterByRegexp(defAssigneeList,
								that.component).toLowerCase());
			}, false);

	// offline-capable submit
	this.dok.forms.namedItem("changeform").addEventListener('submit',
			function(evt) {
				that.submitCallback.call(that, evt);
			}, false);

	// logging all submits for timesheet
	console.log("logSubmits = " + logSubmits);
	if (logSubmits) {
		this.dok.forms.namedItem("changeform").addEventListener("submit",
				function(evt) {
					if (that.addLogRecord() === null) {
						// FIXME doesn't work ... still submitting'
				evt.stopPropagation();
				evt.preventDefault();
			}
		}, false);

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

		var clearLogsUI = this.dok.createElement("li");
		clearLogsUI.innerHTML = "\u00A0-\u00A0<a href='#' id='clearLogs'>"
				+ "Clear logs</a>";
		additionalButtons.appendChild(clearLogsUI);
		var clearLogAElem = this.dok.getElementById("clearLogs");
		clearLogAElem.addEventListener("click", function() {
			myStorage.logs = {};
			jetpack.storage.simple.sync();
			this.style.color = EmptyLogsColor;
			clearLogAElem.style.fontWeight = "normal";
			console.log("mystorage.logs wiped out!");
		}, false);

		if (!myStorage.logs) {
			console.log("No myStorage.logs defined!");
			myStorage.logs = {};
		}

		if (myStorage.logs) {
			clearLogAElem.style.color = FullLogsColor;
			clearLogAElem.style.fontWeight = "bolder";
		} else {
			clearLogAElem.style.color = EmptyLogsColor;
			clearLogAElem.style.fontWeight = "normal";
		}
	}
}

var callback = function(doc) {
	var curPage = new BzPage(doc);
};

var options = {};
options.matches = [ "https://bugzilla.redhat.com/show_bug.cgi" ];
jetpack.pageMods.add(callback, options);