Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2015-04-09 22:57:56 -04:00
commit 868f030b98
38 changed files with 1284 additions and 931 deletions

View File

@ -658,6 +658,7 @@ pref("plugins.update.url", "https://www.mozilla.org/%LOCALE%/plugincheck/?utm_so
pref("plugins.update.notifyUser", false);
pref("plugins.click_to_play", true);
pref("plugins.testmode", false);
pref("plugin.default.state", 1);

View File

@ -421,4 +421,5 @@
hidden="true"
label="&emeLearnMoreContextMenu.label;"
accesskey="&emeLearnMoreContextMenu.accesskey;"
onclick="gContextMenu.drmLearnMore(event);"/>
oncommand="gContextMenu.drmLearnMore(event);"
onclick="checkForMiddleClick(this, event);"/>

View File

@ -13,7 +13,8 @@ var gPluginHandler = {
"PluginContent:HideNotificationBar",
"PluginContent:ShowInstallNotification",
"PluginContent:InstallSinglePlugin",
"PluginContent:ShowPluginCrashedNotification",
"PluginContent:ShowNPAPIPluginCrashedNotification",
"PluginContent:ShowGMPCrashedNotification",
"PluginContent:SubmitReport",
"PluginContent:LinkClickCallback",
],
@ -61,12 +62,20 @@ var gPluginHandler = {
case "PluginContent:InstallSinglePlugin":
this.installSinglePlugin(msg.data.pluginInfo);
break;
case "PluginContent:ShowPluginCrashedNotification":
this.showPluginCrashedNotification(msg.target, msg.data.messageString,
msg.data.pluginDumpID, msg.data.browserDumpID);
case "PluginContent:ShowNPAPIPluginCrashedNotification":
this.showNPAPIPluginCrashedNotification(msg.target, msg.data.message,
msg.data.runID);
break;
case "PluginContent:ShowGMPCrashedNotification":
this.showGMPCrashedNotification(msg.target,
msg.data.messageString,
msg.data.pluginDumpID,
msg.data.browserDumpID);
break;
case "PluginContent:SubmitReport":
this.submitReport(msg.data.pluginDumpID, msg.data.browserDumpID, msg.data.keyVals);
if (AppConstants.MOZ_CRASHREPORTER) {
this.submitReport(msg.data.runID, msg.data.keyVals, msg.data.submitURLOptIn);
}
break;
case "PluginContent:LinkClickCallback":
switch (msg.data.name) {
@ -102,15 +111,13 @@ var gPluginHandler = {
openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab");
},
#ifdef MOZ_CRASHREPORTER
submitReport: function submitReport(pluginDumpID, browserDumpID, keyVals) {
keyVals = keyVals || {};
this.CrashSubmit.submit(pluginDumpID, { recordSubmission: true,
extraExtraKeyVals: keyVals });
if (browserDumpID)
this.CrashSubmit.submit(browserDumpID);
submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
if (!AppConstants.MOZ_CRASHREPORTER) {
return;
}
Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", submitURLOptIn);
PluginCrashReporter.submitCrashReport(runID, keyVals);
},
#endif
// Callback for user clicking a "reload page" link
reloadPage: function (browser) {
@ -455,28 +462,86 @@ var gPluginHandler = {
// Crashed-plugin observer. Notified once per plugin crash, before events
// are dispatched to individual plugin instances.
pluginCrashed : function(subject, topic, data) {
NPAPIPluginCrashed : function(subject, topic, data) {
let propertyBag = subject;
if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
!(propertyBag instanceof Ci.nsIWritablePropertyBag2))
return;
#ifdef MOZ_CRASHREPORTER
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
let shouldSubmit = gCrashReporter.submitReports;
let doPrompt = true; // XXX followup to get via gCrashReporter
// Submit automatically when appropriate.
if (pluginDumpID && shouldSubmit && !doPrompt) {
this.submitReport(pluginDumpID, browserDumpID);
// Submission is async, so we can't easily show failure UI.
propertyBag.setPropertyAsBool("submittedCrashReport", true);
!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
!propertyBag.hasKey("runID") ||
!propertyBag.hasKey("pluginName")) {
Cu.reportError("A NPAPI plugin crashed, but the properties of this plugin " +
"cannot be read.");
return;
}
#endif
let runID = propertyBag.getPropertyAsUint32("runID");
let uglyPluginName = propertyBag.getPropertyAsAString("pluginName");
let pluginName = BrowserUtils.makeNicePluginName(uglyPluginName);
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
// If we don't have a minidumpID, we can't (or didn't) submit anything.
// This can happen if the plugin is killed from the task manager.
let state;
if (!AppConstants.MOZ_CRASHREPORTER || !gCrashReporter.enabled) {
// This state tells the user that crash reporting is disabled, so we
// cannot send a report.
state = "noSubmit";
} else if (!pluginDumpID) {
// This state tells the user that there is no crash report available.
state = "noReport";
} else {
// This state asks the user to submit a crash report.
state = "please";
}
let mm = window.getGroupMessageManager("browsers");
mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
{ pluginName, runID, state });
},
showPluginCrashedNotification: function (browser, messageString, pluginDumpID, browserDumpID) {
showNPAPIPluginCrashedNotification: function (browser, messageString, runID) {
let crashReportCallback;
if (AppConstants.MOZ_CRASHREPORTER &&
PluginCrashReporter.hasCrashReport(runID)) {
crashReportCallback = () => {
PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
};
}
this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
},
/**
* For now, GMP crashes are handled separately from NPAPI plugin crashes,
* since the latter are not yet working for e10s. These will be unified
* once e10s support is added for GMP crash reporting in bug 1146955.
*/
showGMPCrashedNotification: function (browser, messageString,
pluginDumpID, browserDumpID) {
let crashReportCallback;
if (AppConstants.MOZ_CRASHREPORTER && pluginDumpID) {
crashReportCallback = () => {
PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
};
}
this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
},
/**
* A helper function for showing the plugin crashed notification bar.
*
* @param browser
* The browser that contains the crashing plugin.
* @param messageString
* The message to display in the notification.
* @param crashReportCallback
* Optional. Pass a function to submit a crash report for this plugin
* crash if a report exists. If no function is passed, the Submit Report
* button will not be added.
*/
_showPluginCrashedNotification: function (browser, messageString, crashReportCallback) {
// If there's already an existing notification bar, don't do anything.
let notificationBox = gBrowser.getNotificationBox(browser);
let notification = notificationBox.getNotificationWithValue("plugin-crashed");
@ -498,16 +563,16 @@ var gPluginHandler = {
callback: function() { browser.reload(); },
}];
#ifdef MOZ_CRASHREPORTER
let submitButton = {
label: submitLabel,
accessKey: submitKey,
popup: null,
callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
};
if (pluginDumpID)
if (AppConstants.MOZ_CRASHREPORTER && crashReportCallback) {
let submitButton = {
label: submitLabel,
accessKey: submitKey,
popup: null,
callback: crashReportCallback,
};
buttons.push(submitButton);
#endif
}
notification = notificationBox.appendNotification(messageString, "plugin-crashed",
iconURL, priority, buttons);

View File

@ -39,6 +39,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
"resource:///modules/AboutHome.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
"resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
"@mozilla.org/browser/favicon-service;1",
"mozIAsyncFavicons");
@ -195,7 +197,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gWebRTCUI",
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
"resource:///modules/TabCrashReporter.jsm");
"resource:///modules/ContentCrashReporters.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashReporters.jsm");
#endif
XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
@ -925,7 +929,7 @@ var gBrowserInit = {
onLoad: function() {
gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed", false);
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
@ -1243,11 +1247,6 @@ var gBrowserInit = {
PanelUI.init();
LightweightThemeListener.init();
#ifdef MOZ_CRASHREPORTER
if (gMultiProcessBrowser)
TabCrashReporter.init();
#endif
Services.telemetry.getHistogramById("E10S_WINDOW").add(gMultiProcessBrowser);
SidebarUI.startDelayedLoad();
@ -1490,7 +1489,7 @@ var gBrowserInit = {
gFxAccounts.uninit();
#endif
Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
try {
gBrowser.removeProgressListener(window.XULBrowserWindow);

View File

@ -78,3 +78,5 @@ skip-if = os == 'linux' || os == 'mac' # Bug 984821
[browser_pluginCrashCommentAndURL.js]
skip-if = !crashreporter
[browser_plugins_added_dynamically.js]
[browser_pluginCrashReportNonDeterminism.js]
skip-if = !crashreporter || os == 'linux' # Bug 1152811

View File

@ -2,85 +2,44 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Cu.import("resource://gre/modules/Services.jsm");
const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
const gTestRoot = getRootDirectory(gTestPath);
var gTestBrowser = null;
const PLUGIN_PAGE = getRootDirectory(gTestPath) + "plugin_big.html";
/**
* Frame script that will be injected into the test browser
* to cause the crash, and then manipulate the crashed plugin
* UI. Specifically, after the crash, we ensure that the
* crashed plugin UI is using the right style rules and that
* the submit URL opt-in defaults to checked. Then, we fill in
* a comment with the crash report, uncheck the submit URL
* opt-in, and send the crash reports.
* Takes an nsIPropertyBag and converts it into a JavaScript Object. It
* will also convert any nsIPropertyBag's within the nsIPropertyBag
* recursively.
*
* @param aBag
* The nsIPropertyBag to convert.
* @return Object
* Keyed on the names of the nsIProperty's within the nsIPropertyBag,
* and mapping to their values.
*/
function frameScript() {
function fail(reason) {
sendAsyncMessage("test:crash-plugin:fail", {
reason: `Failure from frameScript: ${reason}`,
});
}
addMessageListener("test:crash-plugin", () => {
let doc = content.document;
addEventListener("PluginCrashed", (event) => {
let plugin = doc.getElementById("test");
if (!plugin) {
fail("Could not find plugin element");
return;
}
let getUI = (anonid) => {
return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
};
let style = content.getComputedStyle(getUI("pleaseSubmit"));
if (style.display != "block") {
fail("Submission UI visibility is not correct. Expected block, "
+ " got " + style.display);
return;
}
getUI("submitComment").value = "a test comment";
if (!getUI("submitURLOptIn").checked) {
fail("URL opt-in should default to true.");
return;
}
getUI("submitURLOptIn").click();
getUI("submitButton").click();
});
let plugin = doc.getElementById("test");
try {
plugin.crash()
} catch(e) {
function convertPropertyBag(aBag) {
let result = {};
let enumerator = aBag.enumerator;
while(enumerator.hasMoreElements()) {
let { name, value } = enumerator.getNext().QueryInterface(Ci.nsIProperty);
if (value instanceof Ci.nsIPropertyBag) {
value = convertPropertyBag(value);
}
});
result[name] = value;
}
return result;
}
addMessageListener("test:plugin-submit-status", () => {
let doc = content.document;
let plugin = doc.getElementById("test");
let submitStatusEl =
doc.getAnonymousElementByAttribute(plugin, "anonid", "submitStatus");
let submitStatus = submitStatusEl.getAttribute("status");
sendAsyncMessage("test:plugin-submit-status:result", {
submitStatus: submitStatus,
});
function promisePopupNotificationShown(notificationID) {
return new Promise((resolve) => {
waitForNotificationShown(notificationID, resolve);
});
}
// Test that plugin crash submissions still work properly after
// click-to-play activation.
function test() {
// Crashing the plugin takes up a lot of time, so extend the test timeout.
requestLongerTimeout(2);
waitForExplicitFinish();
/**
* Test that plugin crash submissions still work properly after
* click-to-play activation.
*/
add_task(function*() {
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
// The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
@ -95,113 +54,113 @@ function test() {
env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
gTestBrowser = gBrowser.getBrowserForTab(tab);
let mm = gTestBrowser.messageManager;
mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false);
mm.addMessageListener("test:crash-plugin:fail", (message) => {
ok(false, message.data.reason);
});
gTestBrowser.addEventListener("load", onPageLoad, true);
Services.obs.addObserver(onSubmitStatus, "crash-report-status", false);
registerCleanupFunction(function cleanUp() {
env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
env.set("MOZ_CRASHREPORTER_URL", serverURL);
gTestBrowser.removeEventListener("load", onPageLoad, true);
Services.obs.removeObserver(onSubmitStatus, "crash-report-status");
gBrowser.removeCurrentTab();
});
gTestBrowser.contentWindow.location = gTestRoot + "plugin_big.html";
}
function onPageLoad() {
// Force the plugins binding to attach as layout is async.
let plugin = gTestBrowser.contentDocument.getElementById("test");
plugin.clientTop;
executeSoon(afterBindingAttached);
}
yield BrowserTestUtils.withNewTab({
gBrowser,
url: PLUGIN_PAGE,
}, function* (browser) {
let activated = yield ContentTask.spawn(browser, null, function*() {
let plugin = content.document.getElementById("test");
return plugin.QueryInterface(Ci.nsIObjectLoadingContent).activated;
});
ok(!activated, "Plugin should not be activated");
function afterBindingAttached() {
let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
ok(popupNotification, "Should have a click-to-play notification");
// Open up the click-to-play notification popup...
let popupNotification = PopupNotifications.getNotification("click-to-play-plugins",
browser);
ok(popupNotification, "Should have a click-to-play notification");
let plugin = gTestBrowser.contentDocument.getElementById("test");
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
ok(!objLoadingContent.activated, "Plugin should not be activated");
yield promisePopupNotificationShown(popupNotification);
// Simulate clicking the "Allow Always" button.
waitForNotificationShown(popupNotification, function() {
// The primary button in the popup should activate the plugin.
PopupNotifications.panel.firstChild._primaryButton.click();
let condition = function() objLoadingContent.activated;
waitForCondition(condition, pluginActivated, "Waited too long for plugin to activate");
});
}
function pluginActivated() {
let mm = gTestBrowser.messageManager;
mm.sendAsyncMessage("test:crash-plugin");
}
// Prepare a crash report topic observer that only returns when
// the crash report has been successfully sent.
let crashReportChecker = (subject, data) => {
return (data == "success");
};
let crashReportPromise = TestUtils.topicObserved("crash-report-status",
crashReportChecker);
function onSubmitStatus(subj, topic, data) {
try {
// Wait for success or failed, doesn't matter which.
if (data != "success" && data != "failed")
return;
yield ContentTask.spawn(browser, null, function*() {
let plugin = content.document.getElementById("test");
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
let propBag = subj.QueryInterface(Ci.nsIPropertyBag);
if (data == "success") {
let remoteID = getPropertyBagValue(propBag, "serverCrashID");
ok(!!remoteID, "serverCrashID should be set");
yield ContentTaskUtils.waitForCondition(() => {
return plugin.activated;
}, "Waited too long for plugin to activate.");
// Remove the submitted report file.
let file = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
file.initWithPath(Services.crashmanager._submittedDumpsDir);
file.append(remoteID + ".txt");
ok(file.exists(), "Submitted report file should exist");
file.remove(false);
}
try {
plugin.crash();
} catch(e) {}
let extra = getPropertyBagValue(propBag, "extra");
ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag");
let doc = plugin.ownerDocument;
let val = getPropertyBagValue(extra, "PluginUserComment");
is(val, "a test comment",
"Comment in extra data should match comment in textbox");
let getUI = (anonid) => {
return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
};
val = getPropertyBagValue(extra, "PluginContentURL");
ok(val === undefined,
"URL should be absent from extra data when opt-in not checked");
// Now wait until the plugin crash report UI shows itself, which is
// asynchronous.
let statusDiv;
let submitStatus = null;
let mm = gTestBrowser.messageManager;
mm.addMessageListener("test:plugin-submit-status:result", (message) => {
submitStatus = message.data.submitStatus;
yield ContentTaskUtils.waitForCondition(() => {
statusDiv = getUI("submitStatus");
return statusDiv.getAttribute("status") == "please";
}, "Waited too long for plugin to show crash report UI");
// Make sure the UI matches our expectations...
let style = content.getComputedStyle(getUI("pleaseSubmit"));
if (style.display != "block") {
return Promise.reject(`Submission UI visibility is not correct. ` +
`Expected block style, got ${style.display}.`);
}
// Fill the crash report in with some test values that we'll test for in
// the parent.
getUI("submitComment").value = "a test comment";
let optIn = getUI("submitURLOptIn");
if (!optIn.checked) {
return Promise.reject("URL opt-in should default to true.");
}
// Submit the report.
optIn.click();
getUI("submitButton").click();
// And wait for the parent to say that the crash report was submitted
// successfully.
yield ContentTaskUtils.waitForCondition(() => {
return statusDiv.getAttribute("status") == "success";
}, "Timed out waiting for plugin binding to be in success state");
});
mm.sendAsyncMessage("test:plugin-submit-status");
let [subject, data] = yield crashReportPromise;
let condition = () => submitStatus;
waitForCondition(condition, () => {
is(submitStatus, data, "submitStatus data should match");
finish();
}, "Waiting for submitStatus to be reported from frame script");
}
catch (err) {
failWithException(err);
}
}
ok(subject instanceof Ci.nsIPropertyBag,
"The crash report subject should be an nsIPropertyBag.");
function getPropertyBagValue(bag, key) {
try {
var val = bag.getProperty(key);
}
catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
return val;
}
let crashData = convertPropertyBag(subject);
ok(crashData.serverCrashID, "Should have a serverCrashID set.");
function failWithException(err) {
ok(false, "Uncaught exception: " + err + "\n" + err.stack);
}
// Remove the submitted report file after ensuring it exists.
let file = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
file.initWithPath(Services.crashmanager._submittedDumpsDir);
file.append(crashData.serverCrashID + ".txt");
ok(file.exists(), "Submitted report file should exist");
file.remove(false);
ok(crashData.extra, "Extra data should exist");
is(crashData.extra.PluginUserComment, "a test comment",
"Comment in extra data should match comment in textbox");
is(crashData.extra.PluginContentURL, undefined,
"URL should be absent from extra data when opt-in not checked");
});
});

View File

@ -2,61 +2,39 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let gTestBrowser = null;
/**
* Test that plugin crash submissions still work properly after
* click-to-play activation.
*/
add_task(function*() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "about:blank",
}, function* (browser) {
yield ContentTask.spawn(browser, null, function* () {
const GMP_CRASH_EVENT = {
pluginName: "GlobalTestPlugin",
pluginDumpID: "1234",
browserDumpID: "5678",
submittedCrashReport: false,
bubbles: true,
cancelable: true,
gmpPlugin: true,
};
let crashedEventProperties = {
pluginName: "GlobalTestPlugin",
pluginDumpID: "1234",
browserDumpID: "5678",
submittedCrashReport: false,
bubbles: true,
cancelable: true
}
let crashEvent = new content.PluginCrashedEvent("PluginCrashed",
GMP_CRASH_EVENT);
content.dispatchEvent(crashEvent);
});
// Test that plugin crash submissions still work properly after
// click-to-play activation.
let notification = yield waitForNotificationBar("plugin-crashed", browser);
function test() {
waitForExplicitFinish();
let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
gTestBrowser = gBrowser.getBrowserForTab(tab);
gTestBrowser.addEventListener("PluginCrashed", onCrash, false);
gTestBrowser.addEventListener("load", onPageLoad, true);
registerCleanupFunction(function cleanUp() {
gTestBrowser.removeEventListener("PluginCrashed", onCrash, false);
gTestBrowser.removeEventListener("load", onPageLoad, true);
gBrowser.removeTab(tab);
});
}
function onPageLoad() {
executeSoon(generateCrashEvent);
}
function generateCrashEvent() {
let window = gTestBrowser.contentWindow;
let crashedEvent = new window.PluginCrashedEvent("PluginCrashed", crashedEventProperties);
window.dispatchEvent(crashedEvent);
}
function onCrash(event) {
let target = event.target;
is (target, gTestBrowser.contentWindow, "Event target is the window.");
for (let [name, val] of Iterator(crashedEventProperties)) {
let propVal = event[name];
is (propVal, val, "Correct property: " + name + ".");
}
waitForNotificationBar("plugin-crashed", gTestBrowser, (notification) => {
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
let notificationBox = gBrowser.getNotificationBox(browser);
ok(notification, "Infobar was shown.");
is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, "Correct priority.");
is(notification.getAttribute("label"), "The GlobalTestPlugin plugin has crashed.", "Correct message.");
finish();
is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM,
"Correct priority.");
is(notification.getAttribute("label"),
"The GlobalTestPlugin plugin has crashed.",
"Correct message.");
});
}
});

View File

@ -0,0 +1,259 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* With e10s, plugins must run in their own process. This means we have
* three processes at a minimum when we're running a plugin:
*
* 1) The main browser, or "chrome" process
* 2) The content process hosting the plugin instance
* 3) The plugin process
*
* If the plugin process crashes, we cannot be sure if the chrome process
* will hear about it first, or the content process will hear about it
* first. Because of how IPC works, that's really up to the operating system,
* and we assume any guarantees about it, so we have to account for both
* possibilities.
*
* This test exercises the browser's reaction to both possibilities.
*/
const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html";
const CRASHED_MESSAGE = "BrowserPlugins:NPAPIPluginProcessCrashed";
/**
* In order for our test to work, we need to be able to put a plugin
* in a very specific state. Specifically, we need it to match the
* :-moz-handler-crashed pseudoselector. The only way I can find to
* do that is by actually crashing the plugin. So we wait for the
* plugin to crash and show the "please" state (since that will
* only show if both the message from the parent has been received
* AND the PluginCrashed event has fired).
*
* Once in that state, we try to rewind the clock a little bit - we clear
* out the crashData cache in the PluginContent with a message, and we also
* override the pluginFallbackState of the <object> to fool PluginContent
* into believing that the plugin is in a particular state.
*
* @param browser
* The browser that has loaded the CRASH_URL that we need to
* prepare to be in the special state.
* @param pluginFallbackState
* The value we should override the <object>'s pluginFallbackState
* with.
* @return Promise
* The Promise resolves when the plugin has officially been put into
* the crash reporter state, and then "rewound" to have the "status"
* attribute of the statusDiv removed. The resolved Promise returns
* the run ID for the crashed plugin. It rejects if we never get into
* the crash reporter state.
*/
function preparePlugin(browser, pluginFallbackState) {
return ContentTask.spawn(browser, pluginFallbackState, function* (pluginFallbackState) {
let plugin = content.document.getElementById("plugin");
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
// CRASH_URL will load a plugin that crashes immediately. We
// wait until the plugin has finished being put into the crash
// state.
let statusDiv;
yield ContentTaskUtils.waitForCondition(() => {
statusDiv = plugin.ownerDocument
.getAnonymousElementByAttribute(plugin, "anonid",
"submitStatus");
return statusDiv && statusDiv.getAttribute("status") == "please";
}, "Timed out waiting for plugin to be in crash report state");
// "Rewind", by wiping out the status attribute...
statusDiv.removeAttribute("status");
// Somehow, I'm able to get away with overriding the getter for
// this XPCOM object. Probably because I've got chrome privledges.
Object.defineProperty(plugin, "pluginFallbackType", {
get: function() {
return pluginFallbackState;
}
});
return plugin.runID;
}).then((runID) => {
browser.messageManager.sendAsyncMessage("BrowserPlugins:Test:ClearCrashData");
return runID;
});
}
add_task(function* setup() {
// Bypass click-to-play
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
// Clear out any minidumps we create from plugins - we really don't care
// about them.
let crashObserver = (subject, topic, data) => {
if (topic != "plugin-crashed") {
return;
}
let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
let minidumpID = propBag.getPropertyAsAString("pluginDumpID");
let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
minidumpDir.append("minidumps");
let pluginDumpFile = minidumpDir.clone();
pluginDumpFile.append(minidumpID + ".dmp");
let extraFile = minidumpDir.clone();
extraFile.append(minidumpID + ".extra");
ok(pluginDumpFile.exists(), "Found minidump");
ok(extraFile.exists(), "Found extra file");
pluginDumpFile.remove(false);
extraFile.remove(false);
};
Services.obs.addObserver(crashObserver, "plugin-crashed");
// plugins.testmode will make BrowserPlugins:Test:ClearCrashData work.
Services.prefs.setBoolPref("plugins.testmode", true);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("plugins.testmode");
Services.obs.removeObserver(crashObserver, "plugin-crashed");
});
});
/**
* In this case, the chrome process hears about the crash first.
*/
add_task(function* testChromeHearsPluginCrashFirst() {
// Open a remote window so that we can run this test even if e10s is not
// enabled by default.
let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(CRASH_URL);
yield BrowserTestUtils.browserLoaded(browser);
// In this case, we want the <object> to match the -moz-handler-crashed
// pseudoselector, but we want it to seem still active, because the
// content process is not yet supposed to know that the plugin has
// crashed.
let runID = yield preparePlugin(browser,
Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE);
// Send the message down to PluginContent.jsm saying that the plugin has
// crashed, and that we have a crash report.
let mm = browser.messageManager;
mm.sendAsyncMessage(CRASHED_MESSAGE,
{ pluginName: "", runID, state: "please" });
let [gotExpected, msg] = yield ContentTask.spawn(browser, {}, function* () {
// At this point, the content process should have heard the
// plugin crash message from the parent, and we are OK to emit
// the PluginCrashed event.
let plugin = content.document.getElementById("plugin");
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
let statusDiv = plugin.ownerDocument
.getAnonymousElementByAttribute(plugin, "anonid",
"submitStatus");
if (statusDiv.getAttribute("status") == "please") {
return [false, "Did not expect plugin to be in crash report mode yet."];
}
// Now we need the plugin to seem crashed to PluginContent.jsm, without
// actually crashing the plugin again. We hack around this by overriding
// the pluginFallbackType again.
Object.defineProperty(plugin, "pluginFallbackType", {
get: function() {
return Ci.nsIObjectLoadingContent.PLUGIN_CRASHED;
},
});
let event = new content.PluginCrashedEvent("PluginCrashed", {
pluginName: "",
pluginDumpID: "",
browserDumpID: "",
submittedCrashReport: false,
bubbles: true,
cancelable: true,
});
plugin.dispatchEvent(event);
return [statusDiv.getAttribute("status") == "please",
"Should have been showing crash report UI"];
});
ok(gotExpected, msg);
yield BrowserTestUtils.closeWindow(win);
});
/**
* In this case, the content process hears about the crash first.
*/
add_task(function* testContentHearsCrashFirst() {
// Open a remote window so that we can run this test even if e10s is not
// enabled by default.
let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(CRASH_URL);
yield BrowserTestUtils.browserLoaded(browser);
// In this case, we want the <object> to match the -moz-handler-crashed
// pseudoselector, and we want the plugin to seem crashed, since the
// content process in this case has heard about the crash first.
let runID = yield preparePlugin(browser,
Ci.nsIObjectLoadingContent.PLUGIN_CRASHED);
let [gotExpected, msg] = yield ContentTask.spawn(browser, null, function* () {
// At this point, the content process has not yet heard from the
// parent about the crash report. Let's ensure that by making sure
// we're not showing the plugin crash report UI.
let plugin = content.document.getElementById("plugin");
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
let statusDiv = plugin.ownerDocument
.getAnonymousElementByAttribute(plugin, "anonid",
"submitStatus");
if (statusDiv.getAttribute("status") == "please") {
return [false, "Did not expect plugin to be in crash report mode yet."];
}
let event = new content.PluginCrashedEvent("PluginCrashed", {
pluginName: "",
pluginDumpID: "",
browserDumpID: "",
submittedCrashReport: false,
bubbles: true,
cancelable: true,
});
plugin.dispatchEvent(event);
return [statusDiv.getAttribute("status") != "please",
"Should not yet be showing crash report UI"];
});
ok(gotExpected, msg);
// Now send the message down to PluginContent.jsm that the plugin has
// crashed...
let mm = browser.messageManager;
mm.sendAsyncMessage(CRASHED_MESSAGE,
{ pluginName: "", runID, state: "please"});
[gotExpected, msg] = yield ContentTask.spawn(browser, null, function* () {
// At this point, the content process will have heard the message
// from the parent and reacted to it. We should be showing the plugin
// crash report UI now.
let plugin = content.document.getElementById("plugin");
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
let statusDiv = plugin.ownerDocument
.getAnonymousElementByAttribute(plugin, "anonid",
"submitStatus");
return [statusDiv.getAttribute("status") == "please",
"Should have been showing crash report UI"];
});
ok(gotExpected, msg);
yield BrowserTestUtils.closeWindow(win);
});

View File

@ -122,6 +122,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
"resource://gre/modules/UpdateChannel.jsm");
#endif
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
"resource:///modules/ContentCrashReporters.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashReporters.jsm");
#endif
XPCOMUtils.defineLazyGetter(this, "ShellService", function() {
try {
return Cc["@mozilla.org/browser/shell-service;1"].
@ -733,6 +740,11 @@ BrowserGlue.prototype = {
});
#endif
#ifdef MOZ_CRASHREPORTER
TabCrashReporter.init();
PluginCrashReporter.init();
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
AddonWatcher.init(this._notifySlowAddon);

View File

@ -60,6 +60,7 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
eval: () => {}
};
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
const FREETEXT_FILTER_SEARCH_DELAY = 200; // ms
/**
* Object defining the network monitor view components.
@ -350,6 +351,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
this._splitter = $("#network-inspector-view-splitter");
this._summary = $("#requests-menu-network-summary-label");
this._summary.setAttribute("value", L10N.getStr("networkMenu.empty"));
this.userInputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
Prefs.filters.forEach(type => this.filterOn(type));
this.sortContents(this._byTiming);
@ -380,6 +382,13 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
this.requestsFreetextFilterEvent = this.requestsFreetextFilterEvent.bind(this);
this.reFilterRequests = this.reFilterRequests.bind(this);
this.freetextFilterBox = $("#requests-menu-filter-freetext-text");
this.freetextFilterBox.addEventListener("input", this.requestsFreetextFilterEvent, false);
this.freetextFilterBox.addEventListener("command", this.requestsFreetextFilterEvent, false);
$("#toolbar-labels").addEventListener("click", this.requestsMenuSortEvent, false);
$("#requests-menu-footer").addEventListener("click", this.requestsMenuFilterEvent, false);
$("#requests-menu-clear-button").addEventListener("click", this.reqeustsMenuClearEvent, false);
@ -440,6 +449,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
$("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
$("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
$("#requests-menu-clear-button").removeEventListener("click", this.reqeustsMenuClearEvent, false);
this.freetextFilterBox.removeEventListener("input", this.requestsFreetextFilterEvent, false);
this.freetextFilterBox.removeEventListener("command", this.requestsFreetextFilterEvent, false);
this.userInputTimer.cancel();
$("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
$("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
$("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
@ -670,6 +682,31 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
}
},
/**
* Handles the timeout on the freetext filter textbox
*/
requestsFreetextFilterEvent: function() {
this.userInputTimer.cancel();
this._currentFreetextFilter = this.freetextFilterBox.value || "";
if (this._currentFreetextFilter.length === 0) {
this.freetextFilterBox.removeAttribute("filled");
} else {
this.freetextFilterBox.setAttribute("filled", true);
}
this.userInputTimer.initWithCallback(this.reFilterRequests, FREETEXT_FILTER_SEARCH_DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
},
/**
* Refreshes the view contents with the newly selected filters
*/
reFilterRequests: function() {
this.filterContents(this._filterPredicate);
this.refreshSummary();
this.refreshZebra();
},
/**
* Filters all network requests in this container by a specified type.
*
@ -700,9 +737,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
this._disableFilter(aType);
}
this.filterContents(this._filterPredicate);
this.refreshSummary();
this.refreshZebra();
this.reFilterRequests();
},
/**
@ -771,18 +806,14 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
*/
get _filterPredicate() {
let filterPredicates = this._allFilterPredicates;
let currentFreetextFilter = this._currentFreetextFilter;
if (this._activeFilters.length === 1) {
// The simplest case: only one filter active.
return filterPredicates[this._activeFilters[0]].bind(this);
} else {
// Multiple filters active.
return requestItem => {
return this._activeFilters.some(filterName => {
return filterPredicates[filterName].call(this, requestItem);
});
};
}
return requestItem => {
return this._activeFilters.some(filterName => {
return filterPredicates[filterName].call(this, requestItem) &&
filterPredicates["freetext"].call(this, requestItem, currentFreetextFilter);
});
};
},
/**
@ -798,7 +829,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
images: this.isImage,
media: this.isMedia,
flash: this.isFlash,
other: this.isOther
other: this.isOther,
freetext: this.isFreetextMatch
}),
/**
@ -957,6 +989,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
!this.isHtml(e) && !this.isCss(e) && !this.isJs(e) && !this.isXHR(e) &&
!this.isFont(e) && !this.isImage(e) && !this.isMedia(e) && !this.isFlash(e),
isFreetextMatch: function({ attachment: { url } }, text) //no text is a positive match
!text || url.contains(text),
/**
* Predicates used when sorting items.
*
@ -1865,7 +1900,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
_updateQueue: [],
_updateTimeout: null,
_resizeTimeout: null,
_activeFilters: ["all"]
_activeFilters: ["all"],
_currentFreetextFilter: ""
});
/**

View File

@ -47,6 +47,18 @@
</menupopup>
</popupset>
<commandset>
<command id="freeTextFilterCommand"
oncommand="NetMonitorView.RequestsMenu.freetextFilterBox.focus()"/>
</commandset>
<keyset>
<key id="freeTextFilterKey"
key="&netmonitorUI.footer.filterFreetext.key;"
modifiers="accel"
command="freeTextFilterCommand"/>
</keyset>
<deck id="body" class="theme-sidebar" flex="1">
<vbox id="network-inspector-view" flex="1">
@ -752,6 +764,14 @@
data-key="other"
label="&netmonitorUI.footer.filterOther;">
</button>
<spacer id="requests-menu-spacer-textbox"
class="requests-menu-footer-spacer"
flex="0"/>
<textbox id="requests-menu-filter-freetext-text"
class="requests-menu-footer-textbox devtools-searchinput"
type="search"
required="true"
placeholder="&netmonitorUI.footer.filterFreetext.label;"/>
<spacer id="requests-menu-spacer"
class="requests-menu-footer-spacer"
flex="100"/>

View File

@ -5,9 +5,9 @@
* Test if filtering items in the network table works correctly.
*/
const BASIC_REQUESTS = [
{ url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
{ url: "sjs_content-type-test-server.sjs?fmt=css" },
{ url: "sjs_content-type-test-server.sjs?fmt=js" },
{ url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined&text=sample" },
{ url: "sjs_content-type-test-server.sjs?fmt=css&text=sample" },
{ url: "sjs_content-type-test-server.sjs?fmt=js&text=sample" },
];
const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
@ -23,6 +23,15 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
function test() {
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
function setFreetextFilter(value) {
// Set the text and manually call all callbacks synchronously to avoid the timeout
RequestsMenu.freetextFilterBox.value = value;
RequestsMenu.requestsFreetextFilterEvent();
RequestsMenu.userInputTimer.cancel();
RequestsMenu.reFilterRequests();
}
info("Starting test... ");
let { $, NetMonitorView } = aMonitor.panelWin;
@ -96,16 +105,37 @@ function test() {
testFilterButtons(aMonitor, "all");
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
})
.then(() => {
// Text in filter box that matches nothing should hide all.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
setFreetextFilter("foobar");
return testContents([0, 0, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Text in filter box that matches should filter out everything else.
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
setFreetextFilter("sample");
return testContents([1, 1, 1, 0, 0, 0, 0, 0]);
})
// ...then combine multiple filters together.
.then(() => {
// Enable filtering for html and css; should show request of both type.
setFreetextFilter("");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
// Html and css filter enabled and text filter should show just the html and css match.
// Should not show both the items that match the button plus the items that match the text.
setFreetextFilter("sample");
return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
})
.then(() => {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
setFreetextFilter("");
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0]);
return testContents([1, 1, 0, 0, 0, 0, 0, 1]);
})

View File

@ -364,6 +364,11 @@ StyleSheetEditor.prototype = {
* Promise that will resolve when the style editor is loaded.
*/
load: function(inputElement) {
if (this._isDestroyed) {
return promise.reject("Won't load source editor as the style sheet has " +
"already been removed from Style Editor.");
}
this._inputElement = inputElement;
let config = {

View File

@ -3238,7 +3238,7 @@ Widgets.ObjectRenderers.add({
render: function()
{
let { ownProperties, safeGetterValues } = this.objectActor.preview;
let { ownProperties, safeGetterValues } = this.objectActor.preview || {};
if ((!ownProperties && !safeGetterValues) || this.options.concise) {
this._renderConciseObject();
return;
@ -3274,7 +3274,7 @@ Widgets.ObjectRenderers.add({
render: function()
{
let { ownProperties, safeGetterValues } = this.objectActor.preview;
let { ownProperties, safeGetterValues } = this.objectActor.preview || {};
if ((!ownProperties && !safeGetterValues) || this.options.concise) {
this._renderConciseObject();
return;

View File

@ -335,6 +335,7 @@ skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
[browser_webconsole_network_panel.js]
[browser_webconsole_notifications.js]
[browser_webconsole_open-links-without-callback.js]
[browser_webconsole_promise.js]
[browser_webconsole_output_copy_newlines.js]
[browser_webconsole_output_order.js]
[browser_webconsole_property_provider.js]

View File

@ -0,0 +1,51 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Bug 1148759 - Test the webconsole can display promises inside objects.
const TEST_URI = "data:text/html;charset=utf8,test for console and promises";
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
let LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH;
DebuggerServer.LONG_STRING_LENGTH = 100;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = 50;
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
let initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
let inputTests = [
// 0
{
input: "({ x: Promise.resolve() })",
output: "Object { x: Promise }",
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object"
},
];
longString = initialString = null;
function test() {
requestLongerTimeout(2);
registerCleanupFunction(() => {
DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
});
Task.spawn(function*() {
let {tab} = yield loadTab(TEST_URI);
let hud = yield openConsole(tab);
return checkOutputForInputs(hud, inputTests);
}).then(finishUp);
}
function finishUp() {
longString = initialString = inputTests = null;
finishTest();
}

View File

@ -529,9 +529,12 @@ WiFiRuntime.prototype = {
// |openDialog| is typically a blocking API, so |executeSoon| to get around this
DevToolsUtils.executeSoon(() => {
// Height determines the size of the QR code. Force a minimum size to
// improve scanability.
const MIN_HEIGHT = 600;
let win = Services.wm.getMostRecentWindow("devtools:webide");
let width = win.outerWidth * 0.8;
let height = win.outerHeight * 0.5;
let height = Math.max(win.outerHeight * 0.5, MIN_HEIGHT);
win.openDialog("chrome://webide/content/wifi-auth.xhtml",
WINDOW_ID,
"modal=yes,width=" + width + ",height=" + height, session);

View File

@ -124,6 +124,11 @@
- in the network details footer for the "Other" filtering button. -->
<!ENTITY netmonitorUI.footer.filterOther "Other">
<!-- LOCALIZATION NOTE (netmonitorUI.footer.filterFreetext): This is the label displayed
- in the network details footer for the url filtering textbox. -->
<!ENTITY netmonitorUI.footer.filterFreetext.label "Filter URLs">
<!ENTITY netmonitorUI.footer.filterFreetext.key "F">
<!-- LOCALIZATION NOTE (netmonitorUI.footer.clear): This is the label displayed
- in the network details footer for the "Clear" button. -->
<!ENTITY netmonitorUI.footer.clear "Clear">

View File

@ -0,0 +1,207 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "TabCrashReporter", "PluginCrashReporter" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
"resource://gre/modules/CrashSubmit.jsm");
this.TabCrashReporter = {
init: function () {
if (this.initialized)
return;
this.initialized = true;
Services.obs.addObserver(this, "ipc:content-shutdown", false);
Services.obs.addObserver(this, "oop-frameloader-crashed", false);
this.childMap = new Map();
this.browserMap = new WeakMap();
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "ipc:content-shutdown":
aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (!aSubject.get("abnormal"))
return;
this.childMap.set(aSubject.get("childID"), aSubject.get("dumpID"));
break;
case "oop-frameloader-crashed":
aSubject.QueryInterface(Ci.nsIFrameLoader);
let browser = aSubject.ownerElement;
if (!browser)
return;
this.browserMap.set(browser, aSubject.childID);
break;
}
},
submitCrashReport: function (aBrowser) {
let childID = this.browserMap.get(aBrowser);
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);
}
},
removeSubmitCheckboxesForSameCrash: function(childID) {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
if (!window.gMultiProcessBrowser)
continue;
for (let browser of window.gBrowser.browsers) {
if (browser.isRemoteBrowser)
continue;
let doc = browser.contentDocument;
if (!doc.documentURI.startsWith("about:tabcrashed"))
continue;
if (this.browserMap.get(browser) == childID) {
this.browserMap.delete(browser);
browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
}
}
}
},
onAboutTabCrashedLoad: function (aBrowser) {
if (!this.childMap)
return;
let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
if (!dumpID)
return;
aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
}
}
this.PluginCrashReporter = {
/**
* Makes the PluginCrashReporter ready to hear about and
* submit crash reports.
*/
init() {
if (this.initialized) {
return;
}
this.initialized = true;
this.crashReports = new Map();
Services.obs.addObserver(this, "plugin-crashed", false);
},
observe(subject, topic, data) {
if (topic != "plugin-crashed") {
return;
}
let propertyBag = subject;
if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
!propertyBag.hasKey("runID") ||
!propertyBag.hasKey("pluginName")) {
Cu.reportError("PluginCrashReporter can not read plugin information.");
return;
}
let runID = propertyBag.getPropertyAsUint32("runID");
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
if (pluginDumpID) {
this.crashReports.set(runID, { pluginDumpID, browserDumpID });
}
},
/**
* Submit a crash report for a crashed NPAPI plugin.
*
* @param runID
* The runID of the plugin that crashed. A run ID is a unique
* identifier for a particular run of a plugin process - and is
* analogous to a process ID (though it is managed by Gecko instead
* of the operating system).
* @param keyVals
* An object whose key-value pairs will be merged
* with the ".extra" file submitted with the report.
* The properties of htis object will override properties
* of the same name in the .extra file.
*/
submitCrashReport(runID, keyVals) {
if (!this.crashReports.has(runID)) {
Cu.reportError(`Could not find plugin dump IDs for run ID ${runID}.` +
`It is possible that a report was already submitted.`);
return;
}
keyVals = keyVals || {};
let { pluginDumpID, browserDumpID } = this.crashReports.get(runID);
let submissionPromise = CrashSubmit.submit(pluginDumpID, {
recordSubmission: true,
extraExtraKeyVals: keyVals,
});
if (browserDumpID)
CrashSubmit.submit(browserDumpID);
this.broadcastState(runID, "submitting");
submissionPromise.then(() => {
this.broadcastState(runID, "success");
}, () => {
this.broadcastState(runID, "failed");
});
this.crashReports.delete(runID);
},
broadcastState(runID, state) {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
let mm = window.messageManager;
mm.broadcastAsyncMessage("BrowserPlugins:CrashReportSubmitted",
{ runID, state });
}
},
hasCrashReport(runID) {
return this.crashReports.has(runID);
},
/**
* Deprecated mechanism for sending crash reports for GMPs. This
* should be removed when bug 1146955 is fixed.
*/
submitGMPCrashReport(pluginDumpID, browserDumpID) {
CrashSubmit.submit(pluginDumpID, { recordSubmission: true });
if (browserDumpID)
CrashSubmit.submit(browserDumpID);
},
};

View File

@ -34,6 +34,8 @@ PluginContent.prototype = {
this.content = this.global.content;
// Cache of plugin actions for the current page.
this.pluginData = new Map();
// Cache of plugin crash information sent from the parent
this.pluginCrashData = new Map();
// Note that the XBL binding is untrusted
global.addEventListener("PluginBindingAttached", this, true, true);
@ -48,9 +50,29 @@ PluginContent.prototype = {
global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
global.addMessageListener("BrowserPlugins:NotificationShown", this);
global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
},
uninit: function() {
let global = this.global;
global.removeEventListener("PluginBindingAttached", this, true);
global.removeEventListener("PluginCrashed", this, true);
global.removeEventListener("PluginOutdated", this, true);
global.removeEventListener("PluginInstantiated", this, true);
global.removeEventListener("PluginRemoved", this, true);
global.removeEventListener("pagehide", this, true);
global.removeEventListener("pageshow", this, true);
global.removeEventListener("unload", this);
global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
global.removeMessageListener("BrowserPlugins:NotificationShown", this);
global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
delete this.global;
delete this.content;
},
@ -73,6 +95,24 @@ PluginContent.prototype = {
break;
}
break;
case "BrowserPlugins:NPAPIPluginProcessCrashed":
this.NPAPIPluginProcessCrashed({
pluginName: msg.data.pluginName,
runID: msg.data.runID,
state: msg.data.state,
});
break;
case "BrowserPlugins:CrashReportSubmitted":
this.NPAPIPluginCrashReportSubmitted({
runID: msg.data.runID,
state: msg.data.state,
})
break;
case "BrowserPlugins:Test:ClearCrashData":
// This message should ONLY ever be sent by automated tests.
if (Services.prefs.getBoolPref("plugins.testmode")) {
this.pluginCrashData.clear();
}
}
},
@ -97,7 +137,7 @@ PluginContent.prototype = {
}
this._finishRecordingFlashPluginTelemetry();
this.clearPluginDataCache();
this.clearPluginCaches();
},
getPluginUI: function (plugin, anonid) {
@ -308,7 +348,7 @@ PluginContent.prototype = {
!(event.target instanceof Ci.nsIObjectLoadingContent)) {
// If the event target is not a plugin object (i.e., an <object> or
// <embed> element), this call is for a window-global plugin.
this.pluginInstanceCrashed(event.target, event);
this.onPluginCrashed(event.target, event);
return;
}
@ -339,7 +379,7 @@ PluginContent.prototype = {
let shouldShowNotification = false;
switch (eventType) {
case "PluginCrashed":
this.pluginInstanceCrashed(plugin, event);
this.onPluginCrashed(plugin, event);
break;
case "PluginNotFound": {
@ -520,24 +560,31 @@ PluginContent.prototype = {
this.global.sendAsyncMessage("PluginContent:LinkClickCallback", { name: name });
},
submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
submitReport: function submitReport(plugin) {
if (!AppConstants.MOZ_CRASHREPORTER) {
return;
}
let keyVals = {};
if (plugin) {
let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
if (userComment)
keyVals.PluginUserComment = userComment;
if (this.getPluginUI(plugin, "submitURLOptIn").checked)
keyVals.PluginContentURL = plugin.ownerDocument.URL;
if (!plugin) {
Cu.reportError("Attempted to submit crash report without an associated plugin.");
return;
}
if (!(plugin instanceof Ci.nsIObjectLoadingContent)) {
Cu.reportError("Attempted to submit crash report on plugin that does not" +
"implement nsIObjectLoadingContent.");
return;
}
this.global.sendAsyncMessage("PluginContent:SubmitReport", {
pluginDumpID: pluginDumpID,
browserDumpID: browserDumpID,
keyVals: keyVals,
});
let runID = plugin.runID;
let submitURLOptIn = this.getPluginUI(plugin, "submitURLOptIn");
let keyVals = {};
let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
if (userComment)
keyVals.PluginUserComment = userComment;
if (this.getPluginUI(plugin, "submitURLOptIn").checked)
keyVals.PluginContentURL = plugin.ownerDocument.URL;
this.global.sendAsyncMessage("PluginContent:SubmitReport",
{ runID, keyVals, submitURLOptIn });
},
reloadPage: function () {
@ -845,112 +892,134 @@ PluginContent.prototype = {
this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name });
},
clearPluginDataCache: function () {
clearPluginCaches: function () {
this.pluginData.clear();
this.pluginCrashData.clear();
},
hideNotificationBar: function (name) {
this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name });
},
// Crashed-plugin event listener. Called for every instance of a
// plugin in content.
pluginInstanceCrashed: function (target, aEvent) {
/**
* The PluginCrashed event handler. Note that the PluginCrashed event is
* fired for both NPAPI and Gecko Media plugins. In the latter case, the
* target of the event is the document that the GMP is being used in.
*/
onPluginCrashed: function (target, aEvent) {
if (!(aEvent instanceof this.content.PluginCrashedEvent))
return;
let submittedReport = aEvent.submittedCrashReport;
let doPrompt = true; // XXX followup for aEvent.doPrompt;
let submitReports = true; // XXX followup for aEvent.submitReports;
let pluginName = aEvent.pluginName;
let pluginDumpID = aEvent.pluginDumpID;
let browserDumpID = aEvent.browserDumpID;
let gmpPlugin = aEvent.gmpPlugin;
// For non-GMP plugins, remap the plugin name to a more user-presentable form.
if (!gmpPlugin) {
pluginName = BrowserUtils.makeNicePluginName(pluginName);
if (aEvent.gmpPlugin) {
this.GMPCrashed(aEvent);
return;
}
let messageString = gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
if (!(target instanceof Ci.nsIObjectLoadingContent))
return;
let plugin = null, doc;
if (target instanceof Ci.nsIObjectLoadingContent) {
plugin = target;
doc = plugin.ownerDocument;
} else {
doc = target.document;
if (!doc) {
return;
}
// doPrompt is specific to the crashed plugin overlay, and
// therefore is not applicable for window-global plugins.
doPrompt = false;
let crashData = this.pluginCrashData.get(target.runID);
if (!crashData) {
// We haven't received information from the parent yet about
// this crash, so we should hold off showing the crash report
// UI.
return;
}
let status;
// Determine which message to show regarding crash reports.
if (submittedReport) { // submitReports && !doPrompt, handled in observer
status = "submitted";
}
else if (!submitReports && !doPrompt) {
status = "noSubmit";
}
else if (!pluginDumpID) {
// If we don't have a minidumpID, we can't (or didn't) submit anything.
// This can happen if the plugin is killed from the task manager.
status = "noReport";
}
else {
status = "please";
crashData.instances.delete(target);
if (crashData.instances.length == 0) {
this.pluginCrashData.delete(target.runID);
}
// If we don't have a minidumpID, we can't (or didn't) submit anything.
// This can happen if the plugin is killed from the task manager.
if (!pluginDumpID) {
status = "noReport";
}
this.setCrashedNPAPIPluginState({
plugin: target,
state: crashData.state,
message: crashData.message,
});
},
// If we're showing the link to manually trigger report submission, we'll
// want to be able to update all the instances of the UI for this crash to
// show an updated message when a report is submitted.
if (AppConstants.MOZ_CRASHREPORTER && doPrompt) {
let observer = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe : (subject, topic, data) => {
let propertyBag = subject;
if (!(propertyBag instanceof Ci.nsIPropertyBag2))
return;
// Ignore notifications for other crashes.
if (propertyBag.get("minidumpID") != pluginDumpID)
return;
let statusDiv = this.getPluginUI(plugin, "submitStatus");
statusDiv.setAttribute("status", data);
},
NPAPIPluginProcessCrashed: function ({pluginName, runID, state}) {
let message =
gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
[pluginName], 1);
handleEvent : function(event) {
// Not expected to be called, just here for the closure.
let contentWindow = this.global.content;
let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let plugins = cwu.plugins;
for (let plugin of plugins) {
if (plugin instanceof Ci.nsIObjectLoadingContent &&
plugin.runID == runID) {
// The parent has told us that the plugin process has died.
// It's possible that this content process hasn't yet noticed,
// in which case we need to stash this data around until the
// PluginCrashed events get sent up.
if (plugin.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CRASHED) {
// This plugin has already been put into the crashed state by the
// content process, so we can tweak its crash UI without delay.
this.setCrashedNPAPIPluginState({plugin, state, message});
} else {
// The content process hasn't yet determined that the plugin has crashed.
// Stash the data in our map, and throw the plugin into a WeakSet. When
// the PluginCrashed event fires on the <object>/<embed>, we'll retrieve
// the information we need from the Map and remove the instance from the
// WeakSet. Once the WeakSet is empty, we can clear the map.
if (!this.pluginCrashData.has(runID)) {
this.pluginCrashData.set(runID, {
state: state,
message: message,
instances: new WeakSet(),
});
}
let crashData = this.pluginCrashData.get(runID);
crashData.instances.add(plugin);
}
}
// Use a weak reference, so we don't have to remove it...
Services.obs.addObserver(observer, "crash-report-status", true);
// ...alas, now we need something to hold a strong reference to prevent
// it from being GC. But I don't want to manually manage the reference's
// lifetime (which should be no greater than the page).
// Clever solution? Use a closue with an event listener on the document.
// When the doc goes away, so do the listener references and the closure.
doc.addEventListener("mozCleverClosureHack", observer, false);
}
},
let isShowing = false;
setCrashedNPAPIPluginState: function ({plugin, state, message}) {
// Force a layout flush so the binding is attached.
plugin.clientTop;
let overlay = this.getPluginUI(plugin, "main");
let statusDiv = this.getPluginUI(plugin, "submitStatus");
let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
if (plugin) {
// If there's no plugin (an <object> or <embed> element), this call is
// for a window-global plugin. In this case, there's no overlay to show.
isShowing = _setUpPluginOverlay.call(this, plugin, doPrompt);
this.getPluginUI(plugin, "submitButton")
.addEventListener("click", (event) => {
if (event.button != 0 || !event.isTrusted)
return;
this.submitReport(plugin);
});
let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
optInCB.checked = pref.getBoolPref("");
statusDiv.setAttribute("status", state);
let helpIcon = this.getPluginUI(plugin, "helpIcon");
this.addLinkClickCallback(helpIcon, "openHelpPage");
let crashText = this.getPluginUI(plugin, "crashedText");
crashText.textContent = message;
let link = this.getPluginUI(plugin, "reloadLink");
this.addLinkClickCallback(link, "reloadPage");
let isShowing = this.shouldShowOverlay(plugin, overlay);
// Is the <object>'s size too small to hold what we want to show?
if (!isShowing) {
// First try hiding the crash report submission UI.
statusDiv.removeAttribute("status");
isShowing = this.shouldShowOverlay(plugin, overlay);
}
this.setVisibility(plugin, overlay, isShowing);
let doc = plugin.ownerDocument;
let runID = plugin.runID;
if (isShowing) {
// If a previous plugin on the page was too small and resulted in adding a
@ -962,66 +1031,63 @@ PluginContent.prototype = {
// If another plugin on the page was large enough to show our UI, we don't
// want to show a notification bar.
if (!doc.mozNoPluginCrashedNotification) {
this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification", {
messageString: messageString,
pluginDumpID: pluginDumpID,
browserDumpID: browserDumpID,
});
this.global.sendAsyncMessage("PluginContent:ShowNPAPIPluginCrashedNotification",
{ message, runID });
// Remove the notification when the page is reloaded.
doc.defaultView.top.addEventListener("unload", event => {
this.hideNotificationBar("plugin-crashed");
}, false);
}
}
},
// Configure the crashed-plugin placeholder.
// Returns true if the plugin overlay is visible.
function _setUpPluginOverlay(plugin, doPromptSubmit) {
if (!plugin) {
return false;
NPAPIPluginCrashReportSubmitted: function({ runID, state }) {
this.pluginCrashData.delete(runID);
let contentWindow = this.global.content;
let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let plugins = cwu.plugins;
for (let plugin of plugins) {
if (plugin instanceof Ci.nsIObjectLoadingContent &&
plugin.runID == runID) {
let statusDiv = this.getPluginUI(plugin, "submitStatus");
statusDiv.setAttribute("status", state);
}
// Force a layout flush so the binding is attached.
plugin.clientTop;
let overlay = this.getPluginUI(plugin, "main");
let statusDiv = this.getPluginUI(plugin, "submitStatus");
if (doPromptSubmit) {
this.getPluginUI(plugin, "submitButton").addEventListener("click",
function (event) {
if (event.button != 0 || !event.isTrusted)
return;
this.submitReport(pluginDumpID, browserDumpID, plugin);
pref.setBoolPref("", optInCB.checked);
}.bind(this));
let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
optInCB.checked = pref.getBoolPref("");
}
statusDiv.setAttribute("status", status);
let helpIcon = this.getPluginUI(plugin, "helpIcon");
this.addLinkClickCallback(helpIcon, "openHelpPage");
let crashText = this.getPluginUI(plugin, "crashedText");
crashText.textContent = messageString;
let link = this.getPluginUI(plugin, "reloadLink");
this.addLinkClickCallback(link, "reloadPage");
let isShowing = this.shouldShowOverlay(plugin, overlay);
// Is the <object>'s size too small to hold what we want to show?
if (!isShowing) {
// First try hiding the crash report submission UI.
statusDiv.removeAttribute("status");
isShowing = this.shouldShowOverlay(plugin, overlay);
}
this.setVisibility(plugin, overlay, isShowing);
return isShowing;
}
}
},
/**
* Currently, GMP crash events are only handled in the non-e10s case.
* e10s support for GMP crash events is being tracked in bug 1146955.
*/
GMPCrashed: function(aEvent) {
let target = aEvent.target;
let submittedReport = aEvent.submittedCrashReport;
let pluginName = aEvent.pluginName;
let pluginDumpID = aEvent.pluginDumpID;
let browserDumpID = aEvent.browserDumpID;
let gmpPlugin = aEvent.gmpPlugin;
let doc = target.document;
if (!gmpPlugin || !doc) {
// TODO: Throw exception? How did we get here?
return;
}
let messageString =
gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
[pluginName], 1);
this.global.sendAsyncMessage("PluginContent:ShowGMPCrashedNotification", {
messageString: messageString,
pluginDumpID: pluginDumpID,
browserDumpID: browserDumpID,
});
// Remove the notification when the page is reloaded.
doc.defaultView.top.addEventListener("unload", event => {
this.hideNotificationBar("plugin-crashed");
}, false);
},
};

View File

@ -1,101 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "TabCrashReporter" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
"resource://gre/modules/CrashSubmit.jsm");
this.TabCrashReporter = {
init: function () {
if (this.initialized)
return;
this.initialized = true;
Services.obs.addObserver(this, "ipc:content-shutdown", false);
Services.obs.addObserver(this, "oop-frameloader-crashed", false);
this.childMap = new Map();
this.browserMap = new WeakMap();
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "ipc:content-shutdown":
aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (!aSubject.get("abnormal"))
return;
this.childMap.set(aSubject.get("childID"), aSubject.get("dumpID"));
break;
case "oop-frameloader-crashed":
aSubject.QueryInterface(Ci.nsIFrameLoader);
let browser = aSubject.ownerElement;
if (!browser)
return;
this.browserMap.set(browser, aSubject.childID);
break;
}
},
submitCrashReport: function (aBrowser) {
let childID = this.browserMap.get(aBrowser);
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);
}
},
removeSubmitCheckboxesForSameCrash: function(childID) {
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
if (!window.gMultiProcessBrowser)
continue;
for (let browser of window.gBrowser.browsers) {
if (browser.isRemoteBrowser)
continue;
let doc = browser.contentDocument;
if (!doc.documentURI.startsWith("about:tabcrashed"))
continue;
if (this.browserMap.get(browser) == childID) {
this.browserMap.delete(browser);
browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
}
}
}
},
onAboutTabCrashedLoad: function (aBrowser) {
if (!this.childMap)
return;
let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
if (!dumpID)
return;
aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
}
}

View File

@ -16,6 +16,7 @@ EXTRA_JS_MODULES += [
'CastingApps.jsm',
'Chat.jsm',
'ContentClick.jsm',
'ContentCrashReporters.jsm',
'ContentLinkHandler.jsm',
'ContentObservers.jsm',
'ContentSearch.jsm',
@ -38,7 +39,6 @@ EXTRA_JS_MODULES += [
'SelfSupportBackend.jsm',
'SitePermissions.jsm',
'Social.jsm',
'TabCrashReporter.jsm',
'WebappManager.jsm',
'webrtcUI.jsm',
]

View File

@ -269,7 +269,7 @@ browser.jar:
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
skin/classic/browser/devtools/webconsole.png (../shared/devtools/images/webconsole.png)
skin/classic/browser/devtools/webconsole@2x.png (../shared/devtools/images/webconsole@2x.png)
skin/classic/browser/devtools/commandline.css (devtools/commandline.css)
skin/classic/browser/devtools/commandline.css (../shared/devtools/commandline.css)
skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css)
skin/classic/browser/devtools/editor-error.png (../shared/devtools/images/editor-error.png)
skin/classic/browser/devtools/editor-breakpoint.png (../shared/devtools/images/editor-breakpoint.png)

View File

@ -1,154 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.gcli-body {
margin: 0;
font: message-box;
color: hsl(210,30%,85%);
}
#gcli-output-root,
#gcli-tooltip-root {
border: 1px solid hsl(206,37%,4%);
box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
background-image: linear-gradient(hsla(209,18%,18%,0.9), hsl(209,23%,18%));
border-radius: 3px;
}
#gcli-output-root {
padding: 5px 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom: 0;
}
#gcli-tooltip-root {
padding: 5px 0px;
}
#gcli-tooltip-connector {
margin-top: -1px;
margin-left: 8px;
width: 20px;
height: 10px;
border-left: 1px solid hsl(206,37%,4%);
border-right: 1px solid hsl(206,37%,4%);
background-color: hsl(209,23%,18%);
}
.gcli-tt-description,
.gcli-tt-error {
padding: 0 10px;
}
.gcli-row-out {
padding: 0 5px;
line-height: 1.2em;
border-top: none;
border-bottom: none;
color: hsl(210,30%,85%);
}
.gcli-row-out p,
.gcli-row-out h1,
.gcli-row-out h2,
.gcli-row-out h3 {
margin: 5px 0;
}
.gcli-row-out h1,
.gcli-row-out h2,
.gcli-row-out h3,
.gcli-row-out h4,
.gcli-row-out h5,
.gcli-row-out th,
.gcli-row-out strong,
.gcli-row-out pre {
color: hsl(210,30%,95%);
}
.gcli-row-out pre {
font-size: 80%;
}
.gcli-row-out td {
white-space: nowrap;
}
.gcli-out-shortcut,
.gcli-help-synopsis {
padding: 0 3px;
margin: 0 4px;
font-weight: normal;
font-size: 90%;
border-radius: 3px;
background-color: hsl(209,23%,18%);
border: 1px solid hsl(206,37%,4%);
}
.gcli-out-shortcut:before,
.gcli-help-synopsis:before {
color: hsl(210,30%,85%);
-moz-padding-end: 2px;
}
.gcli-help-arrow {
color: #666;
}
.gcli-help-description {
margin: 0 20px;
padding: 0;
}
.gcli-help-parameter {
margin: 0 30px;
padding: 0;
}
.gcli-help-header {
margin: 10px 0 6px;
}
.gcli-menu-name {
-moz-padding-start: 8px;
}
.gcli-menu-desc {
-moz-padding-end: 8px;
color: hsl(210,30%,75%);
}
.gcli-menu-name:hover,
.gcli-menu-desc:hover {
background-color: hsla(0,0%,0%,.3);
}
.gcli-menu-highlight,
.gcli-menu-highlight:hover {
background-color: hsla(0,100%,100%,.1);
}
.gcli-menu-typed {
color: hsl(25,78%,50%);
}
.gcli-menu-more {
font-size: 80%;
text-align: end;
-moz-padding-end: 8px;
}
.gcli-addon-disabled {
opacity: 0.6;
text-decoration: line-through;
}
.gcli-breakpoint-label {
font-weight: bold;
}
.gcli-breakpoint-lineText {
font-family: monospace;
}

View File

@ -395,7 +395,7 @@ browser.jar:
skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png)
skin/classic/browser/devtools/alerticon-warning@2x.png (../shared/devtools/images/alerticon-warning@2x.png)
* skin/classic/browser/devtools/ruleview.css (../shared/devtools/ruleview.css)
skin/classic/browser/devtools/commandline.css (devtools/commandline.css)
skin/classic/browser/devtools/commandline.css (../shared/devtools/commandline.css)
skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css)
skin/classic/browser/devtools/editor-error.png (../shared/devtools/images/editor-error.png)
skin/classic/browser/devtools/editor-breakpoint.png (../shared/devtools/images/editor-breakpoint.png)

View File

@ -697,6 +697,28 @@ label.requests-menu-status-code {
font-weight: 600;
}
#requests-menu-filter-freetext-text {
transition-property: max-width, -moz-padding-end, -moz-padding-start;
transition-duration: 250ms;
transition-timing-function: ease;
}
#requests-menu-filter-freetext-text:not([focused]):not([filled]) > .textbox-input-box {
overflow: hidden;
}
#requests-menu-filter-freetext-text:not([focused]):not([filled]) {
max-width: 20px !important;
-moz-padding-end: 5px;
-moz-padding-start: 22px;
background-position: 8px center, top left, top left;
}
#requests-menu-filter-freetext-text[focused],
#requests-menu-filter-freetext-text[filled] {
max-width: 200px !important;
}
/* Performance analysis buttons */
#requests-menu-network-summary-button {

View File

@ -256,6 +256,9 @@ description > html|a {
#telemetryLearnMore,
#FHRLearnMore,
#crashReporterLearnMore {
/* provide some margin between the links and the label text */
/* !important is needed to override the rules defined in common.css */
-moz-margin-start: 20px !important;
/* center the links */
margin-top: 8px;
margin-bottom: 8px;

View File

@ -1,154 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.gcli-body {
margin: 0;
font: message-box;
color: hsl(210,30%,85%);
}
#gcli-output-root,
#gcli-tooltip-root {
border: 1px solid hsl(206,37%,4%);
box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
background-image: linear-gradient(hsla(209,18%,18%,0.9), hsl(209,23%,18%));
border-radius: 3px;
}
#gcli-output-root {
padding: 5px 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom: 0;
}
#gcli-tooltip-root {
padding: 5px 0px;
}
#gcli-tooltip-connector {
margin-top: -1px;
margin-left: 8px;
width: 20px;
height: 10px;
border-left: 1px solid hsl(206,37%,4%);
border-right: 1px solid hsl(206,37%,4%);
background-color: hsl(209,23%,18%);
}
.gcli-tt-description,
.gcli-tt-error {
padding: 0 10px;
}
.gcli-row-out {
padding: 0 5px;
line-height: 1.2em;
border-top: none;
border-bottom: none;
color: hsl(210,30%,85%);
}
.gcli-row-out p,
.gcli-row-out h1,
.gcli-row-out h2,
.gcli-row-out h3 {
margin: 5px 0;
}
.gcli-row-out h1,
.gcli-row-out h2,
.gcli-row-out h3,
.gcli-row-out h4,
.gcli-row-out h5,
.gcli-row-out th,
.gcli-row-out strong,
.gcli-row-out pre {
color: hsl(210,30%,95%);
}
.gcli-row-out pre {
font-size: 80%;
}
.gcli-row-out td {
white-space: nowrap;
}
.gcli-out-shortcut,
.gcli-help-synopsis {
padding: 0 3px;
margin: 0 4px;
font-weight: normal;
font-size: 90%;
border-radius: 3px;
background-color: hsl(209,23%,18%);
border: 1px solid hsl(206,37%,4%);
}
.gcli-out-shortcut:before,
.gcli-help-synopsis:before {
color: hsl(210,30%,85%);
-moz-padding-end: 2px;
}
.gcli-help-arrow {
color: #666;
}
.gcli-help-description {
margin: 0 20px;
padding: 0;
}
.gcli-help-parameter {
margin: 0 30px;
padding: 0;
}
.gcli-help-header {
margin: 10px 0 6px;
}
.gcli-menu-name {
-moz-padding-start: 8px;
}
.gcli-menu-desc {
-moz-padding-end: 8px;
color: hsl(210,30%,75%);
}
.gcli-menu-name:hover,
.gcli-menu-desc:hover {
background-color: hsla(0,0%,0%,.3);
}
.gcli-menu-highlight,
.gcli-menu-highlight:hover {
background-color: hsla(0,100%,100%,.1);
}
.gcli-menu-typed {
color: hsl(25,78%,50%);
}
.gcli-menu-more {
font-size: 80%;
text-align: end;
-moz-padding-end: 8px;
}
.gcli-addon-disabled {
opacity: 0.6;
text-decoration: line-through;
}
.gcli-breakpoint-label {
font-weight: bold;
}
.gcli-breakpoint-lineText {
font-family: monospace;
}

View File

@ -326,7 +326,7 @@ browser.jar:
skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png)
skin/classic/browser/devtools/alerticon-warning@2x.png (../shared/devtools/images/alerticon-warning@2x.png)
* skin/classic/browser/devtools/ruleview.css (../shared/devtools/ruleview.css)
skin/classic/browser/devtools/commandline.css (devtools/commandline.css)
skin/classic/browser/devtools/commandline.css (../shared/devtools/commandline.css)
skin/classic/browser/devtools/command-paintflashing.png (../shared/devtools/images/command-paintflashing.png)
skin/classic/browser/devtools/command-paintflashing@2x.png (../shared/devtools/images/command-paintflashing@2x.png)
skin/classic/browser/devtools/command-screenshot.png (../shared/devtools/images/command-screenshot.png)

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.tabqueue;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.preferences.GeckoPreferences;
@ -30,6 +31,8 @@ public class TabQueueDispatcher extends Locales.LocaleAwareActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeckoAppShell.ensureCrashHandling();
Intent intent = getIntent();
// For the moment lets exit early and start fennec as normal if we're not in nightly with

View File

@ -35,8 +35,12 @@ function copyStringAndToast(string, notifyString) {
}
}
// Delay filtering while typing in MS
const FILTER_DELAY = 500;
let Passwords = {
_logins: [],
_filterTimer: null,
_getLogins: function() {
let logins;
@ -66,7 +70,19 @@ let Passwords = {
let filterInput = document.getElementById("filter-input");
let filterContainer = document.getElementById("filter-input-container");
filterInput.addEventListener("input", this._filter.bind(this), false);
filterInput.addEventListener("input", (event) => {
// Stop any in-progress filter timer
if (this._filterTimer) {
clearTimeout(this._filterTimer);
this._filterTimer = null;
}
// Start a new timer
this._filterTimer = setTimeout(() => {
this._filter(event);
}, FILTER_DELAY);
}, false);
filterInput.addEventListener("blur", (event) => {
filterContainer.setAttribute("hidden", true);
});
@ -77,6 +93,12 @@ let Passwords = {
}, false);
document.getElementById("filter-clear").addEventListener("click", (event) => {
// Stop any in-progress filter timer
if (this._filterTimer) {
clearTimeout(this._filterTimer);
this._filterTimer = null;
}
filterInput.blur();
filterInput.value = "";
this._loadList(this._logins);
@ -123,55 +145,64 @@ let Passwords = {
}
},
_onLoginClick: function (event) {
let loginItem = event.currentTarget;
let login = loginItem.login;
if (!login) {
debug("No login!");
return;
}
let prompt = new Prompt({
window: window,
});
let menuItems = [
{ label: gStringBundle.GetStringFromName("passwordsMenu.copyPassword") },
{ label: gStringBundle.GetStringFromName("passwordsMenu.copyUsername") },
{ label: gStringBundle.GetStringFromName("passwordsMenu.details") },
{ label: gStringBundle.GetStringFromName("passwordsMenu.delete") }
];
prompt.setSingleChoiceItems(menuItems);
prompt.show((data) => {
// Switch on indices of buttons, as they were added when creating login item.
switch (data.button) {
case 0:
copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
break;
case 1:
copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied"));
break;
case 2:
this._showDetails(loginItem);
history.pushState({ id: login.guid }, document.title);
break;
case 3:
let confirmPrompt = new Prompt({
window: window,
message: gStringBundle.GetStringFromName("passwordsDialog.confirmDelete"),
buttons: [
gStringBundle.GetStringFromName("passwordsDialog.confirm"),
gStringBundle.GetStringFromName("passwordsDialog.cancel") ]
});
confirmPrompt.show((data) => {
switch (data.button) {
case 0:
// Corresponds to "confirm" button.
Services.logins.removeLogin(login);
}
});
}
});
},
_createItemForLogin: function (login) {
let loginItem = document.createElement("div");
loginItem.setAttribute("loginID", login.guid);
loginItem.className = "login-item list-item";
loginItem.addEventListener("click", () => {
let prompt = new Prompt({
window: window,
});
let menuItems = [
{ label: gStringBundle.GetStringFromName("passwordsMenu.copyPassword") },
{ label: gStringBundle.GetStringFromName("passwordsMenu.copyUsername") },
{ label: gStringBundle.GetStringFromName("passwordsMenu.details") },
{ label: gStringBundle.GetStringFromName("passwordsMenu.delete") } ];
prompt.setSingleChoiceItems(menuItems);
prompt.show((data) => {
// Switch on indices of buttons, as they were added when creating login item.
switch (data.button) {
case 0:
copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
break;
case 1:
copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied"));
break;
case 2:
this._showDetails(loginItem);
history.pushState({ id: login.guid }, document.title);
break;
case 3:
let confirmPrompt = new Prompt({
window: window,
message: gStringBundle.GetStringFromName("passwordsDialog.confirmDelete"),
buttons: [
gStringBundle.GetStringFromName("passwordsDialog.confirm"),
gStringBundle.GetStringFromName("passwordsDialog.cancel") ]
});
confirmPrompt.show((data) => {
switch (data.button) {
case 0:
// Corresponds to "confirm" button.
Services.logins.removeLogin(login);
}
});
}
});
}, true);
loginItem.addEventListener("click", this, true);
// Create item icon.
let img = document.createElement("div");
@ -230,6 +261,10 @@ let Passwords = {
this._onPopState(event);
break;
}
case "click": {
this._onLoginClick(event);
break;
}
}
},

View File

@ -9,17 +9,13 @@ from marionette_driver.keys import Keys
class TestWindowHandles(MarionetteTestCase):
def test_new_tab_window_handles(self):
keys = [Keys.SHIFT]
keys = []
if self.marionette.session_capabilities['platformName'] == 'DARWIN':
keys.append(Keys.META)
else:
keys.append(Keys.CONTROL)
keys.append('a')
# Put some history in the tab so this results in a fresh tab opening.
self.marionette.navigate("about:blank")
self.marionette.navigate("data:text/html, <div>Text</div>")
self.marionette.navigate("about:blank")
keys.append('t')
origin_win = self.marionette.current_window_handle
@ -30,9 +26,9 @@ class TestWindowHandles(MarionetteTestCase):
self.wait_for_condition(lambda mn: len(mn.window_handles) == 2)
handles = self.marionette.window_handles
handles.remove(origin_win)
addons_page = handles.pop()
self.marionette.switch_to_window(addons_page)
self.assertEqual(self.marionette.get_url(), "about:addons")
new_tab = handles.pop()
self.marionette.switch_to_window(new_tab)
self.assertEqual(self.marionette.get_url(), "about:newtab")
self.marionette.close()
self.marionette.switch_to_window(origin_win)

View File

@ -112,20 +112,28 @@ this.BrowserTestUtils = {
* {
* private: A boolean indicating if the window should be
* private
* remote: A boolean indicating if the window should run
* remote browser tabs or not. If omitted, the window
* will choose the profile default state.
* }
* @return {Promise}
* Resolves with the new window once it is loaded.
*/
openNewBrowserWindow(options) {
openNewBrowserWindow(options={}) {
let argString = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
argString.data = "";
let features = "chrome,dialog=no,all";
if (options && options.private || false) {
if (options.private) {
features += ",private";
}
if (options.hasOwnProperty("remote")) {
let remoteState = options.remote ? "remote" : "non-remote";
features += `,${remoteState}`;
}
let win = Services.ww.openWindow(
null, Services.prefs.getCharPref("browser.chromeURL"), "_blank",
features, argString);

View File

@ -645,10 +645,15 @@ this.DownloadIntegration = {
Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit"));
// Permanently downloaded files are made accessible by other users on
// this system, while temporary downloads are marked as read-only.
let unixMode = isTemporaryDownload ? 0o400 : 0o666;
// On Unix, the umask of the process is respected. This call has no
// effect on Windows.
yield OS.File.setPermissions(aDownload.target.path, { unixMode });
let options = {};
if (isTemporaryDownload) {
options.unixMode = 0o400;
options.winAttributes = {readOnly: true};
} else {
options.unixMode = 0o666;
}
// On Unix, the umask of the process is respected.
yield OS.File.setPermissions(aDownload.target.path, options);
} catch (ex) {
// We should report errors with making the permissions less restrictive
// or marking the file as read-only on Unix and Mac, but this should not

View File

@ -144,25 +144,6 @@ nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIFile* aTarget,
}
}
#ifdef XP_WIN
// Adjust file attributes so that by default, new files are indexed by
// desktop search services. Skip off those that land in the temp folder.
nsCOMPtr<nsIFile> tempDir, fileDir;
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
NS_ENSURE_SUCCESS(rv, rv);
aTarget->GetParent(getter_AddRefs(fileDir));
bool isTemp = false;
if (fileDir) {
fileDir->Equals(tempDir, &isTemp);
}
nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(aTarget));
if (!isTemp && localFileWin) {
localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
}
#endif
#endif
return NS_OK;

View File

@ -193,8 +193,9 @@ add_task(function test_basic_tryToKeepPartialData()
*/
add_task(function test_unix_permissions()
{
// This test is only executed on Linux and Mac.
if (Services.appinfo.OS != "Darwin" && Services.appinfo.OS != "Linux") {
// This test is only executed on some Desktop systems.
if (Services.appinfo.OS != "Darwin" && Services.appinfo.OS != "Linux" &&
Services.appinfo.OS != "WINNT") {
do_print("Skipping test.");
return;
}
@ -228,12 +229,20 @@ add_task(function test_unix_permissions()
yield promiseDownloadStopped(download);
}
// Temporary downloads should be read-only and not accessible to other
// users, while permanently downloaded files should be readable and
// writable as specified by the system umask.
let isTemporary = launchWhenSucceeded && (autoDelete || isPrivate);
do_check_eq((yield OS.File.stat(download.target.path)).unixMode,
isTemporary ? 0o400 : (0o666 & ~OS.Constants.Sys.umask));
let stat = yield OS.File.stat(download.target.path);
if (Services.appinfo.OS == "WINNT") {
// On Windows
// Temporary downloads should be read-only
do_check_eq(stat.winAttributes.readOnly, isTemporary ? true : false);
} else {
// On Linux, Mac
// Temporary downloads should be read-only and not accessible to other
// users, while permanently downloaded files should be readable and
// writable as specified by the system umask.
do_check_eq(stat.unixMode,
isTemporary ? 0o400 : (0o666 & ~OS.Constants.Sys.umask));
}
}
}
}

View File

@ -604,7 +604,7 @@ AboutReader.prototype = {
return;
}
if (article && article.url == url) {
if (article) {
this._showContent(article);
} else if (this._articlePromise) {
// If we were promised an article, show an error message if there's a failure.