/*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*"); 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 += "\n"; xml += "\n"; xml += "" + method + "\n"; xml += "\n"; // do individual parameters for ( var i = 0; i < this.params.length; i++) { var data = this.params[i]; xml += "\n"; xml += "" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data), data) + "\n"; xml += "\n"; } xml += "\n"; xml += ""; 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 + ""; return xml; }; XMLRPCMessage.doBooleanXML = function(data) { var value = (data == true) ? 1 : 0; var xml = "" + value + ""; return xml; }; XMLRPCMessage.doDateXML = function(data) { var xml = ""; xml += dateToISO8601(data); xml += ""; return xml; }; XMLRPCMessage.doArrayXML = function(data) { var xml = "\n"; for ( var i = 0; i < data.length; i++) { xml += "" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]), data[i]) + "\n"; } xml += "\n"; return xml; }; XMLRPCMessage.doStructXML = function(data) { var xml = "\n"; for ( var i in data) { xml += "\n"; xml += "" + i + "\n"; xml += "" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]), data[i]) + "\n"; xml += "\n"; } xml += "\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 = "

" + title + "

"; 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 * *
* * 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 * 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 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 += "
\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 += "

" + currentDay + "

"; } body.innerHTML += "

Bug " + x.title + "" + " \n
" + x.comment + "

"; }); }; 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" + "BugZap config"; 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" + "Generate timesheet"; 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" + "Clear logs"; 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);