mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
52676fa9cc
Before, we were comparing Principals sent up from PluginContent against the Principal of the selected browser - this was in order to account for races (and intermittent oranges) where the browser has moved away from a page before a CTP message has been received by the parent. Comparing Principals works beautifully, except in the case of Data URIs, since Data URIs inherit the Principals of the page they were linked from. That means that if we're on a page with a link to a Data URI that embeds a plugin, if we quickly browse to that page and back before a CTP message reaches the parent, when the CTP message finally arrives, the Principals will match. We solve this by sending the location as well as the Principal, to do a final comparison if the Principals do match. --HG-- extra : rebase_source : 095706306d241a254f62b28527a96a427d7e55c3 extra : histedit_source : 3afe814271166f70afce6a423bbb7809c5a983c7
525 lines
20 KiB
JavaScript
525 lines
20 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, msg.data.location);
|
|
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, msg.data.location);
|
|
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;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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, location) {
|
|
// 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;
|
|
}
|
|
|
|
// Data URIs, when linked to from some page, inherit the principal of that
|
|
// page. That means that we also need to compare the actual locations to
|
|
// ensure we aren't getting a message from a Data URI that we're no longer
|
|
// looking at.
|
|
let receivedURI = BrowserUtils.makeURI(location);
|
|
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
|
|
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, location) {
|
|
// 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;
|
|
}
|
|
|
|
// Data URIs, when linked to from some page, inherit the principal of that
|
|
// page. That means that we also need to compare the actual locations to
|
|
// ensure we aren't getting a message from a Data URI that we're no longer
|
|
// looking at.
|
|
let receivedURI = BrowserUtils.makeURI(location);
|
|
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
|
|
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();
|
|
|