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; }