mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
868f030b98
@ -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);
|
||||
|
||||
|
@ -421,4 +421,5 @@
|
||||
hidden="true"
|
||||
label="&emeLearnMoreContextMenu.label;"
|
||||
accesskey="&emeLearnMoreContextMenu.accesskey;"
|
||||
onclick="gContextMenu.drmLearnMore(event);"/>
|
||||
oncommand="gContextMenu.drmLearnMore(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
@ -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.");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
@ -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);
|
||||
|
@ -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: ""
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -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"/>
|
||||
|
@ -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]);
|
||||
})
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
|
@ -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">
|
||||
|
207
browser/modules/ContentCrashReporters.jsm
Normal file
207
browser/modules/ContentCrashReporters.jsm
Normal 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);
|
||||
},
|
||||
};
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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',
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user