Bug 1282484 - Add a mechanism to control plugin fallback content. r=qDot, a=lizzard
authorFelipe Gomes <felipc@gmail.com>
Tue, 24 Jan 2017 03:07:41 -0200
changeset 375914 70fa5013cfb8859a90a4f226ee7763f20397ecd0
parent 375913 c6db1230ce238d787f7ba3ea1180d4f117276bd9
child 375915 a4ab3c577fe8f9f0d17dc31255401a20c93798bb
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersqDot, lizzard
bugs1282484, 1282485, 1316102, 1282487, 1282486
milestone53.0a2
Bug 1282484 - Add a mechanism to control plugin fallback content. r=qDot, a=lizzard MozReview-Commit-ID: Lt4tOdFdQzN * * * Bug 1282484 - Cache the value of PreferFallback() because ShouldPlay() is called several times during the load process. r=qDot MozReview-Commit-ID: DqjCHssaJde * * * Bug 1282484 - Write structure for pref-configurable fallback rules list, and include initial set of rules. r=qDot * * * Bug 1282485 - Plugin fallback rule - don't use fallback when the descendant has an embed element * * * Bug 1316102 - Plugin fallback rule - use fallback when it contains a <video> element * * * Bug 1282487 - Plugin fallback rule - don't use fallback when it contains links to adobe.com * * * Bug 1282486 - Plugin fallback rule - don't use fallback when it contains 'install Flash' instructions MozReview-Commit-ID: DexVBrAfaTb
dom/base/nsObjectLoadingContent.cpp
dom/base/nsObjectLoadingContent.h
modules/libpref/init/all.js
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -104,16 +104,18 @@
 #include "mozilla/dom/HTMLObjectElement.h"
 #endif
 
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 static const char *kPrefJavaMIME = "plugin.java.mime";
 static const char *kPrefYoutubeRewrite = "plugins.rewrite_youtube_embeds";
 static const char *kPrefBlockURIs = "browser.safebrowsing.blockedURIs.enabled";
+static const char *kPrefFavorFallbackMode = "plugins.favorfallback.mode";
+static const char *kPrefFavorFallbackRules = "plugins.favorfallback.rules";
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::net;
 
 static LogModule*
 GetObjectLog()
 {
@@ -660,17 +662,19 @@ nsObjectLoadingContent::nsObjectLoadingC
   , mChannelLoaded(false)
   , mInstantiating(false)
   , mNetworkCreated(true)
   , mActivated(false)
   , mContentBlockingDisabled(false)
   , mIsStopping(false)
   , mIsLoading(false)
   , mScriptRequested(false)
-  , mRewrittenYoutubeEmbed(false) {}
+  , mRewrittenYoutubeEmbed(false)
+  , mPreferFallback(false)
+  , mPreferFallbackKnown(false) {}
 
 nsObjectLoadingContent::~nsObjectLoadingContent()
 {
   // Should have been unbound from the tree at this point, and
   // CheckPluginStopEvent keeps us alive
   if (mFrameLoader) {
     NS_NOTREACHED("Should not be tearing down frame loaders at this point");
     mFrameLoader->Destroy();
@@ -3367,45 +3371,191 @@ nsObjectLoadingContent::ShouldPlay(Fallb
       uint64_t nowms = PR_Now() / 1000;
       permissionManager->UpdateExpireTime(
         topDoc->NodePrincipal(), permissionString.Data(), false,
         nowms + sSessionTimeoutMinutes * 60 * 1000,
         nowms / 1000 + uint64_t(sPersistentTimeoutDays) * 24 * 60 * 60 * 1000);
     }
     switch (permission) {
     case nsIPermissionManager::ALLOW_ACTION:
+      if (PreferFallback(false /* isPluginClickToPlay */)) {
+        aReason = eFallbackAlternate;
+        return false;
+      }
+
       return true;
     case nsIPermissionManager::DENY_ACTION:
       aReason = eFallbackDisabled;
       return false;
     case nsIPermissionManager::PROMPT_ACTION:
+      if (PreferFallback(true /* isPluginClickToPlay */)) {
+        // False is already returned in this case, but
+        // it's important to correctly set aReason too.
+        aReason = eFallbackAlternate;
+      }
+
       return false;
     case nsIPermissionManager::UNKNOWN_ACTION:
       break;
     default:
       MOZ_ASSERT(false);
       return false;
     }
   }
 
   // No site-specific permissions. Vulnerable plugins are automatically CtP
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     return false;
   }
 
+  if (PreferFallback(enabledState == nsIPluginTag::STATE_CLICKTOPLAY)) {
+    aReason = eFallbackAlternate;
+    return false;
+  }
+
   switch (enabledState) {
   case nsIPluginTag::STATE_ENABLED:
     return true;
   case nsIPluginTag::STATE_CLICKTOPLAY:
     return false;
   }
   MOZ_CRASH("Unexpected enabledState");
 }
 
+bool
+nsObjectLoadingContent::FavorFallbackMode(bool aIsPluginClickToPlay) {
+  if (!IsFlashMIME(mContentType)) {
+    return false;
+  }
+
+  nsCString prefString;
+  if (NS_SUCCEEDED(Preferences::GetCString(kPrefFavorFallbackMode, &prefString))) {
+    if (aIsPluginClickToPlay &&
+        prefString.EqualsLiteral("follow-ctp")) {
+      return true;
+    }
+
+    if (prefString.EqualsLiteral("always")) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
+nsObjectLoadingContent::HasGoodFallback() {
+  nsCOMPtr<nsIContent> thisContent =
+  do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+  NS_ASSERTION(thisContent, "must be a content");
+
+  if (!thisContent->IsHTMLElement(nsGkAtoms::object) ||
+      mContentType.IsEmpty()) {
+    return false;
+  }
+
+  nsTArray<nsCString> rulesList;
+  nsCString prefString;
+  if (NS_SUCCEEDED(Preferences::GetCString(kPrefFavorFallbackRules, &prefString))) {
+      ParseString(prefString, ',', rulesList);
+  }
+
+  for (uint32_t i = 0; i < rulesList.Length(); ++i) {
+    // RULE "embed":
+    // Don't use fallback content if the object contains an <embed> inside its
+    // fallback content.
+    if (rulesList[i].EqualsLiteral("embed")) {
+      nsTArray<nsINodeList*> childNodes;
+      for (nsIContent* child = thisContent->GetFirstChild();
+           child;
+           child = child->GetNextNode(thisContent)) {
+        if (child->IsHTMLElement(nsGkAtoms::embed)) {
+          return false;
+        }
+      }
+    }
+
+    // RULE "video":
+    // Use fallback content if the object contains a <video> inside its
+    // fallback content.
+    if (rulesList[i].EqualsLiteral("video")) {
+      nsTArray<nsINodeList*> childNodes;
+      for (nsIContent* child = thisContent->GetFirstChild();
+           child;
+           child = child->GetNextNode(thisContent)) {
+        if (child->IsHTMLElement(nsGkAtoms::video)) {
+          return true;
+        }
+      }
+    }
+
+    // RULE "adobelink":
+    // Don't use fallback content when it has a link to adobe's website.
+    if (rulesList[i].EqualsLiteral("adobelink")) {
+      nsTArray<nsINodeList*> childNodes;
+      for (nsIContent* child = thisContent->GetFirstChild();
+           child;
+           child = child->GetNextNode(thisContent)) {
+        if (child->IsHTMLElement(nsGkAtoms::a)) {
+          nsCOMPtr<nsIURI> href = child->GetHrefURI();
+          if (href) {
+            nsAutoCString asciiHost;
+            nsresult rv = href->GetAsciiHost(asciiHost);
+            if (NS_SUCCEEDED(rv) &&
+                !asciiHost.IsEmpty() &&
+                (asciiHost.EqualsLiteral("adobe.com") ||
+                 StringEndsWith(asciiHost, NS_LITERAL_CSTRING(".adobe.com")))) {
+              return false;
+            }
+          }
+        }
+      }
+    }
+
+    // RULE "installinstructions":
+    // Don't use fallback content when the text content on the fallback appears
+    // to contain instructions to install or download Flash.
+    if (rulesList[i].EqualsLiteral("installinstructions")) {
+      nsAutoString textContent;
+      ErrorResult rv;
+      thisContent->GetTextContent(textContent, rv);
+      bool hasText =
+        !rv.Failed() &&
+        (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("Flash"), textContent) ||
+         CaseInsensitiveFindInReadable(NS_LITERAL_STRING("Install"), textContent) ||
+         CaseInsensitiveFindInReadable(NS_LITERAL_STRING("Download"), textContent));
+
+      if (hasText) {
+        return false;
+      }
+    }
+
+    // RULE "true":
+    // By having a rule that returns true, we can put it at the end of the rules list
+    // to change the default-to-false behavior to be default-to-true.
+    if (rulesList[i].EqualsLiteral("true")) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
+nsObjectLoadingContent::PreferFallback(bool aIsPluginClickToPlay) {
+  if (mPreferFallbackKnown) {
+    return mPreferFallback;
+  }
+
+  mPreferFallbackKnown = true;
+  mPreferFallback = FavorFallbackMode(aIsPluginClickToPlay) && HasGoodFallback();
+  return mPreferFallback;
+}
+
 nsIDocument*
 nsObjectLoadingContent::GetContentDocument(nsIPrincipal& aSubjectPrincipal)
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 
   if (!thisContent->IsInComposedDoc()) {
     return nullptr;
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -455,16 +455,42 @@ class nsObjectLoadingContent : public ns
     /**
      * If this object is allowed to play plugin content, or if it would display
      * click-to-play instead.
      * NOTE that this does not actually check if the object is a loadable plugin
      * NOTE This ignores the current activated state. The caller should check this if appropriate.
      */
     bool ShouldPlay(FallbackType &aReason, bool aIgnoreCurrentType);
 
+    /**
+     * This method tells if the fallback content should be attempted to be used
+     * over the original object content.
+     * It will look at prefs and this plugin's CTP state to make a decision.
+     *
+     * NOTE that this doesn't say whether the fallback _will_ be used, only whether
+     * we should look into it to possibly use it. The final answer will be
+     * given by the PreferFallback method.
+     *
+     * @param aIsPluginClickToPlay Whether this object instance is CTP.
+     */
+    bool FavorFallbackMode(bool aIsPluginClickToPlay);
+
+    /**
+     * Whether the page has provided good fallback content to this object.
+     */
+    bool HasGoodFallback();
+
+    /**
+     * This method tells the final answer on whether this object's fallback
+     * content should be used instead of the original plugin content.
+     *
+     * @param aIsPluginClickToPlay Whether this object instance is CTP.
+     */
+    bool PreferFallback(bool aIsPluginClickToPlay);
+
     /*
      * Helper to check if mBaseURI can be used by java as a codebase
      */
     bool CheckJavaCodebase();
 
     /**
      * Helper to check if our current URI passes policy
      *
@@ -674,16 +700,21 @@ class nsObjectLoadingContent : public ns
     bool                        mScriptRequested : 1;
 
     // True if object represents an object/embed tag pointing to a flash embed
     // for a youtube video. When possible (see IsRewritableYoutubeEmbed function
     // comments for details), we change these to try to load HTML5 versions of
     // videos.
     bool                        mRewrittenYoutubeEmbed : 1;
 
+    // Cache the answer of PreferFallback() because ShouldPlay is called several
+    // times during the load process.
+    bool                        mPreferFallback : 1;
+    bool                        mPreferFallbackKnown : 1;
+
     nsWeakFrame                 mPrintFrame;
 
     RefPtr<nsPluginInstanceOwner> mInstanceOwner;
     nsTArray<mozilla::dom::MozPluginParameter> mCachedAttributes;
     nsTArray<mozilla::dom::MozPluginParameter> mCachedParameters;
 };
 
 #endif
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2819,16 +2819,30 @@ pref("plugin.java.mime", "application/x-
 
 // How long in minutes we will allow a plugin to work after the user has chosen
 // to allow it "now"
 pref("plugin.sessionPermissionNow.intervalInMinutes", 60);
 // How long in days we will allow a plugin to work after the user has chosen
 // to allow it persistently.
 pref("plugin.persistentPermissionAlways.intervalInDays", 90);
 
+// This pref can take 3 possible string values:
+// "always"     - always use favor fallback mode
+// "follow-ctp" - activate if ctp is active for the given
+//                plugin object (could be due to a plugin-wide
+//                setting or a site-specific setting)
+// "never"      - never use favor fallback mode
+pref("plugins.favorfallback.mode", "never");
+
+// A comma-separated list of rules to follow when deciding
+// whether an object has been provided with good fallback content.
+// The valid values can be found at nsObjectLoadingContent::HasGoodFallback.
+pref("plugins.favorfallback.rules", "");
+
+
 // Set IPC timeouts for plugins and tabs, except in leak-checking and
 // dynamic analysis builds.  (NS_FREE_PERMANENT_DATA is C++ only, so
 // approximate its definition here.)
 #if !defined(DEBUG) && !defined(MOZ_ASAN) && !defined(MOZ_VALGRIND) && !defined(MOZ_TSAN)
 // How long a plugin is allowed to process a synchronous IPC message
 // before we consider it "hung".
 pref("dom.ipc.plugins.timeoutSecs", 45);
 // How long a plugin process will wait for a response from the parent