browser/base/content/browser-plugins.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 5
michael@0 6 var gPluginHandler = {
michael@0 7 PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash",
michael@0 8 PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification",
michael@0 9 PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
michael@0 10 PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
michael@0 11
michael@0 12 getPluginUI: function (plugin, anonid) {
michael@0 13 return plugin.ownerDocument.
michael@0 14 getAnonymousElementByAttribute(plugin, "anonid", anonid);
michael@0 15 },
michael@0 16
michael@0 17 #ifdef MOZ_CRASHREPORTER
michael@0 18 get CrashSubmit() {
michael@0 19 delete this.CrashSubmit;
michael@0 20 Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
michael@0 21 return this.CrashSubmit;
michael@0 22 },
michael@0 23 #endif
michael@0 24
michael@0 25 _getPluginInfo: function (pluginElement) {
michael@0 26 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
michael@0 27 pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
michael@0 28
michael@0 29 let tagMimetype;
michael@0 30 let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin");
michael@0 31 let pluginTag = null;
michael@0 32 let permissionString = null;
michael@0 33 let fallbackType = null;
michael@0 34 let blocklistState = null;
michael@0 35
michael@0 36 tagMimetype = pluginElement.actualType;
michael@0 37 if (tagMimetype == "") {
michael@0 38 tagMimetype = pluginElement.type;
michael@0 39 }
michael@0 40
michael@0 41 if (gPluginHandler.isKnownPlugin(pluginElement)) {
michael@0 42 pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
michael@0 43 pluginName = gPluginHandler.makeNicePluginName(pluginTag.name);
michael@0 44
michael@0 45 permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
michael@0 46 fallbackType = pluginElement.defaultFallbackType;
michael@0 47 blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
michael@0 48 // Make state-softblocked == state-notblocked for our purposes,
michael@0 49 // they have the same UI. STATE_OUTDATED should not exist for plugin
michael@0 50 // items, but let's alias it anyway, just in case.
michael@0 51 if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
michael@0 52 blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
michael@0 53 blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
michael@0 54 }
michael@0 55 }
michael@0 56
michael@0 57 return { mimetype: tagMimetype,
michael@0 58 pluginName: pluginName,
michael@0 59 pluginTag: pluginTag,
michael@0 60 permissionString: permissionString,
michael@0 61 fallbackType: fallbackType,
michael@0 62 blocklistState: blocklistState,
michael@0 63 };
michael@0 64 },
michael@0 65
michael@0 66 // Map the plugin's name to a filtered version more suitable for user UI.
michael@0 67 makeNicePluginName : function (aName) {
michael@0 68 if (aName == "Shockwave Flash")
michael@0 69 return "Adobe Flash";
michael@0 70
michael@0 71 // Clean up the plugin name by stripping off parenthetical clauses,
michael@0 72 // trailing version numbers or "plugin".
michael@0 73 // EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar"
michael@0 74 // Do this by first stripping the numbers, etc. off the end, and then
michael@0 75 // removing "Plugin" (and then trimming to get rid of any whitespace).
michael@0 76 // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
michael@0 77 let newName = aName.replace(/\(.*?\)/g, "").
michael@0 78 replace(/[\s\d\.\-\_\(\)]+$/, "").
michael@0 79 replace(/\bplug-?in\b/i, "").trim();
michael@0 80 return newName;
michael@0 81 },
michael@0 82
michael@0 83 /**
michael@0 84 * Update the visibility of the plugin overlay.
michael@0 85 */
michael@0 86 setVisibility : function (plugin, overlay, shouldShow) {
michael@0 87 overlay.classList.toggle("visible", shouldShow);
michael@0 88 },
michael@0 89
michael@0 90 /**
michael@0 91 * Check whether the plugin should be visible on the page. A plugin should
michael@0 92 * not be visible if the overlay is too big, or if any other page content
michael@0 93 * overlays it.
michael@0 94 *
michael@0 95 * This function will handle showing or hiding the overlay.
michael@0 96 * @returns true if the plugin is invisible.
michael@0 97 */
michael@0 98 shouldShowOverlay : function (plugin, overlay) {
michael@0 99 // If the overlay size is 0, we haven't done layout yet. Presume that
michael@0 100 // plugins are visible until we know otherwise.
michael@0 101 if (overlay.scrollWidth == 0) {
michael@0 102 return true;
michael@0 103 }
michael@0 104
michael@0 105 // Is the <object>'s size too small to hold what we want to show?
michael@0 106 let pluginRect = plugin.getBoundingClientRect();
michael@0 107 // XXX bug 446693. The text-shadow on the submitted-report text at
michael@0 108 // the bottom causes scrollHeight to be larger than it should be.
michael@0 109 let overflows = (overlay.scrollWidth > pluginRect.width) ||
michael@0 110 (overlay.scrollHeight - 5 > pluginRect.height);
michael@0 111 if (overflows) {
michael@0 112 return false;
michael@0 113 }
michael@0 114
michael@0 115 // Is the plugin covered up by other content so that it is not clickable?
michael@0 116 // Floating point can confuse .elementFromPoint, so inset just a bit
michael@0 117 let left = pluginRect.left + 2;
michael@0 118 let right = pluginRect.right - 2;
michael@0 119 let top = pluginRect.top + 2;
michael@0 120 let bottom = pluginRect.bottom - 2;
michael@0 121 let centerX = left + (right - left) / 2;
michael@0 122 let centerY = top + (bottom - top) / 2;
michael@0 123 let points = [[left, top],
michael@0 124 [left, bottom],
michael@0 125 [right, top],
michael@0 126 [right, bottom],
michael@0 127 [centerX, centerY]];
michael@0 128
michael@0 129 if (right <= 0 || top <= 0) {
michael@0 130 return false;
michael@0 131 }
michael@0 132
michael@0 133 let contentWindow = plugin.ownerDocument.defaultView;
michael@0 134 let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 135 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 136
michael@0 137 for (let [x, y] of points) {
michael@0 138 let el = cwu.elementFromPoint(x, y, true, true);
michael@0 139 if (el !== plugin) {
michael@0 140 return false;
michael@0 141 }
michael@0 142 }
michael@0 143
michael@0 144 return true;
michael@0 145 },
michael@0 146
michael@0 147 addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
michael@0 148 // XXX just doing (callback)(arg) was giving a same-origin error. bug?
michael@0 149 let self = this;
michael@0 150 let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
michael@0 151 linkNode.addEventListener("click",
michael@0 152 function(evt) {
michael@0 153 if (!evt.isTrusted)
michael@0 154 return;
michael@0 155 evt.preventDefault();
michael@0 156 if (callbackArgs.length == 0)
michael@0 157 callbackArgs = [ evt ];
michael@0 158 (self[callbackName]).apply(self, callbackArgs);
michael@0 159 },
michael@0 160 true);
michael@0 161
michael@0 162 linkNode.addEventListener("keydown",
michael@0 163 function(evt) {
michael@0 164 if (!evt.isTrusted)
michael@0 165 return;
michael@0 166 if (evt.keyCode == evt.DOM_VK_RETURN) {
michael@0 167 evt.preventDefault();
michael@0 168 if (callbackArgs.length == 0)
michael@0 169 callbackArgs = [ evt ];
michael@0 170 evt.preventDefault();
michael@0 171 (self[callbackName]).apply(self, callbackArgs);
michael@0 172 }
michael@0 173 },
michael@0 174 true);
michael@0 175 },
michael@0 176
michael@0 177 // Helper to get the binding handler type from a plugin object
michael@0 178 _getBindingType : function(plugin) {
michael@0 179 if (!(plugin instanceof Ci.nsIObjectLoadingContent))
michael@0 180 return null;
michael@0 181
michael@0 182 switch (plugin.pluginFallbackType) {
michael@0 183 case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
michael@0 184 return "PluginNotFound";
michael@0 185 case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
michael@0 186 return "PluginDisabled";
michael@0 187 case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
michael@0 188 return "PluginBlocklisted";
michael@0 189 case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
michael@0 190 return "PluginOutdated";
michael@0 191 case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
michael@0 192 return "PluginClickToPlay";
michael@0 193 case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
michael@0 194 return "PluginVulnerableUpdatable";
michael@0 195 case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
michael@0 196 return "PluginVulnerableNoUpdate";
michael@0 197 case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
michael@0 198 return "PluginPlayPreview";
michael@0 199 default:
michael@0 200 // Not all states map to a handler
michael@0 201 return null;
michael@0 202 }
michael@0 203 },
michael@0 204
michael@0 205 supportedPlugins: {
michael@0 206 "mimetypes": {
michael@0 207 "application/x-shockwave-flash": "flash",
michael@0 208 "application/futuresplash": "flash",
michael@0 209 "application/x-java-.*": "java",
michael@0 210 "application/x-director": "shockwave",
michael@0 211 "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime",
michael@0 212 "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime",
michael@0 213 "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime",
michael@0 214 "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime",
michael@0 215 "application/x-unknown": "test",
michael@0 216 },
michael@0 217
michael@0 218 "plugins": {
michael@0 219 "flash": {
michael@0 220 "displayName": "Flash",
michael@0 221 "installWINNT": true,
michael@0 222 "installDarwin": true,
michael@0 223 "installLinux": true,
michael@0 224 },
michael@0 225 "java": {
michael@0 226 "displayName": "Java",
michael@0 227 "installWINNT": true,
michael@0 228 "installDarwin": true,
michael@0 229 "installLinux": true,
michael@0 230 },
michael@0 231 "shockwave": {
michael@0 232 "displayName": "Shockwave",
michael@0 233 "installWINNT": true,
michael@0 234 "installDarwin": true,
michael@0 235 },
michael@0 236 "quicktime": {
michael@0 237 "displayName": "QuickTime",
michael@0 238 "installWINNT": true,
michael@0 239 },
michael@0 240 "test": {
michael@0 241 "displayName": "Test plugin",
michael@0 242 "installWINNT": true,
michael@0 243 "installLinux": true,
michael@0 244 "installDarwin": true,
michael@0 245 }
michael@0 246 }
michael@0 247 },
michael@0 248
michael@0 249 nameForSupportedPlugin: function (aMimeType) {
michael@0 250 for (let type in this.supportedPlugins.mimetypes) {
michael@0 251 let re = new RegExp(type);
michael@0 252 if (re.test(aMimeType)) {
michael@0 253 return this.supportedPlugins.mimetypes[type];
michael@0 254 }
michael@0 255 }
michael@0 256 return null;
michael@0 257 },
michael@0 258
michael@0 259 canInstallThisMimeType: function (aMimeType) {
michael@0 260 let os = Services.appinfo.OS;
michael@0 261 let pluginName = this.nameForSupportedPlugin(aMimeType);
michael@0 262 if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) {
michael@0 263 return true;
michael@0 264 }
michael@0 265 return false;
michael@0 266 },
michael@0 267
michael@0 268 handleEvent : function(event) {
michael@0 269 let eventType = event.type;
michael@0 270
michael@0 271 if (eventType == "PluginRemoved") {
michael@0 272 let doc = event.target;
michael@0 273 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
michael@0 274 if (browser)
michael@0 275 this._setPluginNotificationIcon(browser);
michael@0 276 return;
michael@0 277 }
michael@0 278
michael@0 279 let plugin = event.target;
michael@0 280 let doc = plugin.ownerDocument;
michael@0 281
michael@0 282 if (!(plugin instanceof Ci.nsIObjectLoadingContent))
michael@0 283 return;
michael@0 284
michael@0 285 if (eventType == "PluginBindingAttached") {
michael@0 286 // The plugin binding fires this event when it is created.
michael@0 287 // As an untrusted event, ensure that this object actually has a binding
michael@0 288 // and make sure we don't handle it twice
michael@0 289 let overlay = this.getPluginUI(plugin, "main");
michael@0 290 if (!overlay || overlay._bindingHandled) {
michael@0 291 return;
michael@0 292 }
michael@0 293 overlay._bindingHandled = true;
michael@0 294
michael@0 295 // Lookup the handler for this binding
michael@0 296 eventType = this._getBindingType(plugin);
michael@0 297 if (!eventType) {
michael@0 298 // Not all bindings have handlers
michael@0 299 return;
michael@0 300 }
michael@0 301 }
michael@0 302
michael@0 303 let shouldShowNotification = false;
michael@0 304 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
michael@0 305 if (!browser)
michael@0 306 return;
michael@0 307
michael@0 308 switch (eventType) {
michael@0 309 case "PluginCrashed":
michael@0 310 this.pluginInstanceCrashed(plugin, event);
michael@0 311 break;
michael@0 312
michael@0 313 case "PluginNotFound":
michael@0 314 let installable = this.showInstallNotification(plugin, eventType);
michael@0 315 // For non-object plugin tags, register a click handler to install the
michael@0 316 // plugin. Object tags can, and often do, deal with that themselves,
michael@0 317 // so don't stomp on the page developers toes.
michael@0 318 if (installable && !(plugin instanceof HTMLObjectElement)) {
michael@0 319 let installStatus = this.getPluginUI(plugin, "installStatus");
michael@0 320 installStatus.setAttribute("installable", "true");
michael@0 321 let iconStatus = this.getPluginUI(plugin, "icon");
michael@0 322 iconStatus.setAttribute("installable", "true");
michael@0 323
michael@0 324 let installLink = this.getPluginUI(plugin, "installPluginLink");
michael@0 325 this.addLinkClickCallback(installLink, "installSinglePlugin", plugin);
michael@0 326 }
michael@0 327 break;
michael@0 328
michael@0 329 case "PluginBlocklisted":
michael@0 330 case "PluginOutdated":
michael@0 331 shouldShowNotification = true;
michael@0 332 break;
michael@0 333
michael@0 334 case "PluginVulnerableUpdatable":
michael@0 335 let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
michael@0 336 this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
michael@0 337 /* FALLTHRU */
michael@0 338
michael@0 339 case "PluginVulnerableNoUpdate":
michael@0 340 case "PluginClickToPlay":
michael@0 341 this._handleClickToPlayEvent(plugin);
michael@0 342 let overlay = this.getPluginUI(plugin, "main");
michael@0 343 let pluginName = this._getPluginInfo(plugin).pluginName;
michael@0 344 let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
michael@0 345 let overlayText = this.getPluginUI(plugin, "clickToPlay");
michael@0 346 overlayText.textContent = messageString;
michael@0 347 if (eventType == "PluginVulnerableUpdatable" ||
michael@0 348 eventType == "PluginVulnerableNoUpdate") {
michael@0 349 let vulnerabilityString = gNavigatorBundle.getString(eventType);
michael@0 350 let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
michael@0 351 vulnerabilityText.textContent = vulnerabilityString;
michael@0 352 }
michael@0 353 shouldShowNotification = true;
michael@0 354 break;
michael@0 355
michael@0 356 case "PluginPlayPreview":
michael@0 357 this._handlePlayPreviewEvent(plugin);
michael@0 358 break;
michael@0 359
michael@0 360 case "PluginDisabled":
michael@0 361 // Screw the disabled message. It messes with HTML5 fallback on YouTube
michael@0 362 let plugin_overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
michael@0 363 if (plugin_overlay != null)
michael@0 364 plugin_overlay.style.visibility = "hidden";
michael@0 365 break;
michael@0 366
michael@0 367 case "PluginInstantiated":
michael@0 368 shouldShowNotification = true;
michael@0 369 break;
michael@0 370 }
michael@0 371
michael@0 372 // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
michael@0 373 if (eventType != "PluginCrashed") {
michael@0 374 let overlay = this.getPluginUI(plugin, "main");
michael@0 375 if (overlay != null) {
michael@0 376 this.setVisibility(plugin, overlay,
michael@0 377 this.shouldShowOverlay(plugin, overlay));
michael@0 378 let resizeListener = (event) => {
michael@0 379 this.setVisibility(plugin, overlay,
michael@0 380 this.shouldShowOverlay(plugin, overlay));
michael@0 381 this._setPluginNotificationIcon(browser);
michael@0 382 };
michael@0 383 plugin.addEventListener("overflow", resizeListener);
michael@0 384 plugin.addEventListener("underflow", resizeListener);
michael@0 385 }
michael@0 386 }
michael@0 387
michael@0 388 let closeIcon = this.getPluginUI(plugin, "closeIcon");
michael@0 389 if (closeIcon) {
michael@0 390 closeIcon.addEventListener("click", function(aEvent) {
michael@0 391 if (aEvent.button == 0 && aEvent.isTrusted)
michael@0 392 gPluginHandler.hideClickToPlayOverlay(plugin);
michael@0 393 }, true);
michael@0 394 }
michael@0 395
michael@0 396 if (shouldShowNotification) {
michael@0 397 this._showClickToPlayNotification(browser, plugin, false);
michael@0 398 }
michael@0 399 },
michael@0 400
michael@0 401 isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
michael@0 402 return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
michael@0 403 Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
michael@0 404 },
michael@0 405
michael@0 406 canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
michael@0 407 // if this isn't a known plugin, we can't activate it
michael@0 408 // (this also guards pluginHost.getPermissionStringForType against
michael@0 409 // unexpected input)
michael@0 410 if (!gPluginHandler.isKnownPlugin(objLoadingContent))
michael@0 411 return false;
michael@0 412
michael@0 413 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
michael@0 414 let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
michael@0 415 let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal;
michael@0 416 let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
michael@0 417
michael@0 418 let isFallbackTypeValid =
michael@0 419 objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
michael@0 420 objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
michael@0 421
michael@0 422 if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) {
michael@0 423 // checking if play preview is subject to CTP rules
michael@0 424 let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType);
michael@0 425 isFallbackTypeValid = !playPreviewInfo.ignoreCTP;
michael@0 426 }
michael@0 427
michael@0 428 return !objLoadingContent.activated &&
michael@0 429 pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
michael@0 430 isFallbackTypeValid;
michael@0 431 },
michael@0 432
michael@0 433 hideClickToPlayOverlay: function(aPlugin) {
michael@0 434 let overlay = this.getPluginUI(aPlugin, "main");
michael@0 435 if (overlay) {
michael@0 436 overlay.classList.remove("visible");
michael@0 437 }
michael@0 438 },
michael@0 439
michael@0 440 stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
michael@0 441 let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
michael@0 442 if (objLoadingContent.activated)
michael@0 443 return;
michael@0 444
michael@0 445 if (aPlayPlugin)
michael@0 446 objLoadingContent.playPlugin();
michael@0 447 else
michael@0 448 objLoadingContent.cancelPlayPreview();
michael@0 449 },
michael@0 450
michael@0 451 newPluginInstalled : function(event) {
michael@0 452 // browser elements are anonymous so we can't just use target.
michael@0 453 var browser = event.originalTarget;
michael@0 454 // clear the plugin list, now that at least one plugin has been installed
michael@0 455 browser.missingPlugins = null;
michael@0 456
michael@0 457 var notificationBox = gBrowser.getNotificationBox(browser);
michael@0 458 var notification = notificationBox.getNotificationWithValue("missing-plugins");
michael@0 459 if (notification)
michael@0 460 notificationBox.removeNotification(notification);
michael@0 461
michael@0 462 // reload the browser to make the new plugin show.
michael@0 463 browser.reload();
michael@0 464 },
michael@0 465
michael@0 466 // Callback for user clicking on a missing (unsupported) plugin.
michael@0 467 installSinglePlugin: function (plugin) {
michael@0 468 var missingPlugins = new Map();
michael@0 469
michael@0 470 var pluginInfo = this._getPluginInfo(plugin);
michael@0 471 missingPlugins.set(pluginInfo.mimetype, pluginInfo);
michael@0 472
michael@0 473 openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
michael@0 474 "PFSWindow", "chrome,centerscreen,resizable=yes",
michael@0 475 {plugins: missingPlugins, browser: gBrowser.selectedBrowser});
michael@0 476 },
michael@0 477
michael@0 478 // Callback for user clicking on a disabled plugin
michael@0 479 managePlugins: function (aEvent) {
michael@0 480 BrowserOpenAddonsMgr("addons://list/plugin");
michael@0 481 },
michael@0 482
michael@0 483 // Callback for user clicking on the link in a click-to-play plugin
michael@0 484 // (where the plugin has an update)
michael@0 485 openPluginUpdatePage: function (aEvent) {
michael@0 486 openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab");
michael@0 487 },
michael@0 488
michael@0 489 #ifdef MOZ_CRASHREPORTER
michael@0 490 submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
michael@0 491 let keyVals = {};
michael@0 492 if (plugin) {
michael@0 493 let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
michael@0 494 if (userComment)
michael@0 495 keyVals.PluginUserComment = userComment;
michael@0 496 if (this.getPluginUI(plugin, "submitURLOptIn").checked)
michael@0 497 keyVals.PluginContentURL = plugin.ownerDocument.URL;
michael@0 498 }
michael@0 499 this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals });
michael@0 500 if (browserDumpID)
michael@0 501 this.CrashSubmit.submit(browserDumpID);
michael@0 502 },
michael@0 503 #endif
michael@0 504
michael@0 505 // Callback for user clicking a "reload page" link
michael@0 506 reloadPage: function (browser) {
michael@0 507 browser.reload();
michael@0 508 },
michael@0 509
michael@0 510 // Callback for user clicking the help icon
michael@0 511 openHelpPage: function () {
michael@0 512 openHelpLink("plugin-crashed", false);
michael@0 513 },
michael@0 514
michael@0 515 showInstallNotification: function (aPlugin) {
michael@0 516 let hideMissingPluginsNotification =
michael@0 517 Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION);
michael@0 518 if (hideMissingPluginsNotification) {
michael@0 519 return false;
michael@0 520 }
michael@0 521
michael@0 522 let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument
michael@0 523 .defaultView.top.document);
michael@0 524 if (!browser.missingPlugins)
michael@0 525 browser.missingPlugins = new Map();
michael@0 526
michael@0 527 let pluginInfo = this._getPluginInfo(aPlugin);
michael@0 528 browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo);
michael@0 529
michael@0 530 // only show notification for small subset of plugins
michael@0 531 let mimetype = pluginInfo.mimetype.split(";")[0];
michael@0 532 if (!this.canInstallThisMimeType(mimetype))
michael@0 533 return false;
michael@0 534
michael@0 535 let pluginIdentifier = this.nameForSupportedPlugin(mimetype);
michael@0 536 if (!pluginIdentifier)
michael@0 537 return false;
michael@0 538
michael@0 539 let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName;
michael@0 540
michael@0 541 // don't show several notifications
michael@0 542 let notification = PopupNotifications.getNotification("plugins-not-found", browser);
michael@0 543 if (notification)
michael@0 544 return true;
michael@0 545
michael@0 546 let messageString = gNavigatorBundle.getString("installPlugin.message");
michael@0 547 let mainAction = {
michael@0 548 label: gNavigatorBundle.getFormattedString("installPlugin.button.label",
michael@0 549 [displayName]),
michael@0 550 accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"),
michael@0 551 callback: function () {
michael@0 552 openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
michael@0 553 "PFSWindow", "chrome,centerscreen,resizable=yes",
michael@0 554 {plugins: browser.missingPlugins, browser: browser});
michael@0 555 }
michael@0 556 };
michael@0 557 let secondaryActions = null;
michael@0 558 let options = { dismissed: true };
michael@0 559
michael@0 560 let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH);
michael@0 561 if (pluginIdentifier == "flash" && showForFlash) {
michael@0 562 let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH;
michael@0 563 secondaryActions = [{
michael@0 564 label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"),
michael@0 565 accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"),
michael@0 566 callback: function () {
michael@0 567 Services.prefs.setBoolPref(prefNotifyMissingFlash, false);
michael@0 568 }
michael@0 569 }];
michael@0 570 options.dismissed = false;
michael@0 571 }
michael@0 572 PopupNotifications.show(browser, "plugins-not-found",
michael@0 573 messageString, "plugin-install-notification-icon",
michael@0 574 mainAction, secondaryActions, options);
michael@0 575 return true;
michael@0 576 },
michael@0 577 // Event listener for click-to-play plugins.
michael@0 578 _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
michael@0 579 let doc = aPlugin.ownerDocument;
michael@0 580 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
michael@0 581 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
michael@0 582 let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
michael@0 583 // guard against giving pluginHost.getPermissionStringForType a type
michael@0 584 // not associated with any known plugin
michael@0 585 if (!gPluginHandler.isKnownPlugin(objLoadingContent))
michael@0 586 return;
michael@0 587 let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
michael@0 588 let principal = doc.defaultView.top.document.nodePrincipal;
michael@0 589 let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
michael@0 590
michael@0 591 let overlay = this.getPluginUI(aPlugin, "main");
michael@0 592
michael@0 593 if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
michael@0 594 if (overlay) {
michael@0 595 overlay.classList.remove("visible");
michael@0 596 }
michael@0 597 return;
michael@0 598 }
michael@0 599
michael@0 600 if (overlay) {
michael@0 601 overlay.addEventListener("click", gPluginHandler._overlayClickListener, true);
michael@0 602 }
michael@0 603 },
michael@0 604
michael@0 605 _overlayClickListener: {
michael@0 606 handleEvent: function PH_handleOverlayClick(aEvent) {
michael@0 607 let plugin = document.getBindingParent(aEvent.target);
michael@0 608 let contentWindow = plugin.ownerDocument.defaultView.top;
michael@0 609 // gBrowser.getBrowserForDocument does not exist in the case where we
michael@0 610 // drag-and-dropped a tab from a window containing only that tab. In
michael@0 611 // that case, the window gets destroyed.
michael@0 612 let browser = gBrowser.getBrowserForDocument ?
michael@0 613 gBrowser.getBrowserForDocument(contentWindow.document) :
michael@0 614 null;
michael@0 615 // If browser is null here, we've been drag-and-dropped from another
michael@0 616 // window, and this is the wrong click handler.
michael@0 617 if (!browser) {
michael@0 618 aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true);
michael@0 619 return;
michael@0 620 }
michael@0 621 let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
michael@0 622 // Have to check that the target is not the link to update the plugin
michael@0 623 if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
michael@0 624 (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
michael@0 625 aEvent.button == 0 && aEvent.isTrusted) {
michael@0 626 gPluginHandler._showClickToPlayNotification(browser, plugin, true);
michael@0 627 aEvent.stopPropagation();
michael@0 628 aEvent.preventDefault();
michael@0 629 }
michael@0 630 }
michael@0 631 },
michael@0 632
michael@0 633 _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
michael@0 634 let doc = aPlugin.ownerDocument;
michael@0 635 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
michael@0 636 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
michael@0 637 let pluginInfo = this._getPluginInfo(aPlugin);
michael@0 638 let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype);
michael@0 639
michael@0 640 let previewContent = this.getPluginUI(aPlugin, "previewPluginContent");
michael@0 641 let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
michael@0 642 if (!iframe) {
michael@0 643 // lazy initialization of the iframe
michael@0 644 iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
michael@0 645 iframe.className = "previewPluginContentFrame";
michael@0 646 previewContent.appendChild(iframe);
michael@0 647
michael@0 648 // Force a style flush, so that we ensure our binding is attached.
michael@0 649 aPlugin.clientTop;
michael@0 650 }
michael@0 651 iframe.src = playPreviewInfo.redirectURL;
michael@0 652
michael@0 653 // MozPlayPlugin event can be dispatched from the extension chrome
michael@0 654 // code to replace the preview content with the native plugin
michael@0 655 previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
michael@0 656 if (!aEvent.isTrusted)
michael@0 657 return;
michael@0 658
michael@0 659 previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
michael@0 660
michael@0 661 let playPlugin = !aEvent.detail;
michael@0 662 gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
michael@0 663
michael@0 664 // cleaning up: removes overlay iframe from the DOM
michael@0 665 let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
michael@0 666 if (iframe)
michael@0 667 previewContent.removeChild(iframe);
michael@0 668 }, true);
michael@0 669
michael@0 670 if (!playPreviewInfo.ignoreCTP) {
michael@0 671 gPluginHandler._showClickToPlayNotification(browser, aPlugin, false);
michael@0 672 }
michael@0 673 },
michael@0 674
michael@0 675 reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
michael@0 676 let browser = gBrowser.selectedBrowser;
michael@0 677 let contentWindow = browser.contentWindow;
michael@0 678 let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 679 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 680 let plugins = cwu.plugins;
michael@0 681 for (let plugin of plugins) {
michael@0 682 let overlay = this.getPluginUI(plugin, "main");
michael@0 683 if (overlay)
michael@0 684 overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
michael@0 685 let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
michael@0 686 if (gPluginHandler.canActivatePlugin(objLoadingContent))
michael@0 687 gPluginHandler._handleClickToPlayEvent(plugin);
michael@0 688 }
michael@0 689 gPluginHandler._showClickToPlayNotification(browser, null, false);
michael@0 690 },
michael@0 691
michael@0 692 _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
michael@0 693 if (event == "showing") {
michael@0 694 Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
michael@0 695 .add(!this.options.primaryPlugin);
michael@0 696 // Histograms always start at 0, even though our data starts at 1
michael@0 697 let histogramCount = this.options.pluginData.size - 1;
michael@0 698 if (histogramCount > 4) {
michael@0 699 histogramCount = 4;
michael@0 700 }
michael@0 701 Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
michael@0 702 .add(histogramCount);
michael@0 703 }
michael@0 704 else if (event == "dismissed") {
michael@0 705 // Once the popup is dismissed, clicking the icon should show the full
michael@0 706 // list again
michael@0 707 this.options.primaryPlugin = null;
michael@0 708 }
michael@0 709 },
michael@0 710
michael@0 711 // Match the behaviour of nsPermissionManager
michael@0 712 _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) {
michael@0 713 if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) {
michael@0 714 return "(null)";
michael@0 715 }
michael@0 716
michael@0 717 try {
michael@0 718 if (principal.URI.host)
michael@0 719 return principal.URI.host;
michael@0 720 } catch (e) {}
michael@0 721
michael@0 722 return principal.origin;
michael@0 723 },
michael@0 724
michael@0 725 /**
michael@0 726 * Called from the plugin doorhanger to set the new permissions for a plugin
michael@0 727 * and activate plugins if necessary.
michael@0 728 * aNewState should be either "allownow" "allowalways" or "block"
michael@0 729 */
michael@0 730 _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) {
michael@0 731 let permission;
michael@0 732 let expireType;
michael@0 733 let expireTime;
michael@0 734 let histogram =
michael@0 735 Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
michael@0 736
michael@0 737 // Update the permission manager.
michael@0 738 // Also update the current state of pluginInfo.fallbackType so that
michael@0 739 // subsequent opening of the notification shows the current state.
michael@0 740 switch (aNewState) {
michael@0 741 case "allownow":
michael@0 742 permission = Ci.nsIPermissionManager.ALLOW_ACTION;
michael@0 743 expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
michael@0 744 expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
michael@0 745 histogram.add(0);
michael@0 746 aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
michael@0 747 break;
michael@0 748
michael@0 749 case "allowalways":
michael@0 750 permission = Ci.nsIPermissionManager.ALLOW_ACTION;
michael@0 751 expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
michael@0 752 expireTime = Date.now() +
michael@0 753 Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
michael@0 754 histogram.add(1);
michael@0 755 aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
michael@0 756 break;
michael@0 757
michael@0 758 case "block":
michael@0 759 permission = Ci.nsIPermissionManager.PROMPT_ACTION;
michael@0 760 expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
michael@0 761 expireTime = 0;
michael@0 762 histogram.add(2);
michael@0 763 switch (aPluginInfo.blocklistState) {
michael@0 764 case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
michael@0 765 aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
michael@0 766 break;
michael@0 767 case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
michael@0 768 aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
michael@0 769 break;
michael@0 770 default:
michael@0 771 aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
michael@0 772 }
michael@0 773 break;
michael@0 774
michael@0 775 // In case a plugin has already been allowed in another tab, the "continue allowing" button
michael@0 776 // shouldn't change any permissions but should run the plugin-enablement code below.
michael@0 777 case "continue":
michael@0 778 aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
michael@0 779 break;
michael@0 780 default:
michael@0 781 Cu.reportError(Error("Unexpected plugin state: " + aNewState));
michael@0 782 return;
michael@0 783 }
michael@0 784
michael@0 785 let browser = aNotification.browser;
michael@0 786 let contentWindow = browser.contentWindow;
michael@0 787 if (aNewState != "continue") {
michael@0 788 let principal = contentWindow.document.nodePrincipal;
michael@0 789 Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
michael@0 790 permission, expireType, expireTime);
michael@0 791 aPluginInfo.pluginPermissionType = expireType;
michael@0 792 }
michael@0 793
michael@0 794 // Manually activate the plugins that would have been automatically
michael@0 795 // activated.
michael@0 796 let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 797 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 798 let plugins = cwu.plugins;
michael@0 799 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
michael@0 800
michael@0 801 let pluginFound = false;
michael@0 802 for (let plugin of plugins) {
michael@0 803 plugin.QueryInterface(Ci.nsIObjectLoadingContent);
michael@0 804 if (!gPluginHandler.isKnownPlugin(plugin)) {
michael@0 805 continue;
michael@0 806 }
michael@0 807 if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
michael@0 808 pluginFound = true;
michael@0 809 if (aNewState == "block") {
michael@0 810 plugin.reload(true);
michael@0 811 } else {
michael@0 812 if (gPluginHandler.canActivatePlugin(plugin)) {
michael@0 813 let overlay = this.getPluginUI(plugin, "main");
michael@0 814 if (overlay) {
michael@0 815 overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
michael@0 816 }
michael@0 817 plugin.playPlugin();
michael@0 818 }
michael@0 819 }
michael@0 820 }
michael@0 821 }
michael@0 822
michael@0 823 // If there are no instances of the plugin on the page any more, what the
michael@0 824 // user probably needs is for us to allow and then refresh.
michael@0 825 if (aNewState != "block" && !pluginFound) {
michael@0 826 browser.reload();
michael@0 827 }
michael@0 828
michael@0 829 this._setPluginNotificationIcon(browser);
michael@0 830 },
michael@0 831
michael@0 832 _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) {
michael@0 833 let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
michael@0 834 let plugins = [];
michael@0 835
michael@0 836 // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need
michael@0 837 // to collect all the plugins
michael@0 838 if (aPlugin === null) {
michael@0 839 let contentWindow = aBrowser.contentWindow;
michael@0 840 let contentDoc = aBrowser.contentDocument;
michael@0 841 let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 842 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 843 // cwu.plugins may contain non-plugin <object>s, filter them out
michael@0 844 plugins = cwu.plugins.filter((plugin) =>
michael@0 845 plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
michael@0 846
michael@0 847 if (plugins.length == 0) {
michael@0 848 if (notification) {
michael@0 849 PopupNotifications.remove(notification);
michael@0 850 }
michael@0 851 return;
michael@0 852 }
michael@0 853 } else {
michael@0 854 plugins = [aPlugin];
michael@0 855 }
michael@0 856
michael@0 857 // If this is a new notification, create a pluginData map, otherwise append
michael@0 858 let pluginData;
michael@0 859 if (notification) {
michael@0 860 pluginData = notification.options.pluginData;
michael@0 861 } else {
michael@0 862 pluginData = new Map();
michael@0 863 }
michael@0 864
michael@0 865 let principal = aBrowser.contentDocument.nodePrincipal;
michael@0 866 let principalHost = this._getHostFromPrincipal(principal);
michael@0 867
michael@0 868 for (var plugin of plugins) {
michael@0 869 let pluginInfo = this._getPluginInfo(plugin);
michael@0 870 if (pluginInfo.permissionString === null) {
michael@0 871 Cu.reportError("No permission string for active plugin.");
michael@0 872 continue;
michael@0 873 }
michael@0 874 if (pluginData.has(pluginInfo.permissionString)) {
michael@0 875 continue;
michael@0 876 }
michael@0 877
michael@0 878 let permissionObj = Services.perms.
michael@0 879 getPermissionObject(principal, pluginInfo.permissionString, false);
michael@0 880 if (permissionObj) {
michael@0 881 pluginInfo.pluginPermissionHost = permissionObj.host;
michael@0 882 pluginInfo.pluginPermissionType = permissionObj.expireType;
michael@0 883 }
michael@0 884 else {
michael@0 885 pluginInfo.pluginPermissionHost = principalHost;
michael@0 886 pluginInfo.pluginPermissionType = undefined;
michael@0 887 }
michael@0 888
michael@0 889 let url;
michael@0 890 // TODO: allow the blocklist to specify a better link, bug 873093
michael@0 891 if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
michael@0 892 url = Services.urlFormatter.formatURLPref("plugins.update.url");
michael@0 893 }
michael@0 894 else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
michael@0 895 url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
michael@0 896 }
michael@0 897 else {
michael@0 898 url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
michael@0 899 }
michael@0 900 pluginInfo.detailsLink = url;
michael@0 901
michael@0 902 pluginData.set(pluginInfo.permissionString, pluginInfo);
michael@0 903 }
michael@0 904
michael@0 905 let primaryPluginPermission = null;
michael@0 906 if (aShowNow) {
michael@0 907 primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString;
michael@0 908 }
michael@0 909
michael@0 910 if (notification) {
michael@0 911 // Don't modify the notification UI while it's on the screen, that would be
michael@0 912 // jumpy and might allow clickjacking.
michael@0 913 if (aShowNow) {
michael@0 914 notification.options.primaryPlugin = primaryPluginPermission;
michael@0 915 notification.reshow();
michael@0 916 setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0);
michael@0 917 }
michael@0 918 return;
michael@0 919 }
michael@0 920
michael@0 921 let options = {
michael@0 922 dismissed: !aShowNow,
michael@0 923 eventCallback: this._clickToPlayNotificationEventCallback,
michael@0 924 primaryPlugin: primaryPluginPermission,
michael@0 925 pluginData: pluginData
michael@0 926 };
michael@0 927 PopupNotifications.show(aBrowser, "click-to-play-plugins",
michael@0 928 "", "plugins-notification-icon",
michael@0 929 null, null, options);
michael@0 930 setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0);
michael@0 931 },
michael@0 932
michael@0 933 _setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) {
michael@0 934 // Because this is called on a timeout, sanity-check before continuing
michael@0 935 if (!aBrowser.docShell || !aBrowser.contentWindow) {
michael@0 936 return;
michael@0 937 }
michael@0 938
michael@0 939 let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
michael@0 940 if (!notification)
michael@0 941 return;
michael@0 942
michael@0 943 // Make a copy of the actions, removing active plugins and checking for
michael@0 944 // outdated plugins.
michael@0 945 let haveInsecure = false;
michael@0 946 let actions = new Map();
michael@0 947 for (let action of notification.options.pluginData.values()) {
michael@0 948 switch (action.fallbackType) {
michael@0 949 // haveInsecure will trigger the red flashing icon and the infobar
michael@0 950 // styling below
michael@0 951 case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
michael@0 952 case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
michael@0 953 haveInsecure = true;
michael@0 954 // fall through
michael@0 955
michael@0 956 case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
michael@0 957 actions.set(action.permissionString, action);
michael@0 958 continue;
michael@0 959 }
michael@0 960 }
michael@0 961
michael@0 962 // check for hidden plugins
michael@0 963 let contentWindow = aBrowser.contentWindow;
michael@0 964 let contentDoc = aBrowser.contentDocument;
michael@0 965 let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 966 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 967 for (let plugin of cwu.plugins) {
michael@0 968 let info = this._getPluginInfo(plugin);
michael@0 969 if (!actions.has(info.permissionString)) {
michael@0 970 continue;
michael@0 971 }
michael@0 972 let fallbackType = info.fallbackType;
michael@0 973 if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
michael@0 974 actions.delete(info.permissionString);
michael@0 975 if (actions.size == 0) {
michael@0 976 break;
michael@0 977 }
michael@0 978 continue;
michael@0 979 }
michael@0 980 if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
michael@0 981 fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
michael@0 982 fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
michael@0 983 continue;
michael@0 984 }
michael@0 985 let overlay = this.getPluginUI(plugin, "main");
michael@0 986 if (!overlay) {
michael@0 987 continue;
michael@0 988 }
michael@0 989 let shouldShow = this.shouldShowOverlay(plugin, overlay);
michael@0 990 this.setVisibility(plugin, overlay, shouldShow);
michael@0 991 if (shouldShow) {
michael@0 992 actions.delete(info.permissionString);
michael@0 993 if (actions.size == 0) {
michael@0 994 break;
michael@0 995 }
michael@0 996 }
michael@0 997 }
michael@0 998
michael@0 999 // Set up the icon
michael@0 1000 document.getElementById("plugins-notification-icon").classList.
michael@0 1001 toggle("plugin-blocked", haveInsecure);
michael@0 1002
michael@0 1003 // Now configure the notification bar
michael@0 1004
michael@0 1005 let notificationBox = gBrowser.getNotificationBox(aBrowser);
michael@0 1006
michael@0 1007 function hideNotification() {
michael@0 1008 let n = notificationBox.getNotificationWithValue("plugin-hidden");
michael@0 1009 if (n) {
michael@0 1010 notificationBox.removeNotification(n, true);
michael@0 1011 }
michael@0 1012 }
michael@0 1013
michael@0 1014 // There are three different cases when showing an infobar:
michael@0 1015 // 1. A single type of plugin is hidden on the page. Show the UI for that
michael@0 1016 // plugin.
michael@0 1017 // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
michael@0 1018 // with the vulnerable styling.
michael@0 1019 // 2b. Multiple types of plugins are hidden on the page, but none are
michael@0 1020 // vulnerable. Show the nonvulnerable multi-UI.
michael@0 1021 function showNotification() {
michael@0 1022 let n = notificationBox.getNotificationWithValue("plugin-hidden");
michael@0 1023 if (n) {
michael@0 1024 // If something is already shown, just keep it
michael@0 1025 return;
michael@0 1026 }
michael@0 1027
michael@0 1028 Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN").
michael@0 1029 add(true);
michael@0 1030
michael@0 1031 let message;
michael@0 1032 // Icons set directly cannot be manipulated using moz-image-region, so
michael@0 1033 // we use CSS classes instead.
michael@0 1034 let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal);
michael@0 1035 let brand = document.getElementById("bundle_brand").getString("brandShortName");
michael@0 1036
michael@0 1037 if (actions.size == 1) {
michael@0 1038 let pluginInfo = [...actions.values()][0];
michael@0 1039 let pluginName = pluginInfo.pluginName;
michael@0 1040
michael@0 1041 switch (pluginInfo.fallbackType) {
michael@0 1042 case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
michael@0 1043 message = gNavigatorBundle.getFormattedString(
michael@0 1044 "pluginActivateNew.message",
michael@0 1045 [pluginName, host]);
michael@0 1046 break;
michael@0 1047 case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
michael@0 1048 message = gNavigatorBundle.getFormattedString(
michael@0 1049 "pluginActivateOutdated.message",
michael@0 1050 [pluginName, host, brand]);
michael@0 1051 break;
michael@0 1052 case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
michael@0 1053 message = gNavigatorBundle.getFormattedString(
michael@0 1054 "pluginActivateVulnerable.message",
michael@0 1055 [pluginName, host, brand]);
michael@0 1056 }
michael@0 1057 } else {
michael@0 1058 // Multi-plugin
michael@0 1059 message = gNavigatorBundle.getFormattedString(
michael@0 1060 "pluginActivateMultiple.message", [host]);
michael@0 1061
michael@0 1062 for (let action of actions.values()) {
michael@0 1063 if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) {
michael@0 1064 break;
michael@0 1065 }
michael@0 1066 }
michael@0 1067 }
michael@0 1068
michael@0 1069 let buttons = [
michael@0 1070 {
michael@0 1071 label: gNavigatorBundle.getString("pluginContinueBlocking.label"),
michael@0 1072 accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"),
michael@0 1073 callback: function() {
michael@0 1074 Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK").
michael@0 1075 add(true);
michael@0 1076
michael@0 1077 Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal,
michael@0 1078 "plugin-hidden-notification",
michael@0 1079 Services.perms.DENY_ACTION);
michael@0 1080 }
michael@0 1081 },
michael@0 1082 {
michael@0 1083 label: gNavigatorBundle.getString("pluginActivateTrigger.label"),
michael@0 1084 accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"),
michael@0 1085 callback: function() {
michael@0 1086 Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW").
michael@0 1087 add(true);
michael@0 1088
michael@0 1089 let curNotification =
michael@0 1090 PopupNotifications.getNotification("click-to-play-plugins",
michael@0 1091 aBrowser);
michael@0 1092 if (curNotification) {
michael@0 1093 curNotification.reshow();
michael@0 1094 }
michael@0 1095 }
michael@0 1096 }
michael@0 1097 ];
michael@0 1098 n = notificationBox.
michael@0 1099 appendNotification(message, "plugin-hidden", null,
michael@0 1100 notificationBox.PRIORITY_INFO_HIGH, buttons);
michael@0 1101 if (haveInsecure) {
michael@0 1102 n.classList.add('pluginVulnerable');
michael@0 1103 }
michael@0 1104 }
michael@0 1105
michael@0 1106 if (actions.size == 0) {
michael@0 1107 hideNotification();
michael@0 1108 } else {
michael@0 1109 let notificationPermission = Services.perms.testPermissionFromPrincipal(
michael@0 1110 aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification");
michael@0 1111 if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
michael@0 1112 hideNotification();
michael@0 1113 } else {
michael@0 1114 showNotification();
michael@0 1115 }
michael@0 1116 }
michael@0 1117 },
michael@0 1118
michael@0 1119 // Crashed-plugin observer. Notified once per plugin crash, before events
michael@0 1120 // are dispatched to individual plugin instances.
michael@0 1121 pluginCrashed : function(subject, topic, data) {
michael@0 1122 let propertyBag = subject;
michael@0 1123 if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
michael@0 1124 !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
michael@0 1125 return;
michael@0 1126
michael@0 1127 #ifdef MOZ_CRASHREPORTER
michael@0 1128 let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
michael@0 1129 let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
michael@0 1130 let shouldSubmit = gCrashReporter.submitReports;
michael@0 1131 let doPrompt = true; // XXX followup to get via gCrashReporter
michael@0 1132
michael@0 1133 // Submit automatically when appropriate.
michael@0 1134 if (pluginDumpID && shouldSubmit && !doPrompt) {
michael@0 1135 this.submitReport(pluginDumpID, browserDumpID);
michael@0 1136 // Submission is async, so we can't easily show failure UI.
michael@0 1137 propertyBag.setPropertyAsBool("submittedCrashReport", true);
michael@0 1138 }
michael@0 1139 #endif
michael@0 1140 },
michael@0 1141
michael@0 1142 // Crashed-plugin event listener. Called for every instance of a
michael@0 1143 // plugin in content.
michael@0 1144 pluginInstanceCrashed: function (plugin, aEvent) {
michael@0 1145 // Ensure the plugin and event are of the right type.
michael@0 1146 if (!(aEvent instanceof Ci.nsIDOMCustomEvent))
michael@0 1147 return;
michael@0 1148
michael@0 1149 let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2);
michael@0 1150 let submittedReport = propBag.getPropertyAsBool("submittedCrashReport");
michael@0 1151 let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt");
michael@0 1152 let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports");
michael@0 1153 let pluginName = propBag.getPropertyAsAString("pluginName");
michael@0 1154 let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID");
michael@0 1155 let browserDumpID = propBag.getPropertyAsAString("browserDumpID");
michael@0 1156
michael@0 1157 // Remap the plugin name to a more user-presentable form.
michael@0 1158 pluginName = this.makeNicePluginName(pluginName);
michael@0 1159
michael@0 1160 let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
michael@0 1161
michael@0 1162 //
michael@0 1163 // Configure the crashed-plugin placeholder.
michael@0 1164 //
michael@0 1165
michael@0 1166 // Force a layout flush so the binding is attached.
michael@0 1167 plugin.clientTop;
michael@0 1168 let overlay = this.getPluginUI(plugin, "main");
michael@0 1169 let statusDiv = this.getPluginUI(plugin, "submitStatus");
michael@0 1170 let doc = plugin.ownerDocument;
michael@0 1171 #ifdef MOZ_CRASHREPORTER
michael@0 1172 let status;
michael@0 1173
michael@0 1174 // Determine which message to show regarding crash reports.
michael@0 1175 if (submittedReport) { // submitReports && !doPrompt, handled in observer
michael@0 1176 status = "submitted";
michael@0 1177 }
michael@0 1178 else if (!submitReports && !doPrompt) {
michael@0 1179 status = "noSubmit";
michael@0 1180 }
michael@0 1181 else { // doPrompt
michael@0 1182 status = "please";
michael@0 1183 this.getPluginUI(plugin, "submitButton").addEventListener("click",
michael@0 1184 function (event) {
michael@0 1185 if (event.button != 0 || !event.isTrusted)
michael@0 1186 return;
michael@0 1187 this.submitReport(pluginDumpID, browserDumpID, plugin);
michael@0 1188 pref.setBoolPref("", optInCB.checked);
michael@0 1189 }.bind(this));
michael@0 1190 let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
michael@0 1191 let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
michael@0 1192 optInCB.checked = pref.getBoolPref("");
michael@0 1193 }
michael@0 1194
michael@0 1195 // If we don't have a minidumpID, we can't (or didn't) submit anything.
michael@0 1196 // This can happen if the plugin is killed from the task manager.
michael@0 1197 if (!pluginDumpID) {
michael@0 1198 status = "noReport";
michael@0 1199 }
michael@0 1200
michael@0 1201 statusDiv.setAttribute("status", status);
michael@0 1202
michael@0 1203 let helpIcon = this.getPluginUI(plugin, "helpIcon");
michael@0 1204 this.addLinkClickCallback(helpIcon, "openHelpPage");
michael@0 1205
michael@0 1206 // If we're showing the link to manually trigger report submission, we'll
michael@0 1207 // want to be able to update all the instances of the UI for this crash to
michael@0 1208 // show an updated message when a report is submitted.
michael@0 1209 if (doPrompt) {
michael@0 1210 let observer = {
michael@0 1211 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 1212 Ci.nsISupportsWeakReference]),
michael@0 1213 observe : function(subject, topic, data) {
michael@0 1214 let propertyBag = subject;
michael@0 1215 if (!(propertyBag instanceof Ci.nsIPropertyBag2))
michael@0 1216 return;
michael@0 1217 // Ignore notifications for other crashes.
michael@0 1218 if (propertyBag.get("minidumpID") != pluginDumpID)
michael@0 1219 return;
michael@0 1220 statusDiv.setAttribute("status", data);
michael@0 1221 },
michael@0 1222
michael@0 1223 handleEvent : function(event) {
michael@0 1224 // Not expected to be called, just here for the closure.
michael@0 1225 }
michael@0 1226 }
michael@0 1227
michael@0 1228 // Use a weak reference, so we don't have to remove it...
michael@0 1229 Services.obs.addObserver(observer, "crash-report-status", true);
michael@0 1230 // ...alas, now we need something to hold a strong reference to prevent
michael@0 1231 // it from being GC. But I don't want to manually manage the reference's
michael@0 1232 // lifetime (which should be no greater than the page).
michael@0 1233 // Clever solution? Use a closue with an event listener on the document.
michael@0 1234 // When the doc goes away, so do the listener references and the closure.
michael@0 1235 doc.addEventListener("mozCleverClosureHack", observer, false);
michael@0 1236 }
michael@0 1237 #endif
michael@0 1238
michael@0 1239 let crashText = this.getPluginUI(plugin, "crashedText");
michael@0 1240 crashText.textContent = messageString;
michael@0 1241
michael@0 1242 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
michael@0 1243
michael@0 1244 let link = this.getPluginUI(plugin, "reloadLink");
michael@0 1245 this.addLinkClickCallback(link, "reloadPage", browser);
michael@0 1246
michael@0 1247 let notificationBox = gBrowser.getNotificationBox(browser);
michael@0 1248
michael@0 1249 let isShowing = this.shouldShowOverlay(plugin, overlay);
michael@0 1250
michael@0 1251 // Is the <object>'s size too small to hold what we want to show?
michael@0 1252 if (!isShowing) {
michael@0 1253 // First try hiding the crash report submission UI.
michael@0 1254 statusDiv.removeAttribute("status");
michael@0 1255
michael@0 1256 isShowing = this.shouldShowOverlay(plugin, overlay);
michael@0 1257 }
michael@0 1258 this.setVisibility(plugin, overlay, isShowing);
michael@0 1259
michael@0 1260 if (isShowing) {
michael@0 1261 // If a previous plugin on the page was too small and resulted in adding a
michael@0 1262 // notification bar, then remove it because this plugin instance it big
michael@0 1263 // enough to serve as in-content notification.
michael@0 1264 hideNotificationBar();
michael@0 1265 doc.mozNoPluginCrashedNotification = true;
michael@0 1266 } else {
michael@0 1267 // If another plugin on the page was large enough to show our UI, we don't
michael@0 1268 // want to show a notification bar.
michael@0 1269 if (!doc.mozNoPluginCrashedNotification)
michael@0 1270 showNotificationBar(pluginDumpID, browserDumpID);
michael@0 1271 }
michael@0 1272
michael@0 1273 function hideNotificationBar() {
michael@0 1274 let notification = notificationBox.getNotificationWithValue("plugin-crashed");
michael@0 1275 if (notification)
michael@0 1276 notificationBox.removeNotification(notification, true);
michael@0 1277 }
michael@0 1278
michael@0 1279 function showNotificationBar(pluginDumpID, browserDumpID) {
michael@0 1280 // If there's already an existing notification bar, don't do anything.
michael@0 1281 let notification = notificationBox.getNotificationWithValue("plugin-crashed");
michael@0 1282 if (notification)
michael@0 1283 return;
michael@0 1284
michael@0 1285 // Configure the notification bar
michael@0 1286 let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
michael@0 1287 let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
michael@0 1288 let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
michael@0 1289 let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
michael@0 1290 let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
michael@0 1291 let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
michael@0 1292
michael@0 1293 let buttons = [{
michael@0 1294 label: reloadLabel,
michael@0 1295 accessKey: reloadKey,
michael@0 1296 popup: null,
michael@0 1297 callback: function() { browser.reload(); },
michael@0 1298 }];
michael@0 1299 #ifdef MOZ_CRASHREPORTER
michael@0 1300 let submitButton = {
michael@0 1301 label: submitLabel,
michael@0 1302 accessKey: submitKey,
michael@0 1303 popup: null,
michael@0 1304 callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
michael@0 1305 };
michael@0 1306 if (pluginDumpID)
michael@0 1307 buttons.push(submitButton);
michael@0 1308 #endif
michael@0 1309
michael@0 1310 let notification = notificationBox.appendNotification(messageString, "plugin-crashed",
michael@0 1311 iconURL, priority, buttons);
michael@0 1312
michael@0 1313 // Add the "learn more" link.
michael@0 1314 let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
michael@0 1315 let link = notification.ownerDocument.createElementNS(XULNS, "label");
michael@0 1316 link.className = "text-link";
michael@0 1317 link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
michael@0 1318 let crashurl = formatURL("app.support.baseURL", true);
michael@0 1319 crashurl += "plugin-crashed-notificationbar";
michael@0 1320 link.href = crashurl;
michael@0 1321
michael@0 1322 let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
michael@0 1323 description.appendChild(link);
michael@0 1324
michael@0 1325 // Remove the notfication when the page is reloaded.
michael@0 1326 doc.defaultView.top.addEventListener("unload", function() {
michael@0 1327 notificationBox.removeNotification(notification);
michael@0 1328 }, false);
michael@0 1329 }
michael@0 1330
michael@0 1331 }
michael@0 1332 };

mercurial