browser/base/content/browser-plugins.js

Wed, 31 Dec 2014 07:16:47 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:16:47 +0100
branch
TOR_BUG_9701
changeset 3
141e0f1194b1
permissions
-rw-r--r--

Revert simplistic fix pending revisit of Mozilla integration attempt.

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

mercurial