Bug 776208 - Plugin preview overlay. r=johns,jaws,margaret, sr=joshmoz

This commit is contained in:
Yury 2012-08-24 16:18:16 -04:00
parent 6be509e906
commit b3e51b2f82
14 changed files with 278 additions and 10 deletions

View File

@ -150,6 +150,10 @@ var gPluginHandler = {
self._handleClickToPlayEvent(plugin);
break;
case "PluginPlayPreview":
self._handlePlayPreviewEvent(plugin);
break;
case "PluginDisabled":
let manageLink = doc.getAnonymousElementByAttribute(plugin, "class", "managePluginsLink");
self.addLinkClickCallback(manageLink, "managePlugins");
@ -165,6 +169,11 @@ var gPluginHandler = {
}
},
canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
return !objLoadingContent.activated &&
objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW;
},
activatePlugins: function PH_activatePlugins(aContentWindow) {
let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
browser._clickToPlayPluginsActivated = true;
@ -173,7 +182,7 @@ var gPluginHandler = {
let plugins = cwu.plugins;
for (let plugin of plugins) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
if (!objLoadingContent.activated)
if (gPluginHandler.canActivatePlugin(objLoadingContent))
objLoadingContent.playPlugin();
}
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
@ -183,14 +192,14 @@ var gPluginHandler = {
activateSinglePlugin: function PH_activateSinglePlugin(aContentWindow, aPlugin) {
let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
if (!objLoadingContent.activated)
if (gPluginHandler.canActivatePlugin(objLoadingContent))
objLoadingContent.playPlugin();
let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let haveUnplayedPlugins = cwu.plugins.some(function(plugin) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
return (plugin != aPlugin && !objLoadingContent.activated);
return (plugin != aPlugin && gPluginHandler.canActivatePlugin(objLoadingContent));
});
let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
@ -200,6 +209,17 @@ var gPluginHandler = {
}
},
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;
@ -290,6 +310,46 @@ var gPluginHandler = {
gPluginHandler._showClickToPlayNotification(browser);
},
_handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
let doc = aPlugin.ownerDocument;
let previewContent = doc.getAnonymousElementByAttribute(aPlugin, "class", "previewPluginContent");
if (!previewContent) {
// the XBL binding is not attached (element is display:none), fallback to click-to-play logic
gPluginHandler.stopPlayPreview(aPlugin, false);
return;
}
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;
}
let pluginInfo = getPluginInfo(aPlugin);
let playPreviewUri = "data:application/x-moz-playpreview;," + pluginInfo.mimetype;
iframe.src = playPreviewUri;
// 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);
},
reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
if (!Services.prefs.getBoolPref("plugins.click_to_play"))
return;

View File

@ -1020,6 +1020,7 @@ var gBrowserInit = {
gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
gBrowser.addEventListener("PluginDisabled", gPluginHandler, true);
gBrowser.addEventListener("PluginClickToPlay", gPluginHandler, true);
gBrowser.addEventListener("PluginPlayPreview", gPluginHandler, true);
gBrowser.addEventListener("PluginVulnerableUpdatable", gPluginHandler, true);
gBrowser.addEventListener("PluginVulnerableNoUpdate", gPluginHandler, true);
gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);

View File

@ -21,7 +21,7 @@ interface nsIURI;
* This interface represents a content node that loads objects.
*/
[scriptable, uuid(8ed953b4-5022-4a49-bed4-6818f85dc113)]
[scriptable, uuid(a812424b-4820-4e28-96c8-dd2b69e36496)]
interface nsIObjectLoadingContent : nsISupports
{
/**
@ -55,6 +55,8 @@ interface nsIObjectLoadingContent : nsISupports
const unsigned long PLUGIN_VULNERABLE_UPDATABLE = 9;
// The plugin is vulnerable (no update available)
const unsigned long PLUGIN_VULNERABLE_NO_UPDATE = 10;
// The plugin is in play preview mode
const unsigned long PLUGIN_PLAY_PREVIEW = 11;
/**
* The actual mime type (the one we got back from the network
@ -113,8 +115,8 @@ interface nsIObjectLoadingContent : nsISupports
void playPlugin();
/**
* This attribute will return true if the plugin has been activated
* and false if the plugin is still in the click-to-play state.
* This attribute will return true if the plugin has been activated and
* false if the plugin is still in the click-to-play or play preview state.
*/
readonly attribute boolean activated;
@ -130,4 +132,9 @@ interface nsIObjectLoadingContent : nsISupports
readonly attribute nsIURI srcURI;
readonly attribute unsigned long pluginFallbackType;
/**
* This method will disable the play-preview plugin state.
*/
void cancelPlayPreview();
};

View File

@ -188,6 +188,9 @@ nsPluginErrorEvent::Run()
case nsObjectLoadingContent::eFallbackClickToPlay:
type = NS_LITERAL_STRING("PluginClickToPlay");
break;
case nsObjectLoadingContent::eFallbackPlayPreview:
type = NS_LITERAL_STRING("PluginPlayPreview");
break;
case nsObjectLoadingContent::eFallbackUnsupported:
type = NS_LITERAL_STRING("PluginNotFound");
break;
@ -659,6 +662,7 @@ nsObjectLoadingContent::nsObjectLoadingContent()
, mInstantiating(false)
, mNetworkCreated(true)
, mActivated(false)
, mPlayPreviewCanceled(false)
, mIsStopping(false)
, mIsLoading(false)
, mSrcStreamLoading(false) {}
@ -1041,6 +1045,8 @@ nsObjectLoadingContent::ObjectState() const
return NS_EVENT_STATE_USERDISABLED;
case eFallbackClickToPlay:
return NS_EVENT_STATE_TYPE_CLICK_TO_PLAY;
case eFallbackPlayPreview:
return NS_EVENT_STATE_TYPE_PLAY_PREVIEW;
case eFallbackDisabled:
return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_DISABLED;
case eFallbackBlocklisted:
@ -1427,6 +1433,7 @@ nsObjectLoadingContent::UpdateObjectParameters()
if (stateInvalid) {
newType = eType_Null;
newMime.Truncate();
} else if (useChannel) {
// If useChannel is set above, we considered it in setting newMime
newType = GetTypeOfContent(newMime);
@ -1645,6 +1652,15 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
}
}
// Items resolved as Image/Document will not be checked for previews, as well
// as invalid plugins (they will not have the mContentType set).
if ((mType == eType_Null || mType == eType_Plugin) && ShouldPreview()) {
// If plugin preview exists, we shall use it
LOG(("OBJLC [%p]: Using plugin preview", this));
mType = eType_Null;
fallbackType = eFallbackPlayPreview;
}
// If we're a plugin but shouldn't start yet, load fallback with
// reason click-to-play instead
FallbackType clickToPlayReason;
@ -2451,7 +2467,7 @@ NS_IMETHODIMP
nsObjectLoadingContent::GetActivated(bool *aActivated)
{
FallbackType reason;
*aActivated = ShouldPlay(reason);
*aActivated = ShouldPlay(reason) && !ShouldPreview();
return NS_OK;
}
@ -2463,6 +2479,31 @@ nsObjectLoadingContent::GetPluginFallbackType(uint32_t* aPluginFallbackType)
return NS_OK;
}
NS_IMETHODIMP
nsObjectLoadingContent::CancelPlayPreview()
{
if (!nsContentUtils::IsCallerChrome())
return NS_ERROR_NOT_AVAILABLE;
if (mPlayPreviewCanceled || mActivated)
return NS_OK;
mPlayPreviewCanceled = true;
return LoadObject(true, true);
}
bool
nsObjectLoadingContent::ShouldPreview()
{
if (mPlayPreviewCanceled || mActivated)
return false;
nsRefPtr<nsPluginHost> pluginHost =
already_AddRefed<nsPluginHost>(nsPluginHost::GetInst());
return pluginHost->IsPluginPlayPreviewForType(mContentType.get());
}
bool
nsObjectLoadingContent::ShouldPlay(FallbackType &aReason)
{

View File

@ -82,7 +82,10 @@ class nsObjectLoadingContent : public nsImageLoadingContent
// The plugin is vulnerable (update available)
eFallbackVulnerableUpdatable = nsIObjectLoadingContent::PLUGIN_VULNERABLE_UPDATABLE,
// The plugin is vulnerable (no update available)
eFallbackVulnerableNoUpdate = nsIObjectLoadingContent::PLUGIN_VULNERABLE_NO_UPDATE
eFallbackVulnerableNoUpdate = nsIObjectLoadingContent::PLUGIN_VULNERABLE_NO_UPDATE,
// The plugin is disabled and play preview content is displayed until
// the extension code enables it by sending the MozPlayPlugin event
eFallbackPlayPreview = nsIObjectLoadingContent::PLUGIN_PLAY_PREVIEW
};
nsObjectLoadingContent();
@ -290,6 +293,11 @@ class nsObjectLoadingContent : public nsImageLoadingContent
*/
bool ShouldPlay(FallbackType &aReason);
/**
* If the object should display preview content for the current mContentType
*/
bool ShouldPreview();
/**
* Helper to check if our current URI passes policy
*
@ -423,6 +431,9 @@ class nsObjectLoadingContent : public nsImageLoadingContent
// activated by PlayPlugin(). (see ShouldPlay())
bool mActivated : 1;
// Used to keep track of whether or not a plugin is blocked by play-preview.
bool mPlayPreviewCanceled : 1;
// Protects DoStopPlugin from reentry (bug 724781).
bool mIsStopping : 1;

View File

@ -252,6 +252,8 @@ private:
#define NS_EVENT_STATE_LTR NS_DEFINE_EVENT_STATE_MACRO(43)
// Element is rtl (for :dir pseudo-class)
#define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(44)
// Handler for play preview plugin
#define NS_EVENT_STATE_TYPE_PLAY_PREVIEW NS_DEFINE_EVENT_STATE_MACRO(45)
/**
* NOTE: do not go over 63 without updating nsEventStates::InternalType!

View File

@ -12,7 +12,7 @@
"@mozilla.org/plugin/host;1"
%}
[scriptable, uuid(28F1F9E1-CD23-4FE2-BCC8-BBB0B2D49A4A)]
[scriptable, uuid(fdb56ce3-89ac-4293-be64-9f4be88004cc)]
interface nsIPluginHost : nsISupports
{
/**
@ -72,5 +72,14 @@ interface nsIPluginHost : nsISupports
* subdomains is found, return true.
*/
boolean siteHasData(in nsIPluginTag plugin, in AUTF8String domain);
/**
* Registers the play preview plugin mode for specific mime type
*
* @param mimeType - specified mime type
*/
void registerPlayPreviewMimeType(in AUTF8String mimeType);
void unregisterPlayPreviewMimeType(in AUTF8String mimeType);
};

View File

@ -1309,6 +1309,17 @@ nsPluginHost::IsPluginClickToPlayForType(const char* aMimeType)
}
}
bool
nsPluginHost::IsPluginPlayPreviewForType(const char* aMimeType)
{
for (uint32_t i = 0; i < mPlayPreviewMimeTypes.Length(); i++) {
nsCString mt = mPlayPreviewMimeTypes[i];
if (PL_strcasecmp(mt.get(), aMimeType) == 0)
return true;
}
return false;
}
nsresult
nsPluginHost::GetBlocklistStateForType(const char *aMimeType, uint32_t *aState)
{
@ -1815,6 +1826,27 @@ nsPluginHost::EnumerateSiteData(const nsACString& domain,
return NS_OK;
}
NS_IMETHODIMP
nsPluginHost::RegisterPlayPreviewMimeType(const nsACString& mimeType)
{
mPlayPreviewMimeTypes.AppendElement(mimeType);
return NS_OK;
}
NS_IMETHODIMP
nsPluginHost::UnregisterPlayPreviewMimeType(const nsACString& mimeType)
{
nsCAutoString mimeTypeToRemove(mimeType);
for (uint32_t i = mPlayPreviewMimeTypes.Length(); i > 0;) {
nsCString mt = mPlayPreviewMimeTypes[--i];
if (PL_strcasecmp(mt.get(), mimeTypeToRemove.get()) == 0) {
mPlayPreviewMimeTypes.RemoveElementAt(i);
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain,
uint64_t flags, int64_t maxAge)

View File

@ -86,6 +86,7 @@ public:
nsresult IsPluginEnabledForType(const char* aMimeType);
nsresult IsPluginEnabledForExtension(const char* aExtension, const char* &aMimeType);
bool IsPluginClickToPlayForType(const char *aMimeType);
bool IsPluginPlayPreviewForType(const char *aMimeType);
nsresult GetBlocklistStateForType(const char *aMimeType, uint32_t *state);
nsresult GetPluginCount(uint32_t* aPluginCount);
@ -281,6 +282,7 @@ private:
nsRefPtr<nsPluginTag> mPlugins;
nsRefPtr<nsPluginTag> mCachedPlugins;
nsRefPtr<nsInvalidPluginTag> mInvalidPlugins;
nsTArray<nsCString> mPlayPreviewMimeTypes;
bool mPluginsLoaded;
bool mDontShowBadPluginMessage;

View File

@ -154,6 +154,8 @@ CSS_STATE_PSEUDO_CLASS(mozTypeUnsupportedPlatform, ":-moz-type-unsupported-platf
NS_EVENT_STATE_TYPE_UNSUPPORTED_PLATFORM)
CSS_STATE_PSEUDO_CLASS(mozHandlerClickToPlay, ":-moz-handler-clicktoplay",
NS_EVENT_STATE_TYPE_CLICK_TO_PLAY)
CSS_STATE_PSEUDO_CLASS(mozHandlerPlayPreview, ":-moz-handler-playpreview",
NS_EVENT_STATE_TYPE_PLAY_PREVIEW)
CSS_STATE_PSEUDO_CLASS(mozHandlerVulnerableUpdatable, ":-moz-handler-vulnerable-updatable",
NS_EVENT_STATE_VULNERABLE_UPDATABLE)
CSS_STATE_PSEUDO_CLASS(mozHandlerVulnerableNoUpdate, ":-moz-handler-vulnerable-no-update",

View File

@ -2187,6 +2187,7 @@ Tab.prototype = {
this.browser.addEventListener("scroll", this, true);
this.browser.addEventListener("MozScrolledAreaChanged", this, true);
this.browser.addEventListener("PluginClickToPlay", this, true);
this.browser.addEventListener("PluginPlayPreview", this, true);
this.browser.addEventListener("PluginNotFound", this, true);
this.browser.addEventListener("pageshow", this, true);
@ -2280,6 +2281,7 @@ Tab.prototype = {
this.browser.removeEventListener("DOMWillOpenModalDialog", this, true);
this.browser.removeEventListener("scroll", this, true);
this.browser.removeEventListener("PluginClickToPlay", this, true);
this.browser.removeEventListener("PluginPlayPreview", this, true);
this.browser.removeEventListener("PluginNotFound", this, true);
this.browser.removeEventListener("MozScrolledAreaChanged", this, true);
@ -2842,6 +2844,52 @@ Tab.prototype = {
break;
}
case "PluginPlayPreview": {
let plugin = aEvent.target;
// Force a style flush, so that we ensure our binding is attached.
plugin.clientTop;
let doc = plugin.ownerDocument;
let previewContent = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
if (!previewContent) {
// If the plugin is hidden, fallback to click-to-play logic
PluginHelper.stopPlayPreview(plugin, false);
break;
}
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;
}
let mimeType = PluginHelper.getPluginMimeType(plugin);
let playPreviewUri = "data:application/x-moz-playpreview;," + mimeType;
iframe.src = playPreviewUri;
// MozPlayPlugin event can be dispatched from the extension chrome
// code to replace the preview content with the native plugin
previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(e) {
if (!e.isTrusted)
return;
previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
let playPlugin = !aEvent.detail;
PluginHelper.stopPlayPreview(plugin, playPlugin);
// cleaning up: removes overlay iframe from the DOM
let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
if (iframe)
previewContent.removeChild(iframe);
}, true);
break;
}
case "PluginNotFound": {
let plugin = aEvent.target;
plugin.clientTop; // force style flush
@ -5237,10 +5285,22 @@ var PluginHelper = {
playPlugin: function(plugin) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
if (!objLoadingContent.activated)
if (!objLoadingContent.activated &&
objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW)
objLoadingContent.playPlugin();
},
stopPlayPreview: function(plugin, playPlugin) {
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
if (objLoadingContent.activated)
return;
if (playPlugin)
objLoadingContent.playPlugin();
else
objLoadingContent.cancelPlayPreview();
},
getPluginPreference: function getPluginPreference() {
let pluginDisable = Services.prefs.getBoolPref("plugin.disable");
if (pluginDisable)
@ -5277,6 +5337,22 @@ var PluginHelper = {
(overlay.scrollHeight - 5 > pluginRect.height);
return overflows;
},
getPluginMimeType: function (plugin) {
var tagMimetype;
if (plugin instanceof HTMLAppletElement) {
tagMimetype = "application/x-java-vm";
} else {
tagMimetype = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent)
.actualType;
if (tagMimetype == "") {
tagMimetype = plugin.type;
}
}
return tagMimetype;
}
};

View File

@ -56,6 +56,7 @@
<html:span class="helpIcon" role="link"/>
</html:div>
</xul:vbox>
<html:div class="previewPluginContent"><!-- iframe and its src will be set at runtime --></html:div>
<html:div style="display:none;"><children/></html:div>
</content>
</binding>

View File

@ -8,18 +8,21 @@ embed:-moz-handler-disabled,
embed:-moz-handler-blocked,
embed:-moz-handler-crashed,
embed:-moz-handler-clicktoplay,
embed:-moz-handler-playpreview,
embed:-moz-handler-vulnerable-updatable,
embed:-moz-handler-vulnerable-no-update,
applet:-moz-handler-disabled,
applet:-moz-handler-blocked,
applet:-moz-handler-crashed,
applet:-moz-handler-clicktoplay,
applet:-moz-handler-playpreview,
applet:-moz-handler-vulnerable-updatable,
applet:-moz-handler-vulnerable-no-update,
object:-moz-has-handlerref:-moz-handler-disabled,
object:-moz-has-handlerref:-moz-handler-blocked,
object:-moz-handler-crashed,
object:-moz-handler-clicktoplay,
object:-moz-handler-playpreview,
object:-moz-handler-vulnerable-updatable,
object:-moz-handler-vulnerable-no-update {
display: inline-block;

View File

@ -47,6 +47,27 @@ html|applet:not([height]), html|applet[height=""] {
direction: rtl;
}
:-moz-handler-playpreview .mainBox {
display: none;
}
.previewPluginContent {
display: none;
}
.previewPluginContent > iframe {
width: inherit;
height: inherit;
border: none;
}
:-moz-handler-playpreview .previewPluginContent {
display: block;
width: inherit;
height: inherit;
overflow: hidden;
}
.msg {
display: none;
}