Sat, 03 Jan 2015 20:18:00 +0100
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 }