Mypal68/browser/base/content/browser-plugins.js
2024-08-04 00:24:53 +03:00

577 lines
18 KiB
JavaScript

/* 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 = {
MESSAGES: [
"PluginContent:ShowClickToPlayNotification",
"PluginContent:RemoveNotification",
"PluginContent:InstallSinglePlugin",
"PluginContent:ShowPluginCrashedNotification",
"PluginContent:SubmitReport",
"PluginContent:LinkClickCallback",
],
init() {
const mm = window.messageManager;
for (let msg of this.MESSAGES) {
mm.addMessageListener(msg, this);
}
window.addEventListener("unload", this);
},
uninit() {
const mm = window.messageManager;
for (let msg of this.MESSAGES) {
mm.removeMessageListener(msg, this);
}
window.removeEventListener("unload", this);
},
handleEvent(event) {
if (event.type == "unload") {
this.uninit();
}
},
receiveMessage(msg) {
switch (msg.name) {
case "PluginContent:ShowClickToPlayNotification":
this.showClickToPlayNotification(
msg.target,
msg.data.plugins,
msg.data.showNow,
msg.data.principal,
msg.data.location
);
break;
case "PluginContent:RemoveNotification":
this.removeNotification(msg.target, msg.data.name);
break;
case "PluginContent:InstallSinglePlugin":
this.installSinglePlugin(msg.data.pluginInfo);
break;
case "PluginContent:ShowPluginCrashedNotification":
this.showPluginCrashedNotification(
msg.target,
msg.data.messageString,
msg.data.pluginID
);
break;
case "PluginContent:SubmitReport":
if (AppConstants.MOZ_CRASHREPORTER) {
this.submitReport(
msg.data.runID,
msg.data.keyVals,
msg.data.submitURLOptIn
);
}
break;
case "PluginContent:LinkClickCallback":
switch (msg.data.name) {
case "managePlugins":
case "openHelpPage":
case "openPluginUpdatePage":
this[msg.data.name](msg.data.pluginTag);
break;
}
break;
default:
Cu.reportError(
"gPluginHandler did not expect to handle message " + msg.name
);
break;
}
},
// Callback for user clicking on a disabled plugin
managePlugins() {
BrowserOpenAddonsMgr("addons://list/plugin");
},
// Callback for user clicking on the link in a click-to-play plugin
// (where the plugin has an update)
async openPluginUpdatePage(pluginTag) {
let { Blocklist } = ChromeUtils.import(
"resource://gre/modules/Blocklist.jsm"
);
let url = await Blocklist.getPluginBlockURL(pluginTag);
openTrustedLinkIn(url, "tab");
},
submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
if (!AppConstants.MOZ_CRASHREPORTER) {
return;
}
Services.prefs.setBoolPref(
"dom.ipc.plugins.reportCrashURL",
submitURLOptIn
);
PluginCrashReporter.submitCrashReport(runID, keyVals);
},
// Callback for user clicking a "reload page" link
reloadPage(browser) {
browser.reload();
},
// Callback for user clicking the help icon
openHelpPage() {
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(aBrowser, aPluginInfo, aNewState) {
let permission;
let expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
let histogram = Services.telemetry.getHistogramById(
"PLUGINS_NOTIFICATION_USER_ACTION_2"
);
let notification = PopupNotifications.getNotification(
"click-to-play-plugins",
aBrowser
);
// 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;
histogram.add(0);
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
notification.options.extraAttr = "active";
break;
case "allowalways":
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
histogram.add(1);
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
notification.options.extraAttr = "active";
break;
case "block":
permission = Ci.nsIPermissionManager.PROMPT_ACTION;
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:
// PLUGIN_CLICK_TO_PLAY_QUIET will only last until they reload the page, at
// which point it will be PLUGIN_CLICK_TO_PLAY (the overlays will appear)
aPluginInfo.fallbackType =
Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET;
}
notification.options.extraAttr = "inactive";
break;
case "blockalways":
permission =
Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET;
histogram.add(3);
aPluginInfo.fallbackType =
Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET;
notification.options.extraAttr = "inactive";
break;
// In case a plugin has already been allowed/disallowed in another tab, the
// buttons matching the existing block state shouldn't change any permissions
// but should run the plugin-enablement code below.
case "continue":
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
notification.options.extraAttr = "active";
break;
case "continueblocking":
aPluginInfo.fallbackType =
Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET;
notification.options.extraAttr = "inactive";
break;
default:
Cu.reportError(Error("Unexpected plugin state: " + aNewState));
return;
}
if (aNewState != "continue" && aNewState != "continueblocking") {
let principal = notification.options.principal;
Services.perms.addFromPrincipal(
principal,
aPluginInfo.permissionString,
permission,
expireType,
0
);
aPluginInfo.pluginPermissionType = expireType;
}
aBrowser.messageManager.sendAsyncMessage("BrowserPlugins:ActivatePlugins", {
pluginInfo: aPluginInfo,
newState: aNewState,
});
},
showClickToPlayNotification(browser, plugins, showNow, principal, location) {
// 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 = Services.io.newURI(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 (let pluginInfo of plugins) {
if (pluginData.has(pluginInfo.permissionString)) {
continue;
}
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();
}
return;
}
if (plugins.length == 1) {
let pluginInfo = plugins[0];
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
let active =
pluginInfo.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
let options = {
dismissed: !showNow,
hideClose: true,
persistent: showNow,
eventCallback: this._clickToPlayNotificationEventCallback,
primaryPlugin: primaryPluginPermission,
popupIconClass: "plugin-icon",
extraAttr: active ? "active" : "inactive",
pluginData,
principal,
};
let description;
if (
pluginInfo.fallbackType ==
Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE
) {
description = gNavigatorBundle.getString(
"flashActivate.outdated.message"
);
} else {
description = gNavigatorBundle.getString("flashActivate.message");
}
let badge = document.getElementById("plugin-icon-badge");
badge.setAttribute("animate", "true");
badge.addEventListener("animationend", function animListener(event) {
if (
event.animationName == "blink-badge" &&
badge.hasAttribute("animate")
) {
badge.removeAttribute("animate");
badge.removeEventListener("animationend", animListener);
}
});
let weakBrowser = Cu.getWeakReference(browser);
let mainAction = {
callback: ({ checkboxChecked }) => {
let browserRef = weakBrowser.get();
if (browserRef) {
if (checkboxChecked) {
this._updatePluginPermission(
browserRef,
pluginInfo,
"allowalways"
);
} else if (
pluginInfo.fallbackType ==
Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE
) {
this._updatePluginPermission(browserRef, pluginInfo, "continue");
} else {
this._updatePluginPermission(browserRef, pluginInfo, "allownow");
}
}
},
label: gNavigatorBundle.getString("flashActivate.allow"),
accessKey: gNavigatorBundle.getString("flashActivate.allow.accesskey"),
dismiss: true,
};
let secondaryActions = null;
if (!isWindowPrivate) {
options.checkbox = {
label: gNavigatorBundle.getString("flashActivate.remember"),
};
secondaryActions = [
{
callback: ({ checkboxChecked }) => {
let browserRef = weakBrowser.get();
if (browserRef) {
if (checkboxChecked) {
this._updatePluginPermission(
browserRef,
pluginInfo,
"blockalways"
);
} else if (
pluginInfo.fallbackType ==
Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE
) {
this._updatePluginPermission(browserRef, pluginInfo, "block");
} else {
this._updatePluginPermission(
browserRef,
pluginInfo,
"continueblocking"
);
}
}
},
label: gNavigatorBundle.getString("flashActivate.noAllow"),
accessKey: gNavigatorBundle.getString(
"flashActivate.noAllow.accesskey"
),
dismiss: true,
},
];
}
PopupNotifications.show(
browser,
"click-to-play-plugins",
description,
"plugins-notification-icon",
mainAction,
secondaryActions,
options
);
// Check if the plugin is insecure and update the notification icon accordingly.
let haveInsecure = false;
switch (pluginInfo.fallbackType) {
// haveInsecure will trigger the red flashing icon and the infobar
// styling below
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
haveInsecure = true;
}
document
.getElementById("plugins-notification-icon")
.classList.toggle("plugin-blocked", haveInsecure);
} else {
this.removeNotification(browser, "click-to-play-plugins");
}
},
removeNotification(browser, name) {
let notification = PopupNotifications.getNotification(name, browser);
if (notification) {
PopupNotifications.remove(notification);
}
},
contextMenuCommand(browser, plugin, command) {
browser.messageManager.sendAsyncMessage(
"BrowserPlugins:ContextMenuCommand",
{ command },
{ plugin }
);
},
// Crashed-plugin observer. Notified once per plugin crash, before events
// are dispatched to individual plugin instances.
NPAPIPluginCrashed(subject, topic, data) {
let propertyBag = subject;
if (
!(propertyBag instanceof Ci.nsIPropertyBag2) ||
!(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;
}
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,
});
},
/**
* Shows a plugin-crashed notification bar for a browser that has had an
* invisiable NPAPI plugin crash, or a GMP plugin crash.
*
* @param browser
* The browser to show the notification for.
* @param messageString
* The string to put in the notification bar
* @param pluginID
* The unique-per-process identifier for the NPAPI plugin or GMP.
* For a GMP, this is the pluginID. For NPAPI plugins (where "pluginID"
* means something different), this is the runID.
*/
showPluginCrashedNotification(browser, messageString, pluginID) {
// 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://global/skin/plugins/pluginGeneric.svg";
let reloadLabel = gNavigatorBundle.getString(
"crashedpluginsMessage.reloadButton.label"
);
let reloadKey = gNavigatorBundle.getString(
"crashedpluginsMessage.reloadButton.accesskey"
);
let buttons = [
{
label: reloadLabel,
accessKey: reloadKey,
popup: null,
callback() {
browser.reload();
},
},
];
if (
AppConstants.MOZ_CRASHREPORTER &&
PluginCrashReporter.hasCrashReport(pluginID)
) {
let submitLabel = gNavigatorBundle.getString(
"crashedpluginsMessage.submitButton.label"
);
let submitKey = gNavigatorBundle.getString(
"crashedpluginsMessage.submitButton.accesskey"
);
let submitButton = {
label: submitLabel,
accessKey: submitKey,
popup: null,
callback: () => {
PluginCrashReporter.submitCrashReport(pluginID);
},
};
buttons.push(submitButton);
}
notification = notificationBox.appendNotification(
messageString,
"plugin-crashed",
iconURL,
priority,
buttons
);
// Add the "learn more" link.
let link = notification.ownerDocument.createXULElement("label", {
is: "text-link",
});
link.setAttribute(
"value",
gNavigatorBundle.getString("crashedpluginsMessage.learnMore")
);
let crashurl = formatURL("app.support.baseURL", true);
crashurl += "plugin-crashed-notificationbar";
link.href = crashurl;
notification.messageText.appendChild(link);
},
};
gPluginHandler.init();