Bug 1350381 - Change Flash blocking to allow the setting "Ask to Activate" to control CTA of unknown documents. draft
authorKirk Steuber <ksteuber@mozilla.com>
Fri, 24 Mar 2017 14:15:02 -0700
changeset 553254 0f8960918de6a31dc8aaa79eb73a2a358ccefcfa
parent 504632 4c987b7ed54a630a7de76adcc2eb00dab49d5dfd
child 622009 897b094b31d3ef68d532cf958f3ca9c9f6436514
push id51573
push userksteuber@mozilla.com
push dateWed, 29 Mar 2017 17:31:36 +0000
bugs1350381
milestone55.0a1
Bug 1350381 - Change Flash blocking to allow the setting "Ask to Activate" to control CTA of unknown documents. Previously, we operated under the understanding that with Flash blocking activated, non-whitelisted documents would be set to CTA. We are changing that such that now, documents will only be CTA'ed if Flash is set to "Ask to Activate". Flash blocking will now behave according to the following chart: User Setting Flash block Whitelisted sites Blacklisted sites Unlisted sites "Never ..." Off Deny Deny Deny "Ask ..." Off Ask Ask Ask "Always ..." Off Allow Allow Allow "Never ..." On Deny Deny Deny "Ask ..." On Allow Deny Ask "Always ..." On Allow Deny Allow This patch also completely reworks the flash blocking testing. Test data and most code remains consolidated, but will be run in multiple different tests. This avoids having to extend the timeout for Flash block testing to an extremely long length. The new Flash block testing additionally tests each of the six cases (rows) in the table above. MozReview-Commit-ID: 5aPGUEiUiCv
dom/base/nsDocument.cpp
dom/base/nsObjectLoadingContent.cpp
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/browser_flashblock_off_with_always_activate.js
toolkit/components/url-classifier/tests/browser/browser_flashblock_off_with_ask_to_activate.js
toolkit/components/url-classifier/tests/browser/browser_flashblock_off_with_never_activate.js
toolkit/components/url-classifier/tests/browser/browser_flashblock_on_with_always_activate.js
toolkit/components/url-classifier/tests/browser/browser_flashblock_on_with_ask_to_activate.js
toolkit/components/url-classifier/tests/browser/browser_flashblock_on_with_never_activate.js
toolkit/components/url-classifier/tests/browser/classifierTester.js
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13173,22 +13173,21 @@ ArrayContainsTable(const nsTArray<nsCStr
  *
  * For more information, see
  * toolkit/components/url-classifier/flash-block-lists.rst
  */
 FlashClassification
 nsDocument::PrincipalFlashClassification()
 {
   nsresult rv;
-  bool isThirdPartyDoc = IsThirdParty();
 
   // If flash blocking is disabled, it is equivalent to all sites being
-  // whitelisted.
+  // on neither list.
   if (!Preferences::GetBool("plugins.flashBlock.enabled")) {
-    return FlashClassification::Allowed;
+    return FlashClassification::Unknown;
   }
 
   nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
   if (principal->GetIsNullPrincipal()) {
     return FlashClassification::Denied;
   }
 
   nsCOMPtr<nsIURI> classificationURI;
@@ -13206,16 +13205,18 @@ nsDocument::PrincipalFlashClassification
   Preferences::GetCString("urlclassifier.flashAllowExceptTable",
                           &allowExceptionsTables);
   MaybeAddTableToTableList(allowExceptionsTables, tables);
   Preferences::GetCString("urlclassifier.flashTable", &denyTables);
   MaybeAddTableToTableList(denyTables, tables);
   Preferences::GetCString("urlclassifier.flashExceptTable",
                           &denyExceptionsTables);
   MaybeAddTableToTableList(denyExceptionsTables, tables);
+
+  bool isThirdPartyDoc = IsThirdParty();
   if (isThirdPartyDoc) {
     Preferences::GetCString("urlclassifier.flashSubDocTable",
                             &subDocDenyTables);
     MaybeAddTableToTableList(subDocDenyTables, tables);
     Preferences::GetCString("urlclassifier.flashSubDocExceptTable",
                             &subDocDenyExceptionsTables);
     MaybeAddTableToTableList(subDocDenyExceptionsTables, tables);
   }
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3269,17 +3269,17 @@ nsObjectLoadingContent::ShouldPlay(Fallb
     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)
-  FlashClassification documentClassification = FlashClassification::Allowed;
+  FlashClassification documentClassification = FlashClassification::Unknown;
   if (IsFlashMIME(mContentType)) {
     documentClassification = ownerDoc->DocumentFlashClassification();
   }
   if (documentClassification == FlashClassification::Denied) {
     aReason = eFallbackSuppressed;
     return false;
   }
 
@@ -3344,20 +3344,26 @@ nsObjectLoadingContent::ShouldPlay(Fallb
     return false;
   }
 
   if (PreferFallback(enabledState == nsIPluginTag::STATE_CLICKTOPLAY)) {
     aReason = eFallbackAlternate;
     return false;
   }
 
+  // On the following switch we don't need to handle the case where
+  // documentClassification is FlashClassification::Denied because
+  // that's already handled above.
   switch (enabledState) {
   case nsIPluginTag::STATE_ENABLED:
-    return documentClassification == FlashClassification::Allowed;
+    return true;
   case nsIPluginTag::STATE_CLICKTOPLAY:
+    if (documentClassification == FlashClassification::Allowed) {
+      return true;
+    }
     return false;
   }
   MOZ_CRASH("Unexpected enabledState");
 }
 
 bool
 nsObjectLoadingContent::FavorFallbackMode(bool aIsPluginClickToPlay) {
   if (!IsFlashMIME(mContentType)) {
--- a/toolkit/components/url-classifier/tests/browser/browser.ini
+++ b/toolkit/components/url-classifier/tests/browser/browser.ini
@@ -1,6 +1,13 @@
 [DEFAULT]
 support-files =
   flash_block_frame.html
   classifierHelper.js
+  classifierTester.js
 
-[browser_flash_block_lists.js]
+[browser_flashblock_on_with_never_activate.js]
+[browser_flashblock_off_with_never_activate.js]
+[browser_flashblock_on_with_ask_to_activate.js]
+[browser_flashblock_off_with_ask_to_activate.js]
+[browser_flashblock_on_with_always_activate.js]
+[browser_flashblock_off_with_always_activate.js]
+
deleted file mode 100644
--- a/toolkit/components/url-classifier/tests/browser/browser_flash_block_lists.js
+++ /dev/null
@@ -1,364 +0,0 @@
-/* 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(3);
-
-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,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    name: "Nested unknown domains",
-    domains: ["http://example.com", "http://example.org"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    name: "Allowed domain",
-    domains: ["http://flashallow.example.com"],
-    expectedActivated: true,
-    expectedHasRunningPlugin: true,
-    pluginListed: true,
-    expectedFlashClassification: "allowed"
-  },
-  {
-    name: "Allowed nested domain",
-    domains: ["http://example.com", "http://flashallow.example.com"],
-    expectedActivated: true,
-    expectedHasRunningPlugin: true,
-    pluginListed: true,
-    expectedFlashClassification: "allowed"
-  },
-  {
-    name: "Subdocument of allowed domain",
-    domains: ["http://flashallow.example.com", "http://example.com"],
-    expectedActivated: true,
-    expectedHasRunningPlugin: true,
-    pluginListed: true,
-    expectedFlashClassification: "allowed"
-  },
-  {
-    name: "Exception to allowed domain",
-    domains: ["http://exception.flashallow.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    name: "Blocked domain",
-    domains: ["http://flashblock.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    name: "Nested blocked domain",
-    domains: ["http://example.com", "http://flashblock.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    name: "Subdocument of blocked subdocument",
-    domains: ["http://example.com", "http://flashblock.example.com", "http://example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    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_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    name: "Exception to blocked domain",
-    domains: ["http://exception.flashblock.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    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,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    name: "Sub-document blocked domain",
-    domains: ["http://example.com", "http://subdocument.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    name: "Sub-document blocked domain in non-Third-Party context",
-    domains: ["http://subdocument.example.com", "http://subdocument.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    name: "Sub-document blocked domain differing only by scheme",
-    domains: ["http://subdocument.example.com", "https://subdocument.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    name: "Sub-document blocked subdocument of an allowed domain",
-    domains: ["http://flashallow.example.com", "http://subdocument.example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    name: "Subdocument of Sub-document blocked domain",
-    domains: ["http://example.com", "http://subdocument.example.com", "http://example.com"],
-    expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
-    expectedActivated: false,
-    expectedHasRunningPlugin: false,
-    pluginListed: false,
-    expectedFlashClassification: "denied"
-  },
-  {
-    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,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  },
-  {
-    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,
-    pluginListed: true,
-    expectedFlashClassification: "unknown"
-  }
-];
-
-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 getPluginInfo(browser, depth) {
-  return ContentTask.spawn(browser,
-                           {iframeId: IFRAME_ID, depth: depth},
-                           function* ({iframeId, depth}) {
-    let doc = content.document;
-    let win = content.window;
-    for (let i = 0; i < depth; ++i) {
-      let frame = doc.getElementById(iframeId);
-      doc = frame.contentDocument;
-      win = frame.contentWindow;
-    }
-
-    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,
-      listed: ("Shockwave Flash" in win.navigator.plugins),
-      flashClassification: doc.documentFlashClassification
-    };
-  });
-}
-
-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 pluginInfo = yield getPluginInfo(tab.linkedBrowser, iframeDomains.length);
-
-    if ("expectedPluginFallbackType" in testCase) {
-      is(pluginInfo.pluginFallbackType, testCase.expectedPluginFallbackType,
-        "Plugin should have the correct fallback type");
-    }
-    if ("expectedActivated" in testCase) {
-      is(pluginInfo.activated, testCase.expectedActivated,
-        "Plugin should have the correct activation");
-    }
-    if ("expectedHasRunningPlugin" in testCase) {
-      is(pluginInfo.hasRunningPlugin, testCase.expectedHasRunningPlugin,
-        "Plugin should have the correct 'plugin running' state");
-    }
-    if ("pluginListed" in testCase) {
-      is(pluginInfo.listed, testCase.pluginListed,
-        "Plugin's existance in navigator.plugins should match expected")
-    }
-    if ("expectedFlashClassification" in testCase) {
-      is(pluginInfo.flashClassification, testCase.expectedFlashClassification,
-        "Page's classification should match expected");
-    }
-
-    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 pluginInfo = yield getPluginInfo(tab.linkedBrowser, iframeDomains.length);
-
-    // With flashblock disabled, all plugins should be activated.
-    ok(pluginInfo.activated, "Plugin should be activated");
-    ok(pluginInfo.hasRunningPlugin, "Plugin should be running");
-    ok(pluginInfo.listed, "Flash should be listed in navigator.plugins");
-    is(pluginInfo.flashClassification, "allowed",
-       "Page's classification should be 'allowed'");
-
-    yield BrowserTestUtils.removeTab(tab);
-  }
-});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flashblock_off_with_always_activate.js
@@ -0,0 +1,43 @@
+/* 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");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+  this);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierTester.js",
+  this);
+
+add_task(function* checkFlashBlockLists() {
+  classifierTester.setPrefs({
+    setDBs: true,
+    flashBlockEnable: false,
+    flashSetting: classifierTester.ALWAYS_ACTIVATE_PREF_VALUE
+  });
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(classifierTester.dbUrls);
+
+  for (let testCase of classifierTester.testCases) {
+    info(`RUNNING TEST: ${testCase.name} (Ask to Activate, Flashblock off)`);
+    let tab = yield classifierTester.buildTestCaseInNewTab(gBrowser, testCase);
+
+    let depth = testCase.domains.length - 1;
+    let pluginInfo = yield classifierTester.getPluginInfo(tab.linkedBrowser,
+                                                          depth);
+
+    classifierTester.checkPluginInfo(pluginInfo,
+                                     "unknown",
+                                     classifierTester.ALWAYS_ACTIVATE_PREF_VALUE);
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flashblock_off_with_ask_to_activate.js
@@ -0,0 +1,43 @@
+/* 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");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+  this);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierTester.js",
+  this);
+
+add_task(function* checkFlashBlockLists() {
+  classifierTester.setPrefs({
+    setDBs: true,
+    flashBlockEnable: false,
+    flashSetting: classifierTester.ASK_TO_ACTIVATE_PREF_VALUE
+  });
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(classifierTester.dbUrls);
+
+  for (let testCase of classifierTester.testCases) {
+    info(`RUNNING TEST: ${testCase.name} (Ask to Activate, Flashblock off)`);
+    let tab = yield classifierTester.buildTestCaseInNewTab(gBrowser, testCase);
+
+    let depth = testCase.domains.length - 1;
+    let pluginInfo = yield classifierTester.getPluginInfo(tab.linkedBrowser,
+                                                          depth);
+
+    classifierTester.checkPluginInfo(pluginInfo,
+                                     "unknown",
+                                     classifierTester.ASK_TO_ACTIVATE_PREF_VALUE);
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flashblock_off_with_never_activate.js
@@ -0,0 +1,43 @@
+/* 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");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+  this);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierTester.js",
+  this);
+
+add_task(function* checkFlashBlockLists() {
+  classifierTester.setPrefs({
+    setDBs: true,
+    flashBlockEnable: false,
+    flashSetting: classifierTester.NEVER_ACTIVATE_PREF_VALUE
+  });
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(classifierTester.dbUrls);
+
+  for (let testCase of classifierTester.testCases) {
+    info(`RUNNING TEST: ${testCase.name} (Never Activate, Flashblock off)`);
+    let tab = yield classifierTester.buildTestCaseInNewTab(gBrowser, testCase);
+
+    let depth = testCase.domains.length - 1;
+    let pluginInfo = yield classifierTester.getPluginInfo(tab.linkedBrowser,
+                                                          depth);
+
+    classifierTester.checkPluginInfo(pluginInfo,
+                                     "unknown",
+                                     classifierTester.NEVER_ACTIVATE_PREF_VALUE);
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flashblock_on_with_always_activate.js
@@ -0,0 +1,43 @@
+/* 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");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+  this);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierTester.js",
+  this);
+
+add_task(function* checkFlashBlockLists() {
+  classifierTester.setPrefs({
+    setDBs: true,
+    flashBlockEnable: true,
+    flashSetting: classifierTester.ALWAYS_ACTIVATE_PREF_VALUE
+  });
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(classifierTester.dbUrls);
+
+  for (let testCase of classifierTester.testCases) {
+    info(`RUNNING TEST: ${testCase.name} (Ask to Activate, Flashblock on)`);
+    let tab = yield classifierTester.buildTestCaseInNewTab(gBrowser, testCase);
+
+    let depth = testCase.domains.length - 1;
+    let pluginInfo = yield classifierTester.getPluginInfo(tab.linkedBrowser,
+                                                          depth);
+
+    classifierTester.checkPluginInfo(pluginInfo,
+                                     testCase.expectedFlashClassification,
+                                     classifierTester.ALWAYS_ACTIVATE_PREF_VALUE);
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flashblock_on_with_ask_to_activate.js
@@ -0,0 +1,43 @@
+/* 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");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+  this);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierTester.js",
+  this);
+
+add_task(function* checkFlashBlockLists() {
+  classifierTester.setPrefs({
+    setDBs: true,
+    flashBlockEnable: true,
+    flashSetting: classifierTester.ASK_TO_ACTIVATE_PREF_VALUE
+  });
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(classifierTester.dbUrls);
+
+  for (let testCase of classifierTester.testCases) {
+    info(`RUNNING TEST: ${testCase.name} (Ask to Activate, Flashblock on)`);
+    let tab = yield classifierTester.buildTestCaseInNewTab(gBrowser, testCase);
+
+    let depth = testCase.domains.length - 1;
+    let pluginInfo = yield classifierTester.getPluginInfo(tab.linkedBrowser,
+                                                          depth);
+
+    classifierTester.checkPluginInfo(pluginInfo,
+                                     testCase.expectedFlashClassification,
+                                     classifierTester.ASK_TO_ACTIVATE_PREF_VALUE);
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flashblock_on_with_never_activate.js
@@ -0,0 +1,43 @@
+/* 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");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+                     getService(Ci.mozIJSSubScriptLoader);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+  this);
+scriptLoader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierTester.js",
+  this);
+
+add_task(function* checkFlashBlockLists() {
+  classifierTester.setPrefs({
+    setDBs: true,
+    flashBlockEnable: true,
+    flashSetting: classifierTester.NEVER_ACTIVATE_PREF_VALUE
+  });
+
+  yield classifierHelper.waitForInit();
+  yield classifierHelper.addUrlToDB(classifierTester.dbUrls);
+
+  for (let testCase of classifierTester.testCases) {
+    info(`RUNNING TEST: ${testCase.name} (Never Activate, Flashblock on)`);
+    let tab = yield classifierTester.buildTestCaseInNewTab(gBrowser, testCase);
+
+    let depth = testCase.domains.length - 1;
+    let pluginInfo = yield classifierTester.getPluginInfo(tab.linkedBrowser,
+                                                          depth);
+
+    classifierTester.checkPluginInfo(pluginInfo,
+                                     testCase.expectedFlashClassification,
+                                     classifierTester.NEVER_ACTIVATE_PREF_VALUE);
+
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/classifierTester.js
@@ -0,0 +1,337 @@
+/* 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/. */
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+var classifierTester = {
+  URL_PATH: "/browser/toolkit/components/url-classifier/tests/browser/flash_block_frame.html",
+  OBJECT_ID: "testObject",
+  IFRAME_ID: "testFrame",
+  FLASHBLOCK_ENABLE_PREF: "plugins.flashBlock.enabled",
+  FLASH_PLUGIN_USER_SETTING_PREF: "plugin.state.flash",
+  NEVER_ACTIVATE_PREF_VALUE: 0,
+  ASK_TO_ACTIVATE_PREF_VALUE: 1,
+  ALWAYS_ACTIVATE_PREF_VALUE: 2,
+  ALLOW_CTA_PREF: "plugins.click_to_play",
+
+  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"
+    }
+  ],
+
+  setPrefs: function ({setDBs = true, flashBlockEnable = true, flashSetting = classifierTester.ALWAYS_ACTIVATE_PREF_VALUE} = {}) {
+    if (setDBs) {
+      for (let dbData of classifierTester.dbUrls) {
+        Services.prefs.setCharPref(dbData.pref, dbData.db);
+      }
+    }
+    Services.prefs.setBoolPref(classifierTester.FLASHBLOCK_ENABLE_PREF,
+                               flashBlockEnable);
+    Services.prefs.setIntPref(classifierTester.FLASH_PLUGIN_USER_SETTING_PREF,
+                              flashSetting);
+    Services.prefs.setBoolPref(classifierTester.ALLOW_CTA_PREF, true);
+  },
+
+  unsetPrefs: function () {
+    for (let dbData of classifierTester.dbUrls) {
+      Services.prefs.clearUserPref(dbData.pref);
+    }
+    Services.prefs.clearUserPref(classifierTester.FLASHBLOCK_ENABLE_PREF);
+    Services.prefs.clearUserPref(classifierTester.FLASH_PLUGIN_USER_SETTING_PREF);
+    Services.prefs.clearUserPref(classifierTester.ALLOW_CTA_PREF);
+  },
+
+  // 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.
+  testCases: [
+    {
+      name: "Unknown domain",
+      domains: ["http://example.com"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Nested unknown domains",
+      domains: ["http://example.com", "http://example.org"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Allowed domain",
+      domains: ["http://flashallow.example.com"],
+      expectedFlashClassification: "allowed"
+    },
+    {
+      name: "Allowed nested domain",
+      domains: ["http://example.com", "http://flashallow.example.com"],
+      expectedFlashClassification: "allowed"
+    },
+    {
+      name: "Subdocument of allowed domain",
+      domains: ["http://flashallow.example.com", "http://example.com"],
+      expectedFlashClassification: "allowed"
+    },
+    {
+      name: "Exception to allowed domain",
+      domains: ["http://exception.flashallow.example.com"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Blocked domain",
+      domains: ["http://flashblock.example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Nested blocked domain",
+      domains: ["http://example.com", "http://flashblock.example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Subdocument of blocked subdocument",
+      domains: ["http://example.com", "http://flashblock.example.com", "http://example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Blocked subdocument nested among in allowed documents",
+      domains: ["http://flashallow.example.com", "http://flashblock.example.com", "http://flashallow.example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Exception to blocked domain",
+      domains: ["http://exception.flashblock.example.com"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Sub-document blocked domain in top-level context",
+      domains: ["http://subdocument.example.com"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Sub-document blocked domain",
+      domains: ["http://example.com", "http://subdocument.example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Sub-document blocked domain in non-Third-Party context",
+      domains: ["http://subdocument.example.com", "http://subdocument.example.com"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Sub-document blocked domain differing only by scheme",
+      domains: ["http://subdocument.example.com", "https://subdocument.example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Sub-document blocked subdocument of an allowed domain",
+      domains: ["http://flashallow.example.com", "http://subdocument.example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Subdocument of Sub-document blocked domain",
+      domains: ["http://example.com", "http://subdocument.example.com", "http://example.com"],
+      expectedFlashClassification: "denied"
+    },
+    {
+      name: "Sub-document exception in top-level context",
+      domains: ["http://exception.subdocument.example.com"],
+      expectedFlashClassification: "unknown"
+    },
+    {
+      name: "Sub-document blocked domain exception",
+      domains: ["http://example.com", "http://exception.subdocument.example.com"],
+      expectedFlashClassification: "unknown"
+    }
+  ],
+
+  // Returns null if this value should not be verified given the combination
+  // of inputs
+  expectedPluginFallbackType: function (classification, flashSetting) {
+    switch(classification) {
+      case "unknown":
+        if (flashSetting == classifierTester.ALWAYS_ACTIVATE_PREF_VALUE) {
+          return null;
+        } else if (flashSetting == classifierTester.ASK_TO_ACTIVATE_PREF_VALUE) {
+          return Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
+        } else if (flashSetting == classifierTester.NEVER_ACTIVATE_PREF_VALUE) {
+          return Ci.nsIObjectLoadingContent.PLUGIN_DISABLED;
+        }
+        break;
+      case "allowed":
+        if (flashSetting == classifierTester.NEVER_ACTIVATE_PREF_VALUE) {
+          return Ci.nsIObjectLoadingContent.PLUGIN_DISABLED;
+        }
+        return null;
+      case "denied":
+        return Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED;
+    }
+    throw new Error("Invalid classification or flash setting");
+  },
+
+  // Returns null if this value should not be verified given the combination
+  // of inputs
+  expectedActivated: function (classification, flashSetting) {
+    switch(classification) {
+      case "unknown":
+        return (flashSetting == classifierTester.ALWAYS_ACTIVATE_PREF_VALUE);
+      case "allowed":
+        return (flashSetting != classifierTester.NEVER_ACTIVATE_PREF_VALUE);
+      case "denied":
+        return false;
+    }
+    throw new Error("Invalid classification or flash setting");
+  },
+
+  // Returns null if this value should not be verified given the combination
+  // of inputs
+  expectedHasRunningPlugin: function (classification, flashSetting) {
+    switch(classification) {
+      case "unknown":
+        return (flashSetting == classifierTester.ALWAYS_ACTIVATE_PREF_VALUE);
+      case "allowed":
+        return (flashSetting != classifierTester.NEVER_ACTIVATE_PREF_VALUE);
+      case "denied":
+        return false;
+    }
+    throw new Error("Invalid classification or flash setting");
+  },
+
+  // Returns null if this value should not be verified given the combination
+  // of inputs
+  expectedPluginListed: function (classification, flashSetting) {
+    if (flashSetting == classifierTester.ASK_TO_ACTIVATE_PREF_VALUE &&
+        Services.prefs.getCharPref('plugins.navigator.hidden_ctp_plugin') == "Shockwave Flash") {
+      return false;
+    }
+    switch(classification) {
+      case "unknown":
+      case "allowed":
+        return (flashSetting != classifierTester.NEVER_ACTIVATE_PREF_VALUE);
+      case "denied":
+        return false;
+    }
+    throw new Error("Invalid classification or flash setting");
+  },
+
+  buildTestCaseInNewTab: function (browser, testCase) {
+    return Task.spawn(function* () {
+      let iframeDomains = testCase.domains.slice();
+      let pageDomain = iframeDomains.shift();
+      let tab = yield BrowserTestUtils.openNewForegroundTab(browser,
+                                                            pageDomain + classifierTester.URL_PATH);
+
+      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 + classifierTester.URL_PATH + "?date=" + Date.now() + "rand=" + Math.random();
+        let domainLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, url);
+
+        ContentTask.spawn(tab.linkedBrowser, {iframeId: classifierTester.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;
+      }
+      return tab;
+    });
+  },
+
+  getPluginInfo: function (browser, depth) {
+    return ContentTask.spawn(browser,
+                             {iframeId: classifierTester.IFRAME_ID, depth: depth},
+                             function* ({iframeId, depth}) {
+      let doc = content.document;
+      let win = content.window;
+      for (let i = 0; i < depth; ++i) {
+        let frame = doc.getElementById(iframeId);
+        doc = frame.contentDocument;
+        win = frame.contentWindow;
+      }
+
+      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,
+        listed: ("Shockwave Flash" in win.navigator.plugins),
+        flashClassification: doc.documentFlashClassification
+      };
+    });
+  },
+
+  checkPluginInfo: function (pluginInfo, expectedClassification, flashSetting) {
+    is(pluginInfo.flashClassification, expectedClassification,
+       "Page's classification should match expected");
+
+    let expectedPluginFallbackType =
+      classifierTester.expectedPluginFallbackType(pluginInfo.flashClassification,
+                                                  flashSetting);
+    if (expectedPluginFallbackType != null) {
+      is(pluginInfo.pluginFallbackType, expectedPluginFallbackType,
+         "Plugin should have the correct fallback type");
+    }
+
+    let expectedActivated =
+      classifierTester.expectedActivated(pluginInfo.flashClassification,
+                                         flashSetting);
+    if (expectedActivated != null) {
+      is(pluginInfo.activated, expectedActivated,
+         "Plugin should have the correct activation");
+    }
+
+    let expectedHasRunningPlugin =
+      classifierTester.expectedHasRunningPlugin(pluginInfo.flashClassification,
+                                                flashSetting);
+    if (expectedHasRunningPlugin != null) {
+      is(pluginInfo.hasRunningPlugin, expectedHasRunningPlugin,
+         "Plugin should have the correct 'plugin running' state");
+    }
+
+    let expectedPluginListed =
+      classifierTester.expectedPluginListed(pluginInfo.flashClassification,
+                                            flashSetting);
+    if (expectedPluginListed != null) {
+      is(pluginInfo.listed, expectedPluginListed,
+         "Plugin's existance in navigator.plugins should match expected");
+    }
+  }
+};
+registerCleanupFunction(classifierTester.unsetPrefs);