mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
6954f71b6b
When the PluginRemoved event is fired when changing locations, it's fired asynchronously such that the document that the plugin belongs to has already been unloaded. This was causing the hidden plugin notification to appear in some cases when users browsed away from documents that had hidden plugins in them. Now we pass the Principal for the unloading document back to the parent and do a comparison with the current browser Principal to ensure that they match before showing the hidden plugin notification. --HG-- rename : browser/base/content/test/plugins/plugin_small.html => browser/base/content/test/plugins/plugin_small_2.html extra : rebase_source : e748e3b09de77cc7796b1a78f8e39a23af64049a
510 lines
19 KiB
JavaScript
510 lines
19 KiB
JavaScript
# -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
# 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/.
|
|
|
|
var gPluginHandler = {
|
|
PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
|
|
PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
|
|
MESSAGES: [
|
|
"PluginContent:ShowClickToPlayNotification",
|
|
"PluginContent:RemoveNotification",
|
|
"PluginContent:UpdateHiddenPluginUI",
|
|
"PluginContent:HideNotificationBar",
|
|
"PluginContent:ShowInstallNotification",
|
|
"PluginContent:InstallSinglePlugin",
|
|
"PluginContent:ShowPluginCrashedNotification",
|
|
"PluginContent:SubmitReport",
|
|
"PluginContent:LinkClickCallback",
|
|
],
|
|
|
|
init: function () {
|
|
const mm = window.messageManager;
|
|
for (let msg of this.MESSAGES) {
|
|
mm.addMessageListener(msg, this);
|
|
}
|
|
window.addEventListener("unload", this);
|
|
},
|
|
|
|
uninit: function () {
|
|
const mm = window.messageManager;
|
|
for (let msg of this.MESSAGES) {
|
|
mm.removeMessageListener(msg, this);
|
|
}
|
|
window.removeEventListener("unload", this);
|
|
},
|
|
|
|
handleEvent: function (event) {
|
|
if (event.type == "unload") {
|
|
this.uninit();
|
|
}
|
|
},
|
|
|
|
receiveMessage: function (msg) {
|
|
switch (msg.name) {
|
|
case "PluginContent:ShowClickToPlayNotification":
|
|
this.showClickToPlayNotification(msg.target, msg.data.plugins, msg.data.showNow,
|
|
msg.principal, msg.data.host);
|
|
break;
|
|
case "PluginContent:RemoveNotification":
|
|
this.removeNotification(msg.target, msg.data.name);
|
|
break;
|
|
case "PluginContent:UpdateHiddenPluginUI":
|
|
this.updateHiddenPluginUI(msg.target, msg.data.haveInsecure, msg.data.actions,
|
|
msg.principal, msg.data.host);
|
|
break;
|
|
case "PluginContent:HideNotificationBar":
|
|
this.hideNotificationBar(msg.target, msg.data.name);
|
|
break;
|
|
case "PluginContent:ShowInstallNotification":
|
|
return this.showInstallNotification(msg.target, msg.data.pluginInfo);
|
|
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);
|
|
break;
|
|
case "PluginContent:SubmitReport":
|
|
this.submitReport(msg.data.pluginDumpID, msg.data.browserDumpID, msg.data.keyVals);
|
|
break;
|
|
case "PluginContent:LinkClickCallback":
|
|
switch (msg.data.name) {
|
|
case "managePlugins":
|
|
case "openHelpPage":
|
|
case "openPluginUpdatePage":
|
|
this[msg.data.name].apply(this);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
Cu.reportError("gPluginHandler did not expect to handle message " + msg.name);
|
|
break;
|
|
}
|
|
},
|
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
get CrashSubmit() {
|
|
delete this.CrashSubmit;
|
|
Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
|
|
return this.CrashSubmit;
|
|
},
|
|
#endif
|
|
|
|
// Callback for user clicking on a disabled plugin
|
|
managePlugins: function () {
|
|
BrowserOpenAddonsMgr("addons://list/plugin");
|
|
},
|
|
|
|
// Callback for user clicking on the link in a click-to-play plugin
|
|
// (where the plugin has an update)
|
|
openPluginUpdatePage: function () {
|
|
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);
|
|
},
|
|
#endif
|
|
|
|
// Callback for user clicking a "reload page" link
|
|
reloadPage: function (browser) {
|
|
browser.reload();
|
|
},
|
|
|
|
// Callback for user clicking the help icon
|
|
openHelpPage: function () {
|
|
openHelpLink("plugin-crashed", false);
|
|
},
|
|
|
|
_clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
|
|
if (event == "showing") {
|
|
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
|
|
.add(!this.options.primaryPlugin);
|
|
// Histograms always start at 0, even though our data starts at 1
|
|
let histogramCount = this.options.pluginData.size - 1;
|
|
if (histogramCount > 4) {
|
|
histogramCount = 4;
|
|
}
|
|
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
|
|
.add(histogramCount);
|
|
}
|
|
else if (event == "dismissed") {
|
|
// Once the popup is dismissed, clicking the icon should show the full
|
|
// list again
|
|
this.options.primaryPlugin = null;
|
|
}
|
|
else if (event == "removed") {
|
|
// Once the notification is removed, let the content script clear any
|
|
// caches it may have populated.
|
|
this.browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationRemoved");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called from the plugin doorhanger to set the new permissions for a plugin
|
|
* and activate plugins if necessary.
|
|
* aNewState should be either "allownow" "allowalways" or "block"
|
|
*/
|
|
_updatePluginPermission: function (aNotification, aPluginInfo, aNewState) {
|
|
let permission;
|
|
let expireType;
|
|
let expireTime;
|
|
let histogram =
|
|
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
|
|
|
|
// Update the permission manager.
|
|
// Also update the current state of pluginInfo.fallbackType so that
|
|
// subsequent opening of the notification shows the current state.
|
|
switch (aNewState) {
|
|
case "allownow":
|
|
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
|
expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
|
|
expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
|
|
histogram.add(0);
|
|
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
|
break;
|
|
|
|
case "allowalways":
|
|
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
|
expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
|
|
expireTime = Date.now() +
|
|
Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
|
|
histogram.add(1);
|
|
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
|
break;
|
|
|
|
case "block":
|
|
permission = Ci.nsIPermissionManager.PROMPT_ACTION;
|
|
expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
|
|
expireTime = 0;
|
|
histogram.add(2);
|
|
switch (aPluginInfo.blocklistState) {
|
|
case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
|
|
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
|
|
break;
|
|
case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
|
|
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
|
|
break;
|
|
default:
|
|
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
|
|
}
|
|
break;
|
|
|
|
// In case a plugin has already been allowed in another tab, the "continue allowing" button
|
|
// shouldn't change any permissions but should run the plugin-enablement code below.
|
|
case "continue":
|
|
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
|
break;
|
|
default:
|
|
Cu.reportError(Error("Unexpected plugin state: " + aNewState));
|
|
return;
|
|
}
|
|
|
|
let browser = aNotification.browser;
|
|
let contentWindow = browser.contentWindow;
|
|
if (aNewState != "continue") {
|
|
let principal = aNotification.options.principal;
|
|
Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
|
|
permission, expireType, expireTime);
|
|
aPluginInfo.pluginPermissionType = expireType;
|
|
}
|
|
|
|
browser.messageManager.sendAsyncMessage("BrowserPlugins:ActivatePlugins", {
|
|
pluginInfo: aPluginInfo,
|
|
newState: aNewState,
|
|
});
|
|
},
|
|
|
|
showClickToPlayNotification: function (browser, plugins, showNow, principal, host) {
|
|
// It is possible that we've received a message from the frame script to show
|
|
// a click to play notification for a principal that no longer matches the one
|
|
// that the browser's content now has assigned (ie, the browser has browsed away
|
|
// after the message was sent, but before the message was received). In that case,
|
|
// we should just ignore the message.
|
|
if (!principal.equals(browser.contentPrincipal)) {
|
|
return;
|
|
}
|
|
|
|
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
|
|
|
|
// If this is a new notification, create a pluginData map, otherwise append
|
|
let pluginData;
|
|
if (notification) {
|
|
pluginData = notification.options.pluginData;
|
|
} else {
|
|
pluginData = new Map();
|
|
}
|
|
|
|
for (var pluginInfo of plugins) {
|
|
if (pluginData.has(pluginInfo.permissionString)) {
|
|
continue;
|
|
}
|
|
|
|
let url;
|
|
// TODO: allow the blocklist to specify a better link, bug 873093
|
|
if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
|
|
url = Services.urlFormatter.formatURLPref("plugins.update.url");
|
|
}
|
|
else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
|
|
url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
|
|
}
|
|
else {
|
|
url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
|
|
}
|
|
pluginInfo.detailsLink = url;
|
|
|
|
pluginData.set(pluginInfo.permissionString, pluginInfo);
|
|
}
|
|
|
|
let primaryPluginPermission = null;
|
|
if (showNow) {
|
|
primaryPluginPermission = plugins[0].permissionString;
|
|
}
|
|
|
|
if (notification) {
|
|
// Don't modify the notification UI while it's on the screen, that would be
|
|
// jumpy and might allow clickjacking.
|
|
if (showNow) {
|
|
notification.options.primaryPlugin = primaryPluginPermission;
|
|
notification.reshow();
|
|
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
|
|
}
|
|
return;
|
|
}
|
|
|
|
let options = {
|
|
dismissed: !showNow,
|
|
eventCallback: this._clickToPlayNotificationEventCallback,
|
|
primaryPlugin: primaryPluginPermission,
|
|
pluginData: pluginData,
|
|
principal: principal,
|
|
host: host,
|
|
};
|
|
PopupNotifications.show(browser, "click-to-play-plugins",
|
|
"", "plugins-notification-icon",
|
|
null, null, options);
|
|
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
|
|
},
|
|
|
|
removeNotification: function (browser, name) {
|
|
let notification = PopupNotifications.getNotification(name, browser);
|
|
if (notification)
|
|
PopupNotifications.remove(notification);
|
|
},
|
|
|
|
hideNotificationBar: function (browser, name) {
|
|
let notificationBox = gBrowser.getNotificationBox(browser);
|
|
let notification = notificationBox.getNotificationWithValue(name);
|
|
if (notification)
|
|
notificationBox.removeNotification(notification, true);
|
|
},
|
|
|
|
updateHiddenPluginUI: function (browser, haveInsecure, actions, principal, host) {
|
|
// It is possible that we've received a message from the frame script to show
|
|
// the hidden plugin notification for a principal that no longer matches the one
|
|
// that the browser's content now has assigned (ie, the browser has browsed away
|
|
// after the message was sent, but before the message was received). In that case,
|
|
// we should just ignore the message.
|
|
if (!principal.equals(browser.contentPrincipal)) {
|
|
return;
|
|
}
|
|
|
|
// Set up the icon
|
|
document.getElementById("plugins-notification-icon").classList.
|
|
toggle("plugin-blocked", haveInsecure);
|
|
|
|
// Now configure the notification bar
|
|
let notificationBox = gBrowser.getNotificationBox(browser);
|
|
|
|
function hideNotification() {
|
|
let n = notificationBox.getNotificationWithValue("plugin-hidden");
|
|
if (n) {
|
|
notificationBox.removeNotification(n, true);
|
|
}
|
|
}
|
|
|
|
// There are three different cases when showing an infobar:
|
|
// 1. A single type of plugin is hidden on the page. Show the UI for that
|
|
// plugin.
|
|
// 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
|
|
// with the vulnerable styling.
|
|
// 2b. Multiple types of plugins are hidden on the page, but none are
|
|
// vulnerable. Show the nonvulnerable multi-UI.
|
|
function showNotification() {
|
|
let n = notificationBox.getNotificationWithValue("plugin-hidden");
|
|
if (n) {
|
|
// If something is already shown, just keep it
|
|
return;
|
|
}
|
|
|
|
Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN").
|
|
add(true);
|
|
|
|
let message;
|
|
// Icons set directly cannot be manipulated using moz-image-region, so
|
|
// we use CSS classes instead.
|
|
let brand = document.getElementById("bundle_brand").getString("brandShortName");
|
|
|
|
if (actions.length == 1) {
|
|
let pluginInfo = actions[0];
|
|
let pluginName = pluginInfo.pluginName;
|
|
|
|
switch (pluginInfo.fallbackType) {
|
|
case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
|
|
message = gNavigatorBundle.getFormattedString(
|
|
"pluginActivateNew.message",
|
|
[pluginName, host]);
|
|
break;
|
|
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
|
|
message = gNavigatorBundle.getFormattedString(
|
|
"pluginActivateOutdated.message",
|
|
[pluginName, host, brand]);
|
|
break;
|
|
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
|
|
message = gNavigatorBundle.getFormattedString(
|
|
"pluginActivateVulnerable.message",
|
|
[pluginName, host, brand]);
|
|
}
|
|
} else {
|
|
// Multi-plugin
|
|
message = gNavigatorBundle.getFormattedString(
|
|
"pluginActivateMultiple.message", [host]);
|
|
}
|
|
|
|
let buttons = [
|
|
{
|
|
label: gNavigatorBundle.getString("pluginContinueBlocking.label"),
|
|
accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"),
|
|
callback: function() {
|
|
Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK").
|
|
add(true);
|
|
|
|
Services.perms.addFromPrincipal(principal,
|
|
"plugin-hidden-notification",
|
|
Services.perms.DENY_ACTION);
|
|
}
|
|
},
|
|
{
|
|
label: gNavigatorBundle.getString("pluginActivateTrigger.label"),
|
|
accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"),
|
|
callback: function() {
|
|
Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW").
|
|
add(true);
|
|
|
|
let curNotification =
|
|
PopupNotifications.getNotification("click-to-play-plugins",
|
|
browser);
|
|
if (curNotification) {
|
|
curNotification.reshow();
|
|
}
|
|
}
|
|
}
|
|
];
|
|
n = notificationBox.
|
|
appendNotification(message, "plugin-hidden", null,
|
|
notificationBox.PRIORITY_INFO_HIGH, buttons);
|
|
if (haveInsecure) {
|
|
n.classList.add('pluginVulnerable');
|
|
}
|
|
}
|
|
|
|
if (actions.length == 0) {
|
|
hideNotification();
|
|
} else {
|
|
let notificationPermission = Services.perms.testPermissionFromPrincipal(
|
|
principal, "plugin-hidden-notification");
|
|
if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
|
|
hideNotification();
|
|
} else {
|
|
showNotification();
|
|
}
|
|
}
|
|
},
|
|
|
|
contextMenuCommand: function (browser, plugin, command) {
|
|
browser.messageManager.sendAsyncMessage("BrowserPlugins:ContextMenuCommand",
|
|
{ command: command }, { plugin: plugin });
|
|
},
|
|
|
|
// Crashed-plugin observer. Notified once per plugin crash, before events
|
|
// are dispatched to individual plugin instances.
|
|
pluginCrashed : 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);
|
|
}
|
|
#endif
|
|
},
|
|
|
|
showPluginCrashedNotification: function (browser, messageString, pluginDumpID, browserDumpID) {
|
|
// If there's already an existing notification bar, don't do anything.
|
|
let notificationBox = gBrowser.getNotificationBox(browser);
|
|
let notification = notificationBox.getNotificationWithValue("plugin-crashed");
|
|
if (notification)
|
|
return;
|
|
|
|
// Configure the notification bar
|
|
let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
|
|
let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
|
|
let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
|
|
let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
|
|
let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
|
|
let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
|
|
|
|
let buttons = [{
|
|
label: reloadLabel,
|
|
accessKey: reloadKey,
|
|
popup: null,
|
|
callback: function() { browser.reload(); },
|
|
}];
|
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
let submitButton = {
|
|
label: submitLabel,
|
|
accessKey: submitKey,
|
|
popup: null,
|
|
callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
|
|
};
|
|
if (pluginDumpID)
|
|
buttons.push(submitButton);
|
|
#endif
|
|
|
|
notification = notificationBox.appendNotification(messageString, "plugin-crashed",
|
|
iconURL, priority, buttons);
|
|
|
|
// Add the "learn more" link.
|
|
let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|
let link = notification.ownerDocument.createElementNS(XULNS, "label");
|
|
link.className = "text-link";
|
|
link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
|
|
let crashurl = formatURL("app.support.baseURL", true);
|
|
crashurl += "plugin-crashed-notificationbar";
|
|
link.href = crashurl;
|
|
|
|
let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
|
|
description.appendChild(link);
|
|
},
|
|
};
|
|
|
|
gPluginHandler.init();
|
|
|