/*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 var RHColor = new Color(158, 41, 43); // RGB 158, 41, 43; HSL 359, 1, 39 var FedoraColor = new Color(0, 40, 103); // RGB 0, 40, 103; HSL 359, 1, 39 var RawhideColor = new Color(0, 119, 0); // or "green", or RGB 0, 119, 0, or HSL 120, 0, 23 var RHITColor = new Color(102, 0, 102); // RGB 102, 0, 102; HSL 300, 0, 20 var SalmonPink = new Color(255, 224, 176); // RGB 255, 224, 176; HSL 36, 2, 85 var ReporterColor = new Color(255, 255, 166); // RGB 255, 255, 166; HSL 60, 2, 83 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*$"); var ChipsetRE = new RegExp("^\\(--\\) ([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 queryButtonAvailable = false; var upstreamButtonAvailable = 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; upstreamBugzillasArray = response.upstreamBugzillas; chipIDsGroupings = response.chipIDsGroupings; topRow = response.topRow; bottomRow = response.bottomRow; }); // Get card translation table loadJSON(PCIIDsURL, function (response) { PCI_ID_Array = response; }); //============================================================== /** * 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); }; // ============================================================================ // 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 /** * 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)); }; /** * Set additional keyword if it isn't there * * @param str string with the keyword * @return none */ BzPage.prototype.setKeyword = function (str) { // Could be probably rewrote to use this.addTextToTextBox, but // it would be too complicated with a little gain. var keywordInput = this.dok.getElementById('keywords'); var kwd = keywordInput.value.trim(); var strRE = new RegExp(str); if (!strRE.test(kwd)) { keywordInput.value = kwd ? kwd + ", " + str : str; } }; BzPage.prototype.getOptionValue = function (id) { return this.dok.getElementById(id).value; }; /* 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()); /* FIXME what is element with type="select-one" ??? 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 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); }; /** * 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"; } // don't remove the current content of the comment box, // just behave accordingly if (textBox.value.length > 0) { textBox.value = textBox.value + separator; } textBox.value = textBox.value + string2BAdded; }; 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); }; BzPage.prototype.isTriaged = function() { if (this.version > 11) { return this.hasKeyword("Triaged"); } else { return this.dok.getElementById("bug_status"). value.toUpperCase() !== "NEW"; } }; /** * 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"; } }; /** */ BzPage.prototype.groupIDs = function (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 */ BzPage.prototype.checkChipStringFromID = function (manufacturerNo,cardNo) { var soughtID = (manufacturerNo+","+cardNo).toUpperCase(); var outList = PCI_ID_Array[soughtID]; if (outList) { return outList; } else { return ""; } }; /** * 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) { var outStr = ""; var cardIDStr = ""; var cardIDArr = []; chipSwitchboard: if (driverStr === "RADEON") { var cardID = iLine.replace(ATIgetIDRE,"$1"); cardIDArr = this.checkChipStringFromID("1002",cardID); if (cardIDArr.length > 0) { cardIDStr = cardIDArr[0]; if (cardIDArr[1]) { optionStr = cardIDArr[1]; outStr = this.groupIDs(driverStr,cardIDStr)+"/" + optionStr; } else { outStr = this.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 = this.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) { console.log("No Xorg.0.log attachments found."); 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); }); 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); } }; /** * */ BzPage.prototype.sendBugUpstream = function() { var url = filterByRegexp(upstreamBugzillasArray, this.getOptionValue("component")); var text = this.dok.getElementById("short_desc_nonedit_display"). textContent.trim() + "\n\n"; text += this.collectComments(); jetpack.clipboard.set(text); var ret = jetpack.tabs.open(url); console.log("opening new tab returned"); console.log(ret); }; /** * 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(); // getting id // $("a:contains('Details')", inElem); // FIXME isn't there more simple way to replace above? querySelector? 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","reASSIGN","","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); } // 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); } }; /////////////////////////////////////////////////////////////////////////////// function BzPage(doc) { this.dok = doc; var that = this; this.originalButton = this.dok.getElementById("commit"); console.log(this.dok.querySelector("#header ul.links li:last-of-type")); var loginArr = this.dok. querySelector("#header ul.links li:last-of-type"). textContent.split("\n"); this.login = loginArr[loginArr.length-1].trim(); // the following is quite awful brutal hack console.log("on __parent__ navigator.online = " + jetpack.__parent__.navigator.onLine); 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(); 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 = ""; var bugTitle = this.dok.getElementById("short_desc_nonedit_display"). textContent; if (AbrtRE.test(bugTitle)) { 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); var develWhiteboardElem = this.dok.getElementById("cf_devel_whiteboard"); var develWhiteboard = develWhiteboardElem ? develWhiteboardElem. value : "btparsed"; if (!(this.hasKeyword("Triaged") || (/btparsed/.test(develWhiteboard)))) { 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("cf_devel_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); } 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);