diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js index 633ef109563..cfb56dcfa28 100644 --- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -8,81 +8,10 @@ var gPluginHandler = { PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification", PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes", PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays", - MESSAGES: [ - "PluginContent:ShowClickToPlayNotification", - "PluginContent:RemoveNotification", - "PluginContent:UpdateHiddenPluginUI", - "PluginContent:HideNotificationBar", - "PluginContent:ShowInstallNotification", - "PluginContent:InstallSinglePlugin", - "PluginContent:ShowPluginCrashedNotification", - "PluginContent:SubmitReport", - "PluginContent:LinkClickCallback", - ], - init: function () { - const mm = window.messageManager; - for (let msg of this.MESSAGES) { - mm.addMessageListener(msg, this); - } - window.addEventListener("unload", this); - }, - - uninit: function () { - const mm = window.messageManager; - for (let msg of this.MESSAGES) { - mm.removeMessageListener(msg, this); - } - window.removeEventListener("unload", this); - }, - - handleEvent: function (event) { - if (event.type == "unload") { - this.uninit(); - } - }, - - receiveMessage: function (msg) { - switch (msg.name) { - case "PluginContent:ShowClickToPlayNotification": - this.showClickToPlayNotification(msg.target, msg.data.plugins, msg.data.showNow, - msg.principal, msg.data.host); - break; - case "PluginContent:RemoveNotification": - this.removeNotification(msg.target, msg.data.name); - break; - case "PluginContent:UpdateHiddenPluginUI": - this.updateHiddenPluginUI(msg.target, msg.data.haveInsecure, msg.data.actions, - msg.principal, msg.data.host); - break; - case "PluginContent:HideNotificationBar": - this.hideNotificationBar(msg.target, msg.data.name); - break; - case "PluginContent:ShowInstallNotification": - return this.showInstallNotification(msg.target, msg.data.pluginInfo); - case "PluginContent:InstallSinglePlugin": - this.installSinglePlugin(msg.data.pluginInfo); - break; - case "PluginContent:ShowPluginCrashedNotification": - this.showPluginCrashedNotification(msg.target, msg.data.messageString, - msg.data.pluginDumpID, msg.data.browserDumpID); - break; - case "PluginContent:SubmitReport": - this.submitReport(msg.data.pluginDumpID, msg.data.browserDumpID, msg.data.keyVals); - break; - case "PluginContent:LinkClickCallback": - switch (msg.data.name) { - case "managePlugins": - case "openHelpPage": - case "openPluginUpdatePage": - this[msg.data.name].apply(this); - break; - } - break; - default: - Cu.reportError("gPluginHandler did not expect to handle message " + msg.name); - break; - } + getPluginUI: function (plugin, anonid) { + return plugin.ownerDocument. + getAnonymousElementByAttribute(plugin, "anonid", anonid); }, #ifdef MOZ_CRASHREPORTER @@ -93,6 +22,186 @@ var gPluginHandler = { }, #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; + + 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 parenthetical clauses, + // trailing version numbers or "plugin". + // EG, "Foo Bar (Linux) 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(/\(.*?\)/g, ""). + replace(/[\s\d\.\-\_\(\)]+$/, ""). + replace(/\bplug-?in\b/i, "").trim(); + return newName; + }, + + /** + * Update the visibility of the plugin overlay. + */ + setVisibility : function (plugin, overlay, shouldShow) { + overlay.classList.toggle("visible", shouldShow); + }, + + /** + * Check whether the plugin should be visible on the page. A plugin should + * not be visible if the overlay is too big, or if any other page content + * overlays it. + * + * This function will handle showing or hiding the overlay. + * @returns true if the plugin is invisible. + */ + shouldShowOverlay : function (plugin, overlay) { + // If the overlay size is 0, we haven't done layout yet. Presume that + // plugins are visible until we know otherwise. + if (overlay.scrollWidth == 0) { + return true; + } + + // Is the '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 > Math.ceil(pluginRect.width)) || + (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height)); + if (overflows) { + return false; + } + + // Is the plugin covered up by other content so that it is not clickable? + // Floating point can confuse .elementFromPoint, so inset just a bit + let left = pluginRect.left + 2; + let right = pluginRect.right - 2; + let top = pluginRect.top + 2; + let bottom = pluginRect.bottom - 2; + let centerX = left + (right - left) / 2; + let centerY = top + (bottom - top) / 2; + let points = [[left, top], + [left, bottom], + [right, top], + [right, bottom], + [centerX, centerY]]; + + if (right <= 0 || top <= 0) { + return false; + } + + let contentWindow = plugin.ownerDocument.defaultView; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + for (let [x, y] of points) { + let el = cwu.elementFromPoint(x, y, true, true); + if (el !== plugin) { + return false; + } + } + + return true; + }, + + 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", @@ -156,6 +265,196 @@ var gPluginHandler = { return false; }, + handleEvent : function(event) { + let eventType = event.type; + + if (eventType == "PluginRemoved") { + let doc = event.target; + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); + if (browser) + this._setPluginNotificationIcon(browser); + return; + } + + if (eventType == "PluginCrashed" && + !(event.target instanceof Ci.nsIObjectLoadingContent)) { + // If the event target is not a plugin object (i.e., an or + // element), this call is for a window-global plugin. + this.pluginInstanceCrashed(event.target, event); + return; + } + + let plugin = event.target; + let 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); + if (!browser) + return; + + 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": + shouldShowNotification = true; + break; + } + + // Show the in-content UI if it's not too big. The crashed plugin handler already did this. + if (eventType != "PluginCrashed") { + let overlay = this.getPluginUI(plugin, "main"); + if (overlay != null) { + this.setVisibility(plugin, overlay, + this.shouldShowOverlay(plugin, overlay)); + let resizeListener = (event) => { + this.setVisibility(plugin, overlay, + this.shouldShowOverlay(plugin, overlay)); + this._setPluginNotificationIcon(browser); + }; + plugin.addEventListener("overflow", resizeListener); + plugin.addEventListener("underflow", resizeListener); + } + } + + let closeIcon = this.getPluginUI(plugin, "closeIcon"); + if (closeIcon) { + closeIcon.addEventListener("click", function(aEvent) { + if (aEvent.button == 0 && aEvent.isTrusted) + gPluginHandler.hideClickToPlayOverlay(plugin); + }, true); + } + + if (shouldShowNotification) { + this._showClickToPlayNotification(browser, plugin, false); + } + }, + + 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.classList.remove("visible"); + } + }, + + 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; @@ -172,8 +471,10 @@ var gPluginHandler = { }, // Callback for user clicking on a missing (unsupported) plugin. - installSinglePlugin: function (pluginInfo) { + installSinglePlugin: function (plugin) { var missingPlugins = new Map(); + + var pluginInfo = this._getPluginInfo(plugin); missingPlugins.set(pluginInfo.mimetype, pluginInfo); openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", @@ -182,19 +483,27 @@ var gPluginHandler = { }, // Callback for user clicking on a disabled plugin - managePlugins: function () { + 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 () { + openPluginUpdatePage: function (aEvent) { openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab"); }, #ifdef MOZ_CRASHREPORTER - submitReport: function submitReport(pluginDumpID, browserDumpID, keyVals) { - keyVals = keyVals || {}; + 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, { recordSubmission: true, extraExtraKeyVals: keyVals }); if (browserDumpID) @@ -212,16 +521,19 @@ var gPluginHandler = { openHelpLink("plugin-crashed", false); }, - showInstallNotification: function (browser, pluginInfo) { + showInstallNotification: function (aPlugin) { let hideMissingPluginsNotification = Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION); if (hideMissingPluginsNotification) { return false; } + 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 @@ -271,6 +583,120 @@ var gPluginHandler = { 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.classList.remove("visible"); + } + return; + } + + if (overlay) { + overlay.addEventListener("click", gPluginHandler._overlayClickListener, 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, true); + 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, aPlugin, false); + } + }, + + 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, null, false); + }, _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) { if (event == "showing") { @@ -289,11 +715,20 @@ var gPluginHandler = { // list again this.options.primaryPlugin = null; } - else if (event == "removed") { - // Once the notification is removed, let the content script clear any - // caches it may have populated. - this.browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationRemoved"); + }, + + // 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; }, /** @@ -301,7 +736,7 @@ var gPluginHandler = { * and activate plugins if necessary. * aNewState should be either "allownow" "allowalways" or "block" */ - _updatePluginPermission: function (aNotification, aPluginInfo, aNewState) { + _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) { let permission; let expireType; let expireTime; @@ -359,20 +794,74 @@ var gPluginHandler = { let browser = aNotification.browser; let contentWindow = browser.contentWindow; if (aNewState != "continue") { - let principal = aNotification.options.principal; + let principal = contentWindow.document.nodePrincipal; Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString, permission, expireType, expireTime); aPluginInfo.pluginPermissionType = expireType; } - browser.messageManager.sendAsyncMessage("BrowserPlugins:ActivatePlugins", { - pluginInfo: aPluginInfo, - newState: aNewState, - }); + // 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); + + let pluginFound = false; + for (let plugin of plugins) { + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (!gPluginHandler.isKnownPlugin(plugin)) { + continue; + } + if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { + pluginFound = true; + if (aNewState == "block") { + plugin.reload(true); + } else { + if (gPluginHandler.canActivatePlugin(plugin)) { + let overlay = this.getPluginUI(plugin, "main"); + if (overlay) { + overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); + } + plugin.playPlugin(); + } + } + } + } + + // If there are no instances of the plugin on the page any more, what the + // user probably needs is for us to allow and then refresh. + if (aNewState != "block" && !pluginFound) { + browser.reload(); + } + + this._setPluginNotificationIcon(browser); }, - showClickToPlayNotification: function (browser, plugins, showNow, principal, host) { - let notification = PopupNotifications.getNotification("click-to-play-plugins", browser); + _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) { + let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); + let plugins = []; + + // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need + // to collect all the plugins + if (aPlugin === null) { + let contentWindow = aBrowser.contentWindow; + let contentDoc = aBrowser.contentDocument; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // cwu.plugins may contain non-plugin s, filter them out + plugins = cwu.plugins.filter((plugin) => + plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); + + if (plugins.length == 0) { + if (notification) { + PopupNotifications.remove(notification); + } + return; + } + } else { + plugins = [aPlugin]; + } // If this is a new notification, create a pluginData map, otherwise append let pluginData; @@ -382,11 +871,30 @@ var gPluginHandler = { pluginData = new Map(); } - for (var pluginInfo of plugins) { + let principal = aBrowser.contentDocument.nodePrincipal; + let principalHost = this._getHostFromPrincipal(principal); + + for (var plugin of plugins) { + let pluginInfo = this._getPluginInfo(plugin); + if (pluginInfo.permissionString === null) { + Cu.reportError("No permission string for active plugin."); + continue; + } if (pluginData.has(pluginInfo.permissionString)) { continue; } + 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) { @@ -404,55 +912,106 @@ var gPluginHandler = { } let primaryPluginPermission = null; - if (showNow) { - primaryPluginPermission = plugins[0].permissionString; + if (aShowNow) { + primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString; } if (notification) { // Don't modify the notification UI while it's on the screen, that would be // jumpy and might allow clickjacking. - if (showNow) { + if (aShowNow) { notification.options.primaryPlugin = primaryPluginPermission; notification.reshow(); - browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown"); + setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); } return; } let options = { - dismissed: !showNow, + dismissed: !aShowNow, eventCallback: this._clickToPlayNotificationEventCallback, primaryPlugin: primaryPluginPermission, - pluginData: pluginData, - principal: principal, - host: host, + pluginData: pluginData }; - PopupNotifications.show(browser, "click-to-play-plugins", + PopupNotifications.show(aBrowser, "click-to-play-plugins", "", "plugins-notification-icon", null, null, options); - browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown"); + setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); }, - removeNotification: function (browser, name) { - let notification = PopupNotifications.getNotification(name, browser); - if (notification) - PopupNotifications.remove(notification); - }, + _setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) { + // Because this is called on a timeout, sanity-check before continuing + if (!aBrowser.docShell || !aBrowser.contentWindow) { + return; + } - hideNotificationBar: function (browser, name) { - let notificationBox = gBrowser.getNotificationBox(browser); - let notification = notificationBox.getNotificationWithValue(name); - if (notification) - notificationBox.removeNotification(notification, true); - }, + let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); + if (!notification) + return; + + // Make a copy of the actions, removing active plugins and checking for + // outdated plugins. + let haveInsecure = false; + let actions = new Map(); + for (let action of notification.options.pluginData.values()) { + switch (action.fallbackType) { + // haveInsecure will trigger the red flashing icon and the infobar + // styling below + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: + haveInsecure = true; + // fall through + + case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: + actions.set(action.permissionString, action); + continue; + } + } + + // check for hidden plugins + let contentWindow = aBrowser.contentWindow; + let contentDoc = aBrowser.contentDocument; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + for (let plugin of cwu.plugins) { + let info = this._getPluginInfo(plugin); + if (!actions.has(info.permissionString)) { + continue; + } + let fallbackType = info.fallbackType; + if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) { + actions.delete(info.permissionString); + if (actions.size == 0) { + break; + } + continue; + } + if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && + fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE && + fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) { + continue; + } + let overlay = this.getPluginUI(plugin, "main"); + if (!overlay) { + continue; + } + let shouldShow = this.shouldShowOverlay(plugin, overlay); + this.setVisibility(plugin, overlay, shouldShow); + if (shouldShow) { + actions.delete(info.permissionString); + if (actions.size == 0) { + break; + } + } + } - updateHiddenPluginUI: function (browser, haveInsecure, actions, principal, host) { // Set up the icon document.getElementById("plugins-notification-icon").classList. toggle("plugin-blocked", haveInsecure); // Now configure the notification bar - let notificationBox = gBrowser.getNotificationBox(browser); + + let notificationBox = gBrowser.getNotificationBox(aBrowser); function hideNotification() { let n = notificationBox.getNotificationWithValue("plugin-hidden"); @@ -481,10 +1040,11 @@ var gPluginHandler = { let message; // Icons set directly cannot be manipulated using moz-image-region, so // we use CSS classes instead. + let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal); let brand = document.getElementById("bundle_brand").getString("brandShortName"); - if (actions.length == 1) { - let pluginInfo = actions[0]; + if (actions.size == 1) { + let pluginInfo = [...actions.values()][0]; let pluginName = pluginInfo.pluginName; switch (pluginInfo.fallbackType) { @@ -507,6 +1067,12 @@ var gPluginHandler = { // Multi-plugin message = gNavigatorBundle.getFormattedString( "pluginActivateMultiple.message", [host]); + + for (let action of actions.values()) { + if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) { + break; + } + } } let buttons = [ @@ -517,7 +1083,7 @@ var gPluginHandler = { Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK"). add(true); - Services.perms.addFromPrincipal(principal, + Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification", Services.perms.DENY_ACTION); } @@ -531,7 +1097,7 @@ var gPluginHandler = { let curNotification = PopupNotifications.getNotification("click-to-play-plugins", - browser); + aBrowser); if (curNotification) { curNotification.reshow(); } @@ -546,11 +1112,11 @@ var gPluginHandler = { } } - if (actions.length == 0) { + if (actions.size == 0) { hideNotification(); } else { let notificationPermission = Services.perms.testPermissionFromPrincipal( - principal, "plugin-hidden-notification"); + aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification"); if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) { hideNotification(); } else { @@ -559,11 +1125,6 @@ var gPluginHandler = { } }, - contextMenuCommand: function (browser, plugin, command) { - browser.messageManager.sendAsyncMessage("BrowserPlugins:ContextMenuCommand", - { command: command }, { plugin: plugin }); - }, - // Crashed-plugin observer. Notified once per plugin crash, before events // are dispatched to individual plugin instances. pluginCrashed : function(subject, topic, data) { @@ -587,54 +1148,237 @@ var gPluginHandler = { #endif }, - showPluginCrashedNotification: function (browser, messageString, pluginDumpID, browserDumpID) { - // If there's already an existing notification bar, don't do anything. - let notificationBox = gBrowser.getNotificationBox(browser); - let notification = notificationBox.getNotificationWithValue("plugin-crashed"); - if (notification) + // Crashed-plugin event listener. Called for every instance of a + // plugin in content. + pluginInstanceCrashed: function (target, aEvent) { + // Ensure the plugin and event are of the right type. + if (!(aEvent instanceof Ci.nsIDOMCustomEvent)) 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 propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); + let submittedReport = propBag.getPropertyAsBool("submittedCrashReport"); + let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt"); + let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports"); + let pluginName = propBag.getPropertyAsAString("pluginName"); + let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID"); + let browserDumpID = null; + let gmpPlugin = false; - let buttons = [{ - label: reloadLabel, - accessKey: reloadKey, - popup: null, - callback: function() { browser.reload(); }, - }]; + try { + browserDumpID = propBag.getPropertyAsAString("browserDumpID"); + } catch (e) { + // For GMP crashes we don't get a browser dump. + } + try { + gmpPlugin = propBag.getPropertyAsBool("gmpPlugin"); + } catch (e) { + // This property is only set for GMP plugins. + } + + // For non-GMP plugins, remap the plugin name to a more user-presentable form. + if (!gmpPlugin) { + pluginName = this.makeNicePluginName(pluginName); + } + + let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); + + let plugin = null, doc; + if (target instanceof Ci.nsIObjectLoadingContent) { + plugin = target; + doc = plugin.ownerDocument; + } else { + doc = target.document; + if (!doc) { + return; + } + // doPrompt is specific to the crashed plugin overlay, and + // therefore is not applicable for window-global plugins. + doPrompt = false; + } + + let status; #ifdef MOZ_CRASHREPORTER - let submitButton = { - label: submitLabel, - accessKey: submitKey, - popup: null, - callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); }, - }; - if (pluginDumpID) - buttons.push(submitButton); + // Determine which message to show regarding crash reports. + if (submittedReport) { // submitReports && !doPrompt, handled in observer + status = "submitted"; + } + else if (!submitReports && !doPrompt) { + status = "noSubmit"; + } + else if (!pluginDumpID) { + // If we don't have a minidumpID, we can't (or didn't) submit anything. + // This can happen if the plugin is killed from the task manager. + status = "noReport"; + } + else { + status = "please"; + } + + // 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 : (subject, topic, data) => { + let propertyBag = subject; + if (!(propertyBag instanceof Ci.nsIPropertyBag2)) + return; + // Ignore notifications for other crashes. + if (propertyBag.get("minidumpID") != pluginDumpID) + return; + + let statusDiv = this.getPluginUI(plugin, "submitStatus"); + 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 notification = notificationBox.appendNotification(messageString, "plugin-crashed", - iconURL, priority, buttons); + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); + let notificationBox = gBrowser.getNotificationBox(browser); + let isShowing = false; - // 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; + if (plugin) { + // If there's no plugin (an or element), this call is + // for a window-global plugin. In this case, there's no overlay to show. + isShowing = _setUpPluginOverlay.call(this, plugin, doPrompt, browser); + } - let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); - description.appendChild(link); - }, + 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); + } + + // Configure the crashed-plugin placeholder. + // Returns true if the plugin overlay is visible. + function _setUpPluginOverlay(plugin, doPromptSubmit, browser) { + if (!plugin) { + return false; + } + + // Force a layout flush so the binding is attached. + plugin.clientTop; + let overlay = this.getPluginUI(plugin, "main"); + let statusDiv = this.getPluginUI(plugin, "submitStatus"); + + if (doPromptSubmit) { + 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(""); + } + + statusDiv.setAttribute("status", status); + + let helpIcon = this.getPluginUI(plugin, "helpIcon"); + this.addLinkClickCallback(helpIcon, "openHelpPage"); + + let crashText = this.getPluginUI(plugin, "crashedText"); + crashText.textContent = messageString; + + let link = this.getPluginUI(plugin, "reloadLink"); + this.addLinkClickCallback(link, "reloadPage", browser); + + let isShowing = this.shouldShowOverlay(plugin, overlay); + + // Is the 's size too small to hold what we want to show? + if (!isShowing) { + // First try hiding the crash report submission UI. + statusDiv.removeAttribute("status"); + + isShowing = this.shouldShowOverlay(plugin, overlay); + } + this.setVisibility(plugin, overlay, isShowing); + + return isShowing; + } + } }; - -gPluginHandler.init(); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 87c0c53007a..12eee24de19 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -258,6 +258,12 @@ XPCOMUtils.defineLazyGetter(this, "PageMenu", function() { */ function pageShowEventHandlers(persisted) { XULBrowserWindow.asyncUpdateUI(); + + // The PluginClickToPlay events are not fired when navigating using the + // BF cache. |persisted| is true when the page is loaded from the + // BF cache, so this code reshows the notification if necessary. + if (persisted) + gPluginHandler.reshowClickToPlayNotification(); } function UpdateBackForwardCommands(aWebNavigation) { @@ -774,6 +780,13 @@ var gBrowserInit = { gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false); + // Note that the XBL binding is untrusted + gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true); + gBrowser.addEventListener("PluginCrashed", gPluginHandler, true); + gBrowser.addEventListener("PluginOutdated", gPluginHandler, true); + gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true); + gBrowser.addEventListener("PluginRemoved", gPluginHandler, true); + gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true); Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false); diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 3cc0768e6e2..7550f13eb74 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -16,8 +16,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils", "resource://gre/modules/InsecurePasswordUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PluginContent", - "resource:///modules/PluginContent.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "UITour", @@ -495,9 +493,6 @@ ClickEventHandler.init(); ContentLinkHandler.init(this); -// TODO: Load this lazily so the JSM is run only if a relevant event/message fires. -let pluginContent = new PluginContent(global); - addEventListener("DOMWebNotificationClicked", function(event) { sendAsyncMessage("DOMWebNotificationClicked", {}); }, false); diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 7082af8e335..660819c41ba 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -1315,11 +1315,11 @@ nsContextMenu.prototype = { }, playPlugin: function() { - gPluginHandler.contextMenuCommand(this.browser, this.target, "play"); + gPluginHandler._showClickToPlayNotification(this.browser, this.target, true); }, hidePlugin: function() { - gPluginHandler.contextMenuCommand(this.browser, this.target, "hide"); + gPluginHandler.hideClickToPlayOverlay(this.target); }, // Generate email address and put it on clipboard. diff --git a/browser/base/content/test/plugins/browser_CTP_crashreporting.js b/browser/base/content/test/plugins/browser_CTP_crashreporting.js index c92bb2e01f8..4fd5439223d 100644 --- a/browser/base/content/test/plugins/browser_CTP_crashreporting.js +++ b/browser/base/content/test/plugins/browser_CTP_crashreporting.js @@ -8,72 +8,6 @@ const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browse const gTestRoot = getRootDirectory(gTestPath); var gTestBrowser = null; -/** - * Frame script that will be injected into the test browser - * to cause the crash, and then manipulate the crashed plugin - * UI. Specifically, after the crash, we ensure that the - * crashed plugin UI is using the right style rules and that - * the submit URL opt-in defaults to checked. Then, we fill in - * a comment with the crash report, uncheck the submit URL - * opt-in, and send the crash reports. - */ -function frameScript() { - function fail(reason) { - sendAsyncMessage("test:crash-plugin:fail", { - reason: `Failure from frameScript: ${reason}`, - }); - } - - addMessageListener("test:crash-plugin", () => { - let doc = content.document; - - addEventListener("PluginCrashed", (event) => { - let plugin = doc.getElementById("test"); - if (!plugin) { - fail("Could not find plugin element"); - return; - } - - let getUI = (anonid) => { - return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid); - }; - - let style = content.getComputedStyle(getUI("pleaseSubmit")); - if (style.display != "block") { - fail("Submission UI visibility is not correct. Expected block, " - + " got " + style.display); - return; - } - - getUI("submitComment").value = "a test comment"; - if (!getUI("submitURLOptIn").checked) { - fail("URL opt-in should default to true."); - return; - } - - getUI("submitURLOptIn").click(); - getUI("submitButton").click(); - }); - - let plugin = doc.getElementById("test"); - try { - plugin.crash() - } catch(e) { - } - }); - - addMessageListener("test:plugin-submit-status", () => { - let doc = content.document; - let plugin = doc.getElementById("test"); - let submitStatusEl = - doc.getAnonymousElementByAttribute(plugin, "anonid", "submitStatus"); - let submitStatus = submitStatusEl.getAttribute("status"); - sendAsyncMessage("test:plugin-submit-status:result", { - submitStatus: submitStatus, - }); - }); -} - // Test that plugin crash submissions still work properly after // click-to-play activation. @@ -97,18 +31,14 @@ function test() { let tab = gBrowser.loadOneTab("about:blank", { inBackground: false }); gTestBrowser = gBrowser.getBrowserForTab(tab); - let mm = gTestBrowser.messageManager; - mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false); - mm.addMessageListener("test:crash-plugin:fail", (message) => { - ok(false, message.data.reason); - }); - + gTestBrowser.addEventListener("PluginCrashed", onCrash, false); gTestBrowser.addEventListener("load", onPageLoad, true); Services.obs.addObserver(onSubmitStatus, "crash-report-status", false); registerCleanupFunction(function cleanUp() { env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); env.set("MOZ_CRASHREPORTER_URL", serverURL); + gTestBrowser.removeEventListener("PluginCrashed", onCrash, false); gTestBrowser.removeEventListener("load", onPageLoad, true); Services.obs.removeObserver(onSubmitStatus, "crash-report-status"); gBrowser.removeCurrentTab(); @@ -140,8 +70,31 @@ function afterBindingAttached() { } function pluginActivated() { - let mm = gTestBrowser.messageManager; - mm.sendAsyncMessage("test:crash-plugin"); + let plugin = gTestBrowser.contentDocument.getElementById("test"); + try { + plugin.crash(); + } catch (e) { + // The plugin crashed in the above call, an exception is expected. + } +} + +function onCrash() { + try { + let plugin = gBrowser.contentDocument.getElementById("test"); + let elt = gPluginHandler.getPluginUI.bind(gPluginHandler, plugin); + let style = + gBrowser.contentWindow.getComputedStyle(elt("pleaseSubmit")); + is(style.display, "block", "Submission UI visibility should be correct"); + + elt("submitComment").value = "a test comment"; + is(elt("submitURLOptIn").checked, true, "URL opt-in should default to true"); + EventUtils.synthesizeMouseAtCenter(elt("submitURLOptIn"), {}, gTestBrowser.contentWindow); + EventUtils.synthesizeMouseAtCenter(elt("submitButton"), {}, gTestBrowser.contentWindow); + // And now wait for the submission status notification. + } + catch (err) { + failWithException(err); + } } function onSubmitStatus(subj, topic, data) { @@ -175,23 +128,19 @@ function onSubmitStatus(subj, topic, data) { ok(val === undefined, "URL should be absent from extra data when opt-in not checked"); - let submitStatus = null; - let mm = gTestBrowser.messageManager; - mm.addMessageListener("test:plugin-submit-status:result", (message) => { - submitStatus = message.data.submitStatus; + // Execute this later in case the event to change submitStatus has not + // have been dispatched yet. + executeSoon(function () { + let plugin = gBrowser.contentDocument.getElementById("test"); + let elt = gPluginHandler.getPluginUI.bind(gPluginHandler, plugin); + is(elt("submitStatus").getAttribute("status"), data, + "submitStatus data should match"); }); - - mm.sendAsyncMessage("test:plugin-submit-status"); - - let condition = () => submitStatus; - waitForCondition(condition, () => { - is(submitStatus, data, "submitStatus data should match"); - finish(); - }, "Waiting for submitStatus to be reported from frame script"); } catch (err) { failWithException(err); } + finish(); } function getPropertyBagValue(bag, key) { diff --git a/browser/base/content/test/plugins/browser_CTP_drag_drop.js b/browser/base/content/test/plugins/browser_CTP_drag_drop.js index 66b7de28aa3..989e079dcd1 100644 --- a/browser/base/content/test/plugins/browser_CTP_drag_drop.js +++ b/browser/base/content/test/plugins/browser_CTP_drag_drop.js @@ -29,11 +29,11 @@ function handleEvent() { function part1() { gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent); - waitForNotificationPopup("click-to-play-plugins", gBrowser.selectedBrowser, () => { - gNextTest = part2; - gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); - gNewWindow.addEventListener("load", handleEvent, true); - }); + ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab"); + + gNextTest = part2; + gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + gNewWindow.addEventListener("load", handleEvent, true); } function part2() { @@ -62,10 +62,10 @@ function part4() { function part5() { gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent); - waitForNotificationPopup("click-to-play-plugins", gBrowser.selectedBrowser, () => { - gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); - waitForFocus(part6, gNewWindow); - }); + ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab"); + + gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + waitForFocus(part6, gNewWindow); } function part6() { @@ -92,10 +92,8 @@ function part8() { let plugin = gNewWindow.gBrowser.selectedBrowser.contentDocument.getElementById("test"); let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - waitForCondition(() => objLoadingContent.activated, shutdown, "plugin should be activated now"); -} + ok(objLoadingContent.activated, "plugin should be activated now"); -function shutdown() { gNewWindow.close(); gNewWindow = null; finish(); diff --git a/browser/base/content/test/plugins/browser_CTP_iframe.js b/browser/base/content/test/plugins/browser_CTP_iframe.js index f459a3fcef2..9b96807b265 100644 --- a/browser/base/content/test/plugins/browser_CTP_iframe.js +++ b/browser/base/content/test/plugins/browser_CTP_iframe.js @@ -74,15 +74,13 @@ function test1() { let plugin = doc.getElementById("test"); ok(plugin, "Test 1, Found plugin in page"); - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => { - let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); - ok(overlay.classList.contains("visible"), "Test 1, Plugin overlay should exist, not be hidden"); - let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + ok(overlay.classList.contains("visible"), "Test 1, Plugin overlay should exist, not be hidden"); + let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon") - EventUtils.synthesizeMouseAtCenter(closeIcon, {}, frame.contentWindow); - let condition = () => !overlay.classList.contains("visible"); - waitForCondition(condition, test2, "Test 1, Waited too long for the overlay to become invisible."); - }); + EventUtils.synthesizeMouseAtCenter(closeIcon, {}, frame.contentWindow); + let condition = () => !overlay.classList.contains("visible"); + waitForCondition(condition, test2, "Test 1, Waited too long for the overlay to become invisible."); } function test2() { diff --git a/browser/base/content/test/plugins/browser_CTP_notificationBar.js b/browser/base/content/test/plugins/browser_CTP_notificationBar.js index f0cd4063fea..597b7875450 100644 --- a/browser/base/content/test/plugins/browser_CTP_notificationBar.js +++ b/browser/base/content/test/plugins/browser_CTP_notificationBar.js @@ -63,37 +63,43 @@ function runAfterPluginBindingAttached(func) { // Tests for the notification bar for hidden plugins. function test1() { - info("Test 1 - expecting a notification bar for hidden plugins."); - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => { - waitForNotificationBar("plugin-hidden", gTestBrowser, () => { + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification, "Test 1: There should be a plugin notification"); + + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + + waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null, + () => { // Don't use setTestPluginEnabledState here because we already saved the // prior value getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED; prepareTest(test2, gTestRoot + "plugin_small.html"); - }); - }); + }, + "Test 1, expected to have a plugin notification bar"); } function test2() { - info("Test 2 - expecting no plugin notification bar on visible plugins."); - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => { - let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification, "Test 2: There should be a plugin notification"); - waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null, - () => { - getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY; - prepareTest(test3, gTestRoot + "plugin_overlayed.html"); - }, - "expected to not have a plugin notification bar" - ); - }); + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + + waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null, + () => { + getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY; + prepareTest(test3, gTestRoot + "plugin_overlayed.html"); + }, + "Test 2, expected to not have a plugin notification bar"); } function test3() { - info("Test 3 - expecting a plugin notification bar when plugins are overlaid"); - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => { - waitForNotificationBar("plugin-hidden", gTestBrowser, test3b); - }); + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification, "Test 3: There should be a plugin notification"); + + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null, + test3b, + "Test 3, expected the plugin infobar to be triggered when plugin was overlayed"); } function test3b() @@ -112,10 +118,13 @@ function test3b() } function test4() { - info("Test 4 - expecting a plugin notification bar when plugins are overlaid offscreen") - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => { - waitForNotificationBar("plugin-hidden", gTestBrowser, test4b); - }); + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification, "Test 4: There should be a plugin notification"); + + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null, + test4b, + "Test 4, expected the plugin infobar to be triggered when plugin was overlayed"); } function test4b() { @@ -132,6 +141,9 @@ function test4b() { prepareTest(runAfterPluginBindingAttached(test5), gHttpTestRoot + "plugin_small.html"); } +// Test that the notification bar is getting dismissed when directly activating plugins +// via the doorhanger. + function test5() { let notificationBox = gBrowser.getNotificationBox(gTestBrowser); waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null, @@ -139,27 +151,23 @@ function test5() { "Test 5, expected a notification bar for hidden plugins"); } -// Test that the notification bar is getting dismissed when directly activating plugins -// via the doorhanger. - function test6() { - info("Test 6 - expecting the doorhanger to be dismissed when directly activating plugins."); - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, (notification) => { - let plugin = gTestBrowser.contentDocument.getElementById("test"); - ok(plugin, "Test 6, Found plugin in page"); - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, - "Test 6, Plugin should be click-to-play"); + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 6, Should have a click-to-play notification"); + let plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(plugin, "Test 6, Found plugin in page"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 6, Plugin should be click-to-play"); - // simulate "always allow" - notification.reshow(); - PopupNotifications.panel.firstChild._primaryButton.click(); + // simulate "always allow" + notification.reshow(); + PopupNotifications.panel.firstChild._primaryButton.click(); - let notificationBox = gBrowser.getNotificationBox(gTestBrowser); - waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null, - test7, - "Test 6, expected the notification bar for hidden plugins to get dismissed"); - }); + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null, + test7, + "Test 6, expected the notification bar for hidden plugins to get dismissed"); } function test7() { diff --git a/browser/base/content/test/plugins/browser_bug743421.js b/browser/base/content/test/plugins/browser_bug743421.js index 10f7f62835a..0df83fe80ff 100644 --- a/browser/base/content/test/plugins/browser_bug743421.js +++ b/browser/base/content/test/plugins/browser_bug743421.js @@ -60,8 +60,10 @@ function test1b() { // Click the activate button on doorhanger to make sure it works popupNotification.reshow(); PopupNotifications.panel.firstChild._primaryButton.click(); - var condition = function() objLoadingContent.activated; - waitForCondition(condition, test1c, "Test 1b, Waited too long for plugin activation"); + + ok(objLoadingContent.activated, "Test 1b, Doorhanger should activate plugin"); + + test1c(); } function test1c() { diff --git a/browser/base/content/test/plugins/browser_bug820497.js b/browser/base/content/test/plugins/browser_bug820497.js index e5ca402f88a..d64a7bbb96d 100644 --- a/browser/base/content/test/plugins/browser_bug820497.js +++ b/browser/base/content/test/plugins/browser_bug820497.js @@ -36,14 +36,12 @@ function pluginBindingAttached() { ok(testplugin, "should have test plugin"); var secondtestplugin = doc.getElementById("secondtest"); ok(!secondtestplugin, "should not yet have second test plugin"); - var notification; - waitForNotificationPopup("click-to-play-plugins", gTestBrowser, (notification => { - ok(notification, "should have popup notification"); - // We don't set up the action list until the notification is shown - notification.reshow(); - is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification"); - XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addSecondPlugin(); - })); + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "should have popup notification"); + // We don't set up the action list until the notification is shown + notification.reshow(); + is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification"); + XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addSecondPlugin(); } else if (gNumPluginBindingsAttached == 2) { var doc = gTestBrowser.contentDocument; var testplugin = doc.getElementById("test"); @@ -53,8 +51,8 @@ function pluginBindingAttached() { var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); ok(notification, "should have popup notification"); notification.reshow(); - let condition = () => (notification.options.pluginData.size == 2); - waitForCondition(condition, finish, "Waited too long for 2 types of plugins in popup notification"); + is(notification.options.pluginData.size, 2, "should be 2 types of plugin in the popup notification"); + finish(); } else { ok(false, "if we've gotten here, something is quite wrong"); } diff --git a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js index 68778dacf08..1c1b6970751 100644 --- a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js +++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js @@ -59,11 +59,11 @@ function onCrash(event) { is (propVal, val, "Correct property in detail propBag: " + name + "."); } - waitForNotificationBar("plugin-crashed", gTestBrowser, (notification) => { - let notificationBox = gBrowser.getNotificationBox(gTestBrowser); - ok(notification, "Infobar was shown."); - is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, "Correct priority."); - is(notification.getAttribute("label"), "The GlobalTestPlugin plugin has crashed.", "Correct message."); - finish(); - }); + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + let notification = notificationBox.getNotificationWithValue("plugin-crashed"); + + ok(notification, "Infobar was shown."); + is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, "Correct priority."); + is(notification.getAttribute("label"), "The GlobalTestPlugin plugin has crashed.", "Correct message."); + finish(); } diff --git a/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js index 216878a21ce..9dc803d01f3 100644 --- a/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js +++ b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js @@ -6,80 +6,9 @@ Cu.import("resource://gre/modules/CrashSubmit.jsm", this); Cu.import("resource://gre/modules/Services.jsm"); const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html"; + const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; -/** - * Frame script that will be injected into the test browser - * to cause plugin crashes, and then manipulate the crashed plugin - * UI. The specific actions and checks that occur in the frame - * script for the crashed plugin UI are set in the - * test:crash-plugin message object sent from the parent. The actions - * and checks that the parent can specify are: - * - * pleaseSubmitStyle: the display style that the pleaseSubmit anonymous element - * should have - example "block", "none". - * submitComment: the comment that should be put into the crash report - * urlOptIn: true if the submitURLOptIn element should be checked. - * sendCrashMessage: if true, the frame script will send a - * test:crash-plugin:crashed message when the plugin has - * crashed. This is used for the last test case, and - * causes the frame script to skip any of the crashed - * plugin UI manipulation, since the last test shows - * no crashed plugin UI. - */ -function frameScript() { - function fail(reason) { - sendAsyncMessage("test:crash-plugin:fail", { - reason: `Failure from frameScript: ${reason}`, - }); - } - - addMessageListener("test:crash-plugin", (message) => { - addEventListener("PluginCrashed", function onPluginCrashed(event) { - removeEventListener("PluginCrashed", onPluginCrashed); - - let doc = content.document; - let plugin = doc.getElementById("plugin"); - if (!plugin) { - fail("Could not find plugin element"); - return; - } - - let getUI = (anonid) => { - return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid); - }; - - let style = content.getComputedStyle(getUI("pleaseSubmit")); - if (style.display != message.data.pleaseSubmitStyle) { - fail("Submission UI visibility is not correct. Expected " + - `${message.data.pleaseSubmitStyle} and got ${style.display}`); - return; - } - - if (message.data.sendCrashMessage) { - let propBag = event.detail.QueryInterface(Ci.nsIPropertyBag2); - let crashID = propBag.getPropertyAsAString("pluginDumpID"); - sendAsyncMessage("test:crash-plugin:crashed", { - crashID: crashID, - }); - return; - } - - if (message.data.submitComment) { - getUI("submitComment").value = message.data.submitComment; - } - getUI("submitURLOptIn").checked = message.data.urlOptIn; - getUI("submitButton").click(); - }); - - let plugin = content.document.getElementById("test"); - try { - plugin.crash() - } catch(e) { - } - }); -} - function test() { // Crashing the plugin takes up a lot of time, so extend the test timeout. requestLongerTimeout(runs.length); @@ -100,18 +29,14 @@ function test() { let tab = gBrowser.loadOneTab("about:blank", { inBackground: false }); let browser = gBrowser.getBrowserForTab(tab); - let mm = browser.messageManager; - mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", true); - - mm.addMessageListener("test:crash-plugin:fail", (message) => { - ok(false, message.data.reason); - }); - + browser.addEventListener("PluginCrashed", onCrash, false); Services.obs.addObserver(onSubmitStatus, "crash-report-status", false); registerCleanupFunction(function cleanUp() { env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); env.set("MOZ_CRASHREPORTER_URL", serverURL); + gBrowser.selectedBrowser.removeEventListener("PluginCrashed", onCrash, + false); Services.obs.removeObserver(onSubmitStatus, "crash-report-status"); gBrowser.removeCurrentTab(); }); @@ -151,24 +76,6 @@ function doNextRun() { memo[arg] = currentRun[arg]; return memo; }, {}); - let mm = gBrowser.selectedBrowser.messageManager; - - if (!currentRun.shouldSubmittionUIBeVisible) { - mm.addMessageListener("test:crash-plugin:crash", function onCrash(message) { - mm.removeMessageListener("test:crash-plugin:crash", onCrash); - - ok(!!message.data.crashID, "pluginDumpID should be set"); - CrashSubmit.delete(message.data.crashID); - doNextRun(); - }); - } - - mm.sendAsyncMessage("test:crash-plugin", { - pleaseSubmitStyle: currentRun.shouldSubmissionUIBeVisible ? "block" : "none", - submitComment: currentRun.comment, - urlOptIn: currentRun.urlOptIn, - sendOnCrashMessage: !currentRun.shouldSubmissionUIBeVisible, - }); gBrowser.loadURI(CRASH_URL + "?" + encodeURIComponent(JSON.stringify(args))); // And now wait for the crash. @@ -179,6 +86,37 @@ function doNextRun() { } } +function onCrash(event) { + try { + let plugin = gBrowser.contentDocument.getElementById("plugin"); + let elt = gPluginHandler.getPluginUI.bind(gPluginHandler, plugin); + let style = + gBrowser.contentWindow.getComputedStyle(elt("pleaseSubmit")); + is(style.display, + currentRun.shouldSubmissionUIBeVisible ? "block" : "none", + "Submission UI visibility should be correct"); + if (!currentRun.shouldSubmissionUIBeVisible) { + // Done with this run. We don't submit the crash, so we will have to + // remove the dump manually. + let propBag = event.detail.QueryInterface(Ci.nsIPropertyBag2); + let crashID = propBag.getPropertyAsAString("pluginDumpID"); + ok(!!crashID, "pluginDumpID should be set"); + CrashSubmit.delete(crashID); + + doNextRun(); + return; + } + elt("submitComment").value = currentRun.comment; + elt("submitURLOptIn").checked = currentRun.urlOptIn; + elt("submitButton").click(); + // And now wait for the submission status notification. + } + catch (err) { + failWithException(err); + doNextRun(); + } +} + function onSubmitStatus(subj, topic, data) { try { // Wait for success or failed, doesn't matter which. diff --git a/browser/base/content/test/plugins/browser_pluginnotification.js b/browser/base/content/test/plugins/browser_pluginnotification.js index 3841727871e..234a9f81dc2 100644 --- a/browser/base/content/test/plugins/browser_pluginnotification.js +++ b/browser/base/content/test/plugins/browser_pluginnotification.js @@ -56,7 +56,6 @@ TabOpenListener.prototype = { function test() { waitForExplicitFinish(); - SimpleTest.requestCompleteLog(); requestLongerTimeout(2); registerCleanupFunction(function() { clearAllPluginPermissions(); @@ -798,10 +797,7 @@ function test24a() { // simulate "always allow" notification.reshow(); PopupNotifications.panel.firstChild._primaryButton.click(); - waitForCondition(() => objLoadingContent.activated, () => { - prepareTest(test24b, gHttpTestRoot + "plugin_test.html"); - }, "Test 24a, plugin should now be activated."); - + prepareTest(test24b, gHttpTestRoot + "plugin_test.html"); } // did the "always allow" work as intended? @@ -809,11 +805,11 @@ function test24b() { var plugin = gTestBrowser.contentDocument.getElementById("test"); ok(plugin, "Test 24b, Found plugin in page"); var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - waitForCondition(() => objLoadingContent.activated, () => { - setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml", () => { - prepareTest(runAfterPluginBindingAttached(test24c), gHttpTestRoot + "plugin_test.html"); - }); - }, "Test 24b, plugin should be activated"); + ok(objLoadingContent.activated, "Test 24b, plugin should be activated"); + setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml", + function() { + prepareTest(runAfterPluginBindingAttached(test24c), gHttpTestRoot + "plugin_test.html"); + }); } // the plugin is now blocklisted, so it should not automatically load @@ -824,13 +820,13 @@ function test24c() { ok(plugin, "Test 24c, Found plugin in page"); var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 24c, Plugin should be vulnerable/updatable"); - waitForCondition(() => !objLoadingContent.activated, () => { - // simulate "always allow" - notification.reshow(); - PopupNotifications.panel.firstChild._primaryButton.click(); + ok(!objLoadingContent.activated, "Test 24c, plugin should not be activated"); - prepareTest(test24d, gHttpTestRoot + "plugin_test.html"); - }, "Test 24c, plugin should not be activated"); + // simulate "always allow" + notification.reshow(); + PopupNotifications.panel.firstChild._primaryButton.click(); + + prepareTest(test24d, gHttpTestRoot + "plugin_test.html"); } // We should still be able to always allow a plugin after we've seen that it's @@ -839,14 +835,15 @@ function test24d() { var plugin = gTestBrowser.contentDocument.getElementById("test"); ok(plugin, "Test 24d, Found plugin in page"); var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - waitForCondition(() => objLoadingContent.activated, () => { - // this resets the vulnerable plugin permission - setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", () => { - clearAllPluginPermissions(); - resetBlocklist(); - prepareTest(test25, gTestRoot + "plugin_syncRemoved.html"); - }); - }, "Test 24d, plugin should be activated"); + ok(objLoadingContent.activated, "Test 24d, plugin should be activated"); + + // this resets the vulnerable plugin permission + setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", + function() { + clearAllPluginPermissions(); + resetBlocklist(); + prepareTest(test25, gTestRoot + "plugin_syncRemoved.html"); + }); } function test25() { diff --git a/browser/base/content/test/plugins/head.js b/browser/base/content/test/plugins/head.js index a6c3cd42f54..53359e20adf 100644 --- a/browser/base/content/test/plugins/head.js +++ b/browser/base/content/test/plugins/head.js @@ -108,28 +108,3 @@ function setAndUpdateBlocklist(aURL, aCallback) { function resetBlocklist() { Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); } - -function waitForNotificationPopup(notificationID, browser, callback) { - let notification; - waitForCondition( - () => (notification = PopupNotifications.getNotification(notificationID, browser)), - () => { - ok(notification, `Successfully got the ${notificationID} notification popup`); - callback(notification); - }, - `Waited too long for the ${notificationID} notification popup` - ); -} - -function waitForNotificationBar(notificationID, browser, callback) { - let notification; - let notificationBox = gBrowser.getNotificationBox(browser); - waitForCondition( - () => (notification = notificationBox.getNotificationWithValue(notificationID)), - () => { - ok(notification, `Successfully got the ${notificationID} notification bar`); - callback(notification); - }, - `Waited too long for the ${notificationID} notification bar` - ); -} diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml index f5d46e2beeb..aaead336356 100644 --- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -1976,7 +1976,7 @@ return; } - let host = this.notification.options.host; + let host = gPluginHandler._getHostFromPrincipal(this.notification.browser.contentWindow.document.nodePrincipal); this._setupDescription("pluginActivateMultiple.message", null, host); var showBox = document.getAnonymousElementByAttribute(this, "anonid", "plugin-notification-showbox"); diff --git a/browser/modules/PluginContent.jsm b/browser/modules/PluginContent.jsm deleted file mode 100644 index f58ab2f95d8..00000000000 --- a/browser/modules/PluginContent.jsm +++ /dev/null @@ -1,992 +0,0 @@ -# -*- indent-tabs-mode: nil; js-indent-level: 2 -*- -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -"use strict"; - -let Cc = Components.classes; -let Ci = Components.interfaces; -let Cu = Components.utils; - -this.EXPORTED_SYMBOLS = [ "PluginContent" ]; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Timer.jsm"); - -XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() { - const url = "chrome://browser/locale/browser.properties"; - return Services.strings.createBundle(url); -}); - -this.PluginContent = function (global) { - this.init(global); -} - -PluginContent.prototype = { - init: function (global) { - this.global = global; - // Need to hold onto the content window or else it'll get destroyed - this.content = this.global.content; - // Cache of plugin actions for the current page. - this.pluginData = new Map(); - - // Note that the XBL binding is untrusted - global.addEventListener("PluginBindingAttached", this, true, true); - global.addEventListener("PluginCrashed", this, true); - global.addEventListener("PluginOutdated", this, true); - global.addEventListener("PluginInstantiated", this, true); - global.addEventListener("PluginRemoved", this, true); - global.addEventListener("unload", this); - - global.addEventListener("pageshow", (event) => this.onPageShow(event), true); - - global.addMessageListener("BrowserPlugins:ActivatePlugins", this); - global.addMessageListener("BrowserPlugins:NotificationRemoved", this); - global.addMessageListener("BrowserPlugins:NotificationShown", this); - global.addMessageListener("BrowserPlugins:ContextMenuCommand", this); - }, - - uninit: function() { - delete this.global; - delete this.content; - }, - - receiveMessage: function (msg) { - switch (msg.name) { - case "BrowserPlugins:ActivatePlugins": - this.activatePlugins(msg.data.pluginInfo, msg.data.newState); - break; - case "BrowserPlugins:NotificationRemoved": - this.clearPluginDataCache(); - break; - case "BrowserPlugins:NotificationShown": - setTimeout(() => this.updateNotificationUI(), 0); - break; - case "BrowserPlugins:ContextMenuCommand": - switch (msg.data.command) { - case "play": - this._showClickToPlayNotification(msg.objects.plugin, true); - break; - case "hide": - this.hideClickToPlayOverlay(msg.objects.plugin); - break; - } - break; - } - }, - - onPageShow: function (event) { - // Ignore events that aren't from the main document. - if (this.global.content && event.target != this.global.content.document) { - return; - } - - // The PluginClickToPlay events are not fired when navigating using the - // BF cache. |persisted| is true when the page is loaded from the - // BF cache, so this code reshows the notification if necessary. - if (event.persisted) { - this.reshowClickToPlayNotification(); - } - }, - - getPluginUI: function (plugin, anonid) { - return plugin.ownerDocument. - getAnonymousElementByAttribute(plugin, "anonid", anonid); - }, - - _getPluginInfo: function (pluginElement) { - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); - - let tagMimetype; - let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin"); - let pluginTag = null; - let permissionString = null; - let fallbackType = null; - let blocklistState = null; - - tagMimetype = pluginElement.actualType; - if (tagMimetype == "") { - tagMimetype = pluginElement.type; - } - - if (this.isKnownPlugin(pluginElement)) { - pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); - pluginName = this.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 parenthetical clauses, - // trailing version numbers or "plugin". - // EG, "Foo Bar (Linux) 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(/\(.*?\)/g, ""). - replace(/[\s\d\.\-\_\(\)]+$/, ""). - replace(/\bplug-?in\b/i, "").trim(); - return newName; - }, - - /** - * Update the visibility of the plugin overlay. - */ - setVisibility : function (plugin, overlay, shouldShow) { - overlay.classList.toggle("visible", shouldShow); - }, - - /** - * Check whether the plugin should be visible on the page. A plugin should - * not be visible if the overlay is too big, or if any other page content - * overlays it. - * - * This function will handle showing or hiding the overlay. - * @returns true if the plugin is invisible. - */ - shouldShowOverlay : function (plugin, overlay) { - // If the overlay size is 0, we haven't done layout yet. Presume that - // plugins are visible until we know otherwise. - if (overlay.scrollWidth == 0) { - return true; - } - - // Is the '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 > Math.ceil(pluginRect.width)) || - (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height)); - if (overflows) { - return false; - } - - // Is the plugin covered up by other content so that it is not clickable? - // Floating point can confuse .elementFromPoint, so inset just a bit - let left = pluginRect.left + 2; - let right = pluginRect.right - 2; - let top = pluginRect.top + 2; - let bottom = pluginRect.bottom - 2; - let centerX = left + (right - left) / 2; - let centerY = top + (bottom - top) / 2; - let points = [[left, top], - [left, bottom], - [right, top], - [right, bottom], - [centerX, centerY]]; - - if (right <= 0 || top <= 0) { - return false; - } - - let contentWindow = plugin.ownerDocument.defaultView; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - - for (let [x, y] of points) { - let el = cwu.elementFromPoint(x, y, true, true); - if (el !== plugin) { - return false; - } - } - - return true; - }, - - 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; - } - }, - - handleEvent: function (event) { - let eventType = event.type; - - if (eventType == "unload") { - this.uninit(); - return; - } - - if (eventType == "PluginRemoved") { - this.updateNotificationUI(); - return; - } - - if (eventType == "click") { - this.onOverlayClick(event); - return; - } - - if (eventType == "PluginCrashed" && - !(event.target instanceof Ci.nsIObjectLoadingContent)) { - // If the event target is not a plugin object (i.e., an or - // element), this call is for a window-global plugin. - this.pluginInstanceCrashed(event.target, event); - return; - } - - let plugin = event.target; - let 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; - switch (eventType) { - case "PluginCrashed": - this.pluginInstanceCrashed(plugin, event); - break; - - case "PluginNotFound": { - let installable = this.showInstallNotification(plugin, eventType); - let contentWindow = plugin.ownerDocument.defaultView; - // 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 contentWindow.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, "forwardCallback", "openPluginUpdatePage"); - /* FALLTHRU */ - - case "PluginVulnerableNoUpdate": - case "PluginClickToPlay": - this._handleClickToPlayEvent(plugin); - let overlay = this.getPluginUI(plugin, "main"); - let pluginName = this._getPluginInfo(plugin).pluginName; - let messageString = gNavigatorBundle.formatStringFromName("PluginClickToActivate", [pluginName], 1); - let overlayText = this.getPluginUI(plugin, "clickToPlay"); - overlayText.textContent = messageString; - if (eventType == "PluginVulnerableUpdatable" || - eventType == "PluginVulnerableNoUpdate") { - let vulnerabilityString = gNavigatorBundle.GetStringFromName(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, "forwardCallback", "managePlugins"); - shouldShowNotification = true; - break; - - case "PluginInstantiated": - shouldShowNotification = true; - break; - } - - // Show the in-content UI if it's not too big. The crashed plugin handler already did this. - if (eventType != "PluginCrashed") { - let overlay = this.getPluginUI(plugin, "main"); - if (overlay != null) { - this.setVisibility(plugin, overlay, - this.shouldShowOverlay(plugin, overlay)); - let resizeListener = (event) => { - this.setVisibility(plugin, overlay, - this.shouldShowOverlay(plugin, overlay)); - this.updateNotificationUI(); - }; - plugin.addEventListener("overflow", resizeListener); - plugin.addEventListener("underflow", resizeListener); - } - } - - let closeIcon = this.getPluginUI(plugin, "closeIcon"); - if (closeIcon) { - closeIcon.addEventListener("click", event => { - if (event.button == 0 && event.isTrusted) - this.hideClickToPlayOverlay(plugin); - }, true); - } - - if (shouldShowNotification) { - this._showClickToPlayNotification(plugin, false); - } - }, - - isKnownPlugin: function (objLoadingContent) { - return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == - Ci.nsIObjectLoadingContent.TYPE_PLUGIN); - }, - - canActivatePlugin: function (objLoadingContent) { - // if this isn't a known plugin, we can't activate it - // (this also guards pluginHost.getPermissionStringForType against - // unexpected input) - if (!this.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 (plugin) { - let overlay = this.getPluginUI(plugin, "main"); - if (overlay) { - overlay.classList.remove("visible"); - } - }, - - stopPlayPreview: function (plugin, playPlugin) { - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (objLoadingContent.activated) - return; - - if (playPlugin) - objLoadingContent.playPlugin(); - else - objLoadingContent.cancelPlayPreview(); - }, - - // Callback for user clicking on a missing (unsupported) plugin. - installSinglePlugin: function (plugin) { - this.global.sendAsyncMessage("PluginContent:InstallSinglePlugin", { - pluginInfo: this._getPluginInfo(plugin), - }); - }, - - // Forward a link click callback to the chrome process. - forwardCallback: function (name) { - this.global.sendAsyncMessage("PluginContent:LinkClickCallback", { name: name }); - }, - -#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.global.sendAsyncMessage("PluginContent:SubmitReport", { - pluginDumpID: pluginDumpID, - browserDumpID: browserDumpID, - keyVals: keyVals, - }); - }, -#endif - - reloadPage: function () { - this.global.content.location.reload(); - }, - - showInstallNotification: function (plugin) { - let [shown] = this.global.sendSyncMessage("PluginContent:ShowInstallNotification", { - pluginInfo: this._getPluginInfo(plugin), - }); - return shown; - }, - - // Event listener for click-to-play plugins. - _handleClickToPlayEvent: function (plugin) { - let doc = plugin.ownerDocument; - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - // guard against giving pluginHost.getPermissionStringForType a type - // not associated with any known plugin - if (!this.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(plugin, "main"); - - if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { - if (overlay) { - overlay.classList.remove("visible"); - } - return; - } - - if (overlay) { - overlay.addEventListener("click", this, true); - } - }, - - onOverlayClick: function (event) { - let document = event.target.ownerDocument; - let plugin = document.getBindingParent(event.target); - let contentWindow = plugin.ownerDocument.defaultView.top; - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - // Have to check that the target is not the link to update the plugin - if (!(event.originalTarget instanceof contentWindow.HTMLAnchorElement) && - (event.originalTarget.getAttribute('anonid') != 'closeIcon') && - event.button == 0 && event.isTrusted) { - this._showClickToPlayNotification(plugin, true); - event.stopPropagation(); - event.preventDefault(); - } - }, - - _handlePlayPreviewEvent: function (plugin) { - let doc = plugin.ownerDocument; - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - let pluginInfo = this._getPluginInfo(plugin); - let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype); - - let previewContent = this.getPluginUI(plugin, "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. - plugin.clientTop; - } - iframe.src = playPreviewInfo.redirectURL; - - // MozPlayPlugin event can be dispatched from the extension chrome - // code to replace the preview content with the native plugin - let playPluginHandler = (event) => { - if (!event.isTrusted) - return; - - previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); - - let playPlugin = !event.detail; - this.stopPlayPreview(plugin, playPlugin); - - // cleaning up: removes overlay iframe from the DOM - let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; - if (iframe) - previewContent.removeChild(iframe); - }; - - previewContent.addEventListener("MozPlayPlugin", playPluginHandler, true); - - if (!playPreviewInfo.ignoreCTP) { - this._showClickToPlayNotification(plugin, false); - } - }, - - reshowClickToPlayNotification: function () { - let contentWindow = this.global.content; - 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", this, true); - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (this.canActivatePlugin(objLoadingContent)) - this._handleClickToPlayEvent(plugin); - } - this._showClickToPlayNotification(null, false); - }, - - // Match the behaviour of nsPermissionManager - _getHostFromPrincipal: function (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; - }, - - /** - * Activate the plugins that the user has specified. - */ - activatePlugins: function (pluginInfo, newState) { - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let plugins = cwu.plugins; - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - - let pluginFound = false; - for (let plugin of plugins) { - plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (!this.isKnownPlugin(plugin)) { - continue; - } - if (pluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { - pluginFound = true; - if (newState == "block") { - plugin.reload(true); - } else { - if (this.canActivatePlugin(plugin)) { - let overlay = this.getPluginUI(plugin, "main"); - if (overlay) { - overlay.removeEventListener("click", this, true); - } - plugin.playPlugin(); - } - } - } - } - - // If there are no instances of the plugin on the page any more, what the - // user probably needs is for us to allow and then refresh. - if (newState != "block" && !pluginFound) { - this.reloadPage(); - } - this.updateNotificationUI(); - }, - - _showClickToPlayNotification: function (plugin, showNow) { - let plugins = []; - - // If plugin is null, that means the user has navigated back to a page with - // plugins, and we need to collect all the plugins. - if (plugin === null) { - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - // cwu.plugins may contain non-plugin s, filter them out - plugins = cwu.plugins.filter((plugin) => - plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); - - if (plugins.length == 0) { - this.removeNotification("click-to-play-plugins"); - return; - } - } else { - plugins = [plugin]; - } - - let pluginData = this.pluginData; - - let principal = this.global.content.document.nodePrincipal; - let principalHost = this._getHostFromPrincipal(principal); - - for (let p of plugins) { - let pluginInfo = this._getPluginInfo(p); - if (pluginInfo.permissionString === null) { - Cu.reportError("No permission string for active plugin."); - continue; - } - if (pluginData.has(pluginInfo.permissionString)) { - continue; - } - - 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; - } - - this.pluginData.set(pluginInfo.permissionString, pluginInfo); - } - - this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", { - plugins: [... this.pluginData.values()], - showNow: showNow, - host: principalHost, - }, null, principal); - }, - - updateNotificationUI: function () { - // Make a copy of the actions from the last popup notification. - let haveInsecure = false; - let actions = new Map(); - for (let action of this.pluginData.values()) { - switch (action.fallbackType) { - // haveInsecure will trigger the red flashing icon and the infobar - // styling below - case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: - case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: - haveInsecure = true; - // fall through - - case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: - actions.set(action.permissionString, action); - continue; - } - } - - // Remove plugins that are already active, or large enough to show an overlay. - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - for (let plugin of cwu.plugins) { - let info = this._getPluginInfo(plugin); - if (!actions.has(info.permissionString)) { - continue; - } - let fallbackType = info.fallbackType; - if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) { - actions.delete(info.permissionString); - if (actions.size == 0) { - break; - } - continue; - } - if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && - fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE && - fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) { - continue; - } - let overlay = this.getPluginUI(plugin, "main"); - if (!overlay) { - continue; - } - let shouldShow = this.shouldShowOverlay(plugin, overlay); - this.setVisibility(plugin, overlay, shouldShow); - if (shouldShow) { - actions.delete(info.permissionString); - if (actions.size == 0) { - break; - } - } - } - - // If there are any items remaining in `actions` now, they are hidden - // plugins that need a notification bar. - let principal = contentWindow.document.nodePrincipal; - this.global.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", { - haveInsecure: haveInsecure, - actions: [... actions.values()], - host: this._getHostFromPrincipal(principal), - }, null, principal); - }, - - removeNotification: function (name) { - this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name }); - }, - - clearPluginDataCache: function () { - this.pluginData.clear(); - }, - - hideNotificationBar: function (name) { - this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name }); - }, - - // Crashed-plugin event listener. Called for every instance of a - // plugin in content. - pluginInstanceCrashed: function (target, aEvent) { - // Ensure the plugin and event are of the right type. - if (!(aEvent instanceof Ci.nsIDOMCustomEvent)) - return; - - let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); - let submittedReport = propBag.getPropertyAsBool("submittedCrashReport"); - let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt"); - let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports"); - let pluginName = propBag.getPropertyAsAString("pluginName"); - let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID"); - let browserDumpID = null; - let gmpPlugin = false; - - try { - browserDumpID = propBag.getPropertyAsAString("browserDumpID"); - } catch (e) { - // For GMP crashes we don't get a browser dump. - } - - try { - gmpPlugin = propBag.getPropertyAsBool("gmpPlugin"); - } catch (e) { - // This property is only set for GMP plugins. - } - - // For non-GMP plugins, remap the plugin name to a more user-presentable form. - if (!gmpPlugin) { - pluginName = this.makeNicePluginName(pluginName); - } - - let messageString = gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1); - - let plugin = null, doc; - if (target instanceof Ci.nsIObjectLoadingContent) { - plugin = target; - doc = plugin.ownerDocument; - } else { - doc = target.document; - if (!doc) { - return; - } - // doPrompt is specific to the crashed plugin overlay, and - // therefore is not applicable for window-global plugins. - doPrompt = false; - } - - let status; -#ifdef MOZ_CRASHREPORTER - // Determine which message to show regarding crash reports. - if (submittedReport) { // submitReports && !doPrompt, handled in observer - status = "submitted"; - } - else if (!submitReports && !doPrompt) { - status = "noSubmit"; - } - else if (!pluginDumpID) { - // If we don't have a minidumpID, we can't (or didn't) submit anything. - // This can happen if the plugin is killed from the task manager. - status = "noReport"; - } - else { - status = "please"; - } - - // 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"; - } - - // 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 : (subject, topic, data) => { - let propertyBag = subject; - if (!(propertyBag instanceof Ci.nsIPropertyBag2)) - return; - // Ignore notifications for other crashes. - if (propertyBag.get("minidumpID") != pluginDumpID) - return; - let statusDiv = this.getPluginUI(plugin, "submitStatus"); - 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 isShowing = false; - - if (plugin) { - // If there's no plugin (an or element), this call is - // for a window-global plugin. In this case, there's no overlay to show. - isShowing = _setUpPluginOverlay.call(this, plugin, doPrompt); - } - - 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. - this.hideNotificationBar("plugin-crashed"); - 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) { - this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification", { - messageString: messageString, - pluginDumpID: pluginDumpID, - browserDumpID: browserDumpID, - }); - // Remove the notification when the page is reloaded. - doc.defaultView.top.addEventListener("unload", event => { - this.hideNotificationBar("plugin-crashed"); - }, false); - } - } - - // Configure the crashed-plugin placeholder. - // Returns true if the plugin overlay is visible. - function _setUpPluginOverlay(plugin, doPromptSubmit) { - if (!plugin) { - return false; - } - - // Force a layout flush so the binding is attached. - plugin.clientTop; - let overlay = this.getPluginUI(plugin, "main"); - let statusDiv = this.getPluginUI(plugin, "submitStatus"); - - if (doPromptSubmit) { - 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(""); - } - - statusDiv.setAttribute("status", status); - - let helpIcon = this.getPluginUI(plugin, "helpIcon"); - this.addLinkClickCallback(helpIcon, "openHelpPage"); - - let crashText = this.getPluginUI(plugin, "crashedText"); - crashText.textContent = messageString; - - let link = this.getPluginUI(plugin, "reloadLink"); - this.addLinkClickCallback(link, "reloadPage"); - - let isShowing = this.shouldShowOverlay(plugin, overlay); - - // Is the 's size too small to hold what we want to show? - if (!isShowing) { - // First try hiding the crash report submission UI. - statusDiv.removeAttribute("status"); - - isShowing = this.shouldShowOverlay(plugin, overlay); - } - this.setVisibility(plugin, overlay, isShowing); - - return isShowing; - } - } -}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index ff1aabc4f1d..79356db11cf 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -44,7 +44,6 @@ if CONFIG['NIGHTLY_BUILD']: EXTRA_PP_JS_MODULES += [ 'AboutHome.jsm', - 'PluginContent.jsm', 'RecentWindow.jsm', 'UITour.jsm', 'webrtcUI.jsm', diff --git a/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js b/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js index 1b119ca5b50..d4a6b2b6b13 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js +++ b/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js @@ -75,21 +75,19 @@ function part3(aTestPlugin) { } function part4() { - let condition = () => PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser); - waitForCondition(condition, () => { - gPluginBrowser.removeEventListener("PluginBindingAttached", part4); - gBrowser.removeCurrentTab(); + ok(PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser), "part4: should have a click-to-play notification"); + gPluginBrowser.removeEventListener("PluginBindingAttached", part4); + gBrowser.removeCurrentTab(); - let pluginEl = get_addon_element(gManagerWindow, gTestPluginId); - let menu = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "state-menulist"); - let alwaysActivateItem = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "always-activate-menuitem"); - menu.selectedItem = alwaysActivateItem; - alwaysActivateItem.doCommand(); - gBrowser.selectedTab = gBrowser.addTab(); - gPluginBrowser = gBrowser.selectedBrowser; - gPluginBrowser.addEventListener("load", part5, true); - gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html"; - }, "part4: should have a click-to-play notification"); + let pluginEl = get_addon_element(gManagerWindow, gTestPluginId); + let menu = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "state-menulist"); + let alwaysActivateItem = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "always-activate-menuitem"); + menu.selectedItem = alwaysActivateItem; + alwaysActivateItem.doCommand(); + gBrowser.selectedTab = gBrowser.addTab(); + gPluginBrowser = gBrowser.selectedBrowser; + gPluginBrowser.addEventListener("load", part5, true); + gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html"; } function part5() { @@ -120,22 +118,20 @@ function part6() { } function part7() { - let condition = () => PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser); - waitForCondition(condition, () => { - let testPlugin = gPluginBrowser.contentDocument.getElementById("test"); - ok(testPlugin, "part7: should have a plugin element in the page"); - let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent); - ok(!objLoadingContent.activated, "part7: plugin should not be activated"); + ok(PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser), "part7: disabled plugins still show a notification"); + let testPlugin = gPluginBrowser.contentDocument.getElementById("test"); + ok(testPlugin, "part7: should have a plugin element in the page"); + let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "part7: plugin should not be activated"); - gPluginBrowser.removeEventListener("PluginBindingAttached", part7); - gBrowser.removeCurrentTab(); + gPluginBrowser.removeEventListener("PluginBindingAttached", part7); + gBrowser.removeCurrentTab(); - let pluginEl = get_addon_element(gManagerWindow, gTestPluginId); - let details = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "details-btn"); - is_element_visible(details, "part7: details link should be visible"); - EventUtils.synthesizeMouseAtCenter(details, {}, gManagerWindow); - wait_for_view_load(gManagerWindow, part8); - }, "part7: disabled plugins still show a notification"); + let pluginEl = get_addon_element(gManagerWindow, gTestPluginId); + let details = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "details-btn"); + is_element_visible(details, "part7: details link should be visible"); + EventUtils.synthesizeMouseAtCenter(details, {}, gManagerWindow); + wait_for_view_load(gManagerWindow, part8); } function part8() { @@ -184,21 +180,19 @@ function part10() { } function part11() { - let condition = () => PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser); - waitForCondition(condition, () => { - gPluginBrowser.removeEventListener("PluginBindingAttached", part11); - gBrowser.removeCurrentTab(); + ok(PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser), "part11: should have a click-to-play notification"); + gPluginBrowser.removeEventListener("PluginBindingAttached", part11); + gBrowser.removeCurrentTab(); - let pluginTag = getTestPluginTag(); + let pluginTag = getTestPluginTag(); - // causes appDisabled to be set - setAndUpdateBlocklist(gHttpTestRoot + "blockPluginHard.xml", - function() { - close_manager(gManagerWindow, function() { - open_manager("addons://list/plugin", part12); - }); +// causes appDisabled to be set + setAndUpdateBlocklist(gHttpTestRoot + "blockPluginHard.xml", + function() { + close_manager(gManagerWindow, function() { + open_manager("addons://list/plugin", part12); }); - }, "part11: should have a click-to-play notification"); + }); } function part12(aWindow) {