Bug 1307604 - Add allow and deny lists for Flash Blocking. r=bsmedberg, r=bz, r=francois, a=gchang
authorKirk Steuber <ksteuber@mozilla.com>
Tue, 22 Nov 2016 15:01:04 -0800
changeset 378350 81b9af9143f37eb828a8dc744c0c0793739a0fa5
parent 378349 e79d7df3ae4df5c08212767bb3b92178a5b60f1a
child 378351 13ef12e80f26e0c1cec5e862fe2faa9940306c7d
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, bz, francois, gchang
bugs1307604
milestone53.0a2
Bug 1307604 - Add allow and deny lists for Flash Blocking. r=bsmedberg, r=bz, r=francois, a=gchang MozReview-Commit-ID: H2dgJX6Hsz7
build/pgo/server-locations.txt
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/base/nsObjectLoadingContent.cpp
modules/libpref/init/all.js
toolkit/components/url-classifier/SafeBrowsing.jsm
toolkit/components/url-classifier/flash-block-lists.rst
toolkit/components/url-classifier/tests/browser/browser.ini
toolkit/components/url-classifier/tests/browser/browser_flash_block_lists.js
toolkit/components/url-classifier/tests/browser/classifierHelper.js
toolkit/components/url-classifier/tests/browser/flash_block_frame.html
toolkit/components/url-classifier/tests/moz.build
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -177,16 +177,32 @@ http://itisatracker.org:80
 http://trackertest.org:80
 
 https://malware.example.com:443
 https://unwanted.example.com:443
 https://tracking.example.com:443
 https://not-tracking.example.com:443
 https://tracking.example.org:443
 
+#
+# Used while testing flash blocking (Bug 1307604)
+#
+http://flashallow.example.com:80
+http://exception.flashallow.example.com:80
+http://flashblock.example.com:80
+http://exception.flashblock.example.com:80
+http://subdocument.example.com:80
+http://exception.subdocument.example.com:80
+
+#
+# Flash usage can fail unless this URL exists
+#
+http://fpdownload2.macromedia.com:80
+https://fpdownload2.macromedia.com:443
+
 # Bug 1281083
 http://bug1281083.example.com:80
 
 # Bug 483437, 484111
 https://www.bank1.com:443           privileged,cert=escapeattack1
 
 #
 # CONNECT for redirproxy results in a 302 redirect to
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -258,16 +258,18 @@
 
 #include "nsISpeculativeConnect.h"
 
 #include "mozilla/MediaManager.h"
 #ifdef MOZ_WEBRTC
 #include "IPeerConnection.h"
 #endif // MOZ_WEBRTC
 
+#include "nsIURIClassifier.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
 static LazyLogModule gCspPRLog("CSP");
 
@@ -1311,16 +1313,17 @@ nsIDocument::nsIDocument()
   PR_INIT_CLIST(&mDOMMediaQueryLists);
 }
 
 // NOTE! nsDocument::operator new() zeroes out all members, so don't
 // bother initializing members to 0.
 
 nsDocument::nsDocument(const char* aContentType)
   : nsIDocument()
+  , mFlashClassification(FlashClassification::Unclassified)
   , mViewportType(Unknown)
 {
   SetContentTypeInternal(nsDependentCString(aContentType));
 
   if (gDocumentLeakPRLog)
     MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
            ("DOCUMENT %p created", this));
 
@@ -12853,8 +12856,203 @@ nsDocument::CheckCustomElementName(const
   // Throw NotFoundError if 'is' is not-null and definition is null
   if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName,
                                                      aNamespaceID, is)) {
       rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
   }
 
   return is;
 }
+
+/**
+ * Helper function for |nsDocument::PrincipalFlashClassification|
+ *
+ * Adds a table name string to a table list (a comma separated string). The
+ * table will not be added if the name is an empty string.
+ */
+static void
+MaybeAddTableToTableList(const nsACString& aTableNames,
+                         nsACString& aTableList)
+{
+  if (aTableNames.IsEmpty()) {
+    return;
+  }
+  if (!aTableList.IsEmpty()) {
+    aTableList.AppendLiteral(",");
+  }
+  aTableList.Append(aTableNames);
+}
+
+/**
+ * Helper function for |nsDocument::PrincipalFlashClassification|
+ *
+ * Takes an array of table names and a comma separated list of table names
+ * Returns |true| if any table name in the array matches a table name in the
+ * comma separated list.
+ */
+static bool
+ArrayContainsTable(const nsTArray<nsCString>& aTableArray,
+                   const nsACString& aTableNames)
+{
+  for (const nsCString& table : aTableArray) {
+    // This check is sufficient because table names cannot contain commas and
+    // cannot contain another existing table name.
+    if (FindInReadable(table, aTableNames)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Retrieves the classification of the Flash plugins in the document based on
+ * the classification lists.
+ *
+ * For more information, see
+ * toolkit/components/url-classifier/flash-block-lists.rst
+ */
+nsIDocument::FlashClassification
+nsDocument::PrincipalFlashClassification(bool aIsTopLevel)
+{
+  nsresult rv;
+
+  // If flash blocking is disabled, it is equivalent to all sites being
+  // whitelisted.
+  if (!Preferences::GetBool("plugins.flashBlock.enabled")) {
+    return FlashClassification::Allowed;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
+  if (principal->GetIsNullPrincipal()) {
+    return FlashClassification::Denied;
+  }
+
+  nsCOMPtr<nsIURI> classificationURI;
+  rv = principal->GetURI(getter_AddRefs(classificationURI));
+  if (NS_FAILED(rv) || !classificationURI) {
+    return FlashClassification::Denied;
+  }
+
+  nsAutoCString allowTables, allowExceptionsTables,
+                denyTables, denyExceptionsTables,
+                subDocDenyTables, subDocDenyExceptionsTables,
+                tables;
+  Preferences::GetCString("urlclassifier.flashAllowTable", &allowTables);
+  MaybeAddTableToTableList(allowTables, tables);
+  Preferences::GetCString("urlclassifier.flashAllowExceptTable",
+                          &allowExceptionsTables);
+  MaybeAddTableToTableList(allowExceptionsTables, tables);
+  Preferences::GetCString("urlclassifier.flashTable", &denyTables);
+  MaybeAddTableToTableList(denyTables, tables);
+  Preferences::GetCString("urlclassifier.flashExceptTable",
+                          &denyExceptionsTables);
+  MaybeAddTableToTableList(denyExceptionsTables, tables);
+  if (!aIsTopLevel) {
+    Preferences::GetCString("urlclassifier.flashSubDocTable",
+                            &subDocDenyTables);
+    MaybeAddTableToTableList(subDocDenyTables, tables);
+    Preferences::GetCString("urlclassifier.flashSubDocExceptTable",
+                            &subDocDenyExceptionsTables);
+    MaybeAddTableToTableList(subDocDenyExceptionsTables, tables);
+  }
+
+  if (tables.IsEmpty()) {
+    return FlashClassification::Unknown;
+  }
+
+  nsCOMPtr<nsIURIClassifier> uriClassifier =
+    do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    return FlashClassification::Denied;
+  }
+
+  nsTArray<nsCString> results;
+  rv = uriClassifier->ClassifyLocalWithTables(classificationURI,
+                                              tables,
+                                              results);
+  if (NS_FAILED(rv)) {
+    if (rv == NS_ERROR_MALFORMED_URI) {
+      // This means that the URI had no hostname (ex: file://doc.html). In this
+      // case, we allow the default (Unknown plugin) behavior.
+      return FlashClassification::Unknown;
+    } else {
+      return FlashClassification::Denied;
+    }
+  }
+
+  if (results.IsEmpty()) {
+    return FlashClassification::Unknown;
+  }
+
+  if (ArrayContainsTable(results, denyTables) &&
+      !ArrayContainsTable(results, denyExceptionsTables)) {
+    return FlashClassification::Denied;
+  } else if (ArrayContainsTable(results, allowTables) &&
+             !ArrayContainsTable(results, allowExceptionsTables)) {
+    return FlashClassification::Allowed;
+  }
+
+  if (!aIsTopLevel && ArrayContainsTable(results, subDocDenyTables) &&
+      !ArrayContainsTable(results, subDocDenyExceptionsTables)) {
+    return FlashClassification::Denied;
+  }
+
+  return FlashClassification::Unknown;
+}
+
+nsIDocument::FlashClassification
+nsDocument::ComputeFlashClassification()
+{
+  nsCOMPtr<nsIDocShellTreeItem> current = this->GetDocShell();
+  if (!current) {
+    return FlashClassification::Denied;
+  }
+  nsCOMPtr<nsIDocShellTreeItem> parent;
+  DebugOnly<nsresult> rv = current->GetSameTypeParent(getter_AddRefs(parent));
+  MOZ_ASSERT(NS_SUCCEEDED(rv),
+             "nsIDocShellTreeItem::GetSameTypeParent should never fail");
+
+  bool isTopLevel = !parent;
+  FlashClassification classification;
+  if (isTopLevel) {
+    classification = PrincipalFlashClassification(isTopLevel);
+  } else {
+    nsCOMPtr<nsIDocument> parentDocument = GetParentDocument();
+    FlashClassification parentClassification =
+      parentDocument->DocumentFlashClassification();
+
+    if (parentClassification == FlashClassification::Denied) {
+      classification = FlashClassification::Denied;
+    } else {
+      classification = PrincipalFlashClassification(isTopLevel);
+
+      // Allow unknown children to inherit allowed status from parent, but
+      // do not allow denied children to do so.
+      if (classification == FlashClassification::Unknown &&
+          parentClassification == FlashClassification::Allowed) {
+        classification = FlashClassification::Allowed;
+      }
+    }
+  }
+
+  return classification;
+}
+
+/**
+ * Retrieves the classification of plugins in this document. This is dependent
+ * on the classification of this document and all parent documents.
+ * This function is infallible - It must return some classification that
+ * callers can act on.
+ *
+ * This function will NOT return FlashClassification::Unclassified
+ */
+nsIDocument::FlashClassification
+nsDocument::DocumentFlashClassification()
+{
+  if (mFlashClassification == FlashClassification::Unclassified) {
+    FlashClassification result = ComputeFlashClassification();
+    mFlashClassification = result;
+    MOZ_ASSERT(result != FlashClassification::Unclassified,
+      "nsDocument::GetPluginClassification should never return Unclassified");
+  }
+
+  return mFlashClassification;
+}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1300,16 +1300,18 @@ protected:
                               bool aPersisted);
 
   virtual nsPIDOMWindowOuter* GetWindowInternal() const override;
   virtual nsIScriptGlobalObject* GetScriptHandlingObjectInternal() const override;
   virtual bool InternalAllowXULXBL() override;
 
   void UpdateScreenOrientation();
 
+  virtual FlashClassification DocumentFlashClassification() override;
+
 #define NS_DOCUMENT_NOTIFY_OBSERVERS(func_, params_)                        \
   NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, nsIDocumentObserver, \
                                            func_, params_);
 
 #ifdef DEBUG
   void VerifyRootContentState();
 #endif
 
@@ -1320,16 +1322,24 @@ protected:
 
   void NotifyStyleSheetApplicableStateChanged();
 
   // Apply the fullscreen state to the document, and trigger related
   // events. It returns false if the fullscreen element ready check
   // fails and nothing gets changed.
   bool ApplyFullscreen(const FullscreenRequest& aRequest);
 
+  // Retrieves the classification of the Flash plugins in the document based on
+  // the classification lists.
+  FlashClassification PrincipalFlashClassification(bool aIsTopLevel);
+
+  // Attempts to determine the Flash classification of this page based on the
+  // the classification lists and the classification of parent documents.
+  FlashClassification ComputeFlashClassification();
+
   nsTArray<nsIObserver*> mCharSetObservers;
 
   PLDHashTable *mSubDocuments;
 
   // Array of owning references to all children
   nsAttrAndChildArray mChildren;
 
   // Pointer to our parser if we're currently in the process of being
@@ -1364,16 +1374,17 @@ protected:
   // full-screen element onto this stack, and when we cancel full-screen we
   // pop one off this stack, restoring the previous full-screen state
   nsTArray<nsWeakPtr> mFullScreenStack;
 
   // The root of the doc tree in which this document is in. This is only
   // non-null when this document is in fullscreen mode.
   nsWeakPtr mFullscreenRoot;
 
+  FlashClassification mFlashClassification;
 private:
   static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
 
   /**
    * Check if the passed custom element name, aOptions.mIs, is a registered
    * custom element type or not, then return the custom element name for future
    * usage.
    *
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2890,16 +2890,27 @@ public:
   // The URLs passed to these functions should match what
   // JS::DescribeScriptedCaller() returns, since these APIs are used to
   // determine whether some code is being called from a tracking script.
   void NoteScriptTrackingStatus(const nsACString& aURL, bool isTracking);
   bool IsScriptTracking(const nsACString& aURL) const;
 
   bool PrerenderHref(nsIURI* aHref);
 
+  // For more information on Flash classification, see
+  // toolkit/components/url-classifier/flash-block-lists.rst
+  enum class FlashClassification {
+    Unclassified,   // Denotes a classification that has not yet been computed.
+                    // Allows for lazy classification.
+    Unknown,        // Site is not on the whitelist or blacklist
+    Allowed,        // Site is on the Flash whitelist
+    Denied          // Site is on the Flash blacklist
+  };
+  virtual FlashClassification DocumentFlashClassification() = 0;
+
 protected:
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
 
   void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
   {
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3303,16 +3303,17 @@ nsObjectLoadingContent::ShouldPlay(Fallb
   if (!aIgnoreCurrentType && mType != eType_Plugin) {
     return true;
   }
 
   // Order of checks:
   // * Assume a default of click-to-play
   // * If globally disabled, per-site permissions cannot override.
   // * If blocklisted, override the reason with the blocklist reason
+  // * Check if the flash blocking status for this page denies flash from loading.
   // * Check per-site permissions and follow those if specified.
   // * Honor per-plugin disabled permission
   // * Blocklisted plugins are forced to CtP
   // * Check per-plugin permission and follow that.
 
   aReason = eFallbackClickToPlay;
 
   uint32_t enabledState = nsIPluginTag::STATE_DISABLED;
@@ -3338,32 +3339,42 @@ nsObjectLoadingContent::ShouldPlay(Fallb
 
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE) {
     aReason = eFallbackVulnerableUpdatable;
   }
   else if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     aReason = eFallbackVulnerableNoUpdate;
   }
 
-  // Check the permission manager for permission based on the principal of
-  // the toplevel content.
-
+  // Document and window lookup
   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
   MOZ_ASSERT(thisContent);
   nsIDocument* ownerDoc = thisContent->OwnerDoc();
 
   nsCOMPtr<nsPIDOMWindowOuter> window = ownerDoc->GetWindow();
   if (!window) {
     return false;
   }
   nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetTop();
   NS_ENSURE_TRUE(topWindow, false);
   nsCOMPtr<nsIDocument> topDoc = topWindow->GetDoc();
   NS_ENSURE_TRUE(topDoc, false);
 
+  // Check the flash blocking status for this page (this applies to Flash only)
+  nsIDocument::FlashClassification documentClassification = nsIDocument::FlashClassification::Allowed;
+  if (IsFlashMIME(mContentType)) {
+    documentClassification = ownerDoc->DocumentFlashClassification();
+  }
+  if (documentClassification == nsIDocument::FlashClassification::Denied) {
+    aReason = eFallbackSuppressed;
+    return false;
+  }
+
+  // Check the permission manager for permission based on the principal of
+  // the toplevel content.
   nsCOMPtr<nsIPermissionManager> permissionManager = services::GetPermissionManager();
   NS_ENSURE_TRUE(permissionManager, false);
 
   // For now we always say that the system principal uses click-to-play since
   // that maintains current behavior and we have tests that expect this.
   // What we really should do is disable plugins entirely in pages that use
   // the system principal, i.e. in chrome pages. That way the click-to-play
   // code here wouldn't matter at all. Bug 775301 is tracking this.
@@ -3420,17 +3431,17 @@ nsObjectLoadingContent::ShouldPlay(Fallb
 
   if (PreferFallback(enabledState == nsIPluginTag::STATE_CLICKTOPLAY)) {
     aReason = eFallbackAlternate;
     return false;
   }
 
   switch (enabledState) {
   case nsIPluginTag::STATE_ENABLED:
-    return true;
+    return documentClassification == nsIDocument::FlashClassification::Allowed;
   case nsIPluginTag::STATE_CLICKTOPLAY:
     return false;
   }
   MOZ_CRASH("Unexpected enabledState");
 }
 
 bool
 nsObjectLoadingContent::FavorFallbackMode(bool aIsPluginClickToPlay) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5118,17 +5118,17 @@ pref("urlclassifier.downloadBlockTable",
  // Only download the whitelist on Windows, since the whitelist is
  // only useful for suppressing remote lookups for signed binaries which we can
  // only verify on Windows (Bug 974579). Other platforms always do remote lookups.
 pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-digest256");
 #else
 pref("urlclassifier.downloadAllowTable", "");
 #endif
 
-pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,test-flashallow-simple,testexcept-flashallow-simple,test-flash-simple,testexcept-flash-simple,test-flashsubdoc-simple,testexcept-flashsubdoc-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256");
 
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "test-track-simple,base-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
 // The number of random entries to send with a gethash request.
 pref("urlclassifier.gethashnoise", 4);
@@ -5193,16 +5193,25 @@ pref("browser.safebrowsing.provider.mozi
 pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
 // Block lists for tracking protection. The name values will be used as the keys
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.base.name", "mozstdName");
 pref("browser.safebrowsing.provider.mozilla.lists.base.description", "mozstdDesc");
 pref("browser.safebrowsing.provider.mozilla.lists.content.name", "mozfullName");
 pref("browser.safebrowsing.provider.mozilla.lists.content.description", "mozfullDesc");
 
+pref("urlclassifier.flashAllowTable", "test-flashallow-simple");
+pref("urlclassifier.flashAllowExceptTable", "testexcept-flashallow-simple");
+pref("urlclassifier.flashTable", "test-flash-simple");
+pref("urlclassifier.flashExceptTable", "testexcept-flash-simple");
+pref("urlclassifier.flashSubDocTable", "test-flashsubdoc-simple");
+pref("urlclassifier.flashSubDocExceptTable", "testexcept-flashsubdoc-simple");
+
+pref("plugins.flashBlock.enabled", false);
+
 // Allow users to ignore Safe Browsing warnings.
 pref("browser.safebrowsing.allowOverride", true);
 
 #ifdef MOZILLA_OFFICIAL
 // Normally the "client ID" sent in updates is appinfo.name, but for
 // official Firefox releases from Mozilla we use a special identifier.
 pref("browser.safebrowsing.id", "navclient-auto-ffox");
 #else
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -45,17 +45,23 @@ function getLists(prefName) {
 
 const tablePreferences = [
   "urlclassifier.phishTable",
   "urlclassifier.malwareTable",
   "urlclassifier.downloadBlockTable",
   "urlclassifier.downloadAllowTable",
   "urlclassifier.trackingTable",
   "urlclassifier.trackingWhitelistTable",
-  "urlclassifier.blockedTable"
+  "urlclassifier.blockedTable",
+  "urlclassifier.flashAllowTable",
+  "urlclassifier.flashAllowExceptTable",
+  "urlclassifier.flashTable",
+  "urlclassifier.flashExceptTable",
+  "urlclassifier.flashSubDocTable",
+  "urlclassifier.flashSubDocExceptTable"
 ];
 
 this.SafeBrowsing = {
 
   init: function() {
     if (this.initialized) {
       log("Already initialized");
       return;
@@ -107,25 +113,29 @@ this.SafeBrowsing = {
       this.registerTableWithURLs(this.trackingProtectionLists[i]);
     }
     for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
       this.registerTableWithURLs(this.trackingProtectionWhitelists[i]);
     }
     for (let i = 0; i < this.blockedLists.length; ++i) {
       this.registerTableWithURLs(this.blockedLists[i]);
     }
+    for (let i = 0; i < this.flashLists.length; ++i) {
+      this.registerTableWithURLs(this.flashLists[i]);
+    }
   },
 
 
   initialized:          false,
   phishingEnabled:      false,
   malwareEnabled:       false,
   trackingEnabled:      false,
   blockedEnabled:       false,
   trackingAnnotations:  false,
+  flashBlockEnabled:    false,
 
   phishingLists:                [],
   malwareLists:                 [],
   downloadBlockLists:           [],
   downloadAllowLists:           [],
   trackingProtectionLists:      [],
   trackingProtectionWhitelists: [],
   blockedLists:                 [],
@@ -178,24 +188,41 @@ this.SafeBrowsing = {
     log("reading prefs");
 
     this.debug = Services.prefs.getBoolPref("browser.safebrowsing.debug");
     this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled");
     this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
     this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
     this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
     this.trackingAnnotations = Services.prefs.getBoolPref("privacy.trackingprotection.annotate_channels");
+    this.flashBlockEnabled = Services.prefs.getBoolPref("plugins.flashBlock.enabled");
+
+    let flashAllowTable, flashAllowExceptTable, flashTable,
+        flashExceptTable, flashSubDocTable,
+        flashSubDocExceptTable;
 
     [this.phishingLists,
      this.malwareLists,
      this.downloadBlockLists,
      this.downloadAllowLists,
      this.trackingProtectionLists,
      this.trackingProtectionWhitelists,
-     this.blockedLists] = tablePreferences.map(getLists);
+     this.blockedLists,
+     flashAllowTable,
+     flashAllowExceptTable,
+     flashTable,
+     flashExceptTable,
+     flashSubDocTable,
+     flashSubDocExceptTable] = tablePreferences.map(getLists);
+
+    this.flashLists = flashAllowTable.concat(flashAllowExceptTable,
+                                             flashTable,
+                                             flashExceptTable,
+                                             flashSubDocTable,
+                                             flashSubDocExceptTable)
 
     this.updateProviderURLs();
     this.registerTables();
 
     // XXX The listManager backend gets confused if this is called before the
     // lists are registered. So only call it here when a pref changes, and not
     // when doing initialization. I expect to refactor this later, so pardon the hack.
     if (this.initialized) {
@@ -264,17 +291,17 @@ this.SafeBrowsing = {
       }
     }, this);
   },
 
   controlUpdateChecking: function() {
     log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
         this.malwareEnabled, "trackingEnabled:", this.trackingEnabled,
         "blockedEnabled:", this.blockedEnabled, "trackingAnnotations",
-        this.trackingAnnotations);
+        this.trackingAnnotations, "flashBlockEnabled", this.flashBlockEnabled);
 
     let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
                       getService(Ci.nsIUrlListManager);
 
     for (let i = 0; i < this.phishingLists.length; ++i) {
       if (this.phishingEnabled) {
         listManager.enableUpdate(this.phishingLists[i]);
       } else {
@@ -318,16 +345,23 @@ this.SafeBrowsing = {
     }
     for (let i = 0; i < this.blockedLists.length; ++i) {
       if (this.blockedEnabled) {
         listManager.enableUpdate(this.blockedLists[i]);
       } else {
         listManager.disableUpdate(this.blockedLists[i]);
       }
     }
+    for (let i = 0; i < this.flashLists.length; ++i) {
+      if (this.flashBlockEnabled) {
+        listManager.enableUpdate(this.flashLists[i]);
+      } else {
+        listManager.disableUpdate(this.flashLists[i]);
+      }
+    }
     listManager.maybeToggleUpdateChecking();
   },
 
 
   addMozEntries: function() {
     // Add test entries to the DB.
     // XXX bug 779008 - this could be done by DB itself?
     const phishURL    = "itisatrap.org/firefox/its-a-trap.html";
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/flash-block-lists.rst
@@ -0,0 +1,37 @@
+=========================
+List Based Flash Blocking
+=========================
+
+List based Flash blocking currently uses six lists.
+The lists specify what domains/subdomains Flash is allowed to or denied from loading on.
+The domains specified by the lists indicate the domain of the document that the Flash is loaded in, not the domain hosting the Flash content itself.
+
+* Allow List
+* Allow Exceptions List
+* Deny List
+* Deny Exceptions List
+* Sub-Document Deny List
+* Sub-Document Deny Exceptions List
+
+If a page is on a list and the corresponding "Exceptions List", it is treated as though it is not on that list.
+
+Classification
+==============
+
+Documents can be classified as Allow, Deny or Unknown.
+Documents with an Allow classification may load Flash normally.
+Documents with a Deny classification may not load Flash at all.
+A Deny classification overrides an Allow classification.
+The Unknown classification is the fall-through classification; it essentially just means that the document did not receive an Allow or Deny classification.
+Documents with an Unknown classification will have Flash set to Click To Activate.
+
+If the document is at the top level (its address is in the URL bar), then the Deny List is checked first followed by the Allow List to determine its classification.
+
+If the document is not at the top level, it will receive a Deny classification if the classification of the parent document is Deny or if the document is on the Deny List or the Sub-Document Deny List.
+If the document did not receive a Deny classification, it can receive an Allow classification if it is on the Allow List or if the parent document received an Allow classification.
+
+If for any reason, the document has a null principal, it will receive a Deny classification.
+Some examples of documents that would have a null principal are:
+
+* Data URIs <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs> loaded directly from the URL bar. Data URIs loaded by a page should inherit the loading page's permissions.
+* URIs that are rendered with the JSON viewer
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  flash_block_frame.html
+  classifierHelper.js
+
+[browser_flash_block_lists.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flash_block_lists.js
@@ -0,0 +1,296 @@
+/* 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";
+requestLongerTimeout(2);
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+                 this);
+
+const URL_PATH = "/browser/toolkit/components/url-classifier/tests/browser/flash_block_frame.html";
+const OBJECT_ID = "testObject";
+const IFRAME_ID = "testFrame";
+const FLASHBLOCK_ENABLE_PREF = "plugins.flashBlock.enabled";
+
+var dbUrls = [
+  {
+    url: "flashallow.example.com/",
+    db: "test-flashallow-simple",
+    pref: "urlclassifier.flashAllowTable"
+  },
+  {
+    url: "exception.flashallow.example.com/",
+    db: "testexcept-flashallow-simple",
+    pref: "urlclassifier.flashAllowExceptTable"
+  },
+  {
+    url: "flashblock.example.com/",
+    db: "test-flash-simple",
+    pref: "urlclassifier.flashTable"
+  },
+  {
+    url: "exception.flashblock.example.com/",
+    db: "testexcept-flash-simple",
+    pref: "urlclassifier.flashExceptTable"
+  },
+  {
+    url: "subdocument.example.com/",
+    db: "test-flashsubdoc-simple",
+    pref: "urlclassifier.flashThirdPartyTable"
+  },
+  {
+    url: "exception.subdocument.example.com/",
+    db: "testexcept-flashsubdoc-simple",
+    pref: "urlclassifier.flashThirdPartyExceptTable"
+  }
+];
+
+function setDBPrefs() {
+  for (let dbData of dbUrls) {
+    Services.prefs.setCharPref(dbData.pref, dbData.db);
+  }
+  Services.prefs.setBoolPref(FLASHBLOCK_ENABLE_PREF, true);
+}
+
+function unsetDBPrefs() {
+  for (let dbData of dbUrls) {
+    Services.prefs.clearUserPref(dbData.pref);
+  }
+  Services.prefs.clearUserPref(FLASHBLOCK_ENABLE_PREF);
+}
+registerCleanupFunction(unsetDBPrefs);
+
+// The |domains| property describes the domains of the nested documents making
+// up the page. |domains[0]| represents the domain in the URL bar. The last
+// domain in the list is the domain of the most deeply nested iframe.
+// Only the plugin in the most deeply nested document will be checked.
+var testCases = [
+  {
+    name: "Unknown domain",
+    domains: ["http://example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Nested unknown domains",
+    domains: ["http://example.com", "http://example.org"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Allowed domain",
+    domains: ["http://flashallow.example.com"],
+    expectedActivated: true,
+    expectedHasRunningPlugin: true
+  },
+  {
+    name: "Allowed nested domain",
+    domains: ["http://example.com", "http://flashallow.example.com"],
+    expectedActivated: true,
+    expectedHasRunningPlugin: true
+  },
+  {
+    name: "Subdocument of allowed domain",
+    domains: ["http://flashallow.example.com", "http://example.com"],
+    expectedActivated: true,
+    expectedHasRunningPlugin: true
+  },
+  {
+    name: "Exception to allowed domain",
+    domains: ["http://exception.flashallow.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Blocked domain",
+    domains: ["http://flashblock.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Nested blocked domain",
+    domains: ["http://example.com", "http://flashblock.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Subdocument of blocked subdocument",
+    domains: ["http://example.com", "http://flashblock.example.com", "http://example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Blocked subdocument nested among in allowed documents",
+    domains: ["http://flashallow.example.com", "http://flashblock.example.com", "http://flashallow.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Exception to blocked domain",
+    domains: ["http://exception.flashblock.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Sub-document blocked domain in top-level context",
+    domains: ["http://subdocument.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Sub-document blocked domain",
+    domains: ["http://example.com", "http://subdocument.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Sub-document blocked subdocument of an allowed domain",
+    domains: ["http://flashallow.example.com", "http://subdocument.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Subdocument of Sub-document blocked domain",
+    domains: ["http://example.com", "http://subdocument.example.com", "http://example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Sub-document exception in top-level context",
+    domains: ["http://exception.subdocument.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  },
+  {
+    name: "Sub-document blocked domain exception",
+    domains: ["http://example.com", "http://exception.subdocument.example.com"],
+    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+    expectedActivated: false,
+    expectedHasRunningPlugin: false
+  }
+];
+
+function buildDocumentStructure(browser, iframeDomains) {
+  return Task.spawn(function* () {
+    let depth = 0;
+    for (let domain of iframeDomains) {
+      // Firefox does not like to load the same page in its own iframe. Put some
+      // bogus query strings in the URL to make it happy.
+      let url = domain + URL_PATH + "?date=" + Date.now() + "rand=" + Math.random();
+      let domainLoaded = BrowserTestUtils.browserLoaded(browser, true, url);
+
+      ContentTask.spawn(browser, {iframeId: IFRAME_ID, url: url, depth: depth},
+                        function*({iframeId, url, depth}) {
+        let doc = content.document;
+        for (let i = 0; i < depth; ++i) {
+          doc = doc.getElementById(iframeId).contentDocument;
+        }
+        doc.getElementById(iframeId).src = url;
+      });
+
+      yield domainLoaded;
+      ++depth;
+    }
+  });
+}
+
+function getPlugin(browser, depth) {
+  return ContentTask.spawn(browser,
+                           {iframeId: IFRAME_ID, depth: depth},
+                           function* ({iframeId, depth}) {
+    let doc = content.document;
+    for (let i = 0; i < depth; ++i) {
+      doc = doc.getElementById(iframeId).contentDocument;
+    }
+
+    let pluginObj = doc.getElementById("testObject");
+    if (!(pluginObj instanceof Ci.nsIObjectLoadingContent)) {
+      throw new Error("Unable to find plugin!");
+    }
+    return {
+      pluginFallbackType: pluginObj.pluginFallbackType,
+      activated: pluginObj.activated,
+      hasRunningPlugin: pluginObj.hasRunningPlugin
+    };
+  });
+}
+
+add_task(function* checkFlashBlockLists() {
+  setDBPrefs();
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(dbUrls);
+
+  for (let testCase of testCases) {
+    info(`RUNNING TEST: ${testCase.name}`);
+
+    let iframeDomains = testCase.domains.slice();
+    let pageDomain = iframeDomains.shift();
+    let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+                                                          pageDomain + URL_PATH);
+
+    yield buildDocumentStructure(tab.linkedBrowser, iframeDomains);
+
+    let plugin = yield getPlugin(tab.linkedBrowser, iframeDomains.length);
+
+    if ("expectedPluginFallbackType" in testCase) {
+      is(plugin.pluginFallbackType, testCase.expectedPluginFallbackType,
+        "Plugin should have the correct fallback type");
+    }
+    if ("expectedActivated" in testCase) {
+      is(plugin.activated, testCase.expectedActivated,
+        "Plugin should have the correct activation");
+    }
+    if ("expectedHasRunningPlugin" in testCase) {
+      is(plugin.hasRunningPlugin, testCase.expectedHasRunningPlugin,
+        "Plugin should have the correct 'plugin running' state");
+    }
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
+
+add_task(function* checkFlashBlockDisabled() {
+  setDBPrefs();
+  Services.prefs.setBoolPref(FLASHBLOCK_ENABLE_PREF, false);
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(dbUrls);
+
+  for (let testCase of testCases) {
+    info(`RUNNING TEST: ${testCase.name} (flashblock disabled)`);
+
+    let iframeDomains = testCase.domains.slice();
+    let pageDomain = iframeDomains.shift();
+    let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+                                                          pageDomain + URL_PATH);
+
+    yield buildDocumentStructure(tab.linkedBrowser, iframeDomains);
+
+    let plugin = yield getPlugin(tab.linkedBrowser, iframeDomains.length);
+
+    // With flashblock disabled, all plugins should be activated.
+    ok(plugin.activated, "Plugin should be activated");
+    ok(plugin.hasRunningPlugin, "Plugin should be running");
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/classifierHelper.js
@@ -0,0 +1,221 @@
+/* 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/. */
+
+// Created from toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
+// Unfortunately, browser tests cannot load that script as it is too reliant on
+// being loaded in the content process.
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                .getService(Ci.nsIUrlClassifierDBService);
+
+if (typeof(classifierHelper) == "undefined") {
+  var classifierHelper = {};
+}
+
+const ADD_CHUNKNUM = 524;
+const SUB_CHUNKNUM = 523;
+const HASHLEN = 32;
+
+const PREFS = {
+  PROVIDER_LISTS : "browser.safebrowsing.provider.mozilla.lists",
+  DISALLOW_COMPLETIONS : "urlclassifier.disallow_completions",
+  PROVIDER_GETHASHURL : "browser.safebrowsing.provider.mozilla.gethashURL"
+};
+
+// Keep urls added to database, those urls should be automatically
+// removed after test complete.
+classifierHelper._updatesToCleanup = [];
+
+// This function returns a Promise resolved when SafeBrowsing.jsm is initialized.
+// SafeBrowsing.jsm is initialized after mozEntries are added. Add observer
+// to receive "finished" event. For the case when this function is called
+// after the event had already been notified, we lookup entries to see if
+// they are already added to database.
+classifierHelper.waitForInit = function() {
+  let observerService = Cc["@mozilla.org/observer-service;1"]
+                        .getService(Ci.nsIObserverService);
+  let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+               .getService(Ci.nsIScriptSecurityManager);
+  let iosvc = Cc["@mozilla.org/network/io-service;1"]
+              .getService(Ci.nsIIOService);
+
+  // This url must sync with the table, url in SafeBrowsing.jsm addMozEntries
+  const table = "test-phish-simple";
+  const url = "http://itisatrap.org/firefox/its-a-trap.html";
+  let principal = secMan.createCodebasePrincipal(
+    iosvc.newURI(url, null, null), {});
+
+  return new Promise(function(resolve, reject) {
+    observerService.addObserver(function() {
+      resolve();
+    }, "mozentries-update-finished", false);
+
+    let listener = {
+      QueryInterface: function(iid)
+      {
+        if (iid.equals(Ci.nsISupports) ||
+          iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+          return this;
+        throw Cr.NS_ERROR_NO_INTERFACE;
+      },
+
+      handleEvent: function(value)
+      {
+        if (value === table) {
+          resolve();
+        }
+      },
+    };
+    dbService.lookup(principal, table, listener);
+  });
+}
+
+// This function is used to allow completion for specific "list",
+// some lists like "test-malware-simple" is default disabled to ask for complete.
+// "list" is the db we would like to allow it
+// "url" is the completion server
+classifierHelper.allowCompletion = function(lists, url) {
+  for (let list of lists) {
+    // Add test db to provider
+    let pref = Services.prefs.getCharPref(PREFS.PROVIDER_LISTS);
+    pref += "," + list;
+    Services.prefs.setCharPref(PREFS.PROVIDER_LISTS, pref);
+
+    // Rename test db so we will not disallow it from completions
+    pref = Services.prefs.getCharPref(PREFS.DISALLOW_COMPLETIONS);
+    pref = pref.replace(list, list + "-backup");
+    Services.prefs.setCharPref(PREFS.DISALLOW_COMPLETIONS, pref);
+  }
+
+  // Set get hash url
+  Services.prefs.setCharPref(PREFS.PROVIDER_GETHASHURL, url);
+}
+
+// Pass { url: ..., db: ... } to add url to database,
+// Returns a Promise.
+classifierHelper.addUrlToDB = function(updateData) {
+  let testUpdate = "";
+  for (let update of updateData) {
+    let LISTNAME = update.db;
+    let CHUNKDATA = update.url;
+    let CHUNKLEN = CHUNKDATA.length;
+    let HASHLEN = update.len ? update.len : 32;
+
+    classifierHelper._updatesToCleanup.push(update);
+    testUpdate +=
+      "n:1000\n" +
+      "i:" + LISTNAME + "\n" +
+      "ad:1\n" +
+      "a:" + ADD_CHUNKNUM + ":" + HASHLEN + ":" + CHUNKLEN + "\n" +
+      CHUNKDATA;
+  }
+
+  return classifierHelper._update(testUpdate);
+}
+
+// Pass { url: ..., db: ... } to remove url from database,
+// Returns a Promise.
+classifierHelper.removeUrlFromDB = function(updateData) {
+  var testUpdate = "";
+  for (var update of updateData) {
+    var LISTNAME = update.db;
+    var CHUNKDATA = ADD_CHUNKNUM + ":" + update.url;
+    var CHUNKLEN = CHUNKDATA.length;
+    var HASHLEN = update.len ? update.len : 32;
+
+    testUpdate +=
+      "n:1000\n" +
+      "i:" + LISTNAME + "\n" +
+      "s:" + SUB_CHUNKNUM + ":" + HASHLEN + ":" + CHUNKLEN + "\n" +
+      CHUNKDATA;
+  }
+
+  classifierHelper._updatesToCleanup =
+    classifierHelper._updatesToCleanup.filter((v) => {
+      return updateData.indexOf(v) == -1;
+    });
+
+  return classifierHelper._update(testUpdate);
+};
+
+// This API is used to expire all add/sub chunks we have updated
+// by using addUrlToDB and removeUrlFromDB.
+// Returns a Promise.
+classifierHelper.resetDB = function() {
+  var testUpdate = "";
+  for (var update of classifierHelper._updatesToCleanup) {
+    if (testUpdate.includes(update.db))
+      continue;
+
+    testUpdate +=
+      "n:1000\n" +
+      "i:" + update.db + "\n" +
+      "ad:" + ADD_CHUNKNUM + "\n" +
+      "sd:" + SUB_CHUNKNUM + "\n"
+  }
+
+  return classifierHelper._update(testUpdate);
+};
+
+classifierHelper.reloadDatabase = function() {
+  dbService.reloadDatabase();
+}
+
+classifierHelper._update = function(update) {
+  return Task.spawn(function* () {
+    // beginUpdate may fail if there's an existing update in progress
+    // retry until success or testcase timeout.
+    let success = false;
+    while (!success) {
+      try {
+        yield new Promise((resolve, reject) => {
+          let listener = {
+            QueryInterface: function(iid)
+            {
+              if (iid.equals(Ci.nsISupports) ||
+                  iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+                return this;
+
+              throw Cr.NS_ERROR_NO_INTERFACE;
+            },
+            updateUrlRequested: function(url) { },
+            streamFinished: function(status) { },
+            updateError: function(errorCode) {
+              reject(errorCode);
+            },
+            updateSuccess: function(requestedTimeout) {
+              resolve();
+            }
+          };
+          dbService.beginUpdate(listener, "", "");
+          dbService.beginStream("", "");
+          dbService.updateStream(update);
+          dbService.finishStream();
+          dbService.finishUpdate();
+        });
+        success = true;
+      } catch(e) {
+        // Wait 1 second before trying again.
+        yield new Promise(resolve => setTimeout(resolve, 1000));
+      }
+    }
+  });
+};
+
+classifierHelper._cleanup = function() {
+  // Clean all the preferences that may have been touched by classifierHelper
+  for (var pref in PREFS) {
+    Services.prefs.clearUserPref(pref);
+  }
+
+  if (!classifierHelper._updatesToCleanup) {
+    return Promise.resolve();
+  }
+
+  return classifierHelper.resetDB();
+};
+// Cleanup will be called at end of each testcase to remove all the urls added to database.
+registerCleanupFunction(classifierHelper._cleanup);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/flash_block_frame.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test the URI Classifier</title>
+</head>
+<body>
+  <h1>Test page</h1>
+  <object id="testObject" width="100" height="100" type="application/x-shockwave-flash-test"></object>
+  <br>
+  <iframe id="testFrame">
+  </iframe>
+</body>
+</html>
--- a/toolkit/components/url-classifier/tests/moz.build
+++ b/toolkit/components/url-classifier/tests/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
+BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 TESTING_JS_MODULES += [
     'UrlClassifierTestUtils.jsm',
 ]
 
 if CONFIG['ENABLE_TESTS']: