toolkit/content/aboutSupport.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
     7 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
     8 Cu.import("resource://gre/modules/Services.jsm");
     9 Cu.import("resource://gre/modules/Troubleshoot.jsm");
    10 Cu.import("resource://gre/modules/ResetProfile.jsm");
    12 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
    13                                   "resource://gre/modules/PluralForm.jsm");
    15 window.addEventListener("load", function onload(event) {
    16   try {
    17   window.removeEventListener("load", onload, false);
    18   Troubleshoot.snapshot(function (snapshot) {
    19     for (let prop in snapshotFormatters)
    20       snapshotFormatters[prop](snapshot[prop]);
    21   });
    22   populateResetBox();
    23   setupEventListeners();
    24   } catch (e) {
    25     Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
    26   }
    27 }, false);
    29 // Each property in this object corresponds to a property in Troubleshoot.jsm's
    30 // snapshot data.  Each function is passed its property's corresponding data,
    31 // and it's the function's job to update the page with it.
    32 let snapshotFormatters = {
    34   application: function application(data) {
    35     $("application-box").textContent = data.name;
    36     $("useragent-box").textContent = data.userAgent;
    37     $("supportLink").href = data.supportURL;
    38     let version = data.version;
    39     if (data.vendor)
    40       version += " (" + data.vendor + ")";
    41     $("version-box").textContent = version;
    42   },
    44 #ifdef MOZ_CRASHREPORTER
    45   crashes: function crashes(data) {
    46     let strings = stringBundle();
    47     let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
    48     $("crashes-title").textContent =
    49       PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle"))
    50                 .replace("#1", daysRange);
    51     let reportURL;
    52     try {
    53       reportURL = Services.prefs.getCharPref("breakpad.reportURL");
    54       // Ignore any non http/https urls
    55       if (!/^https?:/i.test(reportURL))
    56         reportURL = null;
    57     }
    58     catch (e) { }
    59     if (!reportURL) {
    60       $("crashes-noConfig").style.display = "block";
    61       $("crashes-noConfig").classList.remove("no-copy");
    62       return;
    63     }
    64     else {
    65       $("crashes-allReports").style.display = "block";
    66       $("crashes-allReports").classList.remove("no-copy");
    67     }
    69     if (data.pending > 0) {
    70       $("crashes-allReportsWithPending").textContent =
    71         PluralForm.get(data.pending, strings.GetStringFromName("pendingReports"))
    72                   .replace("#1", data.pending);
    73     }
    75     let dateNow = new Date();
    76     $.append($("crashes-tbody"), data.submitted.map(function (crash) {
    77       let date = new Date(crash.date);
    78       let timePassed = dateNow - date;
    79       let formattedDate;
    80       if (timePassed >= 24 * 60 * 60 * 1000)
    81       {
    82         let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
    83         let daysPassedString = strings.GetStringFromName("crashesTimeDays");
    84         formattedDate = PluralForm.get(daysPassed, daysPassedString)
    85                                   .replace("#1", daysPassed);
    86       }
    87       else if (timePassed >= 60 * 60 * 1000)
    88       {
    89         let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
    90         let hoursPassedString = strings.GetStringFromName("crashesTimeHours");
    91         formattedDate = PluralForm.get(hoursPassed, hoursPassedString)
    92                                   .replace("#1", hoursPassed);
    93       }
    94       else
    95       {
    96         let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
    97         let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes");
    98         formattedDate = PluralForm.get(minutesPassed, minutesPassedString)
    99                                   .replace("#1", minutesPassed);
   100       }
   101       return $.new("tr", [
   102         $.new("td", [
   103           $.new("a", crash.id, null, {href : reportURL + crash.id})
   104         ]),
   105         $.new("td", formattedDate)
   106       ]);
   107     }));
   108   },
   109 #endif
   111   extensions: function extensions(data) {
   112     $.append($("extensions-tbody"), data.map(function (extension) {
   113       return $.new("tr", [
   114         $.new("td", extension.name),
   115         $.new("td", extension.version),
   116         $.new("td", extension.isActive),
   117         $.new("td", extension.id),
   118       ]);
   119     }));
   120   },
   122   experiments: function experiments(data) {
   123     $.append($("experiments-tbody"), data.map(function (experiment) {
   124       return $.new("tr", [
   125         $.new("td", experiment.name),
   126         $.new("td", experiment.id),
   127         $.new("td", experiment.description),
   128         $.new("td", experiment.active),
   129         $.new("td", experiment.endDate),
   130         $.new("td", [
   131           $.new("a", experiment.detailURL, null, {href : experiment.detailURL,})
   132         ]),
   133       ]);
   134     }));
   135   },
   137   modifiedPreferences: function modifiedPreferences(data) {
   138     $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
   139       function ([name, value]) {
   140         return $.new("tr", [
   141           $.new("td", name, "pref-name"),
   142           // Very long preference values can cause users problems when they
   143           // copy and paste them into some text editors.  Long values generally
   144           // aren't useful anyway, so truncate them to a reasonable length.
   145           $.new("td", String(value).substr(0, 120), "pref-value"),
   146         ]);
   147       }
   148     ));
   149   },
   151   graphics: function graphics(data) {
   152     // graphics-info-properties tbody
   153     if ("info" in data) {
   154       let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) {
   155         return $.new("tr", [
   156           $.new("th", prop, "column"),
   157           $.new("td", String(val)),
   158         ]);
   159       });
   160       $.append($("graphics-info-properties"), trs);
   161       delete data.info;
   162     }
   164     // graphics-failures-tbody tbody
   165     if ("failures" in data) {
   166       $.append($("graphics-failures-tbody"), data.failures.map(function (val) {
   167         return $.new("tr", [$.new("td", val)]);
   168       }));
   169       delete data.failures;
   170     }
   172     // graphics-tbody tbody
   174     function localizedMsg(msgArray) {
   175       let nameOrMsg = msgArray.shift();
   176       if (msgArray.length) {
   177         // formatStringFromName logs an NS_ASSERTION failure otherwise that says
   178         // "use GetStringFromName".  Lame.
   179         try {
   180           return strings.formatStringFromName(nameOrMsg, msgArray,
   181                                               msgArray.length);
   182         }
   183         catch (err) {
   184           // Throws if nameOrMsg is not a name in the bundle.  This shouldn't
   185           // actually happen though, since msgArray.length > 1 => nameOrMsg is a
   186           // name in the bundle, not a message, and the remaining msgArray
   187           // elements are parameters.
   188           return nameOrMsg;
   189         }
   190       }
   191       try {
   192         return strings.GetStringFromName(nameOrMsg);
   193       }
   194       catch (err) {
   195         // Throws if nameOrMsg is not a name in the bundle.
   196       }
   197       return nameOrMsg;
   198     }
   200     let out = Object.create(data);
   201     let strings = stringBundle();
   203     out.acceleratedWindows =
   204       data.numAcceleratedWindows + "/" + data.numTotalWindows;
   205     if (data.windowLayerManagerType)
   206       out.acceleratedWindows += " " + data.windowLayerManagerType;
   207     if (data.windowLayerManagerRemote)
   208       out.acceleratedWindows += " (OMTC)";
   209     if (data.numAcceleratedWindowsMessage)
   210       out.acceleratedWindows +=
   211         " " + localizedMsg(data.numAcceleratedWindowsMessage);
   212     delete data.numAcceleratedWindows;
   213     delete data.numTotalWindows;
   214     delete data.windowLayerManagerType;
   215     delete data.numAcceleratedWindowsMessage;
   217     if ("direct2DEnabledMessage" in data) {
   218       out.direct2DEnabled = localizedMsg(data.direct2DEnabledMessage);
   219       delete data.direct2DEnabledMessage;
   220       delete data.direct2DEnabled;
   221     }
   223     if ("directWriteEnabled" in data) {
   224       out.directWriteEnabled = data.directWriteEnabled;
   225       if ("directWriteVersion" in data)
   226         out.directWriteEnabled += " (" + data.directWriteVersion + ")";
   227       delete data.directWriteEnabled;
   228       delete data.directWriteVersion;
   229     }
   231     if ("webglRendererMessage" in data) {
   232       out.webglRenderer = localizedMsg(data.webglRendererMessage);
   233       delete data.webglRendererMessage;
   234       delete data.webglRenderer;
   235     }
   237     let localizedOut = {};
   238     for (let prop in out) {
   239       let val = out[prop];
   240       if (typeof(val) == "string" && !val)
   241         // Ignore properties that are empty strings.
   242         continue;
   243       try {
   244         var localizedName = strings.GetStringFromName(prop);
   245       }
   246       catch (err) {
   247         // This shouldn't happen, but if there's a reported graphics property
   248         // that isn't in the string bundle, don't let it break the page.
   249         localizedName = prop;
   250       }
   251       localizedOut[localizedName] = val;
   252     }
   253     let trs = sortedArrayFromObject(localizedOut).map(function ([prop, val]) {
   254       return $.new("tr", [
   255         $.new("th", prop, "column"),
   256         $.new("td", val),
   257       ]);
   258     });
   259     $.append($("graphics-tbody"), trs);
   260   },
   262   javaScript: function javaScript(data) {
   263     $("javascript-incremental-gc").textContent = data.incrementalGCEnabled;
   264   },
   266   accessibility: function accessibility(data) {
   267     $("a11y-activated").textContent = data.isActive;
   268     $("a11y-force-disabled").textContent = data.forceDisabled || 0;
   269   },
   271   libraryVersions: function libraryVersions(data) {
   272     let strings = stringBundle();
   273     let trs = [
   274       $.new("tr", [
   275         $.new("th", ""),
   276         $.new("th", strings.GetStringFromName("minLibVersions")),
   277         $.new("th", strings.GetStringFromName("loadedLibVersions")),
   278       ])
   279     ];
   280     sortedArrayFromObject(data).forEach(
   281       function ([name, val]) {
   282         trs.push($.new("tr", [
   283           $.new("td", name),
   284           $.new("td", val.minVersion),
   285           $.new("td", val.version),
   286         ]));
   287       }
   288     );
   289     $.append($("libversions-tbody"), trs);
   290   },
   292   userJS: function userJS(data) {
   293     if (!data.exists)
   294       return;
   295     let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
   296     userJSFile.append("user.js");
   297     $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
   298     $("prefs-user-js-section").style.display = "";
   299     // Clear the no-copy class
   300     $("prefs-user-js-section").className = "";
   301   },
   302 };
   304 let $ = document.getElementById.bind(document);
   306 $.new = function $_new(tag, textContentOrChildren, className, attributes) {
   307   let elt = document.createElement(tag);
   308   if (className)
   309     elt.className = className;
   310   if (attributes) {
   311     for (let attrName in attributes)
   312       elt.setAttribute(attrName, attributes[attrName]);
   313   }
   314   if (Array.isArray(textContentOrChildren))
   315     this.append(elt, textContentOrChildren);
   316   else
   317     elt.textContent = String(textContentOrChildren);
   318   return elt;
   319 };
   321 $.append = function $_append(parent, children) {
   322   children.forEach(function (c) parent.appendChild(c));
   323 };
   325 function stringBundle() {
   326   return Services.strings.createBundle(
   327            "chrome://global/locale/aboutSupport.properties");
   328 }
   330 function sortedArrayFromObject(obj) {
   331   let tuples = [];
   332   for (let prop in obj)
   333     tuples.push([prop, obj[prop]]);
   334   tuples.sort(function ([prop1, v1], [prop2, v2]) prop1.localeCompare(prop2));
   335   return tuples;
   336 }
   338 function copyRawDataToClipboard(button) {
   339   if (button)
   340     button.disabled = true;
   341   try {
   342     Troubleshoot.snapshot(function (snapshot) {
   343       if (button)
   344         button.disabled = false;
   345       let str = Cc["@mozilla.org/supports-string;1"].
   346                 createInstance(Ci.nsISupportsString);
   347       str.data = JSON.stringify(snapshot, undefined, 2);
   348       let transferable = Cc["@mozilla.org/widget/transferable;1"].
   349                          createInstance(Ci.nsITransferable);
   350       transferable.init(getLoadContext());
   351       transferable.addDataFlavor("text/unicode");
   352       transferable.setTransferData("text/unicode", str, str.data.length * 2);
   353       Cc["@mozilla.org/widget/clipboard;1"].
   354         getService(Ci.nsIClipboard).
   355         setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
   356 #ifdef ANDROID
   357       // Present a toast notification.
   358       let message = {
   359         type: "Toast:Show",
   360         message: stringBundle().GetStringFromName("rawDataCopied"),
   361         duration: "short"
   362       };
   363       Services.androidBridge.handleGeckoMessage(message);
   364 #endif
   365     });
   366   }
   367   catch (err) {
   368     if (button)
   369       button.disabled = false;
   370     throw err;
   371   }
   372 }
   374 function getLoadContext() {
   375   return window.QueryInterface(Ci.nsIInterfaceRequestor)
   376                .getInterface(Ci.nsIWebNavigation)
   377                .QueryInterface(Ci.nsILoadContext);
   378 }
   380 function copyContentsToClipboard() {
   381   // Get the HTML and text representations for the important part of the page.
   382   let contentsDiv = $("contents");
   383   let dataHtml = contentsDiv.innerHTML;
   384   let dataText = createTextForElement(contentsDiv);
   386   // We can't use plain strings, we have to use nsSupportsString.
   387   let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
   388   let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
   389   let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);
   391   let transferable = Cc["@mozilla.org/widget/transferable;1"]
   392                        .createInstance(Ci.nsITransferable);
   393   transferable.init(getLoadContext());
   395   // Add the HTML flavor.
   396   transferable.addDataFlavor("text/html");
   397   ssHtml.data = dataHtml;
   398   transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2);
   400   // Add the plain text flavor.
   401   transferable.addDataFlavor("text/unicode");
   402   ssText.data = dataText;
   403   transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
   405   // Store the data into the clipboard.
   406   let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
   407                     .getService(Ci.nsIClipboard);
   408   clipboard.setData(transferable, null, clipboard.kGlobalClipboard);
   410 #ifdef ANDROID
   411   // Present a toast notification.
   412   let message = {
   413     type: "Toast:Show",
   414     message: stringBundle().GetStringFromName("textCopied"),
   415     duration: "short"
   416   };
   417   Services.androidBridge.handleGeckoMessage(message);
   418 #endif
   419 }
   421 // Return the plain text representation of an element.  Do a little bit
   422 // of pretty-printing to make it human-readable.
   423 function createTextForElement(elem) {
   424   let serializer = new Serializer();
   425   let text = serializer.serialize(elem);
   427   // Actual CR/LF pairs are needed for some Windows text editors.
   428 #ifdef XP_WIN
   429   text = text.replace(/\n/g, "\r\n");
   430 #endif
   432   return text;
   433 }
   435 function Serializer() {
   436 }
   438 Serializer.prototype = {
   440   serialize: function (rootElem) {
   441     this._lines = [];
   442     this._startNewLine();
   443     this._serializeElement(rootElem);
   444     this._startNewLine();
   445     return this._lines.join("\n").trim() + "\n";
   446   },
   448   // The current line is always the line that writing will start at next.  When
   449   // an element is serialized, the current line is updated to be the line at
   450   // which the next element should be written.
   451   get _currentLine() {
   452     return this._lines.length ? this._lines[this._lines.length - 1] : null;
   453   },
   455   set _currentLine(val) {
   456     return this._lines[this._lines.length - 1] = val;
   457   },
   459   _serializeElement: function (elem) {
   460     if (this._ignoreElement(elem))
   461       return;
   463     // table
   464     if (elem.localName == "table") {
   465       this._serializeTable(elem);
   466       return;
   467     }
   469     // all other elements
   471     let hasText = false;
   472     for (let child of elem.childNodes) {
   473       if (child.nodeType == Node.TEXT_NODE) {
   474         let text = this._nodeText(child);
   475         this._appendText(text);
   476         hasText = hasText || !!text.trim();
   477       }
   478       else if (child.nodeType == Node.ELEMENT_NODE)
   479         this._serializeElement(child);
   480     }
   482     // For headings, draw a "line" underneath them so they stand out.
   483     if (/^h[0-9]+$/.test(elem.localName)) {
   484       let headerText = (this._currentLine || "").trim();
   485       if (headerText) {
   486         this._startNewLine();
   487         this._appendText("-".repeat(headerText.length));
   488       }
   489     }
   491     // Add a blank line underneath block elements but only if they contain text.
   492     if (hasText) {
   493       let display = window.getComputedStyle(elem).getPropertyValue("display");
   494       if (display == "block") {
   495         this._startNewLine();
   496         this._startNewLine();
   497       }
   498     }
   499   },
   501   _startNewLine: function (lines) {
   502     let currLine = this._currentLine;
   503     if (currLine) {
   504       // The current line is not empty.  Trim it.
   505       this._currentLine = currLine.trim();
   506       if (!this._currentLine)
   507         // The current line became empty.  Discard it.
   508         this._lines.pop();
   509     }
   510     this._lines.push("");
   511   },
   513   _appendText: function (text, lines) {
   514     this._currentLine += text;
   515   },
   517   _serializeTable: function (table) {
   518     // Collect the table's column headings if in fact there are any.  First
   519     // check thead.  If there's no thead, check the first tr.
   520     let colHeadings = {};
   521     let tableHeadingElem = table.querySelector("thead");
   522     if (!tableHeadingElem)
   523       tableHeadingElem = table.querySelector("tr");
   524     if (tableHeadingElem) {
   525       let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
   526       // If there's a contiguous run of th's in the children starting from the
   527       // rightmost child, then consider them to be column headings.
   528       for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
   529         if (tableHeadingCols[i].localName != "th")
   530           break;
   531         colHeadings[i] = this._nodeText(tableHeadingCols[i]).trim();
   532       }
   533     }
   534     let hasColHeadings = Object.keys(colHeadings).length > 0;
   535     if (!hasColHeadings)
   536       tableHeadingElem = null;
   538     let trs = table.querySelectorAll("table > tr, tbody > tr");
   539     let startRow =
   540       tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;
   542     if (startRow >= trs.length)
   543       // The table's empty.
   544       return;
   546     if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) {
   547       // Use column headings.  Print each tr as a multi-line chunk like:
   548       //   Heading 1: Column 1 value
   549       //   Heading 2: Column 2 value
   550       for (let i = startRow; i < trs.length; i++) {
   551         if (this._ignoreElement(trs[i]))
   552           continue;
   553         let children = trs[i].querySelectorAll("td");
   554         for (let j = 0; j < children.length; j++) {
   555           let text = "";
   556           if (colHeadings[j])
   557             text += colHeadings[j] + ": ";
   558           text += this._nodeText(children[j]).trim();
   559           this._appendText(text);
   560           this._startNewLine();
   561         }
   562         this._startNewLine();
   563       }
   564       return;
   565     }
   567     // Don't use column headings.  Assume the table has only two columns and
   568     // print each tr in a single line like:
   569     //   Column 1 value: Column 2 value
   570     for (let i = startRow; i < trs.length; i++) {
   571       if (this._ignoreElement(trs[i]))
   572         continue;
   573       let children = trs[i].querySelectorAll("th,td");
   574       let rowHeading = this._nodeText(children[0]).trim();
   575       this._appendText(rowHeading + ": " + this._nodeText(children[1]).trim());
   576       this._startNewLine();
   577     }
   578     this._startNewLine();
   579   },
   581   _ignoreElement: function (elem) {
   582     return elem.classList.contains("no-copy");
   583   },
   585   _nodeText: function (node) {
   586     return node.textContent.replace(/\s+/g, " ");
   587   },
   588 };
   590 function openProfileDirectory() {
   591   // Get the profile directory.
   592   let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
   593   let profileDir = currProfD.path;
   595   // Show the profile directory.
   596   let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
   597                                            "nsILocalFile", "initWithPath");
   598   new nsLocalFile(profileDir).reveal();
   599 }
   601 /**
   602  * Profile reset is only supported for the default profile if the appropriate migrator exists.
   603  */
   604 function populateResetBox() {
   605   if (ResetProfile.resetSupported())
   606     $("reset-box").style.visibility = "visible";
   607 }
   609 /**
   610  * Set up event listeners for buttons.
   611  */
   612 function setupEventListeners(){
   613   $("show-update-history-button").addEventListener("click", function (event) {
   614     var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
   615       prompter.showUpdateHistory(window);
   616   });
   617   $("reset-box-button").addEventListener("click", function (event){
   618     ResetProfile.openConfirmationDialog(window);
   619   });
   620   $("copy-raw-data-to-clipboard").addEventListener("click", function (event){
   621     copyRawDataToClipboard(this);
   622   });
   623   $("copy-to-clipboard").addEventListener("click", function (event){
   624     copyContentsToClipboard();
   625   });
   626   $("profile-dir-button").addEventListener("click", function (event){
   627     openProfileDirectory();
   628   });
   629 }

mercurial