summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base/content/browser-plugins.js
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/base/content/browser-plugins.js')
-rw-r--r--application/basilisk/base/content/browser-plugins.js516
1 files changed, 516 insertions, 0 deletions
diff --git a/application/basilisk/base/content/browser-plugins.js b/application/basilisk/base/content/browser-plugins.js
new file mode 100644
index 000000000..c1bc65860
--- /dev/null
+++ b/application/basilisk/base/content/browser-plugins.js
@@ -0,0 +1,516 @@
+/* -*- 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: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.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.location);
+ break;
+ case "PluginContent:HideNotificationBar":
+ this.hideNotificationBar(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":
+ // Nothing to do here
+ break;
+ case "PluginContent:LinkClickCallback":
+ switch (msg.data.name) {
+ case "managePlugins":
+ case "openHelpPage":
+ case "openPluginUpdatePage":
+ this[msg.data.name].call(this, 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: 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(pluginTag) {
+ let url = Services.blocklist.getPluginInfoURL(pluginTag);
+ if (!url) {
+ url = Services.blocklist.getPluginBlocklistURL(pluginTag);
+ }
+ openUILinkIn(url, "tab");
+ },
+
+ submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
+ /*** STUB ***/
+ return;
+ },
+
+ // 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;
+ 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, 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;
+ }
+
+ // If a block contains an infoURL, we should always prefer that to the default
+ // URL that we construct in-product, even for other blocklist types.
+ let url = Services.blocklist.getPluginInfoURL(pluginInfo.pluginTag);
+
+ if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+ if (!url) {
+ 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,
+ };
+ 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, location) {
+ let origin = principal.originNoSuffix;
+
+ // 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, origin]);
+ break;
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateOutdated.message",
+ [pluginName, origin, brand]);
+ break;
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateVulnerable.message",
+ [pluginName, origin, brand]);
+ }
+ } else {
+ // Multi-plugin
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateMultiple.message", [origin]);
+ }
+
+ 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.
+ NPAPIPluginCrashed : function(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 = "noSubmit";
+
+ 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: function (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://mozapps/skin/plugins/notifyPluginCrashed.png";
+ let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
+ let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
+
+ let buttons = [{
+ label: reloadLabel,
+ accessKey: reloadKey,
+ popup: null,
+ callback: function() { browser.reload(); },
+ }];
+
+ 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();