forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1067 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1067 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 | |
| # This Source Code Form is subject to the terms of the Mozilla Public
 | |
| # License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | |
| 
 | |
| var gPluginHandler = {
 | |
|   PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash",
 | |
|   PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
 | |
|   PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
 | |
| 
 | |
|   getPluginUI: function (plugin, anonid) {
 | |
|     return plugin.ownerDocument.
 | |
|            getAnonymousElementByAttribute(plugin, "anonid", anonid);
 | |
|   },
 | |
| 
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|   get CrashSubmit() {
 | |
|     delete this.CrashSubmit;
 | |
|     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
 | |
|     return this.CrashSubmit;
 | |
|   },
 | |
| #endif
 | |
| 
 | |
|   _getPluginInfo: function (pluginElement) {
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
|     pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
| 
 | |
|     let tagMimetype;
 | |
|     let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin");
 | |
|     let pluginTag = null;
 | |
|     let permissionString = null;
 | |
|     let fallbackType = null;
 | |
|     let blocklistState = null;
 | |
| 
 | |
|     if (pluginElement instanceof HTMLAppletElement) {
 | |
|       tagMimetype = "application/x-java-vm";
 | |
|     } else {
 | |
|       tagMimetype = pluginElement.actualType;
 | |
| 
 | |
|       if (tagMimetype == "") {
 | |
|         tagMimetype = pluginElement.type;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (gPluginHandler.isKnownPlugin(pluginElement)) {
 | |
|       pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
 | |
|       pluginName = gPluginHandler.makeNicePluginName(pluginTag.name);
 | |
| 
 | |
|       permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
 | |
|       fallbackType = pluginElement.defaultFallbackType;
 | |
|       blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
 | |
|       // Make state-softblocked == state-notblocked for our purposes,
 | |
|       // they have the same UI. STATE_OUTDATED should not exist for plugin
 | |
|       // items, but let's alias it anyway, just in case.
 | |
|       if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
 | |
|           blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
 | |
|         blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return { mimetype: tagMimetype,
 | |
|              pluginName: pluginName,
 | |
|              pluginTag: pluginTag,
 | |
|              permissionString: permissionString,
 | |
|              fallbackType: fallbackType,
 | |
|              blocklistState: blocklistState,
 | |
|            };
 | |
|   },
 | |
| 
 | |
|   // Map the plugin's name to a filtered version more suitable for user UI.
 | |
|   makeNicePluginName : function (aName) {
 | |
|     if (aName == "Shockwave Flash")
 | |
|       return "Adobe Flash";
 | |
| 
 | |
|     // Clean up the plugin name by stripping off any trailing version numbers
 | |
|     // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
 | |
|     // Do this by first stripping the numbers, etc. off the end, and then
 | |
|     // removing "Plugin" (and then trimming to get rid of any whitespace).
 | |
|     // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
 | |
|     let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
 | |
|     return newName;
 | |
|   },
 | |
| 
 | |
|   isTooSmall : function (plugin, overlay) {
 | |
|     // Is the <object>'s size too small to hold what we want to show?
 | |
|     let pluginRect = plugin.getBoundingClientRect();
 | |
|     // XXX bug 446693. The text-shadow on the submitted-report text at
 | |
|     //     the bottom causes scrollHeight to be larger than it should be.
 | |
|     let overflows = (overlay.scrollWidth > pluginRect.width) ||
 | |
|                     (overlay.scrollHeight - 5 > pluginRect.height);
 | |
|     return overflows;
 | |
|   },
 | |
| 
 | |
|   addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
 | |
|     // XXX just doing (callback)(arg) was giving a same-origin error. bug?
 | |
|     let self = this;
 | |
|     let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
 | |
|     linkNode.addEventListener("click",
 | |
|                               function(evt) {
 | |
|                                 if (!evt.isTrusted)
 | |
|                                   return;
 | |
|                                 evt.preventDefault();
 | |
|                                 if (callbackArgs.length == 0)
 | |
|                                   callbackArgs = [ evt ];
 | |
|                                 (self[callbackName]).apply(self, callbackArgs);
 | |
|                               },
 | |
|                               true);
 | |
| 
 | |
|     linkNode.addEventListener("keydown",
 | |
|                               function(evt) {
 | |
|                                 if (!evt.isTrusted)
 | |
|                                   return;
 | |
|                                 if (evt.keyCode == evt.DOM_VK_RETURN) {
 | |
|                                   evt.preventDefault();
 | |
|                                   if (callbackArgs.length == 0)
 | |
|                                     callbackArgs = [ evt ];
 | |
|                                   evt.preventDefault();
 | |
|                                   (self[callbackName]).apply(self, callbackArgs);
 | |
|                                 }
 | |
|                               },
 | |
|                               true);
 | |
|   },
 | |
| 
 | |
|   // Helper to get the binding handler type from a plugin object
 | |
|   _getBindingType : function(plugin) {
 | |
|     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
 | |
|       return null;
 | |
| 
 | |
|     switch (plugin.pluginFallbackType) {
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
 | |
|         return "PluginNotFound";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
 | |
|         return "PluginDisabled";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
 | |
|         return "PluginBlocklisted";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
 | |
|         return "PluginOutdated";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
 | |
|         return "PluginClickToPlay";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
 | |
|         return "PluginVulnerableUpdatable";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
 | |
|         return "PluginVulnerableNoUpdate";
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
 | |
|         return "PluginPlayPreview";
 | |
|       default:
 | |
|         // Not all states map to a handler
 | |
|         return null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   supportedPlugins: {
 | |
|     "mimetypes": {
 | |
|       "application/x-shockwave-flash": "flash",
 | |
|       "application/futuresplash": "flash",
 | |
|       "application/x-java-.*": "java",
 | |
|       "application/x-director": "shockwave",
 | |
|       "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime",
 | |
|       "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime",
 | |
|       "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime",
 | |
|       "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime",
 | |
|       "application/x-unknown": "test",
 | |
|     },
 | |
| 
 | |
|     "plugins": {
 | |
|       "flash": {
 | |
|         "displayName": "Flash",
 | |
|         "installWINNT": true,
 | |
|         "installDarwin": true,
 | |
|         "installLinux": true,
 | |
|       },
 | |
|       "java": {
 | |
|         "displayName": "Java",
 | |
|         "installWINNT": true,
 | |
|         "installDarwin": true,
 | |
|         "installLinux": true,
 | |
|       },
 | |
|       "shockwave": {
 | |
|         "displayName": "Shockwave",
 | |
|         "installWINNT": true,
 | |
|         "installDarwin": true,
 | |
|       },
 | |
|       "quicktime": {
 | |
|         "displayName": "QuickTime",
 | |
|         "installWINNT": true,
 | |
|       },
 | |
|       "test": {
 | |
|         "displayName": "Test plugin",
 | |
|         "installWINNT": true,
 | |
|         "installLinux": true,
 | |
|         "installDarwin": true,
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   nameForSupportedPlugin: function (aMimeType) {
 | |
|     for (let type in this.supportedPlugins.mimetypes) {
 | |
|       let re = new RegExp(type);
 | |
|       if (re.test(aMimeType)) {
 | |
|         return this.supportedPlugins.mimetypes[type];
 | |
|       }
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   canInstallThisMimeType: function (aMimeType) {
 | |
|     let os = Services.appinfo.OS;
 | |
|     let pluginName = this.nameForSupportedPlugin(aMimeType);
 | |
|     if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) {
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   handleEvent : function(event) {
 | |
|     let plugin;
 | |
|     let doc;
 | |
| 
 | |
|     let eventType = event.type;
 | |
|     if (eventType === "PluginRemoved") {
 | |
|       doc = event.target;
 | |
|     }
 | |
|     else {
 | |
|       plugin = event.target;
 | |
|       doc = plugin.ownerDocument;
 | |
| 
 | |
|       if (!(plugin instanceof Ci.nsIObjectLoadingContent))
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (eventType == "PluginBindingAttached") {
 | |
|       // The plugin binding fires this event when it is created.
 | |
|       // As an untrusted event, ensure that this object actually has a binding
 | |
|       // and make sure we don't handle it twice
 | |
|       let overlay = this.getPluginUI(plugin, "main");
 | |
|       if (!overlay || overlay._bindingHandled) {
 | |
|         return;
 | |
|       }
 | |
|       overlay._bindingHandled = true;
 | |
| 
 | |
|       // Lookup the handler for this binding
 | |
|       eventType = this._getBindingType(plugin);
 | |
|       if (!eventType) {
 | |
|         // Not all bindings have handlers
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let shouldShowNotification = false;
 | |
|     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
 | |
| 
 | |
|     switch (eventType) {
 | |
|       case "PluginCrashed":
 | |
|         this.pluginInstanceCrashed(plugin, event);
 | |
|         break;
 | |
| 
 | |
|       case "PluginNotFound":
 | |
|         let installable = this.showInstallNotification(plugin, eventType);
 | |
|         // For non-object plugin tags, register a click handler to install the
 | |
|         // plugin. Object tags can, and often do, deal with that themselves,
 | |
|         // so don't stomp on the page developers toes.
 | |
|         if (installable && !(plugin instanceof HTMLObjectElement)) {
 | |
|           let installStatus = this.getPluginUI(plugin, "installStatus");
 | |
|           installStatus.setAttribute("installable", "true");
 | |
|           let iconStatus = this.getPluginUI(plugin, "icon");
 | |
|           iconStatus.setAttribute("installable", "true");
 | |
| 
 | |
|           let installLink = this.getPluginUI(plugin, "installPluginLink");
 | |
|           this.addLinkClickCallback(installLink, "installSinglePlugin", plugin);
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case "PluginBlocklisted":
 | |
|       case "PluginOutdated":
 | |
|         shouldShowNotification = true;
 | |
|         break;
 | |
| 
 | |
|       case "PluginVulnerableUpdatable":
 | |
|         let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
 | |
|         this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
 | |
|         /* FALLTHRU */
 | |
| 
 | |
|       case "PluginVulnerableNoUpdate":
 | |
|       case "PluginClickToPlay":
 | |
|         this._handleClickToPlayEvent(plugin);
 | |
|         let overlay = this.getPluginUI(plugin, "main");
 | |
|         let pluginName = this._getPluginInfo(plugin).pluginName;
 | |
|         let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
 | |
|         let overlayText = this.getPluginUI(plugin, "clickToPlay");
 | |
|         overlayText.textContent = messageString;
 | |
|         if (eventType == "PluginVulnerableUpdatable" ||
 | |
|             eventType == "PluginVulnerableNoUpdate") {
 | |
|           let vulnerabilityString = gNavigatorBundle.getString(eventType);
 | |
|           let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
 | |
|           vulnerabilityText.textContent = vulnerabilityString;
 | |
|         }
 | |
|         shouldShowNotification = true;
 | |
|         break;
 | |
| 
 | |
|       case "PluginPlayPreview":
 | |
|         this._handlePlayPreviewEvent(plugin);
 | |
|         break;
 | |
| 
 | |
|       case "PluginDisabled":
 | |
|         let manageLink = this.getPluginUI(plugin, "managePluginsLink");
 | |
|         this.addLinkClickCallback(manageLink, "managePlugins");
 | |
|         shouldShowNotification = true;
 | |
|         break;
 | |
| 
 | |
|       case "PluginInstantiated":
 | |
|       case "PluginRemoved":
 | |
|         shouldShowNotification = true;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     // Hide the in-content UI if it's too big. The crashed plugin handler already did this.
 | |
|     if (eventType != "PluginCrashed" && eventType != "PluginRemoved") {
 | |
|       let overlay = this.getPluginUI(plugin, "main");
 | |
|       if (overlay != null) {
 | |
|         if (!this.isTooSmall(plugin, overlay))
 | |
|           overlay.style.visibility = "visible";
 | |
| 
 | |
|         plugin.addEventListener("overflow", function(event) {
 | |
|           overlay.style.visibility = "hidden";
 | |
|         });
 | |
|         plugin.addEventListener("underflow", function(event) {
 | |
|           // this is triggered if only one dimension underflows,
 | |
|           // the other dimension might still overflow
 | |
|           if (!gPluginHandler.isTooSmall(plugin, overlay)) {
 | |
|             overlay.style.visibility = "visible";
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Only show the notification after we've done the isTooSmall check, so
 | |
|     // that the notification can decide whether to show the "alert" icon
 | |
|     if (shouldShowNotification) {
 | |
|       this._showClickToPlayNotification(browser);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
 | |
|     return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
 | |
|             Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
 | |
|   },
 | |
| 
 | |
|   canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
 | |
|     // if this isn't a known plugin, we can't activate it
 | |
|     // (this also guards pluginHost.getPermissionStringForType against
 | |
|     // unexpected input)
 | |
|     if (!gPluginHandler.isKnownPlugin(objLoadingContent))
 | |
|       return false;
 | |
| 
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
|     let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
 | |
|     let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal;
 | |
|     let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
 | |
| 
 | |
|     let isFallbackTypeValid =
 | |
|       objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
 | |
|       objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
 | |
| 
 | |
|     if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) {
 | |
|       // checking if play preview is subject to CTP rules
 | |
|       let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType);
 | |
|       isFallbackTypeValid = !playPreviewInfo.ignoreCTP;
 | |
|     }
 | |
| 
 | |
|     return !objLoadingContent.activated &&
 | |
|            pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
 | |
|            isFallbackTypeValid;
 | |
|   },
 | |
| 
 | |
|   hideClickToPlayOverlay: function(aPlugin) {
 | |
|     let overlay = this.getPluginUI(aPlugin, "main");
 | |
|     if (overlay)
 | |
|       overlay.style.visibility = "hidden";
 | |
|   },
 | |
| 
 | |
|   stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
 | |
|     let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|     if (objLoadingContent.activated)
 | |
|       return;
 | |
| 
 | |
|     if (aPlayPlugin)
 | |
|       objLoadingContent.playPlugin();
 | |
|     else
 | |
|       objLoadingContent.cancelPlayPreview();
 | |
|   },
 | |
| 
 | |
|   newPluginInstalled : function(event) {
 | |
|     // browser elements are anonymous so we can't just use target.
 | |
|     var browser = event.originalTarget;
 | |
|     // clear the plugin list, now that at least one plugin has been installed
 | |
|     browser.missingPlugins = null;
 | |
| 
 | |
|     var notificationBox = gBrowser.getNotificationBox(browser);
 | |
|     var notification = notificationBox.getNotificationWithValue("missing-plugins");
 | |
|     if (notification)
 | |
|       notificationBox.removeNotification(notification);
 | |
| 
 | |
|     // reload the browser to make the new plugin show.
 | |
|     browser.reload();
 | |
|   },
 | |
| 
 | |
|   // Callback for user clicking on a missing (unsupported) plugin.
 | |
|   installSinglePlugin: function (plugin) {
 | |
|     var missingPlugins = new Map();
 | |
| 
 | |
|     var pluginInfo = this._getPluginInfo(plugin);
 | |
|     missingPlugins.set(pluginInfo.mimetype, pluginInfo);
 | |
| 
 | |
|     openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
 | |
|                "PFSWindow", "chrome,centerscreen,resizable=yes",
 | |
|                {plugins: missingPlugins, browser: gBrowser.selectedBrowser});
 | |
|   },
 | |
| 
 | |
|   // Callback for user clicking on a disabled plugin
 | |
|   managePlugins: function (aEvent) {
 | |
|     BrowserOpenAddonsMgr("addons://list/plugin");
 | |
|   },
 | |
| 
 | |
|   // Callback for user clicking on the link in a click-to-play plugin
 | |
|   // (where the plugin has an update)
 | |
|   openPluginUpdatePage: function (aEvent) {
 | |
|     openURL(Services.urlFormatter.formatURLPref("plugins.update.url"));
 | |
|   },
 | |
| 
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|   submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
 | |
|     let keyVals = {};
 | |
|     if (plugin) {
 | |
|       let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
 | |
|       if (userComment)
 | |
|         keyVals.PluginUserComment = userComment;
 | |
|       if (this.getPluginUI(plugin, "submitURLOptIn").checked)
 | |
|         keyVals.PluginContentURL = plugin.ownerDocument.URL;
 | |
|     }
 | |
|     this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals });
 | |
|     if (browserDumpID)
 | |
|       this.CrashSubmit.submit(browserDumpID);
 | |
|   },
 | |
| #endif
 | |
| 
 | |
|   // Callback for user clicking a "reload page" link
 | |
|   reloadPage: function (browser) {
 | |
|     browser.reload();
 | |
|   },
 | |
| 
 | |
|   // Callback for user clicking the help icon
 | |
|   openHelpPage: function () {
 | |
|     openHelpLink("plugin-crashed", false);
 | |
|   },
 | |
| 
 | |
|   showInstallNotification: function (aPlugin) {
 | |
|     let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument
 | |
|                                                         .defaultView.top.document);
 | |
|     if (!browser.missingPlugins)
 | |
|       browser.missingPlugins = new Map();
 | |
| 
 | |
|     let pluginInfo = this._getPluginInfo(aPlugin);
 | |
|     browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo);
 | |
| 
 | |
|     // only show notification for small subset of plugins
 | |
|     let mimetype = pluginInfo.mimetype.split(";")[0];
 | |
|     if (!this.canInstallThisMimeType(mimetype))
 | |
|       return false;
 | |
| 
 | |
|     let pluginIdentifier = this.nameForSupportedPlugin(mimetype);
 | |
|     if (!pluginIdentifier)
 | |
|       return false;
 | |
| 
 | |
|     let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName;
 | |
| 
 | |
|     // don't show several notifications
 | |
|     let notification = PopupNotifications.getNotification("plugins-not-found", browser);
 | |
|     if (notification)
 | |
|       return true;
 | |
| 
 | |
|     let messageString = gNavigatorBundle.getString("installPlugin.message");
 | |
|     let mainAction = {
 | |
|       label: gNavigatorBundle.getFormattedString("installPlugin.button.label",
 | |
|                                                  [displayName]),
 | |
|       accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"),
 | |
|       callback: function () {
 | |
|         openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
 | |
|                    "PFSWindow", "chrome,centerscreen,resizable=yes",
 | |
|                    {plugins: browser.missingPlugins, browser: browser});
 | |
|       }
 | |
|     };
 | |
|     let secondaryActions = null;
 | |
|     let options = { dismissed: true };
 | |
| 
 | |
|     let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH);
 | |
|     if (pluginIdentifier == "flash" && showForFlash) {
 | |
|       let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH;
 | |
|       secondaryActions = [{
 | |
|         label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"),
 | |
|         accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"),
 | |
|         callback: function () {
 | |
|           Services.prefs.setBoolPref(prefNotifyMissingFlash, false);
 | |
|         }
 | |
|       }];
 | |
|       options.dismissed = false;
 | |
|     }
 | |
|     PopupNotifications.show(browser, "plugins-not-found",
 | |
|                             messageString, "plugin-install-notification-icon",
 | |
|                             mainAction, secondaryActions, options);
 | |
|     return true;
 | |
|   },
 | |
|   // Event listener for click-to-play plugins.
 | |
|   _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
 | |
|     let doc = aPlugin.ownerDocument;
 | |
|     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
|     let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|     // guard against giving pluginHost.getPermissionStringForType a type
 | |
|     // not associated with any known plugin
 | |
|     if (!gPluginHandler.isKnownPlugin(objLoadingContent))
 | |
|       return;
 | |
|     let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
 | |
|     let principal = doc.defaultView.top.document.nodePrincipal;
 | |
|     let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
 | |
| 
 | |
|     let overlay = this.getPluginUI(aPlugin, "main");
 | |
| 
 | |
|     if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
 | |
|       if (overlay)
 | |
|         overlay.style.visibility = "hidden";
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (overlay) {
 | |
|       overlay.addEventListener("click", gPluginHandler._overlayClickListener, true);
 | |
|       let closeIcon = this.getPluginUI(aPlugin, "closeIcon");
 | |
|       closeIcon.addEventListener("click", function(aEvent) {
 | |
|         if (aEvent.button == 0 && aEvent.isTrusted)
 | |
|           gPluginHandler.hideClickToPlayOverlay(aPlugin);
 | |
|       }, true);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _overlayClickListener: {
 | |
|     handleEvent: function PH_handleOverlayClick(aEvent) {
 | |
|       let plugin = document.getBindingParent(aEvent.target);
 | |
|       let contentWindow = plugin.ownerDocument.defaultView.top;
 | |
|       // gBrowser.getBrowserForDocument does not exist in the case where we
 | |
|       // drag-and-dropped a tab from a window containing only that tab. In
 | |
|       // that case, the window gets destroyed.
 | |
|       let browser = gBrowser.getBrowserForDocument ?
 | |
|                       gBrowser.getBrowserForDocument(contentWindow.document) :
 | |
|                       null;
 | |
|       // If browser is null here, we've been drag-and-dropped from another
 | |
|       // window, and this is the wrong click handler.
 | |
|       if (!browser) {
 | |
|         aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true);
 | |
|         return;
 | |
|       }
 | |
|       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|       // Have to check that the target is not the link to update the plugin
 | |
|       if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
 | |
|           (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
 | |
|           aEvent.button == 0 && aEvent.isTrusted) {
 | |
|         gPluginHandler._showClickToPlayNotification(browser, plugin);
 | |
|         aEvent.stopPropagation();
 | |
|         aEvent.preventDefault();
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
 | |
|     let doc = aPlugin.ownerDocument;
 | |
|     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
|     let pluginInfo = this._getPluginInfo(aPlugin);
 | |
|     let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype);
 | |
| 
 | |
|     let previewContent = this.getPluginUI(aPlugin, "previewPluginContent");
 | |
|     let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
 | |
|     if (!iframe) {
 | |
|       // lazy initialization of the iframe
 | |
|       iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
 | |
|       iframe.className = "previewPluginContentFrame";
 | |
|       previewContent.appendChild(iframe);
 | |
| 
 | |
|       // Force a style flush, so that we ensure our binding is attached.
 | |
|       aPlugin.clientTop;
 | |
|     }
 | |
|     iframe.src = playPreviewInfo.redirectURL;
 | |
| 
 | |
|     // MozPlayPlugin event can be dispatched from the extension chrome
 | |
|     // code to replace the preview content with the native plugin
 | |
|     previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
 | |
|       if (!aEvent.isTrusted)
 | |
|         return;
 | |
| 
 | |
|       previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
 | |
| 
 | |
|       let playPlugin = !aEvent.detail;
 | |
|       gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
 | |
| 
 | |
|       // cleaning up: removes overlay iframe from the DOM
 | |
|       let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
 | |
|       if (iframe)
 | |
|         previewContent.removeChild(iframe);
 | |
|     }, true);
 | |
| 
 | |
|     if (!playPreviewInfo.ignoreCTP) {
 | |
|       gPluginHandler._showClickToPlayNotification(browser);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
 | |
|     let browser = gBrowser.selectedBrowser;
 | |
|     let contentWindow = browser.contentWindow;
 | |
|     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
 | |
|                            .getInterface(Ci.nsIDOMWindowUtils);
 | |
|     let plugins = cwu.plugins;
 | |
|     for (let plugin of plugins) {
 | |
|       let overlay = this.getPluginUI(plugin, "main");
 | |
|       if (overlay)
 | |
|         overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
 | |
|       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|       if (gPluginHandler.canActivatePlugin(objLoadingContent))
 | |
|         gPluginHandler._handleClickToPlayEvent(plugin);
 | |
|     }
 | |
|     gPluginHandler._showClickToPlayNotification(browser);
 | |
|   },
 | |
| 
 | |
|   _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
 | |
|     if (event == "showing") {
 | |
|       gPluginHandler._makeCenterActions(this);
 | |
|       Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
 | |
|         .add(!this.options.primaryPlugin);
 | |
|     }
 | |
|     else if (event == "dismissed") {
 | |
|       // Once the popup is dismissed, clicking the icon should show the full
 | |
|       // list again
 | |
|       this.options.primaryPlugin = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Match the behaviour of nsPermissionManager
 | |
|   _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) {
 | |
|     if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) {
 | |
|       return "(null)";
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       if (principal.URI.host)
 | |
|         return principal.URI.host;
 | |
|     } catch (e) {}
 | |
| 
 | |
|     return principal.origin;
 | |
|   },
 | |
| 
 | |
|   _makeCenterActions: function PH_makeCenterActions(notification) {
 | |
|     let contentWindow = notification.browser.contentWindow;
 | |
|     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
 | |
|                            .getInterface(Ci.nsIDOMWindowUtils);
 | |
| 
 | |
|     let principal = contentWindow.document.nodePrincipal;
 | |
|     // This matches the behavior of nsPermssionManager, used for display purposes only
 | |
|     let principalHost = this._getHostFromPrincipal(principal);
 | |
| 
 | |
|     let centerActions = [];
 | |
|     let pluginsFound = new Set();
 | |
|     for (let plugin of cwu.plugins) {
 | |
|       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|       if (plugin.getContentTypeForMIMEType(plugin.actualType) != Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let pluginInfo = this._getPluginInfo(plugin);
 | |
|       if (pluginInfo.permissionString === null) {
 | |
|         Components.utils.reportError("No permission string for active plugin.");
 | |
|         continue;
 | |
|       }
 | |
|       if (pluginsFound.has(pluginInfo.permissionString)) {
 | |
|         continue;
 | |
|       }
 | |
|       pluginsFound.add(pluginInfo.permissionString);
 | |
| 
 | |
|       // Add the per-site permissions and details URLs to pluginInfo here
 | |
|       // because they are more expensive to compute and so we avoid it in
 | |
|       // the tighter loop above.
 | |
|       let permissionObj = Services.perms.
 | |
|         getPermissionObject(principal, pluginInfo.permissionString, false);
 | |
|       if (permissionObj) {
 | |
|         pluginInfo.pluginPermissionHost = permissionObj.host;
 | |
|         pluginInfo.pluginPermissionType = permissionObj.expireType;
 | |
|       }
 | |
|       else {
 | |
|         pluginInfo.pluginPermissionHost = principalHost;
 | |
|         pluginInfo.pluginPermissionType = undefined;
 | |
|       }
 | |
| 
 | |
|       let url;
 | |
|       // TODO: allow the blocklist to specify a better link, bug 873093
 | |
|       if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
 | |
|         url = Services.urlFormatter.formatURLPref("plugins.update.url");
 | |
|       }
 | |
|       else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
 | |
|         url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
 | |
|       }
 | |
|       pluginInfo.detailsLink = url;
 | |
| 
 | |
|       centerActions.push(pluginInfo);
 | |
|     }
 | |
|     centerActions.sort(function(a, b) {
 | |
|       return a.pluginName.localeCompare(b.pluginName);
 | |
|     });
 | |
| 
 | |
|     notification.options.centerActions = centerActions;
 | |
| 
 | |
|     // Histograms always start at 0, even though our data starts at 1
 | |
|     let histogramCount = centerActions.length - 1;
 | |
|     if (histogramCount > 4) {
 | |
|       histogramCount = 4;
 | |
|     }
 | |
|     Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
 | |
|       .add(histogramCount);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Called from the plugin doorhanger to set the new permissions for a plugin
 | |
|    * and activate plugins if necessary.
 | |
|    * aNewState should be either "allownow" "allowalways" or "block"
 | |
|    */
 | |
|   _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) {
 | |
|     let permission;
 | |
|     let expireType;
 | |
|     let expireTime;
 | |
|     let histogram =
 | |
|       Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
 | |
| 
 | |
|     switch (aNewState) {
 | |
|       case "allownow":
 | |
|         permission = Ci.nsIPermissionManager.ALLOW_ACTION;
 | |
|         expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
 | |
|         expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
 | |
|         histogram.add(0);
 | |
|         break;
 | |
| 
 | |
|       case "allowalways":
 | |
|         permission = Ci.nsIPermissionManager.ALLOW_ACTION;
 | |
|         expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
 | |
|         expireTime = Date.now() +
 | |
|           Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
 | |
|         histogram.add(1);
 | |
|         break;
 | |
| 
 | |
|       case "block":
 | |
|         permission = Ci.nsIPermissionManager.PROMPT_ACTION;
 | |
|         expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
 | |
|         expireTime = 0;
 | |
|         histogram.add(2);
 | |
|         break;
 | |
| 
 | |
|       // In case a plugin has already been allowed in another tab, the "continue allowing" button
 | |
|       // shouldn't change any permissions but should run the plugin-enablement code below.
 | |
|       case "continue":
 | |
|         break;
 | |
|       default:
 | |
|         Cu.reportError(Error("Unexpected plugin state: " + aNewState));
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     let browser = aNotification.browser;
 | |
|     let contentWindow = browser.contentWindow;
 | |
|     if (aNewState != "continue") {
 | |
|       let principal = contentWindow.document.nodePrincipal;
 | |
|       Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
 | |
|                                       permission, expireType, expireTime);
 | |
| 
 | |
|       if (aNewState == "block") {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Manually activate the plugins that would have been automatically
 | |
|     // activated.
 | |
|     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
 | |
|                            .getInterface(Ci.nsIDOMWindowUtils);
 | |
|     let plugins = cwu.plugins;
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
| 
 | |
|     for (let plugin of plugins) {
 | |
|       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|       // canActivatePlugin will return false if this isn't a known plugin type,
 | |
|       // so the pluginHost.getPermissionStringForType call is protected
 | |
|       if (gPluginHandler.canActivatePlugin(plugin) &&
 | |
|           aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
 | |
|         plugin.playPlugin();
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPrimaryPlugin) {
 | |
|     let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
 | |
| 
 | |
|     let contentWindow = aBrowser.contentWindow;
 | |
|     let contentDoc = aBrowser.contentDocument;
 | |
|     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
 | |
|                            .getInterface(Ci.nsIDOMWindowUtils);
 | |
|     let plugins = cwu.plugins;
 | |
|     if (plugins.length == 0) {
 | |
|       if (notification) {
 | |
|         PopupNotifications.remove(notification);
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let icon = 'plugins-notification-icon';
 | |
|     for (let plugin of plugins) {
 | |
|       let fallbackType = plugin.pluginFallbackType;
 | |
|       if (fallbackType == plugin.PLUGIN_VULNERABLE_UPDATABLE ||
 | |
|           fallbackType == plugin.PLUGIN_VULNERABLE_NO_UPDATE ||
 | |
|           fallbackType == plugin.PLUGIN_BLOCKLISTED) {
 | |
|         icon = 'blocked-plugins-notification-icon';
 | |
|         break;
 | |
|       }
 | |
|       if (fallbackType == plugin.PLUGIN_CLICK_TO_PLAY) {
 | |
|         let overlay = this.getPluginUI(plugin, "main");
 | |
|         if (!overlay || overlay.style.visibility == 'hidden') {
 | |
|           icon = 'alert-plugins-notification-icon';
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let dismissed = notification ? notification.dismissed : true;
 | |
|     if (aPrimaryPlugin)
 | |
|       dismissed = false;
 | |
| 
 | |
|     let primaryPluginPermission = null;
 | |
|     if (aPrimaryPlugin) {
 | |
|       primaryPluginPermission = this._getPluginInfo(aPrimaryPlugin).permissionString;
 | |
|     }
 | |
| 
 | |
|     let options = {
 | |
|       dismissed: dismissed,
 | |
|       eventCallback: this._clickToPlayNotificationEventCallback,
 | |
|       primaryPlugin: primaryPluginPermission
 | |
|     };
 | |
|     PopupNotifications.show(aBrowser, "click-to-play-plugins",
 | |
|                             "", icon,
 | |
|                             null, null, options);
 | |
|   },
 | |
| 
 | |
|   // Crashed-plugin observer. Notified once per plugin crash, before events
 | |
|   // are dispatched to individual plugin instances.
 | |
|   pluginCrashed : function(subject, topic, data) {
 | |
|     let propertyBag = subject;
 | |
|     if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
 | |
|         !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
 | |
|      return;
 | |
| 
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|     let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
 | |
|     let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
 | |
|     let shouldSubmit = gCrashReporter.submitReports;
 | |
|     let doPrompt     = true; // XXX followup to get via gCrashReporter
 | |
| 
 | |
|     // Submit automatically when appropriate.
 | |
|     if (pluginDumpID && shouldSubmit && !doPrompt) {
 | |
|       this.submitReport(pluginDumpID, browserDumpID);
 | |
|       // Submission is async, so we can't easily show failure UI.
 | |
|       propertyBag.setPropertyAsBool("submittedCrashReport", true);
 | |
|     }
 | |
| #endif
 | |
|   },
 | |
| 
 | |
|   // Crashed-plugin event listener. Called for every instance of a
 | |
|   // plugin in content.
 | |
|   pluginInstanceCrashed: function (plugin, aEvent) {
 | |
|     // Ensure the plugin and event are of the right type.
 | |
|     if (!(aEvent instanceof Ci.nsIDOMDataContainerEvent))
 | |
|       return;
 | |
| 
 | |
|     let submittedReport = aEvent.getData("submittedCrashReport");
 | |
|     let doPrompt        = true; // XXX followup for .getData("doPrompt");
 | |
|     let submitReports   = true; // XXX followup for .getData("submitReports");
 | |
|     let pluginName      = aEvent.getData("pluginName");
 | |
|     let pluginDumpID    = aEvent.getData("pluginDumpID");
 | |
|     let browserDumpID   = aEvent.getData("browserDumpID");
 | |
| 
 | |
|     // Remap the plugin name to a more user-presentable form.
 | |
|     pluginName = this.makeNicePluginName(pluginName);
 | |
| 
 | |
|     let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
 | |
| 
 | |
|     //
 | |
|     // Configure the crashed-plugin placeholder.
 | |
|     //
 | |
| 
 | |
|     // Force a layout flush so the binding is attached.
 | |
|     plugin.clientTop;
 | |
|     let overlay = this.getPluginUI(plugin, "main");
 | |
|     let statusDiv = this.getPluginUI(plugin, "submitStatus");
 | |
|     let doc = plugin.ownerDocument;
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|     let status;
 | |
| 
 | |
|     // Determine which message to show regarding crash reports.
 | |
|     if (submittedReport) { // submitReports && !doPrompt, handled in observer
 | |
|       status = "submitted";
 | |
|     }
 | |
|     else if (!submitReports && !doPrompt) {
 | |
|       status = "noSubmit";
 | |
|     }
 | |
|     else { // doPrompt
 | |
|       status = "please";
 | |
|       this.getPluginUI(plugin, "submitButton").addEventListener("click",
 | |
|         function (event) {
 | |
|           if (event.button != 0 || !event.isTrusted)
 | |
|             return;
 | |
|           this.submitReport(pluginDumpID, browserDumpID, plugin);
 | |
|           pref.setBoolPref("", optInCB.checked);
 | |
|         }.bind(this));
 | |
|       let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
 | |
|       let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
 | |
|       optInCB.checked = pref.getBoolPref("");
 | |
|     }
 | |
| 
 | |
|     // If we don't have a minidumpID, we can't (or didn't) submit anything.
 | |
|     // This can happen if the plugin is killed from the task manager.
 | |
|     if (!pluginDumpID) {
 | |
|         status = "noReport";
 | |
|     }
 | |
| 
 | |
|     statusDiv.setAttribute("status", status);
 | |
| 
 | |
|     let helpIcon = this.getPluginUI(plugin, "helpIcon");
 | |
|     this.addLinkClickCallback(helpIcon, "openHelpPage");
 | |
| 
 | |
|     // If we're showing the link to manually trigger report submission, we'll
 | |
|     // want to be able to update all the instances of the UI for this crash to
 | |
|     // show an updated message when a report is submitted.
 | |
|     if (doPrompt) {
 | |
|       let observer = {
 | |
|         QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
 | |
|                                                Ci.nsISupportsWeakReference]),
 | |
|         observe : function(subject, topic, data) {
 | |
|           let propertyBag = subject;
 | |
|           if (!(propertyBag instanceof Ci.nsIPropertyBag2))
 | |
|             return;
 | |
|           // Ignore notifications for other crashes.
 | |
|           if (propertyBag.get("minidumpID") != pluginDumpID)
 | |
|             return;
 | |
|           statusDiv.setAttribute("status", data);
 | |
|         },
 | |
| 
 | |
|         handleEvent : function(event) {
 | |
|             // Not expected to be called, just here for the closure.
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Use a weak reference, so we don't have to remove it...
 | |
|       Services.obs.addObserver(observer, "crash-report-status", true);
 | |
|       // ...alas, now we need something to hold a strong reference to prevent
 | |
|       // it from being GC. But I don't want to manually manage the reference's
 | |
|       // lifetime (which should be no greater than the page).
 | |
|       // Clever solution? Use a closue with an event listener on the document.
 | |
|       // When the doc goes away, so do the listener references and the closure.
 | |
|       doc.addEventListener("mozCleverClosureHack", observer, false);
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     let crashText = this.getPluginUI(plugin, "crashedText");
 | |
|     crashText.textContent = messageString;
 | |
| 
 | |
|     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
 | |
| 
 | |
|     let link = this.getPluginUI(plugin, "reloadLink");
 | |
|     this.addLinkClickCallback(link, "reloadPage", browser);
 | |
| 
 | |
|     let notificationBox = gBrowser.getNotificationBox(browser);
 | |
| 
 | |
|     let isShowing = true;
 | |
| 
 | |
|     // Is the <object>'s size too small to hold what we want to show?
 | |
|     if (this.isTooSmall(plugin, overlay)) {
 | |
|       // First try hiding the crash report submission UI.
 | |
|       statusDiv.removeAttribute("status");
 | |
| 
 | |
|       if (this.isTooSmall(plugin, overlay)) {
 | |
|         // Hide the overlay's contents. Use visibility style, so that it doesn't
 | |
|         // collapse down to 0x0.
 | |
|         overlay.style.visibility = "hidden";
 | |
|         isShowing = false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (isShowing) {
 | |
|       // If a previous plugin on the page was too small and resulted in adding a
 | |
|       // notification bar, then remove it because this plugin instance it big
 | |
|       // enough to serve as in-content notification.
 | |
|       hideNotificationBar();
 | |
|       doc.mozNoPluginCrashedNotification = true;
 | |
|     } else {
 | |
|       // If another plugin on the page was large enough to show our UI, we don't
 | |
|       // want to show a notification bar.
 | |
|       if (!doc.mozNoPluginCrashedNotification)
 | |
|         showNotificationBar(pluginDumpID, browserDumpID);
 | |
|     }
 | |
| 
 | |
|     function hideNotificationBar() {
 | |
|       let notification = notificationBox.getNotificationWithValue("plugin-crashed");
 | |
|       if (notification)
 | |
|         notificationBox.removeNotification(notification, true);
 | |
|     }
 | |
| 
 | |
|     function showNotificationBar(pluginDumpID, browserDumpID) {
 | |
|       // If there's already an existing notification bar, don't do anything.
 | |
|       let notification = notificationBox.getNotificationWithValue("plugin-crashed");
 | |
|       if (notification)
 | |
|         return;
 | |
| 
 | |
|       // Configure the notification bar
 | |
|       let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
 | |
|       let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
 | |
|       let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
 | |
|       let reloadKey   = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
 | |
|       let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
 | |
|       let submitKey   = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
 | |
| 
 | |
|       let buttons = [{
 | |
|         label: reloadLabel,
 | |
|         accessKey: reloadKey,
 | |
|         popup: null,
 | |
|         callback: function() { browser.reload(); },
 | |
|       }];
 | |
| #ifdef MOZ_CRASHREPORTER
 | |
|       let submitButton = {
 | |
|         label: submitLabel,
 | |
|         accessKey: submitKey,
 | |
|         popup: null,
 | |
|           callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
 | |
|       };
 | |
|       if (pluginDumpID)
 | |
|         buttons.push(submitButton);
 | |
| #endif
 | |
| 
 | |
|       let notification = notificationBox.appendNotification(messageString, "plugin-crashed",
 | |
|                                                             iconURL, priority, buttons);
 | |
| 
 | |
|       // Add the "learn more" link.
 | |
|       let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 | |
|       let link = notification.ownerDocument.createElementNS(XULNS, "label");
 | |
|       link.className = "text-link";
 | |
|       link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
 | |
|       let crashurl = formatURL("app.support.baseURL", true);
 | |
|       crashurl += "plugin-crashed-notificationbar";
 | |
|       link.href = crashurl;
 | |
| 
 | |
|       let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
 | |
|       description.appendChild(link);
 | |
| 
 | |
|       // Remove the notfication when the page is reloaded.
 | |
|       doc.defaultView.top.addEventListener("unload", function() {
 | |
|         notificationBox.removeNotification(notification);
 | |
|       }, false);
 | |
|     }
 | |
| 
 | |
|   }
 | |
| };
 | 
