michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: var PluginHelper = { michael@0: showDoorHanger: function(aTab) { michael@0: if (!aTab.browser) michael@0: return; michael@0: michael@0: // Even though we may not end up showing a doorhanger, this flag michael@0: // lets us know that we've tried to show a doorhanger. michael@0: aTab.shouldShowPluginDoorhanger = false; michael@0: michael@0: let uri = aTab.browser.currentURI; michael@0: michael@0: // If the user has previously set a plugins permission for this website, michael@0: // either play or don't play the plugins instead of showing a doorhanger. michael@0: let permValue = Services.perms.testPermission(uri, "plugins"); michael@0: if (permValue != Services.perms.UNKNOWN_ACTION) { michael@0: if (permValue == Services.perms.ALLOW_ACTION) michael@0: PluginHelper.playAllPlugins(aTab.browser.contentWindow); michael@0: michael@0: return; michael@0: } michael@0: michael@0: let message = Strings.browser.formatStringFromName("clickToPlayPlugins.message2", michael@0: [uri.host], 1); michael@0: let buttons = [ michael@0: { michael@0: label: Strings.browser.GetStringFromName("clickToPlayPlugins.activate"), michael@0: callback: function(aChecked) { michael@0: // If the user checked "Don't ask again", make a permanent exception michael@0: if (aChecked) michael@0: Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION); michael@0: michael@0: PluginHelper.playAllPlugins(aTab.browser.contentWindow); michael@0: } michael@0: }, michael@0: { michael@0: label: Strings.browser.GetStringFromName("clickToPlayPlugins.dontActivate"), michael@0: callback: function(aChecked) { michael@0: // If the user checked "Don't ask again", make a permanent exception michael@0: if (aChecked) michael@0: Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.DENY_ACTION); michael@0: michael@0: // Other than that, do nothing michael@0: } michael@0: } michael@0: ]; michael@0: michael@0: // Add a checkbox with a "Don't ask again" message if the uri contains a michael@0: // host. Adding a permanent exception will fail if host is not present. michael@0: let options = uri.host ? { checkbox: Strings.browser.GetStringFromName("clickToPlayPlugins.dontAskAgain") } : {}; michael@0: michael@0: NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id, options); michael@0: }, michael@0: michael@0: delayAndShowDoorHanger: function(aTab) { michael@0: // To avoid showing the doorhanger if there are also visible plugin michael@0: // overlays on the page, delay showing the doorhanger to check if michael@0: // visible plugins get added in the near future. michael@0: if (!aTab.pluginDoorhangerTimeout) { michael@0: aTab.pluginDoorhangerTimeout = setTimeout(function() { michael@0: if (this.shouldShowPluginDoorhanger) { michael@0: PluginHelper.showDoorHanger(this); michael@0: } michael@0: }.bind(aTab), 500); michael@0: } michael@0: }, michael@0: michael@0: playAllPlugins: function(aContentWindow) { michael@0: let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: // XXX not sure if we should enable plugins for the parent documents... michael@0: let plugins = cwu.plugins; michael@0: if (!plugins || !plugins.length) michael@0: return; michael@0: michael@0: plugins.forEach(this.playPlugin); michael@0: }, michael@0: michael@0: playPlugin: function(plugin) { michael@0: let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: if (!objLoadingContent.activated) michael@0: objLoadingContent.playPlugin(); michael@0: }, michael@0: michael@0: stopPlayPreview: function(plugin, playPlugin) { michael@0: let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: if (objLoadingContent.activated) michael@0: return; michael@0: michael@0: if (playPlugin) michael@0: objLoadingContent.playPlugin(); michael@0: else michael@0: objLoadingContent.cancelPlayPreview(); michael@0: }, michael@0: michael@0: getPluginPreference: function getPluginPreference() { michael@0: let pluginDisable = Services.prefs.getBoolPref("plugin.disable"); michael@0: if (pluginDisable) michael@0: return "0"; michael@0: michael@0: let state = Services.prefs.getIntPref("plugin.default.state"); michael@0: return state == Ci.nsIPluginTag.STATE_CLICKTOPLAY ? "2" : "1"; michael@0: }, michael@0: michael@0: setPluginPreference: function setPluginPreference(aValue) { michael@0: switch (aValue) { michael@0: case "0": // Enable Plugins = No michael@0: Services.prefs.setBoolPref("plugin.disable", true); michael@0: Services.prefs.clearUserPref("plugin.default.state"); michael@0: break; michael@0: case "1": // Enable Plugins = Yes michael@0: Services.prefs.clearUserPref("plugin.disable"); michael@0: Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED); michael@0: break; michael@0: case "2": // Enable Plugins = Tap to Play (default) michael@0: Services.prefs.clearUserPref("plugin.disable"); michael@0: Services.prefs.clearUserPref("plugin.default.state"); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: // Copied from /browser/base/content/browser.js michael@0: isTooSmall : function (plugin, overlay) { michael@0: // Is the 's size too small to hold what we want to show? michael@0: let pluginRect = plugin.getBoundingClientRect(); michael@0: // XXX bug 446693. The text-shadow on the submitted-report text at michael@0: // the bottom causes scrollHeight to be larger than it should be. michael@0: let overflows = (overlay.scrollWidth > pluginRect.width) || michael@0: (overlay.scrollHeight - 5 > pluginRect.height); michael@0: michael@0: return overflows; michael@0: }, michael@0: michael@0: getPluginMimeType: function (plugin) { michael@0: var tagMimetype = plugin.actualType; michael@0: michael@0: if (tagMimetype == "") { michael@0: tagMimetype = plugin.type; michael@0: } michael@0: michael@0: return tagMimetype; michael@0: }, michael@0: michael@0: handlePluginBindingAttached: function (aTab, aEvent) { michael@0: let plugin = aEvent.target; michael@0: let doc = plugin.ownerDocument; michael@0: let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); michael@0: if (!overlay || overlay._bindingHandled) { michael@0: return; michael@0: } michael@0: overlay._bindingHandled = true; michael@0: michael@0: let eventType = PluginHelper._getBindingType(plugin); michael@0: if (!eventType) { michael@0: // Not all bindings have handlers michael@0: return; michael@0: } michael@0: michael@0: switch (eventType) { michael@0: case "PluginClickToPlay": { michael@0: // Check if plugins have already been activated for this page, or if michael@0: // the user has set a permission to always play plugins on the site michael@0: if (aTab.clickToPlayPluginsActivated || michael@0: Services.perms.testPermission(aTab.browser.currentURI, "plugins") == michael@0: Services.perms.ALLOW_ACTION) { michael@0: PluginHelper.playPlugin(plugin); michael@0: return; michael@0: } michael@0: michael@0: // If the plugin is hidden, or if the overlay is too small, show a michael@0: // doorhanger notification michael@0: if (PluginHelper.isTooSmall(plugin, overlay)) { michael@0: PluginHelper.delayAndShowDoorHanger(aTab); michael@0: } else { michael@0: // There's a large enough visible overlay that we don't need to show michael@0: // the doorhanger. michael@0: aTab.shouldShowPluginDoorhanger = false; michael@0: overlay.classList.add("visible"); michael@0: } michael@0: michael@0: // Add click to play listener to the overlay michael@0: overlay.addEventListener("click", function(e) { michael@0: if (!e.isTrusted) michael@0: return; michael@0: e.preventDefault(); michael@0: let win = e.target.ownerDocument.defaultView.top; michael@0: let tab = BrowserApp.getTabForWindow(win); michael@0: tab.clickToPlayPluginsActivated = true; michael@0: PluginHelper.playAllPlugins(win); michael@0: michael@0: NativeWindow.doorhanger.hide("ask-to-play-plugins", tab.id); michael@0: }, true); michael@0: michael@0: // Add handlers for over- and underflow in case the plugin gets resized michael@0: plugin.addEventListener("overflow", function(event) { michael@0: overlay.classList.remove("visible"); michael@0: PluginHelper.delayAndShowDoorHanger(aTab); michael@0: }); michael@0: plugin.addEventListener("underflow", function(event) { michael@0: // This is also triggered if only one dimension underflows, michael@0: // the other dimension might still overflow michael@0: if (!PluginHelper.isTooSmall(plugin, overlay)) { michael@0: overlay.classList.add("visible"); michael@0: } michael@0: }); michael@0: michael@0: break; michael@0: } michael@0: michael@0: case "PluginPlayPreview": { michael@0: let previewContent = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent"); michael@0: let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: let mimeType = PluginHelper.getPluginMimeType(plugin); michael@0: let playPreviewInfo = pluginHost.getPlayPreviewInfo(mimeType); michael@0: michael@0: if (!playPreviewInfo.ignoreCTP) { michael@0: // Check if plugins have already been activated for this page, or if michael@0: // the user has set a permission to always play plugins on the site michael@0: if (aTab.clickToPlayPluginsActivated || michael@0: Services.perms.testPermission(aTab.browser.currentURI, "plugins") == michael@0: Services.perms.ALLOW_ACTION) { michael@0: PluginHelper.playPlugin(plugin); michael@0: return; michael@0: } michael@0: michael@0: // Always show door hanger for play preview plugins michael@0: PluginHelper.delayAndShowDoorHanger(aTab); michael@0: } michael@0: michael@0: let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; michael@0: if (!iframe) { michael@0: // lazy initialization of the iframe michael@0: iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); michael@0: iframe.className = "previewPluginContentFrame"; michael@0: previewContent.appendChild(iframe); michael@0: } michael@0: iframe.src = playPreviewInfo.redirectURL; michael@0: michael@0: // MozPlayPlugin event can be dispatched from the extension chrome michael@0: // code to replace the preview content with the native plugin michael@0: previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(e) { michael@0: if (!e.isTrusted) michael@0: return; michael@0: michael@0: previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); michael@0: michael@0: let playPlugin = !aEvent.detail; michael@0: PluginHelper.stopPlayPreview(plugin, playPlugin); michael@0: michael@0: // cleaning up: removes overlay iframe from the DOM michael@0: let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; michael@0: if (iframe) michael@0: previewContent.removeChild(iframe); michael@0: }, true); michael@0: break; michael@0: } michael@0: michael@0: case "PluginNotFound": { michael@0: // On devices where we don't support Flash, there will be a michael@0: // "Learn More..." link in the missing plugin error message. michael@0: let learnMoreLink = doc.getAnonymousElementByAttribute(plugin, "class", "unsupportedLearnMoreLink"); michael@0: let learnMoreUrl = Services.urlFormatter.formatURLPref("app.support.baseURL"); michael@0: learnMoreUrl += "mobile-flash-unsupported"; michael@0: learnMoreLink.href = learnMoreUrl; michael@0: overlay.classList.add("visible"); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // Helper to get the binding handler type from a plugin object michael@0: _getBindingType: function(plugin) { michael@0: if (!(plugin instanceof Ci.nsIObjectLoadingContent)) michael@0: return null; michael@0: michael@0: switch (plugin.pluginFallbackType) { michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: michael@0: return "PluginNotFound"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: michael@0: return "PluginClickToPlay"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW: michael@0: return "PluginPlayPreview"; michael@0: default: michael@0: // Not all states map to a handler michael@0: return null; michael@0: } michael@0: } michael@0: };