diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
index c0e188586d9..052a23c8f02 100644
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1882,6 +1882,12 @@ pref("browser.tabs.remote.autostart.1", false);
pref("browser.tabs.remote.autostart.2", true);
#endif
+// For the about:tabcrashed page
+pref("browser.tabs.crashReporting.sendReport", true);
+pref("browser.tabs.crashReporting.includeURL", false);
+pref("browser.tabs.crashReporting.emailMe", false);
+pref("browser.tabs.crashReporting.email", "");
+
#ifdef NIGHTLY_BUILD
#ifndef MOZ_MULET
pref("layers.async-pan-zoom.enabled", true);
diff --git a/browser/base/content/aboutTabCrashed.js b/browser/base/content/aboutTabCrashed.js
index a0abedc0e58..5ff0071a46e 100644
--- a/browser/base/content/aboutTabCrashed.js
+++ b/browser/base/content/aboutTabCrashed.js
@@ -3,27 +3,66 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function parseQueryString() {
- let url = document.documentURI;
- let queryString = url.replace(/^about:tabcrashed?e=tabcrashed/, "");
+ let URL = document.documentURI;
+ let queryString = URL.replace(/^about:tabcrashed?e=tabcrashed/, "");
let titleMatch = queryString.match(/d=([^&]*)/);
- return titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "";
+ let URLMatch = queryString.match(/u=([^&]*)/);
+ return {
+ title: titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "",
+ URL: URLMatch && URLMatch[1] ? decodeURIComponent(URLMatch[1]) : "",
+ };
}
-document.title = parseQueryString();
+function displayUI() {
+ if (!hasReport()) {
+ return;
+ }
-function shouldSendReport() {
- if (!document.documentElement.classList.contains("crashDumpAvailable"))
- return false;
- return document.getElementById("sendReport").checked;
+ let sendCrashReport = document.getElementById("sendReport").checked;
+ let container = document.getElementById("crash-reporter-container");
+ container.hidden = !sendCrashReport;
+}
+
+function hasReport() {
+ return document.documentElement.classList.contains("crashDumpAvailable");
}
function sendEvent(message) {
+ let comments = "";
+ let email = "";
+ let URL = "";
+ let sendCrashReport = false;
+ let emailMe = false;
+ let includeURL = false;
+
+ if (hasReport()) {
+ sendCrashReport = document.getElementById("sendReport").checked;
+ if (sendCrashReport) {
+ comments = document.getElementById("comments").value.trim();
+
+ includeURL = document.getElementById("includeURL").checked;
+ if (includeURL) {
+ URL = parseQueryString().URL.trim();
+ }
+
+ emailMe = document.getElementById("emailMe").checked;
+ if (emailMe) {
+ email = document.getElementById("email").value.trim();
+ }
+ }
+ }
+
let event = new CustomEvent("AboutTabCrashedMessage", {
bubbles: true,
detail: {
message,
- sendCrashReport: shouldSendReport(),
+ sendCrashReport,
+ comments,
+ email,
+ emailMe,
+ includeURL,
+ URL,
},
});
@@ -42,6 +81,17 @@ function restoreAll() {
sendEvent("restoreAll");
}
+document.title = parseQueryString().title;
+
// Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
var event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
document.dispatchEvent(event);
+
+addEventListener("DOMContentLoaded", function() {
+ let sendReport = document.getElementById("sendReport");
+ sendReport.addEventListener("click", function() {
+ displayUI();
+ });
+
+ displayUI();
+});
diff --git a/browser/base/content/aboutTabCrashed.xhtml b/browser/base/content/aboutTabCrashed.xhtml
index ec1f37161bf..9de02cc2102 100644
--- a/browser/base/content/aboutTabCrashed.xhtml
+++ b/browser/base/content/aboutTabCrashed.xhtml
@@ -37,8 +37,22 @@
&tabCrashed.message;
&tabCrashed.reportSent;
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index ed10547395f..c3e933b338e 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1166,7 +1166,15 @@ var gBrowserInit = {
let browser = gBrowser.getBrowserForDocument(ownerDoc);
#ifdef MOZ_CRASHREPORTER
if (event.detail.sendCrashReport) {
- TabCrashReporter.submitCrashReport(browser);
+ TabCrashReporter.submitCrashReport(browser, {
+ comments: event.detail.comments,
+ email: event.detail.email,
+ emailMe: event.detail.emailMe,
+ includeURL: event.detail.includeURL,
+ URL: event.detail.URL,
+ });
+ } else {
+ TabCrashReporter.dontSubmitCrashReport();
}
#endif
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index e2e0b88254b..7eb4300b733 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5355,13 +5355,11 @@
]]>
A%20regular,%20everyday,%20normal%20page.";
+const COMMENTS = "Here's my test comment!";
+const EMAIL = "foo@privacy.com";
+
+/**
+ * For an nsIPropertyBag, returns the value for a given
+ * key.
+ *
+ * @param bag
+ * The nsIPropertyBag to retrieve the value from
+ * @param key
+ * The key that we want to get the value for from the
+ * bag
+ * @returns The value corresponding to the key from the bag,
+ * or null if the value could not be retrieved (for
+ * example, if no value is set at that key).
+*/
+function getPropertyBagValue(bag, key) {
+ try {
+ let val = bag.getProperty(key);
+ return val;
+ } catch(e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ return null;
+}
+
+/**
+ * Returns a Promise that resolves once a crash report has
+ * been submitted. This function will also test the crash
+ * reports extra data to see if it matches expectedExtra.
+ *
+ * @param expectedExtra
+ * An Object whose key-value pairs will be compared
+ * against the key-value pairs in the extra data of the
+ * crash report. A test failure will occur if there is
+ * a mismatch.
+ *
+ * Note that this will only check the values that exist
+ * in expectedExtra. It's possible that the crash report
+ * will contain other extra information that is not
+ * compared against.
+ * @returns Promise
+ */
+function promiseCrashReport(expectedExtra) {
+ return Task.spawn(function*() {
+ info("Starting wait on crash-report-status");
+ let [subject, data] =
+ yield TestUtils.topicObserved("crash-report-status", (subject, data) => {
+ return data == "success";
+ });
+ info("Topic observed!");
+
+ if (!(subject instanceof Ci.nsIPropertyBag2)) {
+ throw new Error("Subject was not a Ci.nsIPropertyBag2");
+ }
+
+ let remoteID = getPropertyBagValue(subject, "serverCrashID");
+ if (!remoteID) {
+ throw new Error("Report should have a server ID");
+ }
+
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(remoteID + ".txt");
+ if (!file.exists()) {
+ throw new Error("Report should have been received by the server");
+ }
+
+ file.remove(false);
+
+ let extra = getPropertyBagValue(subject, "extra");
+ if (!(extra instanceof Ci.nsIPropertyBag2)) {
+ throw new Error("extra was not a Ci.nsIPropertyBag2");
+ }
+
+ info("Iterating crash report extra keys");
+ let enumerator = extra.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let key = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
+ let value = extra.getPropertyAsAString(key);
+ if (key in expectedExtra) {
+ is(value, expectedExtra[key],
+ `Crash report had the right extra value for ${key}`);
+ }
+ }
+ });
+}
+
+/**
+ * Sets up the browser to send crash reports to the local crash report
+ * testing server.
+ */
+add_task(function* setup() {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash
+ // reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ registerCleanupFunction(function() {
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ });
+});
+
+/**
+ * This function returns a Promise that resolves once the following
+ * actions have taken place:
+ *
+ * 1) A new tab is opened up at PAGE
+ * 2) The tab is crashed
+ * 3) The about:tabcrashed page's fields are set in accordance with
+ * fieldValues
+ * 4) The tab is restored
+ * 5) A crash report is received from the testing server
+ * 6) Any tab crash prefs that were overwritten are reset
+ *
+ * @param fieldValues
+ * An Object describing how to set the about:tabcrashed
+ * fields. The following properties are accepted:
+ *
+ * comments (String)
+ * The comments to put in the comment textarea
+ * email (String)
+ * The email address to put in the email address input
+ * emailMe (bool)
+ * The checked value of the "Email me" checkbox
+ * includeURL (bool)
+ * The checked value of the "Include URL" checkbox
+ *
+ * If any of these fields are missing, the defaults from
+ * the user preferences are used.
+ * @param expectedExtra
+ * An Object describing the expected values that the submitted
+ * crash report's extra data should contain.
+ * @returns Promise
+ */
+function crashTabTestHelper(fieldValues, expectedExtra) {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let prefs = TabCrashReporter.prefs;
+ let originalSendReport = prefs.getBoolPref("sendReport");
+ let originalEmailMe = prefs.getBoolPref("emailMe");
+ let originalIncludeURL = prefs.getBoolPref("includeURL");
+ let originalEmail = prefs.getCharPref("email");
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+ let doc = browser.contentDocument;
+
+ // Since about:tabcrashed will run in the parent process, we can safely
+ // manipulate its DOM nodes directly
+ let comments = doc.getElementById("comments");
+ let email = doc.getElementById("email");
+ let emailMe = doc.getElementById("emailMe");
+ let includeURL = doc.getElementById("includeURL");
+
+ if (fieldValues.hasOwnProperty("comments")) {
+ comments.value = fieldValues.comments;
+ }
+
+ if (fieldValues.hasOwnProperty("email")) {
+ email.value = fieldValues.email;
+ }
+
+ if (fieldValues.hasOwnProperty("emailMe")) {
+ emailMe.checked = fieldValues.emailMe;
+ }
+
+ if (fieldValues.hasOwnProperty("includeURL")) {
+ includeURL.checked = fieldValues.includeURL;
+ }
+
+ let crashReport = promiseCrashReport(expectedExtra);
+ let restoreTab = browser.contentDocument.getElementById("restoreTab");
+ restoreTab.click();
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+ yield crashReport;
+
+ // Submitting the crash report may have set some prefs regarding how to
+ // send tab crash reports. Let's reset them for the next test.
+ prefs.setBoolPref("sendReport", originalSendReport);
+ prefs.setBoolPref("emailMe", originalEmailMe);
+ prefs.setBoolPref("includeURL", originalIncludeURL);
+ prefs.setCharPref("email", originalEmail);
+ });
+}
+
+/**
+ * Tests what we send with the crash report by default. By default, we do not
+ * send any comments, the URL of the crashing page, or the email address of
+ * the user.
+ */
+add_task(function* test_default() {
+ yield crashTabTestHelper({}, {
+ "Comments": "",
+ "URL": "",
+ "Email": "",
+ });
+});
+
+/**
+ * Test just sending a comment.
+ */
+add_task(function* test_just_a_comment() {
+ yield crashTabTestHelper({
+ comments: COMMENTS,
+ }, {
+ "Comments": COMMENTS,
+ "URL": "",
+ "Email": "",
+ });
+});
+
+/**
+ * Test that we don't send email if emailMe is unchecked
+ */
+add_task(function* test_no_email() {
+ yield crashTabTestHelper({
+ email: EMAIL,
+ emailMe: false,
+ }, {
+ "Comments": "",
+ "URL": "",
+ "Email": "",
+ });
+});
+
+/**
+ * Test that we can send an email address if emailMe is checked
+ */
+add_task(function* test_yes_email() {
+ yield crashTabTestHelper({
+ email: EMAIL,
+ emailMe: true,
+ }, {
+ "Comments": "",
+ "URL": "",
+ "Email": EMAIL,
+ });
+});
+
+/**
+ * Test that we will send the URL of the page if includeURL is checked.
+ */
+add_task(function* test_send_URL() {
+ yield crashTabTestHelper({
+ includeURL: true,
+ }, {
+ "Comments": "",
+ "URL": PAGE,
+ "Email": "",
+ });
+});
+
+/**
+ * Test that we can send comments, the email address, and the URL
+ */
+add_task(function* test_send_all() {
+ yield crashTabTestHelper({
+ includeURL: true,
+ emailMe: true,
+ email: EMAIL,
+ comments: COMMENTS,
+ }, {
+ "Comments": COMMENTS,
+ "URL": PAGE,
+ "Email": EMAIL,
+ });
+});
+
+/**
+ * Test that if we have an email address stored in prefs, and we decide
+ * not to submit the email address in the next crash report, that we
+ * clear the email address.
+ */
+add_task(function* test_clear_email() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let prefs = TabCrashReporter.prefs;
+ let originalSendReport = prefs.getBoolPref("sendReport");
+ let originalEmailMe = prefs.getBoolPref("emailMe");
+ let originalIncludeURL = prefs.getBoolPref("includeURL");
+ let originalEmail = prefs.getCharPref("email");
+
+ // Pretend that we stored an email address from the previous
+ // crash
+ prefs.setCharPref("email", EMAIL);
+ prefs.setBoolPref("emailMe", true);
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+ let doc = browser.contentDocument;
+
+ // Since about:tabcrashed will run in the parent process, we can safely
+ // manipulate its DOM nodes directly
+ let emailMe = doc.getElementById("emailMe");
+ emailMe.checked = false;
+
+ let crashReport = promiseCrashReport({
+ Email: "",
+ });
+
+ let restoreTab = browser.contentDocument.getElementById("restoreTab");
+ restoreTab.click();
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+ yield crashReport;
+
+ is(prefs.getCharPref("email"), "", "No email address should be stored");
+
+ // Submitting the crash report may have set some prefs regarding how to
+ // send tab crash reports. Let's reset them for the next test.
+ prefs.setBoolPref("sendReport", originalSendReport);
+ prefs.setBoolPref("emailMe", originalEmailMe);
+ prefs.setBoolPref("includeURL", originalIncludeURL);
+ prefs.setCharPref("email", originalEmail);
+ });
+});
diff --git a/browser/components/loop/content/shared/js/views.js b/browser/components/loop/content/shared/js/views.js
index b6d4e1899af..ddb45e5d2ba 100644
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -120,10 +120,12 @@ loop.shared.views = (function(_, mozL10n) {
_handleShareTabs: function() {
this._startScreenShare("browser");
+ this.hideDropdownMenu();
},
_handleShareWindows: function() {
this._startScreenShare("window");
+ this.hideDropdownMenu();
},
_getTitle: function() {
diff --git a/browser/components/loop/content/shared/js/views.jsx b/browser/components/loop/content/shared/js/views.jsx
index c58ef9f3f9c..9b61b39a008 100644
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -120,10 +120,12 @@ loop.shared.views = (function(_, mozL10n) {
_handleShareTabs: function() {
this._startScreenShare("browser");
+ this.hideDropdownMenu();
},
_handleShareWindows: function() {
this._startScreenShare("window");
+ this.hideDropdownMenu();
},
_getTitle: function() {
diff --git a/browser/components/loop/test/shared/views_test.js b/browser/components/loop/test/shared/views_test.js
index ac44e4be118..ebac00ac34d 100644
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -176,6 +176,22 @@ describe("loop.shared.views", function() {
new sharedActions.StartScreenShare({ type: "browser" }));
});
+ it("should close the dropdown on 'browser' option click", function() {
+ var comp = TestUtils.renderIntoDocument(
+ React.createElement(sharedViews.ScreenShareControlButton, {
+ dispatcher: dispatcher,
+ visible: true,
+ state: SCREEN_SHARE_STATES.INACTIVE
+ }));
+
+ sandbox.stub(comp, "hideDropdownMenu");
+
+ TestUtils.Simulate.click(comp.getDOMNode().querySelector(
+ ".screen-share-menu > li"));
+
+ sinon.assert.calledOnce(comp.hideDropdownMenu);
+ });
+
it("should dispatch a 'window' StartScreenShare action on option click",
function() {
var comp = TestUtils.renderIntoDocument(
@@ -193,6 +209,22 @@ describe("loop.shared.views", function() {
new sharedActions.StartScreenShare({ type: "window" }));
});
+ it("should close the dropdown on 'window' option click", function() {
+ var comp = TestUtils.renderIntoDocument(
+ React.createElement(sharedViews.ScreenShareControlButton, {
+ dispatcher: dispatcher,
+ visible: true,
+ state: SCREEN_SHARE_STATES.INACTIVE
+ }));
+
+ sandbox.stub(comp, "hideDropdownMenu");
+
+ TestUtils.Simulate.click(comp.getDOMNode().querySelector(
+ ".screen-share-menu > li:last-child"));
+
+ sinon.assert.calledOnce(comp.hideDropdownMenu);
+ });
+
it("should have the 'window' option enabled", function() {
var comp = TestUtils.renderIntoDocument(
React.createElement(sharedViews.ScreenShareControlButton, {
diff --git a/browser/components/sessionstore/test/browser_async_flushes.js b/browser/components/sessionstore/test/browser_async_flushes.js
index f5496e9e60e..fe6e5b639ff 100644
--- a/browser/components/sessionstore/test/browser_async_flushes.js
+++ b/browser/components/sessionstore/test/browser_async_flushes.js
@@ -69,7 +69,7 @@ add_task(function* test_crash() {
// the content process. The "crash" message makes it first so that we don't
// get a chance to process the flush. The TabStateFlusher however should be
// notified so that the flush still completes.
- let promise1 = crashBrowser(browser);
+ let promise1 = BrowserTestUtils.crashBrowser(browser);
let promise2 = TabStateFlusher.flush(browser);
yield Promise.all([promise1, promise2]);
diff --git a/browser/components/sessionstore/test/browser_crashedTabs.js b/browser/components/sessionstore/test/browser_crashedTabs.js
index 71eca9e0baa..72df2d2a6c4 100644
--- a/browser/components/sessionstore/test/browser_crashedTabs.js
+++ b/browser/components/sessionstore/test/browser_crashedTabs.js
@@ -117,7 +117,7 @@ add_task(function test_crash_page_not_in_history() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
@@ -146,7 +146,7 @@ add_task(function test_revived_history_from_remote() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
// Browse to a new site that will cause the browser to
// become remote again.
@@ -185,7 +185,7 @@ add_task(function test_revived_history_from_non_remote() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
// Browse to a new site that will not cause the browser to
// become remote again.
@@ -235,7 +235,7 @@ add_task(function test_revive_tab_from_session_store() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too.");
// Use SessionStore to revive the tab
@@ -286,7 +286,7 @@ add_task(function test_revive_all_tabs_from_session_store() {
yield TabStateFlusher.flush(browser2);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too.");
// Use SessionStore to revive all the tabs
@@ -331,7 +331,7 @@ add_task(function test_close_tab_after_crash() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
let promise = promiseEvent(gBrowser.tabContainer, "TabClose");
@@ -359,7 +359,7 @@ add_task(function* test_hide_restore_all_button() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
let doc = browser.contentDocument;
let restoreAllButton = doc.getElementById("restoreAll");
@@ -375,7 +375,7 @@ add_task(function* test_hide_restore_all_button() {
yield promiseBrowserLoaded(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
doc = browser.contentDocument;
restoreAllButton = doc.getElementById("restoreAll");
@@ -405,7 +405,7 @@ add_task(function* test_aboutcrashedtabzoom() {
yield TabStateFlusher.flush(browser);
// Crash the tab
- yield crashBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
ok(ZoomManager.getZoomForBrowser(browser) === 1, "zoom should have reset on crash");
diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js
index da4c0328ee0..af326cc7c70 100644
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -538,112 +538,6 @@ function promiseRemoveTab(tab) {
return BrowserTestUtils.removeTab(tab);
}
-/**
- * Returns a Promise that resolves once a remote has experienced
- * a crash. Also does the job of cleaning up the minidump of the crash.
- *
- * @param browser
- * The that will crash
- * @return Promise
- */
-function crashBrowser(browser) {
- /**
- * Returns the directory where crash dumps are stored.
- *
- * @return nsIFile
- */
- function getMinidumpDirectory() {
- let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
- dir.append("minidumps");
- return dir;
- }
-
- /**
- * Removes a file from a directory. This is a no-op if the file does not
- * exist.
- *
- * @param directory
- * The nsIFile representing the directory to remove from.
- * @param filename
- * A string for the file to remove from the directory.
- */
- function removeFile(directory, filename) {
- let file = directory.clone();
- file.append(filename);
- if (file.exists()) {
- file.remove(false);
- }
- }
-
- // This frame script is injected into the remote browser, and used to
- // intentionally crash the tab. We crash by using js-ctypes and dereferencing
- // a bad pointer. The crash should happen immediately upon loading this
- // frame script.
- let frame_script = () => {
- const Cu = Components.utils;
- Cu.import("resource://gre/modules/ctypes.jsm");
-
- let dies = function() {
- privateNoteIntentionalCrash();
- let zero = new ctypes.intptr_t(8);
- let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
- badptr.contents
- };
-
- dump("Et tu, Brute?");
- dies();
- }
-
- let crashCleanupPromise = new Promise((resolve, reject) => {
- let observer = (subject, topic, data) => {
- is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
- ok(subject instanceof Ci.nsIPropertyBag2,
- 'Subject implements nsIPropertyBag2.');
- // we might see this called as the process terminates due to previous tests.
- // We are only looking for "abnormal" exits...
- if (!subject.hasKey("abnormal")) {
- info("This is a normal termination and isn't the one we are looking for...");
- return;
- }
-
- let dumpID;
- if ('nsICrashReporter' in Ci) {
- dumpID = subject.getPropertyAsAString('dumpID');
- ok(dumpID, "dumpID is present and not an empty string");
- }
-
- if (dumpID) {
- let minidumpDirectory = getMinidumpDirectory();
- removeFile(minidumpDirectory, dumpID + '.dmp');
- removeFile(minidumpDirectory, dumpID + '.extra');
- }
-
- Services.obs.removeObserver(observer, 'ipc:content-shutdown');
- info("Crash cleaned up");
- resolve();
- };
-
- Services.obs.addObserver(observer, 'ipc:content-shutdown');
- });
-
- let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => {
- browser.addEventListener("AboutTabCrashedLoad", function onCrash() {
- browser.removeEventListener("AboutTabCrashedLoad", onCrash, false);
- info("about:tabcrashed loaded");
- resolve();
- }, false, true);
- });
-
- // This frame script will crash the remote browser as soon as it is
- // evaluated.
- let mm = browser.messageManager;
- mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
- return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]).then(() => {
- let tab = gBrowser.getTabForBrowser(browser);
- is(tab.getAttribute("crashed"), "true", "Tab should be marked as crashed");
- });
-}
-
// Write DOMSessionStorage data to the given browser.
function modifySessionStorage(browser, data, options = {}) {
return ContentTask.spawn(browser, [data, options], function* ([data, options]) {
diff --git a/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
index 609e001989e..eac62d7211f 100644
--- a/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
@@ -5,6 +5,11 @@
+
+
+
+
+
diff --git a/browser/modules/ContentCrashReporters.jsm b/browser/modules/ContentCrashReporters.jsm
index 34a1bd75b74..7c43d9fbb20 100644
--- a/browser/modules/ContentCrashReporters.jsm
+++ b/browser/modules/ContentCrashReporters.jsm
@@ -17,6 +17,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
"resource://gre/modules/CrashSubmit.jsm");
this.TabCrashReporter = {
+ get prefs() {
+ delete this.prefs;
+ return this.prefs = Services.prefs.getBranch("browser.tabs.crashReporting.");
+ },
+
init: function () {
if (this.initialized)
return;
@@ -47,21 +52,66 @@ this.TabCrashReporter = {
if (!browser)
return;
- this.browserMap.set(browser, aSubject.childID);
+ this.browserMap.set(browser.permanentKey, aSubject.childID);
break;
}
},
- submitCrashReport: function (aBrowser) {
- let childID = this.browserMap.get(aBrowser);
+ /**
+ * Submits a crash report from about:tabcrashed
+ *
+ * @param aBrowser
+ * The that the report was sent from.
+ * @param aFormData
+ * An Object with the following properties:
+ *
+ * includeURL (bool):
+ * Whether to include the URL that the user was on
+ * in the crashed tab before the crash occurred.
+ * URL (String)
+ * The URL that the user was on in the crashed tab
+ * before the crash occurred.
+ * emailMe (bool):
+ * Whether or not to include the user's email address
+ * in the crash report.
+ * email (String):
+ * The email address of the user.
+ * comments (String):
+ * Any additional comments from the user.
+ *
+ * Note that it is expected that all properties are set,
+ * even if they are empty.
+ */
+ submitCrashReport: function (aBrowser, aFormData) {
+ let childID = this.browserMap.get(aBrowser.permanentKey);
let dumpID = this.childMap.get(childID);
if (!dumpID)
return
- if (CrashSubmit.submit(dumpID, { recordSubmission: true })) {
- this.childMap.set(childID, null); // Avoid resubmission.
- this.removeSubmitCheckboxesForSameCrash(childID);
+ CrashSubmit.submit(dumpID, {
+ recordSubmission: true,
+ extraExtraKeyVals: {
+ Comments: aFormData.comments,
+ Email: aFormData.email,
+ URL: aFormData.URL,
+ },
+ }).then(null, Cu.reportError);
+
+ this.prefs.setBoolPref("sendReport", true);
+ this.prefs.setBoolPref("includeURL", aFormData.includeURL);
+ this.prefs.setBoolPref("emailMe", aFormData.emailMe);
+ if (aFormData.emailMe) {
+ this.prefs.setCharPref("email", aFormData.email);
+ } else {
+ this.prefs.setCharPref("email", "");
}
+
+ this.childMap.set(childID, null); // Avoid resubmission.
+ this.removeSubmitCheckboxesForSameCrash(childID);
+ },
+
+ dontSubmitCrashReport: function() {
+ this.prefs.setBoolPref("sendReport", false);
},
removeSubmitCheckboxesForSameCrash: function(childID) {
@@ -79,8 +129,8 @@ this.TabCrashReporter = {
if (!doc.documentURI.startsWith("about:tabcrashed"))
continue;
- if (this.browserMap.get(browser) == childID) {
- this.browserMap.delete(browser);
+ if (this.browserMap.get(browser.permanentKey) == childID) {
+ this.browserMap.delete(browser.permanentKey);
browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
}
@@ -97,11 +147,27 @@ this.TabCrashReporter = {
if (!this.childMap)
return;
- let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
+ let dumpID = this.childMap.get(this.browserMap.get(aBrowser.permanentKey));
if (!dumpID)
return;
- aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
+ let doc = aBrowser.contentDocument;
+
+ doc.documentElement.classList.add("crashDumpAvailable");
+
+ let sendReport = this.prefs.getBoolPref("sendReport");
+ doc.getElementById("sendReport").checked = sendReport;
+
+ let includeURL = this.prefs.getBoolPref("includeURL");
+ doc.getElementById("includeURL").checked = includeURL;
+
+ let emailMe = this.prefs.getBoolPref("emailMe");
+ doc.getElementById("emailMe").checked = emailMe;
+
+ if (emailMe) {
+ let email = this.prefs.getCharPref("email", "");
+ doc.getElementById("email").value = email;
+ }
},
hideRestoreAllButton: function (aBrowser) {
diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css
index 3836d05f275..78f56e19cf9 100644
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -98,12 +98,6 @@
-moz-appearance: -moz-window-titlebar;
}
-@media (-moz-mac-yosemite-theme) {
- #main-window:not(:-moz-lwtheme) > #titlebar {
- -moz-appearance: -moz-mac-vibrancy-light;
- }
-}
-
#main-window:not([tabsintitlebar]) > #titlebar {
height: 22px; /* The native titlebar on OS X is 22px tall. */
}
diff --git a/browser/themes/shared/aboutTabCrashed.css b/browser/themes/shared/aboutTabCrashed.css
index 2ef767eb8b2..e42922ac95d 100644
--- a/browser/themes/shared/aboutTabCrashed.css
+++ b/browser/themes/shared/aboutTabCrashed.css
@@ -9,3 +9,34 @@
#reportSent {
font-weight: bold;
}
+
+#crash-reporter-container {
+ width: 80%;
+ background-color: var(--in-content-box-background-hover);
+ margin: 24px 0;
+ padding: 14px;
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: 2px;
+}
+
+#crash-reporter-title {
+ font-weight: bold;
+ margin: 0 0 14px 0;
+}
+
+input[type="text"],
+textarea {
+ width: 100%;
+ box-sizing: border-box;
+ resize: none;
+}
+
+#options {
+ list-style: none;
+ margin-inline-start: 0;
+}
+
+input[type="text"],
+#options > li {
+ margin: 14px 0 0 0;
+}
diff --git a/browser/themes/shared/incontentprefs/preferences.inc.css b/browser/themes/shared/incontentprefs/preferences.inc.css
index de70f654676..ac431b28fac 100644
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -589,6 +589,7 @@ description > html|a {
}
.fxaAccountBoxButtons > button {
+ text-align: center;
padding-left: 11px;
padding-right: 11px;
margin: 0;
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
index 5337061cd79..cea8a568d71 100644
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2013,7 +2013,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
:root {
- --tab-toolbar-navbar-overlap: 0;
+ --tab-toolbar-navbar-overlap: 0px;
}
#nav-bar {
diff --git a/browser/themes/windows/customizableui/panelUIOverlay.css b/browser/themes/windows/customizableui/panelUIOverlay.css
index 2f5c86c2bfa..a2c59cb8899 100644
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -223,9 +223,7 @@ menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
}
#BMB_bookmarksPopup menupopup[placespopup=true] > hbox {
- /* After fixing of bug 1194480 the box-shadow can be removed again */
- /* box-shadow: none; */
- box-shadow: 0 0 4px rgba(0,0,0,0.02);
+ box-shadow: none;
background: -moz-field;
border: 1px solid ThreeDShadow;
}
diff --git a/devtools/client/debugger/test/mochitest/browser.ini b/devtools/client/debugger/test/mochitest/browser.ini
index 99b1bb03894..4c474972635 100644
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -567,6 +567,8 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_watch-expressions-02.js]
skip-if = e10s && debug
+[browser_dbg_worker-window.js]
+skip-if = e10s && debug
[browser_dbg_WorkerActor.attach.js]
skip-if = e10s && debug
[browser_dbg_WorkerActor.attachThread.js]
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
new file mode 100644
index 00000000000..5558f2ab8f2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
@@ -0,0 +1,43 @@
+// Check to make sure that a worker can be attached to a toolbox
+// directly, and that the toolbox has expected properties.
+
+var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
+var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
+
+add_task(function* () {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let tab = yield addTab(TAB_URL);
+ let { tabs } = yield listTabs(client);
+ let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL));
+
+ yield listWorkers(tabClient);
+ yield createWorkerInTab(tab, WORKER_URL);
+
+ let { workers } = yield listWorkers(tabClient);
+ let [, workerClient] = yield attachWorker(tabClient,
+ findWorker(workers, WORKER_URL));
+
+ let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ "jsdebugger",
+ Toolbox.HostType.WINDOW);
+
+ is(toolbox._host.type, "window", "correct host");
+ ok(toolbox._host._window.document.title.contains(WORKER_URL),
+ "worker URL in host title");
+
+ let toolTabs = toolbox.doc.querySelectorAll(".devtools-tab");
+ let activeTools = [...toolTabs].map(tab=>tab.getAttribute("toolid"));
+
+ is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options",
+ "Correct set of tools supported by worker");
+
+ yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ terminateWorkerInTab(tab, WORKER_URL);
+ yield waitForWorkerClose(workerClient);
+ yield close(client);
+});
diff --git a/devtools/client/framework/target.js b/devtools/client/framework/target.js
index 2ffd2f9e039..e0694c18895 100644
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -856,6 +856,10 @@ WorkerTarget.prototype = {
return true;
},
+ get url() {
+ return this._workerClient.url;
+ },
+
get form() {
return {
from: this._workerClient.actor,
diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js
index 067f4dfc2ce..07696d35a67 100644
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1493,10 +1493,8 @@ Toolbox.prototype = {
toolName = toolboxStrings("toolbox.defaultTitle");
}
let title = toolboxStrings("toolbox.titleTemplate",
- toolName,
- this.target.isAddon ?
- this.target.name :
- this.target.url || this.target.name);
+ toolName, this.target.name ||
+ this.target.url);
this._host.setTitle(title);
},
diff --git a/devtools/client/styleinspector/rule-view.js b/devtools/client/styleinspector/rule-view.js
index f0740eed767..5253d022822 100644
--- a/devtools/client/styleinspector/rule-view.js
+++ b/devtools/client/styleinspector/rule-view.js
@@ -26,6 +26,7 @@ const {
throttle
} = require("devtools/client/styleinspector/utils");
const {
+ escapeCSSComment,
parseDeclarations,
parseSingleValue,
parsePseudoClassesAndAttributes,
@@ -1117,7 +1118,7 @@ TextProperty.prototype = {
// Comment out property declarations that are not enabled
if (!this.enabled) {
- declaration = "/* " + declaration + " */";
+ declaration = "/* " + escapeCSSComment(declaration) + " */";
}
return declaration;
diff --git a/devtools/client/styleinspector/test/browser_ruleview_copy_styles.js b/devtools/client/styleinspector/test/browser_ruleview_copy_styles.js
index cac459e4bc3..f081d26ce52 100644
--- a/devtools/client/styleinspector/test/browser_ruleview_copy_styles.js
+++ b/devtools/client/styleinspector/test/browser_ruleview_copy_styles.js
@@ -103,6 +103,7 @@ add_task(function*() {
"\tbackground-color: #00F;[\\r\\n]+" +
"\tfont-size: 12px;[\\r\\n]+" +
"\tborder-color: #00F !important;[\\r\\n]+" +
+ "\t--var: \"\\*/\";[\\r\\n]+" +
"}",
hidden: {
copyLocation: true,
@@ -144,7 +145,7 @@ add_task(function*() {
},
{
setup: function*() {
- yield disableProperty(view);
+ yield disableProperty(view, 0);
},
desc: "Test Copy Rule with Disabled Property",
node: ruleEditor.rule.textProps[2].editor.nameSpan,
@@ -154,6 +155,30 @@ add_task(function*() {
"\tbackground-color: #00F;[\\r\\n]+" +
"\tfont-size: 12px;[\\r\\n]+" +
"\tborder-color: #00F !important;[\\r\\n]+" +
+ "\t--var: \"\\*/\";[\\r\\n]+" +
+ "}",
+ hidden: {
+ copyLocation: true,
+ copyPropertyDeclaration: false,
+ copyPropertyName: false,
+ copyPropertyValue: true,
+ copySelector: true,
+ copyRule: false
+ }
+ },
+ {
+ setup: function*() {
+ yield disableProperty(view, 4);
+ },
+ desc: "Test Copy Rule with Disabled Property with Comment",
+ node: ruleEditor.rule.textProps[2].editor.nameSpan,
+ menuItem: contextmenu.menuitemCopyRule,
+ expectedPattern: "#testid {[\\r\\n]+" +
+ "\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
+ "\tbackground-color: #00F;[\\r\\n]+" +
+ "\tfont-size: 12px;[\\r\\n]+" +
+ "\tborder-color: #00F !important;[\\r\\n]+" +
+ "\t/\\* --var: \"\\*\\\\\/\"; \\*\/[\\r\\n]+" +
"}",
hidden: {
copyLocation: true,
@@ -241,9 +266,9 @@ function* checkCopyStyle(view, node, menuItem, expectedPattern, hidden) {
view._contextmenu._menupopup.hidePopup();
}
-function* disableProperty(view) {
+function* disableProperty(view, index) {
let ruleEditor = getRuleViewRuleEditor(view, 1);
- let propEditor = ruleEditor.rule.textProps[0].editor;
+ let propEditor = ruleEditor.rule.textProps[index].editor;
info("Disabling a property");
propEditor.enable.click();
diff --git a/devtools/client/styleinspector/test/doc_copystyles.css b/devtools/client/styleinspector/test/doc_copystyles.css
index 7494c11a6fc..83f0c87b126 100644
--- a/devtools/client/styleinspector/test/doc_copystyles.css
+++ b/devtools/client/styleinspector/test/doc_copystyles.css
@@ -7,4 +7,5 @@ html, body, #testid {
background-color: #00F;
font-size: 12px;
border-color: #00F !important;
+ --var: "*/";
}
diff --git a/mobile/android/base/resources/layout/preference_search_tip.xml b/mobile/android/base/resources/layout/preference_search_tip.xml
index 0ff11e18cb5..e03b171b468 100644
--- a/mobile/android/base/resources/layout/preference_search_tip.xml
+++ b/mobile/android/base/resources/layout/preference_search_tip.xml
@@ -2,14 +2,19 @@
-
+
+
+
+ android:paddingRight="6dip"
+ android:layout_weight="1"/>
-
+
+
+
diff --git a/mobile/android/base/webapp/Allocator.java b/mobile/android/base/webapp/Allocator.java
index 9f9786db27d..e7a18bdef75 100644
--- a/mobile/android/base/webapp/Allocator.java
+++ b/mobile/android/base/webapp/Allocator.java
@@ -11,7 +11,6 @@ import org.mozilla.gecko.GeckoAppShell;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
import android.util.Log;
public class Allocator {
@@ -46,6 +45,8 @@ public class Allocator {
SharedPreferences mPrefs;
+ @SuppressWarnings("deprecation") // Suppressing deprecation notification for Context.MODE_MULTI_PROCESS until we
+ // reach a timeline for removal of the whole feature. (Bug 1171213)
protected Allocator(Context context) {
mPrefs = context.getSharedPreferences("webapps", Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
}
diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js
index 34162829049..4d8177823e3 100644
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -65,6 +65,7 @@ var CastingApps = {
mirrorStopMenuId: -1,
_blocked: null,
_bound: null,
+ _interval: 120 * 1000, // 120 seconds
init: function ca_init() {
if (!this.isCastingEnabled()) {
@@ -78,8 +79,8 @@ var CastingApps = {
mediaPlayerDevice.init();
SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
- // Search for devices continuously every 120 seconds
- SimpleServiceDiscovery.search(120 * 1000);
+ // Search for devices continuously
+ SimpleServiceDiscovery.search(this._interval);
this._castMenuId = NativeWindow.contextmenus.add(
Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
@@ -93,6 +94,8 @@ var CastingApps = {
Services.obs.addObserver(this, "Casting:Mirror", false);
Services.obs.addObserver(this, "ssdp-service-found", false);
Services.obs.addObserver(this, "ssdp-service-lost", false);
+ Services.obs.addObserver(this, "application-background", false);
+ Services.obs.addObserver(this, "application-foreground", false);
BrowserApp.deck.addEventListener("TabSelect", this, true);
BrowserApp.deck.addEventListener("pageshow", this, true);
@@ -195,15 +198,20 @@ var CastingApps = {
}
break;
case "ssdp-service-found":
- {
- this.serviceAdded(SimpleServiceDiscovery.findServiceForID(aData));
- break;
- }
+ this.serviceAdded(SimpleServiceDiscovery.findServiceForID(aData));
+ break;
case "ssdp-service-lost":
- {
- this.serviceLost(SimpleServiceDiscovery.findServiceForID(aData));
- break;
- }
+ this.serviceLost(SimpleServiceDiscovery.findServiceForID(aData));
+ break;
+ case "application-background":
+ // Turn off polling while in the background
+ this._interval = SimpleServiceDiscovery.search(0);
+ SimpleServiceDiscovery.stopSearch();
+ break;
+ case "application-foreground":
+ // Turn polling on when app comes back to foreground
+ SimpleServiceDiscovery.search(this._interval);
+ break;
}
},
diff --git a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
index 4b7e03c80ae..2dcf60ad88f 100644
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -17,6 +17,7 @@ this.EXPORTED_SYMBOLS = [
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
@@ -458,5 +459,147 @@ this.BrowserTestUtils = {
tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
}
});
- }
+ },
+
+ /**
+ * Crashes a remote browser tab and cleans up the generated minidumps.
+ * Resolves with the data from the .extra file (the crash annotations).
+ *
+ * @param (Browser) browser
+ * A remote element. Must not be null.
+ *
+ * @returns (Promise)
+ * @resolves An Object with key-value pairs representing the data from the
+ * crash report's extra file (if applicable).
+ */
+ crashBrowser: Task.async(function*(browser) {
+ let extra = {};
+ let KeyValueParser = {};
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ Cu.import("resource://gre/modules/KeyValueParser.jsm", KeyValueParser);
+ }
+
+ if (!browser.isRemoteBrowser) {
+ throw new Error(" needs to be remote in order to crash");
+ }
+
+ /**
+ * Returns the directory where crash dumps are stored.
+ *
+ * @return nsIFile
+ */
+ function getMinidumpDirectory() {
+ let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ dir.append("minidumps");
+ return dir;
+ }
+
+ /**
+ * Removes a file from a directory. This is a no-op if the file does not
+ * exist.
+ *
+ * @param directory
+ * The nsIFile representing the directory to remove from.
+ * @param filename
+ * A string for the file to remove from the directory.
+ */
+ function removeFile(directory, filename) {
+ let file = directory.clone();
+ file.append(filename);
+ if (file.exists()) {
+ file.remove(false);
+ }
+ }
+
+ // This frame script is injected into the remote browser, and used to
+ // intentionally crash the tab. We crash by using js-ctypes and dereferencing
+ // a bad pointer. The crash should happen immediately upon loading this
+ // frame script.
+ let frame_script = () => {
+ const Cu = Components.utils;
+ Cu.import("resource://gre/modules/ctypes.jsm");
+
+ let dies = function() {
+ privateNoteIntentionalCrash();
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+ };
+
+ dump("\nEt tu, Brute?\n");
+ dies();
+ }
+
+ let crashCleanupPromise = new Promise((resolve, reject) => {
+ let observer = (subject, topic, data) => {
+ if (topic != "ipc:content-shutdown") {
+ return reject("Received incorrect observer topic: " + topic);
+ }
+ if (!(subject instanceof Ci.nsIPropertyBag2)) {
+ return reject("Subject did not implement nsIPropertyBag2");
+ }
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ dump("\nThis is a normal termination and isn't the one we are looking for...\n");
+ return;
+ }
+
+ let dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ if (!dumpID) {
+ return reject("dumpID was not present despite crash reporting " +
+ "being enabled");
+ }
+ }
+
+ if (dumpID) {
+ let minidumpDirectory = getMinidumpDirectory();
+ let extrafile = minidumpDirectory.clone();
+ extrafile.append(dumpID + '.extra');
+ if (extrafile.exists()) {
+ dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
+ } else {
+ dump('\nCrashReporter not enabled - will not return any extra data\n');
+ }
+ }
+
+ removeFile(minidumpDirectory, dumpID + '.dmp');
+ removeFile(minidumpDirectory, dumpID + '.extra');
+ }
+
+ Services.obs.removeObserver(observer, 'ipc:content-shutdown');
+ dump("\nCrash cleaned up\n");
+ resolve();
+ };
+
+ Services.obs.addObserver(observer, 'ipc:content-shutdown', false);
+ });
+
+ let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => {
+ browser.addEventListener("AboutTabCrashedLoad", function onCrash() {
+ browser.removeEventListener("AboutTabCrashedLoad", onCrash, false);
+ dump("\nabout:tabcrashed loaded\n");
+ resolve();
+ }, false, true);
+ });
+
+ // This frame script will crash the remote browser as soon as it is
+ // evaluated.
+ let mm = browser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
+
+ yield Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]);
+
+ let gBrowser = browser.ownerDocument.defaultView.gBrowser;
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab.getAttribute("crashed") != "true") {
+ throw new Error("Tab should be marked as crashed");
+ }
+
+ return extra;
+ }),
};
diff --git a/toolkit/content/tests/browser/browser_content_url_annotation.js b/toolkit/content/tests/browser/browser_content_url_annotation.js
index cd42f99b0b6..62667651ccc 100644
--- a/toolkit/content/tests/browser/browser_content_url_annotation.js
+++ b/toolkit/content/tests/browser/browser_content_url_annotation.js
@@ -9,72 +9,6 @@
// Running this test in ASAN is slow.
requestLongerTimeout(2);
-/**
- * Returns a Promise that resolves once a remote has experienced
- * a crash. Resolves with the data from the .extra file (the crash annotations).
- *
- * @param browser
- * The that will crash
- * @return Promise
- */
-function crashBrowser(browser) {
- let kv = {};
- Cu.import("resource://gre/modules/KeyValueParser.jsm", kv);
- // This frame script is injected into the remote browser, and used to
- // intentionally crash the tab. We crash by using js-ctypes and dereferencing
- // a bad pointer. The crash should happen immediately upon loading this
- // frame script.
- let frame_script = () => {
- const Cu = Components.utils;
- Cu.import("resource://gre/modules/ctypes.jsm");
-
- let dies = function() {
- privateNoteIntentionalCrash();
- let zero = new ctypes.intptr_t(8);
- let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
- let crash = badptr.contents;
- };
-
- dump("Et tu, Brute?");
- dies();
- };
-
- function checkSubject(subject, data) {
- return subject instanceof Ci.nsIPropertyBag2 &&
- subject.hasKey("abnormal");
- };
- let crashPromise = TestUtils.topicObserved('ipc:content-shutdown',
- checkSubject);
- let crashDataPromise = crashPromise.then(([subject, data]) => {
- ok(subject instanceof Ci.nsIPropertyBag2);
-
- let dumpID;
- if ('nsICrashReporter' in Ci) {
- dumpID = subject.getPropertyAsAString('dumpID');
- ok(dumpID, "dumpID is present and not an empty string");
- }
-
- let extra = null;
- if (dumpID) {
- let minidumpDirectory = getMinidumpDirectory();
- let extrafile = minidumpDirectory.clone();
- extrafile.append(dumpID + '.extra');
- ok(extrafile.exists(), 'found .extra file');
- extra = kv.parseKeyValuePairsFromFile(extrafile);
- removeFile(minidumpDirectory, dumpID + '.dmp');
- removeFile(minidumpDirectory, dumpID + '.extra');
- }
-
- return extra;
- });
-
- // This frame script will crash the remote browser as soon as it is
- // evaluated.
- let mm = browser.messageManager;
- mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
- return crashDataPromise;
-}
-
/**
* Removes a file from a directory. This is a no-op if the file does not
* exist.
@@ -130,7 +64,7 @@ add_task(function* test_content_url_annotation() {
yield promise;
// Crash the tab
- let annotations = yield crashBrowser(browser);
+ let annotations = yield BrowserTestUtils.crashBrowser(browser);
ok("URL" in annotations, "annotated a URL");
is(annotations.URL, redirect_url,
diff --git a/toolkit/mozapps/downloads/DownloadUtils.jsm b/toolkit/mozapps/downloads/DownloadUtils.jsm
index 9a106ef1cab..2c487594725 100644
--- a/toolkit/mozapps/downloads/DownloadUtils.jsm
+++ b/toolkit/mozapps/downloads/DownloadUtils.jsm
@@ -483,7 +483,7 @@ this.DownloadUtils = {
if (aBytes === Infinity) {
aBytes = "Infinity";
} else {
- if (Intl) {
+ if (typeof Intl != "undefined") {
aBytes = getLocaleNumberFormat(fractionDigits)
.format(aBytes);
} else if (gDecimalSymbol != ".") {
diff --git a/toolkit/themes/shared/in-content/common.inc.css b/toolkit/themes/shared/in-content/common.inc.css
index c3c77d8995b..1b19a93bc21 100644
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -406,13 +406,11 @@ xul|button[type="menu"] > xul|menupopup xul|menuseparator {
/* textboxes */
-*|textbox {
+html|input[type="text"],
+html|textarea,
+xul|textbox {
-moz-appearance: none;
- height: 30px;
color: var(--in-content-text-color);
- line-height: 20px;
- padding-right: 10px;
- padding-left: 10px;
border: 1px solid var(--in-content-box-border-color);
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
@@ -422,12 +420,27 @@ xul|button[type="menu"] > xul|menupopup xul|menuseparator {
background-color: var(--in-content-box-background);
}
-html|textbox:focus,
+xul|textbox {
+ min-height: 30px;
+ padding-right: 10px;
+ padding-left: 10px;
+}
+
+html|input[type="text"],
+html|textarea {
+ font-family: inherit;
+ font-size: inherit;
+ padding: 5px 10px;
+}
+
+html|input[type="text"]:focus,
+html|textarea:focus,
xul|textbox[focused] {
border-color: var(--in-content-border-focus);
}
-html|textbox:disabled,
+html|input[type="text"]:disabled,
+html|textarea:disabled,
xul|textbox[disabled="true"] {
opacity: 0.5;
}