merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 20 Jul 2016 10:59:12 +0200
changeset 389908 3383b0da1a14340ec6096aca542eb73b0f7341d5
parent 389550 5a91e5b49be3c1ba401b057e90c92d7488e3647d (current diff)
parent 389907 df92a916d7ad13c2f2db267ac79d9c69b2bfb3ec (diff)
child 389909 e6f73b2b1b96e4d3fb1b550b78548b92c97ee45c
child 389911 2b7b18973e9394266728299953a09829f857f972
child 389926 a6ca570852571ae98ef10902af8bc9a27543383c
push id23555
push userg.maone@informaction.com
push dateWed, 20 Jul 2016 09:15:43 +0000
reviewersmerge
milestone50.0a1
merge autoland to mozilla-central a=merge
dom/html/HTMLMediaElement.cpp
taskcluster/ci/legacy/tasks/tests/b2g_unittest_base.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_cppunit.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_cppunit_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_cppunit_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_crashtest.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_crashtest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_crashtest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_generic.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_jsreftest.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_jsreftest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_jsreftest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_chrome.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_chrome_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_chrome_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_clipboard.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_clipboard_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_clipboard_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_gl.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_gl_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_gl_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_gpu.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_gpu_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_gpu_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_media.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_media_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_media_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_plain.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_plain_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_mochitest_plain_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_reftest.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_reftest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_reftest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_robocop.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_robocop_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_robocop_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_xpcshell.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_xpcshell_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_android-api-15_xpcshell_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_docker_desktop_generic.yml
taskcluster/ci/legacy/tasks/tests/fx_docker_test_base.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_cppunit.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_cppunit_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_cppunit_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_crashtest.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_crashtest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_crashtest_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_crashtest_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_crashtest_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_crashtest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_external_media_tests.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_external_media_tests_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_external_media_tests_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_local_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_fxui_functional_remote_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_gtest.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_gtest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_gtest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jittests.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jittests_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jittests_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jsreftest.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jsreftest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jsreftest_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jsreftest_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jsreftest_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_jsreftest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_luciddream.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_marionette.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_marionette_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_marionette_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_marionette_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_marionette_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_marionette_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_a11y.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_a11y_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_a11y_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_bc.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_bc_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_bc_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_bc_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_bc_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_bc_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_chrome.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_chrome_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_chrome_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_clipboard.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_clipboard_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_clipboard_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_clipboard_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_clipboard_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_clipboard_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_dt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_dt_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_dt_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_dt_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_dt_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_dt_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gl.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gl_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gl_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gl_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gl_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gl_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gpu.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gpu_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gpu_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gpu_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gpu_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_gpu_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_jetpack.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_jetpack_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_jetpack_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_media.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_media_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_media_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_media_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_media_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_media_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_plain.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_plain_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_plain_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_plain_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_plain_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_plain_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_mochitest_vg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_not_accelerated.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_not_accelerated_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_not_accelerated_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_not_accelerated_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_not_accelerated_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_not_accelerated_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_reftest_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_reftests.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_reftests_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_reftests_e10s.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_reftests_e10s_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_reftests_e10s_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_web_platform_tests_reftests_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_xpcshell.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_xpcshell_dbg.yml
taskcluster/ci/legacy/tasks/tests/fx_linux64_xpcshell_opt.yml
taskcluster/ci/legacy/tasks/tests/fx_win32_base.yml
taskcluster/ci/legacy/tasks/tests/fx_win64_base.yml
taskcluster/ci/legacy/tasks/tests/fx_windows_test_base.yml
taskcluster/ci/legacy/tasks/tests/mulet_build_test.yml
taskcluster/ci/legacy/tasks/tests/mulet_build_unit.yml
taskcluster/ci/legacy/tasks/tests/mulet_gaia_js_integration_tests.yml
taskcluster/ci/legacy/tasks/tests/mulet_gaia_unit.yml
taskcluster/ci/legacy/tasks/tests/mulet_gaia_unit_oop.yml
taskcluster/ci/legacy/tasks/tests/mulet_linter.yml
taskcluster/ci/legacy/tasks/tests/mulet_mochitests.yml
taskcluster/ci/legacy/tasks/tests/mulet_reftests.yml
taskcluster/docs/old.rst
taskcluster/taskgraph/kind/__init__.py
taskcluster/taskgraph/kind/base.py
taskcluster/taskgraph/kind/docker_image.py
taskcluster/taskgraph/kind/legacy.py
taskcluster/taskgraph/test/test_kind_docker_image.py
taskcluster/taskgraph/test/test_kind_legacy.py
testing/web-platform/meta/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/001.html.ini
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -18,17 +18,17 @@ const kWhitelist = [
   // Tracked in bug 1004428.
   {sourceName: /aboutaccounts\/(main|normalize)\.css$/i},
   // TokBox SDK assets, see bug 1032469.
   {sourceName: /loop\/.*sdk-content\/.*\.css$/i},
   // Loop standalone client CSS uses placeholder cross browser pseudo-element
   {sourceName: /loop\/.*\.css$/i,
    errorMessage: /Unknown pseudo-class.*placeholder/i},
   {sourceName: /loop\/.*shared\/css\/common.css$/i,
-   errorMessage: /Unknown property 'user-select'/i},
+   errorMessage: /Unknown property .user-select./i},
   // Highlighter CSS uses a UA-only pseudo-class, see bug 985597.
   {sourceName: /highlighters\.css$/i,
    errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i},
   // Responsive Design Mode CSS uses a UA-only pseudo-class, see Bug 1241714.
   {sourceName: /responsive-ua\.css$/i,
    errorMessage: /Unknown pseudo-class.*moz-dropdown-list/i},
 ];
 
--- a/browser/base/content/test/general/parsingTestHelpers.jsm
+++ b/browser/base/content/test/general/parsingTestHelpers.jsm
@@ -65,17 +65,18 @@ function iterateOverPath(path, extension
       let file = parentDir.clone();
       file.append(entry.name);
       // the build system might leave dead symlinks hanging around, which are
       // returned as part of the directory iterator, but don't actually exist:
       if (file.exists()) {
         let uriSpec = getURLForFile(file);
         files.push(Services.io.newURI(uriSpec, null, null));
       }
-    } else if (entry.name.endsWith(".ja") || entry.name.endsWith(".jar")) {
+    } else if (entry.name.endsWith(".ja") || entry.name.endsWith(".jar") ||
+               entry.name.endsWith(".zip") || entry.name.endsWith(".xpi")) {
       let file = parentDir.clone();
       file.append(entry.name);
       for (let extension of extensions) {
         let jarEntryIterator = generateEntriesFromJarFile(file, extension);
         files.push(...jarEntryIterator);
       }
     }
   };
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -46,16 +46,17 @@ SEARCH_PATHS = [
     'python/jsmin',
     'python/psutil',
     'python/pylru',
     'python/which',
     'python/pystache',
     'python/pyyaml/lib',
     'python/requests',
     'python/slugid',
+    'python/voluptuous',
     'build',
     'build/pymake',
     'config',
     'dom/bindings',
     'dom/bindings/parser',
     'dom/media/test/external',
     'layout/tools/reftest',
     'other-licenses/ply',
--- a/dom/browser-element/mochitest/chrome.ini
+++ b/dom/browser-element/mochitest/chrome.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || e10s
+skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || e10s || toolkit == 'android' # Bug 1287720: takes too long on android
 
 support-files =
   audio.ogg
   async.js
   browserElementTestHelpers.js
   browserElement_ActiveStateChange.js
   browserElement_AudioChannelSeeking.js
   browserElement_AudioChannelMutedByDefault.js
--- a/dom/contacts/tests/chrome.ini
+++ b/dom/contacts/tests/chrome.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-
+skip-if = toolkit == 'android' # Bug 1287455: takes too long to complete on Android
 support-files =
   shared.js
   file_contacts_basics.html
   file_contacts_basics2.html
   file_contacts_blobs.html
   file_contacts_events.html
   file_contacts_getall.html
   file_contacts_getall2.html
@@ -15,21 +15,21 @@ support-files =
   test_migration_chrome.js
   file_migration.html
 
 # renaming with "_a_" to execure before others, since we hardcode open of 
 # database and this messes up with mozContacts when done after mozContacts
 # did opened the database. those should really be xpcshell and not chrome
 # mochitests maybe ...
 [test_contacts_a_shutdown.xul]
-skip-if = os == "android" || buildapp == 'b2g'
+skip-if = buildapp == 'b2g'
 [test_contacts_a_upgrade.xul]
-skip-if = os == "android" || buildapp == 'b2g'
+skip-if = buildapp == 'b2g'
 [test_contacts_a_cache.xul]
-skip-if = os == "android" || buildapp == 'b2g'
+skip-if = buildapp == 'b2g'
 [test_contacts_basics.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_basics2.html]
 skip-if = (toolkit == 'gonk' && debug) || (os == 'win' && os_version == '5.1') #debug-only failure, bug 967258 on XP
 [test_contacts_blobs.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_events.html]
 [test_contacts_getall.html]
@@ -37,9 +37,8 @@ skip-if = (toolkit == 'gonk' && debug) #
 [test_contacts_getall2.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_international.html]
 [test_contacts_substringmatching.html]
 [test_contacts_substringmatchingVE.html]
 [test_contacts_substringmatchingCL.html]
 [test_migration.html]
   support-files +=
-  skip-if = os == "android"
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4709,22 +4709,20 @@ void HTMLMediaElement::SuspendOrResumeEl
   LOG(LogLevel::Debug, ("%p SuspendOrResumeElement(pause=%d, suspendEvents=%d) hidden=%d",
       this, aPauseElement, aSuspendEvents, OwnerDoc()->Hidden()));
 
   if (aPauseElement != mPausedForInactiveDocumentOrChannel) {
     mPausedForInactiveDocumentOrChannel = aPauseElement;
     UpdateSrcMediaStreamPlaying();
     UpdateAudioChannelPlayingState();
     if (aPauseElement) {
-      if (mMediaSource) {
-        ReportTelemetry();
+      ReportTelemetry();
 #ifdef MOZ_EME
-        ReportEMETelemetry();
+      ReportEMETelemetry();
 #endif
-      }
 
 #ifdef MOZ_EME
       // For EME content, force destruction of the CDM client (and CDM
       // instance if this is the last client for that CDM instance) and
       // the CDM's decoder. This ensures the CDM gets reliable and prompt
       // shutdown notifications, as it may have book-keeping it needs
       // to do on shutdown.
       if (mMediaKeys) {
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -8941,17 +8941,17 @@ public:
     return result.forget();
   }
 
   void
   NoteFinishedMaintenance(Maintenance* aMaintenance)
   {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aMaintenance);
-    MOZ_ASSERT(mCurrentMaintenance = aMaintenance);
+    MOZ_ASSERT(mCurrentMaintenance == aMaintenance);
 
     mCurrentMaintenance = nullptr;
     ProcessMaintenanceQueue();
   }
 
   nsThreadPool*
   GetOrCreateThreadPool();
 
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -162,17 +162,22 @@ void InitBrandName()
 
 cubeb* GetCubebContextUnlocked()
 {
   sMutex.AssertCurrentThreadOwns();
   if (sCubebContext) {
     return sCubebContext;
   }
 
-  NS_WARN_IF_FALSE(sBrandName, "Could not get brandName?");
+  if (!sBrandName && NS_IsMainThread()) {
+    InitBrandName();
+  } else {
+    NS_WARN_IF_FALSE(sBrandName,
+        "Did not initialize sbrandName, and not on the main thread?");
+  }
 
   DebugOnly<int> rv = cubeb_init(&sCubebContext, sBrandName);
   NS_WARN_IF_FALSE(rv == CUBEB_OK, "Could not get a cubeb context.");
 
   return sCubebContext;
 }
 
 void ReportCubebBackendUsed()
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -604,16 +604,17 @@ skip-if = (os == 'mac' && os_version == 
 [test_bug686942.html]
 [test_bug726904.html]
 [test_bug874897.html]
 [test_bug879717.html]
 tags=capturestream
 skip-if = os == 'win' && !debug # bug 1140675
 [test_bug883173.html]
 [test_bug895091.html]
+tags=webvtt
 [test_bug895305.html]
 [test_bug919265.html]
 [test_bug957847.html]
 [test_bug1018933.html]
 [test_bug1113600.html]
 tags=capturestream
 [test_bug1242338.html]
 [test_bug1242594.html]
--- a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
+++ b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
@@ -339,16 +339,23 @@ SpeechDispatcherService::Setup()
 
   speechdLib = PR_LoadLibrary("libspeechd.so.2");
 
   if (!speechdLib) {
     NS_WARNING("Failed to load speechd library");
     return;
   }
 
+  if (!PR_FindFunctionSymbol(speechdLib, "spd_get_volume")) {
+    // There is no version getter function, so we rely on a symbol that was
+    // introduced in release 0.8.2 in order to check for ABI compatibility.
+    NS_WARNING("Unsupported version of speechd detected");
+    return;
+  }
+
   for (uint32_t i = 0; i < ArrayLength(kSpeechDispatcherSymbols); i++) {
     *kSpeechDispatcherSymbols[i].function =
       PR_FindFunctionSymbol(speechdLib, kSpeechDispatcherSymbols[i].functionName);
 
     if (!*kSpeechDispatcherSymbols[i].function) {
       NS_WARNING(nsPrintfCString("Failed to find speechd symbol for'%s'",
                                  kSpeechDispatcherSymbols[i].functionName).get());
       return;
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -172,17 +172,17 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
     // 4.1 WebVTT timestamp
     function consumeTimeStamp() {
       var ts = collectTimeStamp(input);
       if (ts === null) {
         throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                               "Malformed timestamp: " + oInput);
       }
       // Remove time stamp from input.
-      input = input.replace(/^[^\sa-zA-Z-]+/, "");
+      input = input.replace(/^[^\s\uFFFDa-zA-Z-]+/, "");
       return ts;
     }
 
     // 4.4.2 WebVTT cue settings
     function consumeCueSettings(input, cue) {
       var settings = new Settings();
 
       parseOptions(input, function (k, v) {
@@ -254,16 +254,28 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
     skipWhitespace();
     cue.endTime = consumeTimeStamp();     // (5) collect cue end time
 
     // 4.1 WebVTT cue settings list.
     skipWhitespace();
     consumeCueSettings(input, cue);
   }
 
+  function onlyContainsWhiteSpaces(input) {
+    return /^[ \f\n\r\t]+$/.test(input);
+  }
+
+  function containsTimeDirectionSymbol(input) {
+    return input.indexOf("-->") !== -1;
+  }
+
+  function maybeIsTimeStampFormat(input) {
+    return /^\s*(\d+:)?(\d{2}):(\d{2})\.(\d+)\s*-->\s*(\d+:)?(\d{2}):(\d{2})\.(\d+)\s*/.test(input);
+  }
+
   var ESCAPE = {
     "&amp;": "&",
     "&lt;": "<",
     "&gt;": ">",
     "&lrm;": "\u200e",
     "&rlm;": "\u200f",
     "&nbsp;": "\u00a0"
   };
@@ -1249,17 +1261,54 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
         // Advance the buffer early in case we fail below.
         if (buffer[pos] === '\r') {
           ++pos;
         }
         if (buffer[pos] === '\n') {
           ++pos;
         }
         self.buffer = buffer.substr(pos);
-        return line;
+        // Spec defined replacement.
+        return line.replace(/[\u0000]/g, "\uFFFD");
+      }
+
+      function createCueIfNeeded() {
+        if (!self.cue) {
+          self.cue = new self.window.VTTCue(0, 0, "");
+        }
+      }
+
+      // Parsing cue identifier and the identifier should be unique.
+      // Return true if the input is a cue identifier.
+      function parseCueIdentifier(input) {
+        if (maybeIsTimeStampFormat(line)) {
+          self.state = "CUE";
+          return false;
+        }
+
+        createCueIfNeeded();
+        // TODO : ensure the cue identifier is unique among all cue identifiers.
+        self.cue.id = containsTimeDirectionSymbol(input) ? "" : input;
+        self.state = "CUE";
+        return true;
+      }
+
+      // Parsing the timestamp and cue settings.
+      // See spec, https://w3c.github.io/webvtt/#collect-webvtt-cue-timings-and-settings
+      function parseCueMayThrow(input) {
+        try {
+          createCueIfNeeded();
+          parseCue(input, self.cue, self.regionList);
+          self.state = "CUETEXT";
+        } catch (e) {
+          self.reportOrThrowError(e);
+          // In case of an error ignore rest of the cue.
+          self.cue = null;
+          self.state = "BADCUE";
+        }
       }
 
       // 3.4 WebVTT region and WebVTT region settings syntax
       function parseRegion(input) {
         var settings = new Settings();
 
         parseOptions(input, function (k, v) {
           switch (k) {
@@ -1312,130 +1361,134 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
           // reference it.
           self.regionList.push({
             id: settings.get("id"),
             region: region
           });
         }
       }
 
-      // WebVTT parser algorithm step1 - step9.
-      function parseSignature(input) {
+      // Parsing the WebVTT signature, it contains parsing algo step1 to step9.
+      // See spec, https://w3c.github.io/webvtt/#file-parsing
+      function parseSignatureMayThrow(input) {
         let signature = collectNextLine();
         if (!/^WEBVTT([ \t].*)?$/.test(signature)) {
           throw new ParsingError(ParsingError.Errors.BadSignature);
+        } else {
+          self.state = "HEADER";
         }
       }
 
-      // 3.2 WebVTT metadata header syntax
-      function parseHeader(input) {
-        parseOptions(input, function (k, v) {
-          switch (k) {
-          case "Region":
-            // 3.3 WebVTT region metadata header syntax
-            parseRegion(v);
+      // Parsing the region and style information.
+      // See spec, https://w3c.github.io/webvtt/#collect-a-webvtt-block
+      //
+      // There are sereval things would appear in header,
+      //   1. Region or Style setting
+      //   2. Garbage (meaningless string)
+      //   3. Empty line
+      //   4. Cue's timestamp
+      // The case 4 happens when there is no line interval between the header
+      // and the cue blocks. In this case, we should preserve the line and
+      // return it for the next phase parsing.
+      function parseHeader() {
+        let line = null;
+        while (self.buffer && self.state === "HEADER") {
+          line = collectNextLine();
+
+          if (/^REGION|^STYLE/i.test(line)) {
+            parseOptions(line, function (k, v) {
+              switch (k.toUpperCase()) {
+              case "REGION":
+                parseRegion(v);
+                break;
+              case "STYLE":
+                // TODO : not supported yet.
+                break;
+              }
+            }, ":");
+          } else if (maybeIsTimeStampFormat(line)) {
+            self.state = "CUE";
+            break;
+          } else if (!line ||
+                     onlyContainsWhiteSpaces(line) ||
+                     containsTimeDirectionSymbol(line)) {
+            // empty line, whitespaces or string contains "-->"
             break;
           }
-        }, /:/);
+        }
+
+        // End parsing header part and doesn't see the timestamp.
+        if (self.state === "HEADER") {
+          self.state = "ID";
+          line = null
+        }
+        return line;
       }
 
       // 5.1 WebVTT file parsing.
       try {
         if (self.state === "INITIAL") {
-          parseSignature();
-          self.state = "HEADER";
+          parseSignatureMayThrow();
         }
 
         var line;
-        var alreadyCollectedLine = false;
+        if (self.state === "HEADER") {
+          line = parseHeader();
+        }
+
         while (self.buffer) {
-          // We can't parse a line until we have the full line.
-          if (!/\r\n|\n/.test(self.buffer)) {
-            return this;
-          }
-
-          if (!alreadyCollectedLine) {
+          if (!line) {
+            // Since the data receiving is async, we need to wait until the
+            // buffer gets the full line.
+            if (!/\r\n|\n/.test(self.buffer)) {
+              return this;
+            }
             line = collectNextLine();
-          } else {
-            alreadyCollectedLine = false;
           }
 
           switch (self.state) {
-          case "HEADER":
-            // 13-18 - Allow a header (metadata) under the WEBVTT line.
-            if (/:/.test(line)) {
-              parseHeader(line);
-            } else if (!line) {
-              // An empty line terminates the header and starts the body (cues).
-              self.state = "ID";
-            }
-            continue;
-          case "NOTE":
-            // Ignore NOTE blocks.
-            if (!line) {
-              self.state = "ID";
-            }
-            continue;
           case "ID":
-            // Check for the start of NOTE blocks.
-            if (/^NOTE($|[ \t])/.test(line)) {
-              self.state = "NOTE";
+            // Ignore NOTE and line terminator
+            if (/^NOTE($|[ \t])/.test(line) || !line) {
               break;
             }
-            // 19-29 - Allow any number of line terminators, then initialize new cue values.
-            if (!line) {
+            // If there is no cue identifier, keep the line and reuse this line
+            // in next iteration.
+            if (!parseCueIdentifier(line)) {
               continue;
             }
-            self.cue = new self.window.VTTCue(0, 0, "");
-            self.state = "CUE";
-            // 30-39 - Check if self line contains an optional identifier or timing data.
-            if (line.indexOf("-->") === -1) {
-              self.cue.id = line;
-              continue;
-            }
-            // Process line as start of a cue.
-            /*falls through*/
+            break;
           case "CUE":
-            // 40 - Collect cue timings and settings.
-            try {
-              parseCue(line, self.cue, self.regionList);
-            } catch (e) {
-              self.reportOrThrowError(e);
-              // In case of an error ignore rest of the cue.
-              self.cue = null;
-              self.state = "BADCUE";
-              continue;
-            }
-            self.state = "CUETEXT";
-            continue;
+            parseCueMayThrow(line);
+            break;
           case "CUETEXT":
-            var hasSubstring = line.indexOf("-->") !== -1;
-            // 34 - If we have an empty line then report the cue.
-            // 35 - If we have the special substring '-->' then report the cue,
-            // but do not collect the line as we need to process the current
-            // one as a new cue.
-            if (!line || hasSubstring && (alreadyCollectedLine = true)) {
+            // Report the cue when (1) get an empty line (2) get the "-->""
+            if (!line || containsTimeDirectionSymbol(line)) {
               // We are done parsing self cue.
               self.oncue && self.oncue(self.cue);
               self.cue = null;
               self.state = "ID";
+              // Keep the line and reuse this line in next iteration.
               continue;
             }
             if (self.cue.text) {
               self.cue.text += "\n";
             }
             self.cue.text += line;
-            continue;
+            break;
           case "BADCUE": // BADCUE
             // 54-62 - Collect and discard the remaining cue.
             if (!line) {
               self.state = "ID";
             }
-            continue;
+            break;
           }
+          // The line was already parsed, empty it to ensure we can get the
+          // new line in next iteration.
+          line = null;
         }
       } catch (e) {
         self.reportOrThrowError(e);
 
         // If we are currently parsing a cue, report what we have.
         if (self.state === "CUETEXT" && self.cue && self.oncue) {
           self.oncue(self.cue);
         }
--- a/dom/settings/tests/chrome.ini
+++ b/dom/settings/tests/chrome.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
+skip-if = toolkit == 'android' # Bug 1287455: takes too long to complete on Android
 support-files =
   file_loadserver.js
   file_bug1110872.js
   file_bug1110872.html
   test_settings_service.js
   test_settings_service_callback.js
 
 [test_settings_service.xul]
--- a/dom/tests/mochitest/notification/chrome.ini
+++ b/dom/tests/mochitest/notification/chrome.ini
@@ -1,8 +1,8 @@
 [DEFAULT]
-
+skip-if = toolkit == 'android' # Bug 1287455: takes too long to complete on Android
 support-files =
   MockServices.js
   NotificationTest.js
 
 [test_notification_noresend.html]
 skip-if = (toolkit == 'gonk') # Mochitest on Gonk registers an app manifest that messes with the logic
--- a/dom/tv/test/mochitest/chrome.ini
+++ b/dom/tv/test/mochitest/chrome.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
+skip-if = toolkit == 'android' # Bug 1287455: takes too long to complete on Android
 support-files =
   head.js
   mock_data.json
 
 [test_tv_permitted_app.html]
 [test_tv_get_tuners.html]
 [test_tv_get_sources.html]
 [test_tv_get_channels.html]
--- a/gfx/2d/RecordedEvent.cpp
+++ b/gfx/2d/RecordedEvent.cpp
@@ -385,26 +385,34 @@ RecordedDrawingEvent::RecordToStream(ost
 }
 
 ReferencePtr
 RecordedDrawingEvent::GetObjectRef() const
 {
   return mDT;
 }
 
-void
+bool
 RecordedDrawTargetCreation::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<DrawTarget> newDT =
     aTranslator->CreateDrawTarget(mRefPtr, mSize, mFormat);
 
-  if (newDT && mHasExistingData) {
+  // If we couldn't create a DrawTarget this will probably cause us to crash
+  // with nullptr later in the playback, so return false to abort.
+  if (!newDT) {
+    return false;
+  }
+
+  if (mHasExistingData) {
     Rect dataRect(0, 0, mExistingData->GetSize().width, mExistingData->GetSize().height);
     newDT->DrawSurface(mExistingData, dataRect, dataRect);
   }
+
+  return true;
 }
 
 void
 RecordedDrawTargetCreation::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mBackendType);
   WriteElement(aStream, mSize);
@@ -450,20 +458,21 @@ RecordedDrawTargetCreation::RecordedDraw
 
 void
 RecordedDrawTargetCreation::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] DrawTarget Creation (Type: " << NameFromBackend(mBackendType) << ", Size: " << mSize.width << "x" << mSize.height << ")";
 }
 
 
-void
+bool
 RecordedDrawTargetDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemoveDrawTarget(mRefPtr);
+  return true;
 }
 
 void
 RecordedDrawTargetDestruction::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
 }
 
@@ -474,22 +483,30 @@ RecordedDrawTargetDestruction::RecordedD
 }
 
 void
 RecordedDrawTargetDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] DrawTarget Destruction";
 }
 
-void
+bool
 RecordedCreateSimilarDrawTarget::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<DrawTarget> newDT =
     aTranslator->GetReferenceDrawTarget()->CreateSimilarDrawTarget(mSize, mFormat);
+
+  // If we couldn't create a DrawTarget this will probably cause us to crash
+  // with nullptr later in the playback, so return false to abort.
+  if (!newDT) {
+    return false;
+  }
+
   aTranslator->AddDrawTarget(mRefPtr, newDT);
+  return true;
 }
 
 void
 RecordedCreateSimilarDrawTarget::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mSize);
   WriteElement(aStream, mFormat);
@@ -570,20 +587,21 @@ struct GenericPattern
     char mSurfPat[sizeof(SurfacePattern)];
   };
 
   PatternStorage *mStorage;
   Pattern *mPattern;
   Translator *mTranslator;
 };
 
-void
+bool
 RecordedFillRect::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->FillRect(mRect, *GenericPattern(mPattern, aTranslator), mOptions);
+  return true;
 }
 
 void
 RecordedFillRect::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mRect);
   WriteElement(aStream, mOptions);
@@ -600,20 +618,21 @@ RecordedFillRect::RecordedFillRect(istre
 
 void
 RecordedFillRect::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] FillRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height << ") ";
   OutputSimplePatternInfo(mPattern, aStringStream);
 }
 
-void
+bool
 RecordedStrokeRect::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->StrokeRect(mRect, *GenericPattern(mPattern, aTranslator), mStrokeOptions, mOptions);
+  return true;
 }
 
 void
 RecordedStrokeRect::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mRect);
   WriteElement(aStream, mOptions);
@@ -633,20 +652,21 @@ RecordedStrokeRect::RecordedStrokeRect(i
 void
 RecordedStrokeRect::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] StrokeRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height
                 << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
   OutputSimplePatternInfo(mPattern, aStringStream);
 }
 
-void
+bool
 RecordedStrokeLine::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->StrokeLine(mBegin, mEnd, *GenericPattern(mPattern, aTranslator), mStrokeOptions, mOptions);
+  return true;
 }
 
 void
 RecordedStrokeLine::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mBegin);
   WriteElement(aStream, mEnd);
@@ -668,20 +688,21 @@ RecordedStrokeLine::RecordedStrokeLine(i
 void
 RecordedStrokeLine::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] StrokeLine (" << mBegin.x << ", " << mBegin.y << " - " << mEnd.x << ", " << mEnd.y
                 << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
   OutputSimplePatternInfo(mPattern, aStringStream);
 }
 
-void
+bool
 RecordedFill::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->Fill(aTranslator->LookupPath(mPath), *GenericPattern(mPattern, aTranslator), mOptions);
+  return true;
 }
 
 RecordedFill::RecordedFill(istream &aStream)
   : RecordedDrawingEvent(FILL, aStream)
 {
   ReadElement(aStream, mPath);
   ReadElement(aStream, mOptions);
   ReadPatternData(aStream, mPattern);
@@ -703,23 +724,24 @@ RecordedFill::OutputSimpleEventInfo(stri
   OutputSimplePatternInfo(mPattern, aStringStream);
 }
 
 RecordedFillGlyphs::~RecordedFillGlyphs()
 {
   delete [] mGlyphs;
 }
 
-void
+bool
 RecordedFillGlyphs::PlayEvent(Translator *aTranslator) const
 {
   GlyphBuffer buffer;
   buffer.mGlyphs = mGlyphs;
   buffer.mNumGlyphs = mNumGlyphs;
   aTranslator->LookupDrawTarget(mDT)->FillGlyphs(aTranslator->LookupScaledFont(mScaledFont), buffer, *GenericPattern(mPattern, aTranslator), mOptions);
+  return true;
 }
 
 RecordedFillGlyphs::RecordedFillGlyphs(istream &aStream)
   : RecordedDrawingEvent(FILLGLYPHS, aStream)
 {
   ReadElement(aStream, mScaledFont);
   ReadElement(aStream, mOptions);
   ReadPatternData(aStream, mPattern);
@@ -741,20 +763,21 @@ RecordedFillGlyphs::RecordToStream(ostre
 
 void
 RecordedFillGlyphs::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] FillGlyphs (" << mScaledFont << ") ";
   OutputSimplePatternInfo(mPattern, aStringStream);
 }
 
-void
+bool
 RecordedMask::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->Mask(*GenericPattern(mSource, aTranslator), *GenericPattern(mMask, aTranslator), mOptions);
+  return true;
 }
 
 RecordedMask::RecordedMask(istream &aStream)
   : RecordedDrawingEvent(MASK, aStream)
 {
   ReadElement(aStream, mOptions);
   ReadPatternData(aStream, mSource);
   ReadPatternData(aStream, mMask);
@@ -773,20 +796,21 @@ void
 RecordedMask::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] Mask (Source: ";
   OutputSimplePatternInfo(mSource, aStringStream);
   aStringStream << " Mask: ";
   OutputSimplePatternInfo(mMask, aStringStream);
 }
 
-void
+bool
 RecordedStroke::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->Stroke(aTranslator->LookupPath(mPath), *GenericPattern(mPattern, aTranslator), mStrokeOptions, mOptions);
+  return true;
 }
 
 void
 RecordedStroke::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mPath);
   WriteElement(aStream, mOptions);
@@ -805,20 +829,21 @@ RecordedStroke::RecordedStroke(istream &
 
 void
 RecordedStroke::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] Stroke ("<< mPath << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
   OutputSimplePatternInfo(mPattern, aStringStream);
 }
 
-void
+bool
 RecordedClearRect::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->ClearRect(mRect);
+  return true;
 }
 
 void
 RecordedClearRect::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mRect);
 }
@@ -830,21 +855,22 @@ RecordedClearRect::RecordedClearRect(ist
 }
 
 void
 RecordedClearRect::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT<< "] ClearRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height << ") ";
 }
 
-void
+bool
 RecordedCopySurface::PlayEvent(Translator *aTranslator) const
 {
 	aTranslator->LookupDrawTarget(mDT)->CopySurface(aTranslator->LookupSourceSurface(mSourceSurface),
                                                   mSourceRect, mDest);
+  return true;
 }
 
 void
 RecordedCopySurface::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mSourceSurface);
   WriteElement(aStream, mSourceRect);
@@ -860,20 +886,21 @@ RecordedCopySurface::RecordedCopySurface
 }
 
 void
 RecordedCopySurface::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT<< "] CopySurface (" << mSourceSurface << ")";
 }
 
-void
+bool
 RecordedPushClip::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->PushClip(aTranslator->LookupPath(mPath));
+  return true;
 }
 
 void
 RecordedPushClip::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mPath);
 }
@@ -885,20 +912,21 @@ RecordedPushClip::RecordedPushClip(istre
 }
 
 void
 RecordedPushClip::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] PushClip (" << mPath << ") ";
 }
 
-void
+bool
 RecordedPushClipRect::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->PushClipRect(mRect);
+  return true;
 }
 
 void
 RecordedPushClipRect::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mRect);
 }
@@ -910,20 +938,21 @@ RecordedPushClipRect::RecordedPushClipRe
 }
 
 void
 RecordedPushClipRect::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] PushClipRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height << ") ";
 }
 
-void
+bool
 RecordedPopClip::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->PopClip();
+  return true;
 }
 
 void
 RecordedPopClip::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
 }
 
@@ -933,23 +962,24 @@ RecordedPopClip::RecordedPopClip(istream
 }
 
 void
 RecordedPopClip::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] PopClip";
 }
 
-void
+bool
 RecordedPushLayer::PlayEvent(Translator *aTranslator) const
 {
   SourceSurface* mask = mMask ? aTranslator->LookupSourceSurface(mMask)
                               : nullptr;
   aTranslator->LookupDrawTarget(mDT)->
     PushLayer(mOpaque, mOpacity, mask, mMaskTransform, mBounds, mCopyBackground);
+  return true;
 }
 
 void
 RecordedPushLayer::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mOpaque);
   WriteElement(aStream, mOpacity);
@@ -972,20 +1002,21 @@ RecordedPushLayer::RecordedPushLayer(ist
 
 void
 RecordedPushLayer::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] PushPLayer (Opaque=" << mOpaque <<
     ", Opacity=" << mOpacity << ", Mask Ref=" << mMask << ") ";
 }
 
-void
+bool
 RecordedPopLayer::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->PopLayer();
+  return true;
 }
 
 void
 RecordedPopLayer::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
 }
 
@@ -995,20 +1026,21 @@ RecordedPopLayer::RecordedPopLayer(istre
 }
 
 void
 RecordedPopLayer::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] PopLayer";
 }
 
-void
+bool
 RecordedSetTransform::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->SetTransform(mTransform);
+  return true;
 }
 
 void
 RecordedSetTransform::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mTransform);
 }
@@ -1021,22 +1053,23 @@ RecordedSetTransform::RecordedSetTransfo
 
 void
 RecordedSetTransform::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] SetTransform [ " << mTransform._11 << " " << mTransform._12 << " ; " <<
     mTransform._21 << " " << mTransform._22 << " ; " << mTransform._31 << " " << mTransform._32 << " ]";
 }
 
-void
+bool
 RecordedDrawSurface::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->
     DrawSurface(aTranslator->LookupSourceSurface(mRefSource), mDest, mSource,
                 mDSOptions, mOptions);
+  return true;
 }
 
 void
 RecordedDrawSurface::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mRefSource);
   WriteElement(aStream, mDest);
@@ -1056,22 +1089,23 @@ RecordedDrawSurface::RecordedDrawSurface
 }
 
 void
 RecordedDrawSurface::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] DrawSurface (" << mRefSource << ")";
 }
 
-void
+bool
 RecordedDrawFilter::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->
     DrawFilter(aTranslator->LookupFilterNode(mNode), mSourceRect,
                 mDestPoint, mOptions);
+  return true;
 }
 
 void
 RecordedDrawFilter::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mNode);
   WriteElement(aStream, mSourceRect);
@@ -1089,22 +1123,23 @@ RecordedDrawFilter::RecordedDrawFilter(i
 }
 
 void
 RecordedDrawFilter::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] DrawFilter (" << mNode << ")";
 }
 
-void
+bool
 RecordedDrawSurfaceWithShadow::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->
     DrawSurfaceWithShadow(aTranslator->LookupSourceSurface(mRefSource),
                           mDest, mColor, mOffset, mSigma, mOp);
+  return true;
 }
 
 void
 RecordedDrawSurfaceWithShadow::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   WriteElement(aStream, mRefSource);
   WriteElement(aStream, mDest);
@@ -1136,17 +1171,17 @@ RecordedPathCreation::RecordedPathCreati
   : RecordedEvent(PATHCREATION), mRefPtr(aPath), mFillRule(aPath->mFillRule), mPathOps(aPath->mPathOps)
 {
 }
 
 RecordedPathCreation::~RecordedPathCreation()
 {
 }
 
-void
+bool
 RecordedPathCreation::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<PathBuilder> builder = 
     aTranslator->GetReferenceDrawTarget()->CreatePathBuilder(mFillRule);
 
   for (size_t i = 0; i < mPathOps.size(); i++) {
     const PathOp &op = mPathOps[i];
     switch (op.mType) {
@@ -1165,16 +1200,17 @@ RecordedPathCreation::PlayEvent(Translat
     case PathOp::OP_CLOSE:
       builder->Close();
       break;
     }
   }
 
   RefPtr<Path> path = builder->Finish();
   aTranslator->AddPath(mRefPtr, path);
+  return true;
 }
 
 void
 RecordedPathCreation::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, uint64_t(mPathOps.size()));
   WriteElement(aStream, mFillRule);
@@ -1221,20 +1257,21 @@ RecordedPathCreation::RecordedPathCreati
 
 }
 
 void
 RecordedPathCreation::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] Path created (OpCount: " << mPathOps.size() << ")";
 }
-void
+bool
 RecordedPathDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemovePath(mRefPtr);
+  return true;
 }
 
 void
 RecordedPathDestruction::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
 }
 
@@ -1252,22 +1289,23 @@ RecordedPathDestruction::OutputSimpleEve
 
 RecordedSourceSurfaceCreation::~RecordedSourceSurfaceCreation()
 {
   if (mDataOwned) {
     delete [] mData;
   }
 }
 
-void
+bool
 RecordedSourceSurfaceCreation::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<SourceSurface> src = aTranslator->GetReferenceDrawTarget()->
     CreateSourceSurfaceFromData(mData, mSize, mSize.width * BytesPerPixel(mFormat), mFormat);
   aTranslator->AddSourceSurface(mRefPtr, src);
+  return true;
 }
 
 void
 RecordedSourceSurfaceCreation::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mSize);
   WriteElement(aStream, mFormat);
@@ -1287,20 +1325,21 @@ RecordedSourceSurfaceCreation::RecordedS
 }
 
 void
 RecordedSourceSurfaceCreation::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] SourceSurface created (Size: " << mSize.width << "x" << mSize.height << ")";
 }
 
-void
+bool
 RecordedSourceSurfaceDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemoveSourceSurface(mRefPtr);
+  return true;
 }
 
 void
 RecordedSourceSurfaceDestruction::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
 }
 
@@ -1315,22 +1354,23 @@ RecordedSourceSurfaceDestruction::Output
 {
   aStringStream << "[" << mRefPtr << "] SourceSurface Destroyed";
 }
 
 RecordedFilterNodeCreation::~RecordedFilterNodeCreation()
 {
 }
 
-void
+bool
 RecordedFilterNodeCreation::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<FilterNode> node = aTranslator->GetReferenceDrawTarget()->
     CreateFilter(mType);
   aTranslator->AddFilterNode(mRefPtr, node);
+  return true;
 }
 
 void
 RecordedFilterNodeCreation::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mType);
 }
@@ -1343,20 +1383,21 @@ RecordedFilterNodeCreation::RecordedFilt
 }
 
 void
 RecordedFilterNodeCreation::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] FilterNode created (Type: " << int(mType) << ")";
 }
 
-void
+bool
 RecordedFilterNodeDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemoveFilterNode(mRefPtr);
+  return true;
 }
 
 void
 RecordedFilterNodeDestruction::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
 }
 
@@ -1374,22 +1415,23 @@ RecordedFilterNodeDestruction::OutputSim
 
 RecordedGradientStopsCreation::~RecordedGradientStopsCreation()
 {
   if (mDataOwned) {
     delete [] mStops;
   }
 }
 
-void
+bool
 RecordedGradientStopsCreation::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<GradientStops> src = aTranslator->GetReferenceDrawTarget()->
     CreateGradientStops(mStops, mNumStops, mExtendMode);
   aTranslator->AddGradientStops(mRefPtr, src);
+  return true;
 }
 
 void
 RecordedGradientStopsCreation::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mExtendMode);
   WriteElement(aStream, mNumStops);
@@ -1408,20 +1450,21 @@ RecordedGradientStopsCreation::RecordedG
 }
 
 void
 RecordedGradientStopsCreation::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] GradientStops created (Stops: " << mNumStops << ")";
 }
 
-void
+bool
 RecordedGradientStopsDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemoveGradientStops(mRefPtr);
+  return true;
 }
 
 void
 RecordedGradientStopsDestruction::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
 }
 
@@ -1432,21 +1475,22 @@ RecordedGradientStopsDestruction::Record
 }
 
 void
 RecordedGradientStopsDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] GradientStops Destroyed";
 }
 
-void
+bool
 RecordedSnapshot::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<SourceSurface> src = aTranslator->LookupDrawTarget(mDT)->Snapshot();
   aTranslator->AddSourceSurface(mRefPtr, src);
+  return true;
 }
 
 void
 RecordedSnapshot::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mDT);
 }
@@ -1464,23 +1508,24 @@ RecordedSnapshot::OutputSimpleEventInfo(
   aStringStream << "[" << mRefPtr << "] Snapshot Created (DT: " << mDT << ")";
 }
 
 RecordedFontData::~RecordedFontData()
 {
   delete[] mData;
 }
 
-void
+bool
 RecordedFontData::PlayEvent(Translator *aTranslator) const
 {
   RefPtr<NativeFontResource> fontResource =
     Factory::CreateNativeFontResource(mData, mFontDetails.size,
                                       aTranslator->GetDesiredFontType());
   aTranslator->AddNativeFontResource(mFontDetails.fontDataKey, fontResource);
+  return true;
 }
 
 void
 RecordedFontData::RecordToStream(std::ostream &aStream) const
 {
   MOZ_ASSERT(mGetFontFileDataSucceeded);
 
   WriteElement(aStream, mFontDetails.fontDataKey);
@@ -1527,33 +1572,34 @@ RecordedFontData::RecordedFontData(istre
   mData = new uint8_t[mFontDetails.size];
   aStream.read((char*)mData, mFontDetails.size);
 }
 
 RecordedFontDescriptor::~RecordedFontDescriptor()
 {
 }
 
-void
+bool
 RecordedFontDescriptor::PlayEvent(Translator *aTranslator) const
 {
   MOZ_ASSERT(mType == FontType::GDI);
 
   NativeFont nativeFont;
   nativeFont.mType = (NativeFontType)mType;
   nativeFont.mFont = (void*)&mData[0];
 
   RefPtr<ScaledFont> font =
     Factory::CreateScaledFontForNativeFont(nativeFont, mFontSize);
 
 #ifdef USE_CAIRO_SCALED_FONT
   static_cast<ScaledFontBase*>(font.get())->PopulateCairoScaledFont();
 #endif
 
   aTranslator->AddScaledFont(mRefPtr, font);
+  return true;
 }
 
 void
 RecordedFontDescriptor::RecordToStream(std::ostream &aStream) const
 {
   MOZ_ASSERT(mHasDesc);
   WriteElement(aStream, mType);
   WriteElement(aStream, mFontSize);
@@ -1583,22 +1629,23 @@ RecordedFontDescriptor::RecordedFontDesc
   ReadElement(aStream, mRefPtr);
 
   size_t size;
   ReadElement(aStream, size);
   mData.resize(size);
   aStream.read((char*)&mData[0], size);
 }
 
-void
+bool
 RecordedScaledFontCreation::PlayEvent(Translator *aTranslator) const
 {
   NativeFontResource *fontResource = aTranslator->LookupNativeFontResource(mFontDataKey);
   RefPtr<ScaledFont> scaledFont = fontResource->CreateScaledFont(mIndex, mGlyphSize);
   aTranslator->AddScaledFont(mRefPtr, scaledFont);
+  return true;
 }
 
 void
 RecordedScaledFontCreation::RecordToStream(std::ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
   WriteElement(aStream, mFontDataKey);
   WriteElement(aStream, mIndex);
@@ -1615,20 +1662,21 @@ RecordedScaledFontCreation::RecordedScal
   : RecordedEvent(SCALEDFONTCREATION)
 {
   ReadElement(aStream, mRefPtr);
   ReadElement(aStream, mFontDataKey);
   ReadElement(aStream, mIndex);
   ReadElement(aStream, mGlyphSize);
 }
 
-void
+bool
 RecordedScaledFontDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemoveScaledFont(mRefPtr);
+  return true;
 }
 
 void
 RecordedScaledFontDestruction::RecordToStream(ostream &aStream) const
 {
   WriteElement(aStream, mRefPtr);
 }
 
@@ -1639,23 +1687,24 @@ RecordedScaledFontDestruction::RecordedS
 }
 
 void
 RecordedScaledFontDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] ScaledFont Destroyed";
 }
 
-void
+bool
 RecordedMaskSurface::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->
     MaskSurface(*GenericPattern(mPattern, aTranslator),
                 aTranslator->LookupSourceSurface(mRefMask),
                 mOffset, mOptions);
+  return true;
 }
 
 void
 RecordedMaskSurface::RecordToStream(ostream &aStream) const
 {
   RecordedDrawingEvent::RecordToStream(aStream);
   RecordPatternData(aStream, mPattern);
   WriteElement(aStream, mRefMask);
@@ -1681,17 +1730,17 @@ RecordedMaskSurface::OutputSimpleEventIn
 
 template<typename T>
 void
 ReplaySetAttribute(FilterNode *aNode, uint32_t aIndex, T aValue)
 {
   aNode->SetAttribute(aIndex, aValue);
 }
 
-void
+bool
 RecordedFilterNodeSetAttribute::PlayEvent(Translator *aTranslator) const
 {
 #define REPLAY_SET_ATTRIBUTE(type, argtype) \
   case ARGTYPE_##argtype: \
   ReplaySetAttribute(aTranslator->LookupFilterNode(mNode), mIndex, *(type*)&mPayload.front()); \
   break
 
   switch (mArgType) {
@@ -1710,16 +1759,18 @@ RecordedFilterNodeSetAttribute::PlayEven
     REPLAY_SET_ATTRIBUTE(Color, COLOR);
   case ARGTYPE_FLOAT_ARRAY:
     aTranslator->LookupFilterNode(mNode)->SetAttribute(
       mIndex,
       reinterpret_cast<const Float*>(&mPayload.front()),
       mPayload.size() / sizeof(Float));
     break;
   }
+
+  return true;
 }
 
 void
 RecordedFilterNodeSetAttribute::RecordToStream(ostream &aStream) const
 {
   RecordedEvent::RecordToStream(aStream);
   WriteElement(aStream, mNode);
   WriteElement(aStream, mIndex);
@@ -1741,26 +1792,28 @@ RecordedFilterNodeSetAttribute::Recorded
 }
 
 void
 RecordedFilterNodeSetAttribute::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ")";
 }
 
-void
+bool
 RecordedFilterNodeSetInput::PlayEvent(Translator *aTranslator) const
 {
   if (mInputFilter) {
     aTranslator->LookupFilterNode(mNode)->SetInput(
       mIndex, aTranslator->LookupFilterNode(mInputFilter));
   } else {
     aTranslator->LookupFilterNode(mNode)->SetInput(
       mIndex, aTranslator->LookupSourceSurface(mInputSurface));
   }
+
+  return true;
 }
 
 void
 RecordedFilterNodeSetInput::RecordToStream(ostream &aStream) const
 {
   RecordedEvent::RecordToStream(aStream);
   WriteElement(aStream, mNode);
   WriteElement(aStream, mIndex);
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -197,17 +197,25 @@ public:
     POPLAYER,
   };
   static const uint32_t kTotalEventTypes = RecordedEvent::FILTERNODESETINPUT + 1;
 
   virtual ~RecordedEvent() {}
 
   static std::string GetEventName(EventType aType);
 
-  virtual void PlayEvent(Translator *aTranslator) const {}
+  /**
+   * Play back this event using the translator. Note that derived classes should
+   * only return false when there is a fatal error, as it will probably mean the
+   * translation will abort.
+   * @param aTranslator Translator to be used for retrieving other referenced
+   *                    objects and making playback decisions.
+   * @return true unless a fatal problem has occurred and playback should abort.
+   */
+  virtual bool PlayEvent(Translator *aTranslator) const { return true; }
 
   virtual void RecordToStream(std::ostream &aStream) const {}
 
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const { }
 
   void RecordPatternData(std::ostream &aStream, const PatternStorage &aPatternStorage) const;
   void ReadPatternData(std::istream &aStream, PatternStorage &aPatternStorage) const;
   void StorePattern(PatternStorage &aDestination, const Pattern &aSource) const;
@@ -257,17 +265,17 @@ protected:
 class RecordedDrawTargetCreation : public RecordedEvent {
 public:
   RecordedDrawTargetCreation(ReferencePtr aRefPtr, BackendType aType, const IntSize &aSize, SurfaceFormat aFormat,
                              bool aHasExistingData = false, SourceSurface *aExistingData = nullptr)
     : RecordedEvent(DRAWTARGETCREATION), mRefPtr(aRefPtr), mBackendType(aType), mSize(aSize), mFormat(aFormat)
     , mHasExistingData(aHasExistingData), mExistingData(aExistingData)
   {}
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "DrawTarget Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 
   ReferencePtr mRefPtr;
@@ -284,17 +292,17 @@ private:
 };
 
 class RecordedDrawTargetDestruction : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedDrawTargetDestruction(ReferencePtr aRefPtr)
     : RecordedEvent(DRAWTARGETDESTRUCTION), mRefPtr(aRefPtr)
   {}
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "DrawTarget Destruction"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 
   ReferencePtr mRefPtr;
@@ -311,17 +319,17 @@ class RecordedCreateSimilarDrawTarget : 
 public:
   RecordedCreateSimilarDrawTarget(ReferencePtr aRefPtr, const IntSize &aSize,
                                   SurfaceFormat aFormat)
     : RecordedEvent(CREATESIMILARDRAWTARGET)
     , mRefPtr(aRefPtr) , mSize(aSize), mFormat(aFormat)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "CreateSimilarDrawTarget"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 
   ReferencePtr mRefPtr;
@@ -337,17 +345,17 @@ private:
 class RecordedFillRect : public RecordedDrawingEvent {
 public:
   RecordedFillRect(DrawTarget *aDT, const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions)
     : RecordedDrawingEvent(FILLRECT, aDT), mRect(aRect), mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "FillRect"; }
 private:
   friend class RecordedEvent;
 
@@ -363,17 +371,17 @@ public:
   RecordedStrokeRect(DrawTarget *aDT, const Rect &aRect, const Pattern &aPattern,
                      const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions)
     : RecordedDrawingEvent(STROKERECT, aDT), mRect(aRect),
       mStrokeOptions(aStrokeOptions), mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "StrokeRect"; }
 private:
   friend class RecordedEvent;
 
@@ -391,17 +399,17 @@ public:
                      const Pattern &aPattern, const StrokeOptions &aStrokeOptions,
                      const DrawOptions &aOptions)
     : RecordedDrawingEvent(STROKELINE, aDT), mBegin(aBegin), mEnd(aEnd),
       mStrokeOptions(aStrokeOptions), mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "StrokeLine"; }
 private:
   friend class RecordedEvent;
 
@@ -417,17 +425,17 @@ private:
 class RecordedFill : public RecordedDrawingEvent {
 public:
   RecordedFill(DrawTarget *aDT, ReferencePtr aPath, const Pattern &aPattern, const DrawOptions &aOptions)
     : RecordedDrawingEvent(FILL, aDT), mPath(aPath), mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "Fill"; }
 private:
   friend class RecordedEvent;
 
@@ -446,17 +454,17 @@ public:
   {
     StorePattern(mPattern, aPattern);
     mNumGlyphs = aNumGlyphs;
     mGlyphs = new Glyph[aNumGlyphs];
     memcpy(mGlyphs, aGlyphs, sizeof(Glyph) * aNumGlyphs);
   }
   virtual ~RecordedFillGlyphs();
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "FillGlyphs"; }
 private:
   friend class RecordedEvent;
 
@@ -473,17 +481,17 @@ class RecordedMask : public RecordedDraw
 public:
   RecordedMask(DrawTarget *aDT, const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions)
     : RecordedDrawingEvent(MASK, aDT), mOptions(aOptions)
   {
     StorePattern(mSource, aSource);
     StorePattern(mMask, aMask);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "Mask"; }
 private:
   friend class RecordedEvent;
 
@@ -499,17 +507,17 @@ public:
   RecordedStroke(DrawTarget *aDT, ReferencePtr aPath, const Pattern &aPattern,
                      const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions)
     : RecordedDrawingEvent(STROKE, aDT), mPath(aPath),
       mStrokeOptions(aStrokeOptions), mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "Stroke"; }
 private:
   friend class RecordedEvent;
 
@@ -523,17 +531,17 @@ private:
 
 class RecordedClearRect : public RecordedDrawingEvent {
 public:
   RecordedClearRect(DrawTarget *aDT, const Rect &aRect)
     : RecordedDrawingEvent(CLEARRECT, aDT), mRect(aRect)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "ClearRect"; }
 private:
   friend class RecordedEvent;
 
@@ -546,17 +554,17 @@ class RecordedCopySurface : public Recor
 public:
   RecordedCopySurface(DrawTarget *aDT, ReferencePtr aSourceSurface,
                       const IntRect &aSourceRect, const IntPoint &aDest)
     : RecordedDrawingEvent(COPYSURFACE, aDT), mSourceSurface(aSourceSurface),
 	  mSourceRect(aSourceRect), mDest(aDest)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "CopySurface"; }
 private:
   friend class RecordedEvent;
 
@@ -569,17 +577,17 @@ private:
 
 class RecordedPushClip : public RecordedDrawingEvent {
 public:
   RecordedPushClip(DrawTarget *aDT, ReferencePtr aPath)
     : RecordedDrawingEvent(PUSHCLIP, aDT), mPath(aPath)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "PushClip"; }
 private:
   friend class RecordedEvent;
 
@@ -590,17 +598,17 @@ private:
 
 class RecordedPushClipRect : public RecordedDrawingEvent {
 public:
   RecordedPushClipRect(DrawTarget *aDT, const Rect &aRect)
     : RecordedDrawingEvent(PUSHCLIPRECT, aDT), mRect(aRect)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "PushClipRect"; }
 private:
   friend class RecordedEvent;
 
@@ -610,17 +618,17 @@ private:
 };
 
 class RecordedPopClip : public RecordedDrawingEvent {
 public:
   MOZ_IMPLICIT RecordedPopClip(DrawTarget *aDT)
     : RecordedDrawingEvent(POPCLIP, aDT)
   {}
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "PopClip"; }
 private:
   friend class RecordedEvent;
 
@@ -633,17 +641,17 @@ public:
                     SourceSurface* aMask, const Matrix& aMaskTransform,
                     const IntRect& aBounds, bool aCopyBackground)
     : RecordedDrawingEvent(PUSHLAYER, aDT), mOpaque(aOpaque)
     , mOpacity(aOpacity), mMask(aMask), mMaskTransform(aMaskTransform)
     , mBounds(aBounds), mCopyBackground(aCopyBackground)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "PushLayer"; }
 private:
   friend class RecordedEvent;
 
@@ -659,17 +667,17 @@ private:
 
 class RecordedPopLayer : public RecordedDrawingEvent {
 public:
   MOZ_IMPLICIT RecordedPopLayer(DrawTarget* aDT)
     : RecordedDrawingEvent(POPLAYER, aDT)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "PopLayer"; }
 private:
   friend class RecordedEvent;
 
@@ -678,17 +686,17 @@ private:
 
 class RecordedSetTransform : public RecordedDrawingEvent {
 public:
   RecordedSetTransform(DrawTarget *aDT, const Matrix &aTransform)
     : RecordedDrawingEvent(SETTRANSFORM, aDT), mTransform(aTransform)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "SetTransform"; }
 private:
   friend class RecordedEvent;
 
@@ -702,17 +710,17 @@ public:
   RecordedDrawSurface(DrawTarget *aDT, ReferencePtr aRefSource, const Rect &aDest,
                       const Rect &aSource, const DrawSurfaceOptions &aDSOptions,
                       const DrawOptions &aOptions)
     : RecordedDrawingEvent(DRAWSURFACE, aDT), mRefSource(aRefSource), mDest(aDest)
     , mSource(aSource), mDSOptions(aDSOptions), mOptions(aOptions)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "DrawSurface"; }
 private:
   friend class RecordedEvent;
 
@@ -730,17 +738,17 @@ public:
   RecordedDrawSurfaceWithShadow(DrawTarget *aDT, ReferencePtr aRefSource, const Point &aDest,
                                 const Color &aColor, const Point &aOffset,
                                 Float aSigma, CompositionOp aOp)
     : RecordedDrawingEvent(DRAWSURFACEWITHSHADOW, aDT), mRefSource(aRefSource), mDest(aDest)
     , mColor(aColor), mOffset(aOffset), mSigma(aSigma), mOp(aOp)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "DrawSurfaceWithShadow"; }
 private:
   friend class RecordedEvent;
 
@@ -760,17 +768,17 @@ public:
                      const Rect &aSourceRect,
                      const Point &aDestPoint,
                      const DrawOptions &aOptions)
     : RecordedDrawingEvent(DRAWFILTER, aDT), mNode(aNode), mSourceRect(aSourceRect)
     , mDestPoint(aDestPoint), mOptions(aOptions)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "DrawFilter"; }
 private:
   friend class RecordedEvent;
 
@@ -782,17 +790,17 @@ private:
   DrawOptions mOptions;
 };
 
 class RecordedPathCreation : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedPathCreation(PathRecording *aPath);
   ~RecordedPathCreation();
   
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "Path Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -806,17 +814,17 @@ private:
 
 class RecordedPathDestruction : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedPathDestruction(PathRecording *aPath)
     : RecordedEvent(PATHDESTRUCTION), mRefPtr(aPath)
   {
   }
   
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "Path Destruction"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -832,17 +840,17 @@ public:
                                 const IntSize &aSize, SurfaceFormat aFormat)
     : RecordedEvent(SOURCESURFACECREATION), mRefPtr(aRefPtr), mData(aData)
     , mStride(aStride), mSize(aSize), mFormat(aFormat), mDataOwned(false)
   {
   }
 
   ~RecordedSourceSurfaceCreation();
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "SourceSurface Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -859,17 +867,17 @@ private:
 
 class RecordedSourceSurfaceDestruction : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedSourceSurfaceDestruction(ReferencePtr aRefPtr)
     : RecordedEvent(SOURCESURFACEDESTRUCTION), mRefPtr(aRefPtr)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "SourceSurface Destruction"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -883,17 +891,17 @@ class RecordedFilterNodeCreation : publi
 public:
   RecordedFilterNodeCreation(ReferencePtr aRefPtr, FilterType aType)
     : RecordedEvent(FILTERNODECREATION), mRefPtr(aRefPtr), mType(aType)
   {
   }
 
   ~RecordedFilterNodeCreation();
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "FilterNode Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -906,17 +914,17 @@ private:
 
 class RecordedFilterNodeDestruction : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedFilterNodeDestruction(ReferencePtr aRefPtr)
     : RecordedEvent(FILTERNODEDESTRUCTION), mRefPtr(aRefPtr)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "FilterNode Destruction"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -932,17 +940,17 @@ public:
                                 uint32_t aNumStops, ExtendMode aExtendMode)
     : RecordedEvent(GRADIENTSTOPSCREATION), mRefPtr(aRefPtr), mStops(aStops)
     , mNumStops(aNumStops), mExtendMode(aExtendMode), mDataOwned(false)
   {
   }
 
   ~RecordedGradientStopsCreation();
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "GradientStops Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -958,17 +966,17 @@ private:
 
 class RecordedGradientStopsDestruction : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedGradientStopsDestruction(ReferencePtr aRefPtr)
     : RecordedEvent(GRADIENTSTOPSDESTRUCTION), mRefPtr(aRefPtr)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "GradientStops Destruction"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -980,17 +988,17 @@ private:
 
 class RecordedSnapshot : public RecordedEvent {
 public:
   RecordedSnapshot(ReferencePtr aRefPtr, DrawTarget *aDT)
     : RecordedEvent(SNAPSHOT), mRefPtr(aRefPtr), mDT(aDT)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "Snapshot"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -1014,17 +1022,17 @@ public:
   explicit RecordedFontData(ScaledFont *aScaledFont)
     : RecordedEvent(FONTDATA), mData(nullptr)
   {
     mGetFontFileDataSucceeded = aScaledFont->GetFontFileData(&FontDataProc, this);
   }
 
   ~RecordedFontData();
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "Font Data"; }
   virtual ReferencePtr GetObjectRef() const { return nullptr; };
 
   void SetFontData(const uint8_t *aData, uint32_t aSize, uint32_t aIndex,
@@ -1060,17 +1068,17 @@ public:
   {
     mHasDesc = aScaledFont->GetFontDescriptor(FontDescCb, this);
   }
 
   ~RecordedFontDescriptor();
 
   bool IsValid() const { return mHasDesc; }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "Font Desc"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 
 private:
@@ -1094,17 +1102,17 @@ public:
   RecordedScaledFontCreation(ReferencePtr aRefPtr,
                              RecordedFontDetails aFontDetails)
     : RecordedEvent(SCALEDFONTCREATION), mRefPtr(aRefPtr)
     , mFontDataKey(aFontDetails.fontDataKey)
     , mGlyphSize(aFontDetails.glyphSize) , mIndex(aFontDetails.index)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "ScaledFont Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 
 private:
@@ -1120,17 +1128,17 @@ private:
 
 class RecordedScaledFontDestruction : public RecordedEvent {
 public:
   MOZ_IMPLICIT RecordedScaledFontDestruction(ReferencePtr aRefPtr)
     : RecordedEvent(SCALEDFONTDESTRUCTION), mRefPtr(aRefPtr)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "ScaledFont Destruction"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 private:
   friend class RecordedEvent;
@@ -1145,17 +1153,17 @@ public:
   RecordedMaskSurface(DrawTarget *aDT, const Pattern &aPattern, ReferencePtr aRefMask,
                       const Point &aOffset, const DrawOptions &aOptions)
     : RecordedDrawingEvent(MASKSURFACE, aDT), mRefMask(aRefMask), mOffset(aOffset)
     , mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
 
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
   
   virtual std::string GetName() const { return "MaskSurface"; }
 private:
   friend class RecordedEvent;
 
@@ -1197,17 +1205,17 @@ public:
 
   RecordedFilterNodeSetAttribute(FilterNode *aNode, uint32_t aIndex, const Float *aFloat, uint32_t aSize)
     : RecordedEvent(FILTERNODESETATTRIBUTE), mNode(aNode), mIndex(aIndex), mArgType(ARGTYPE_FLOAT_ARRAY)
   {
     mPayload.resize(sizeof(Float) * aSize);
     memcpy(&mPayload.front(), aFloat, sizeof(Float) * aSize);
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "SetAttribute"; }
 
   virtual ReferencePtr GetObjectRef() const { return mNode; }
 
 private:
@@ -1232,17 +1240,17 @@ public:
   }
 
   RecordedFilterNodeSetInput(FilterNode *aNode, uint32_t aIndex, SourceSurface *aInputSurface)
     : RecordedEvent(FILTERNODESETINPUT), mNode(aNode), mIndex(aIndex)
     , mInputFilter(nullptr), mInputSurface(aInputSurface)
   {
   }
 
-  virtual void PlayEvent(Translator *aTranslator) const;
+  virtual bool PlayEvent(Translator *aTranslator) const;
   virtual void RecordToStream(std::ostream &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "SetInput"; }
 
   virtual ReferencePtr GetObjectRef() const { return mNode; }
 
 private:
--- a/gfx/cairo/cairo/src/cairo-dwrite-font.cpp
+++ b/gfx/cairo/cairo/src/cairo-dwrite-font.cpp
@@ -636,37 +636,42 @@ cairo_warn cairo_int_status_t
 		// and such, adjust positions by the inverse matrix now.
 		offsets[i].ascenderOffset = -(FLOAT)y;
 		offsets[i].advanceOffset = (FLOAT)x;
 		advances[i] = 0.0;
 	    }
 	    run.fontEmSize = 1.0f;
 	}
 
+	HRESULT hr;
 	if (!transform) {
-	    DWriteFactory::Instance()->CreateGlyphRunAnalysis(&run,
+	    hr = DWriteFactory::Instance()->CreateGlyphRunAnalysis(&run,
 							      1.0f,
 							      NULL,
 							      DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC,
 							      DWRITE_MEASURING_MODE_NATURAL,
 							      0,
 							      0,
 							      &analysis);
 	} else {
 	    DWRITE_MATRIX dwmatrix = _cairo_dwrite_matrix_from_matrix(&dwritesf->mat);
-	    DWriteFactory::Instance()->CreateGlyphRunAnalysis(&run,
+	    hr = DWriteFactory::Instance()->CreateGlyphRunAnalysis(&run,
 							      1.0f,
 							      &dwmatrix,
 							      DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC,
 							      DWRITE_MEASURING_MODE_NATURAL,
 							      0,
 							      0,
 							      &analysis);
 	}
 
+	if (FAILED(hr) || !analysis) {
+	    return CAIRO_INT_STATUS_UNSUPPORTED;
+	}
+
 	RECT r;
 	r.left = 0;
 	r.top = 0;
 	r.right = width;
 	r.bottom = height;
 
 	BYTE *surface = new BYTE[width * height * 3];
 
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -287,21 +287,20 @@ XPCWrappedNativeScope::EnsureContentXBLS
     SandboxOptions options;
     options.wantXrays = false;
     options.wantComponents = true;
     options.proto = global;
     options.sameZoneAs = global;
 
     // Use an nsExpandedPrincipal to create asymmetric security.
     nsIPrincipal* principal = GetPrincipal();
-    nsCOMPtr<nsIExpandedPrincipal> ep;
-    MOZ_ASSERT(!(ep = do_QueryInterface(principal)));
+    MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
     nsTArray< nsCOMPtr<nsIPrincipal> > principalAsArray(1);
     principalAsArray.AppendElement(principal);
-    ep = new nsExpandedPrincipal(principalAsArray);
+    nsCOMPtr<nsIExpandedPrincipal> ep = new nsExpandedPrincipal(principalAsArray);
 
     // Create the sandbox.
     RootedValue v(cx);
     nsresult rv = CreateSandboxObject(cx, &v, ep, options);
     NS_ENSURE_SUCCESS(rv, nullptr);
     mContentXBLScope = &v.toObject();
 
     // Tag it.
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -3259,35 +3259,35 @@ ElementRestyler::Restyle(nsRestyleHint a
 
   // TEMPORARY (until bug 918064):  Call RestyleSelf for each
   // continuation or block-in-inline sibling.
 
   // We must make a single decision on how to process this frame and
   // its descendants, yet RestyleSelf might return different RestyleResult
   // values for the different same-style continuations.  |result| is our
   // overall decision.
-  RestyleResult result = RestyleResult(0);
+  RestyleResult result = RestyleResult::eNone;
   uint32_t swappedStructs = 0;
 
   nsRestyleHint thisRestyleHint = aRestyleHint;
 
   bool haveMoreContinuations = false;
   for (nsIFrame* f = mFrame; f; ) {
     RestyleResult thisResult =
       RestyleSelf(f, thisRestyleHint, &swappedStructs, swaps);
 
-    if (thisResult != eRestyleResult_Stop) {
+    if (thisResult != RestyleResult::eStop) {
       // Calls to RestyleSelf for later same-style continuations must not
-      // return eRestyleResult_Stop, so pass eRestyle_Force in to them.
+      // return RestyleResult::eStop, so pass eRestyle_Force in to them.
       thisRestyleHint = nsRestyleHint(thisRestyleHint | eRestyle_Force);
 
-      if (result == eRestyleResult_Stop) {
-        // We received eRestyleResult_Stop for earlier same-style
-        // continuations, and eRestyleResult_StopWithStyleChange or
-        // eRestyleResult_Continue(AndForceDescendants) for this one; go
+      if (result == RestyleResult::eStop) {
+        // We received RestyleResult::eStop for earlier same-style
+        // continuations, and RestyleResult::eStopWithStyleChange or
+        // RestyleResult::eContinue(AndForceDescendants) for this one; go
         // back and force-restyle the earlier continuations.
         result = thisResult;
         f = mFrame;
         continue;
       }
     }
 
     if (thisResult > result) {
@@ -3316,24 +3316,24 @@ ElementRestyler::Restyle(nsRestyleHint a
   if (haveMoreContinuations && hintToRestore) {
     // If we have more continuations with different style (e.g., because
     // we're inside a ::first-letter or ::first-line), put the restyle
     // hint back.
     mRestyleTracker.AddPendingRestyleToTable(mContent->AsElement(),
                                              hintToRestore, nsChangeHint(0));
   }
 
-  if (result == eRestyleResult_Stop) {
+  if (result == RestyleResult::eStop) {
     MOZ_ASSERT(mFrame->StyleContext() == oldContext,
                "frame should have been left with its old style context");
 
     nsIFrame* unused;
     nsStyleContext* newParent = mFrame->GetParentStyleContext(&unused);
     if (oldContext->GetParent() != newParent) {
-      // If we received eRestyleResult_Stop, then the old style context was
+      // If we received RestyleResult::eStop, then the old style context was
       // left on mFrame.  Since we ended up restyling our parent, change
       // this old style context to point to its new parent.
       LOG_RESTYLE("moving style context %p from old parent %p to new parent %p",
                   oldContext.get(), oldContext->GetParent(), newParent);
       // We keep strong references to the new parent around until the end
       // of the restyle, in case:
       //   (a) we swapped structs between the old and new parent,
       //   (b) some descendants of the old parent are not getting restyled
@@ -3358,24 +3358,24 @@ ElementRestyler::Restyle(nsRestyleHint a
 
     mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
     if (aRestyleHint & eRestyle_SomeDescendants) {
       ConditionallyRestyleChildren();
     }
     return;
   }
 
-  if (result == eRestyleResult_StopWithStyleChange &&
+  if (result == RestyleResult::eStopWithStyleChange &&
       !(mHintsHandled & nsChangeHint_ReconstructFrame)) {
     MOZ_ASSERT(mFrame->StyleContext() != oldContext,
-               "eRestyleResult_StopWithStyleChange should only be returned "
+               "RestyleResult::eStopWithStyleChange should only be returned "
                "if we got a new style context or we will reconstruct");
     MOZ_ASSERT(swappedStructs == 0,
                "should have ensured we didn't swap structs when "
-               "returning eRestyleResult_StopWithStyleChange");
+               "returning RestyleResult::eStopWithStyleChange");
 
     // We need to ensure that all of the frames that inherit their style
     // from oldContext are able to be moved across to newContext.
     // MoveStyleContextsForChildren will check for certain conditions
     // to ensure it is safe to move all of the relevant child style
     // contexts to newContext.  If these conditions fail, it will
     // return false, and we'll have to continue restyling.
     const bool canStop = MoveStyleContextsForChildren(oldContext);
@@ -3392,34 +3392,34 @@ ElementRestyler::Restyle(nsRestyleHint a
       if (aRestyleHint & eRestyle_SomeDescendants) {
         ConditionallyRestyleChildren();
       }
       return;
     }
 
     // Turns out we couldn't stop restyling here.  Process the struct
     // swaps that RestyleSelf would've done had we not returned
-    // eRestyleResult_StopWithStyleChange.
+    // RestyleResult::eStopWithStyleChange.
     for (SwapInstruction& swap : swaps) {
       LOG_RESTYLE("swapping style structs between %p and %p",
                   swap.mOldContext.get(), swap.mNewContext.get());
       swap.mOldContext->SwapStyleData(swap.mNewContext, swap.mStructsToSwap);
       swappedStructs |= swap.mStructsToSwap;
     }
     swaps.Clear();
   }
 
   if (!swappedStructs) {
     // If we swapped any structs from the old context, then we need to keep
     // it alive until after the RestyleChildren call so that we can fix up
     // its descendants' cached structs.
     oldContext = nullptr;
   }
 
-  if (result == eRestyleResult_ContinueAndForceDescendants) {
+  if (result == RestyleResult::eContinueAndForceDescendants) {
     childRestyleHint =
       nsRestyleHint(childRestyleHint | eRestyle_ForceDescendants);
   }
 
   // No need to do this if we're planning to reframe already.
   // It's also important to check mHintsHandled since we use
   // mFrame->StyleContext(), which is out of date if mHintsHandled
   // has a ReconstructFrame hint.  Using an out of date style
@@ -3454,216 +3454,216 @@ ElementRestyler::Restyle(nsRestyleHint a
   mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
 }
 
 /**
  * Depending on the details of the frame we are restyling or its old style
  * context, we may or may not be able to stop restyling after this frame if
  * we find we had no style changes.
  *
- * This function returns eRestyleResult_Stop if it does not find any
+ * This function returns RestyleResult::eStop if it does not find any
  * conditions that would preclude stopping restyling, and
- * eRestyleResult_Continue if it does.
+ * RestyleResult::eContinue if it does.
  */
 void
 ElementRestyler::ComputeRestyleResultFromFrame(nsIFrame* aSelf,
                                                RestyleResult& aRestyleResult,
                                                bool& aCanStopWithStyleChange)
 {
   // We can't handle situations where the primary style context of a frame
   // has not had any style data changes, but its additional style contexts
   // have, so we don't considering stopping if this frame has any additional
   // style contexts.
   if (aSelf->GetAdditionalStyleContext(0)) {
     LOG_RESTYLE_CONTINUE("there are additional style contexts");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   // Style changes might have moved children between the two nsLetterFrames
   // (the one matching ::first-letter and the one containing the rest of the
   // content).  Continue restyling to the children of the nsLetterFrame so
   // that they get the correct style context parent.  Similarly for
   // nsLineFrames.
   nsIAtom* type = aSelf->GetType();
 
   if (type == nsGkAtoms::letterFrame) {
     LOG_RESTYLE_CONTINUE("frame is a letter frame");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   if (type == nsGkAtoms::lineFrame) {
     LOG_RESTYLE_CONTINUE("frame is a line frame");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   // Some style computations depend not on the parent's style, but a grandparent
   // or one the grandparent's ancestors.  An example is an explicit 'inherit'
   // value for align-self, where if the parent frame's value for the property is
   // 'auto' we end up inheriting the computed value from the grandparent.  We
   // can't stop the restyling process on this frame (the one with 'auto', in
   // this example), as the grandparent's computed value might have changed
   // and we need to recompute the child's 'inherit' to that new value.
   nsStyleContext* oldContext = aSelf->StyleContext();
   if (oldContext->HasChildThatUsesGrandancestorStyle()) {
     LOG_RESTYLE_CONTINUE("the old context uses grandancestor style");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   // We ignore all situations that involve :visited style.
   if (oldContext->GetStyleIfVisited()) {
     LOG_RESTYLE_CONTINUE("the old style context has StyleIfVisited");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   nsStyleContext* parentContext = oldContext->GetParent();
   if (parentContext && parentContext->GetStyleIfVisited()) {
     LOG_RESTYLE_CONTINUE("the old style context's parent has StyleIfVisited");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   // We also ignore frames for pseudos, as their style contexts have
   // inheritance structures that do not match the frame inheritance
   // structure.  To avoid enumerating and checking all of the cases
   // where we have this kind of inheritance, we keep restyling past
   // pseudos.
   nsIAtom* pseudoTag = oldContext->GetPseudo();
   if (pseudoTag && !nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
     LOG_RESTYLE_CONTINUE("the old style context is for a pseudo");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   nsIFrame* parent = mFrame->GetParent();
 
   if (parent) {
     // Also if the parent has a pseudo, as this frame's style context will
     // be inheriting from a grandparent frame's style context (or a further
     // ancestor).
     nsIAtom* parentPseudoTag = parent->StyleContext()->GetPseudo();
     if (parentPseudoTag &&
         parentPseudoTag != nsCSSAnonBoxes::mozOtherNonElement) {
       MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::mozText,
                  "Style of text node should not be parent of anything");
       LOG_RESTYLE_CONTINUE("the old style context's parent is for a pseudo");
-      aRestyleResult = eRestyleResult_Continue;
+      aRestyleResult = RestyleResult::eContinue;
       // Parent style context pseudo-ness doesn't affect whether we can
-      // return eRestyleResult_StopWithStyleChange.
+      // return RestyleResult::eStopWithStyleChange.
       //
       // If we had later conditions to check in this function, we would
       // continue to check them, in case we set aCanStopWithStyleChange to
       // false.
     }
   }
 }
 
 void
 ElementRestyler::ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
                                                     nsStyleContext* aNewContext,
                                                     RestyleResult& aRestyleResult,
                                                     bool& aCanStopWithStyleChange)
 {
   // If we've already determined that we must continue styling, we don't
   // need to check anything.
-  if (aRestyleResult == eRestyleResult_Continue && !aCanStopWithStyleChange) {
+  if (aRestyleResult == RestyleResult::eContinue && !aCanStopWithStyleChange) {
     return;
   }
 
   // Keep restyling if the new style context has any style-if-visted style, so
   // that we can avoid the style context tree surgery having to deal to deal
   // with visited styles.
   if (aNewContext->GetStyleIfVisited()) {
     LOG_RESTYLE_CONTINUE("the new style context has StyleIfVisited");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   // If link-related information has changed, or the pseudo for the frame has
   // changed, or the new style context points to a different rule node, we can't
   // leave the old style context on the frame.
   nsStyleContext* oldContext = aSelf->StyleContext();
   if (oldContext->IsLinkContext() != aNewContext->IsLinkContext() ||
       oldContext->RelevantLinkVisited() != aNewContext->RelevantLinkVisited() ||
       oldContext->GetPseudo() != aNewContext->GetPseudo() ||
       oldContext->GetPseudoType() != aNewContext->GetPseudoType()) {
     LOG_RESTYLE_CONTINUE("the old and new style contexts have different link/"
                          "visited/pseudo");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   if (oldContext->RuleNode() != aNewContext->RuleNode()) {
     LOG_RESTYLE_CONTINUE("the old and new style contexts have different "
                          "rulenodes");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     // Continue to check other conditions if aCanStopWithStyleChange might
     // still need to be set to false.
     if (!aCanStopWithStyleChange) {
       return;
     }
   }
 
   // If the old and new style contexts differ in their
   // NS_STYLE_HAS_TEXT_DECORATION_LINES or NS_STYLE_HAS_PSEUDO_ELEMENT_DATA
   // bits, then we must keep restyling so that those new bit values are
   // propagated.
   if (oldContext->HasTextDecorationLines() !=
         aNewContext->HasTextDecorationLines()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_TEXT_DECORATION_LINES differs between old"
                          " and new style contexts");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   if (oldContext->HasPseudoElementData() !=
         aNewContext->HasPseudoElementData()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_PSEUDO_ELEMENT_DATA differs between old"
                          " and new style contexts");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   if (oldContext->ShouldSuppressLineBreak() !=
         aNewContext->ShouldSuppressLineBreak()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_SUPPRESS_LINEBREAK differs"
                          "between old and new style contexts");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   if (oldContext->IsInDisplayNoneSubtree() !=
         aNewContext->IsInDisplayNoneSubtree()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_IN_DISPLAY_NONE_SUBTREE differs between old"
                          " and new style contexts");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   if (oldContext->IsTextCombined() != aNewContext->IsTextCombined()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_IS_TEXT_COMBINED differs between "
                          "old and new style contexts");
-    aRestyleResult = eRestyleResult_Continue;
+    aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 }
 
 bool
 ElementRestyler::SelectorMatchesForRestyle(Element* aElement)
 {
@@ -3805,41 +3805,41 @@ ElementRestyler::RestyleSelf(nsIFrame* a
   LOG_RESTYLE("RestyleSelf %s, aRestyleHint = %s",
               FrameTagToString(aSelf).get(),
               RestyleManager::RestyleHintToString(aRestyleHint).get());
   LOG_RESTYLE_INDENT();
 
   // Initially assume that it is safe to stop restyling.
   //
   // Throughout most of this function, we update the following two variables
-  // independently.  |result| is set to eRestyleResult_Continue when we
-  // detect a condition that would not allow us to return eRestyleResult_Stop.
+  // independently.  |result| is set to RestyleResult::eContinue when we
+  // detect a condition that would not allow us to return RestyleResult::eStop.
   // |canStopWithStyleChange| is set to false when we detect a condition
-  // that would not allow us to return eRestyleResult_StopWithStyleChange.
+  // that would not allow us to return RestyleResult::eStopWithStyleChange.
   //
   // Towards the end of this function, we reconcile these two variables --
   // if |canStopWithStyleChange| is true, we convert |result| into
-  // eRestyleResult_StopWithStyleChange.
-  RestyleResult result = eRestyleResult_Stop;
+  // RestyleResult::eStopWithStyleChange.
+  RestyleResult result = RestyleResult::eStop;
   bool canStopWithStyleChange = true;
 
   if (aRestyleHint & ~eRestyle_SomeDescendants) {
     // If we are doing any restyling of the current element, or if we're
     // forced to continue, we must.
-    result = eRestyleResult_Continue;
+    result = RestyleResult::eContinue;
 
     // If we have to restyle children, we can't return
-    // eRestyleResult_StopWithStyleChange.
+    // RestyleResult::eStopWithStyleChange.
     if (aRestyleHint & (eRestyle_Subtree | eRestyle_Force |
                         eRestyle_ForceDescendants)) {
       canStopWithStyleChange = false;
     }
   }
 
-  // We only consider returning eRestyleResult_StopWithStyleChange if this
+  // We only consider returning RestyleResult::eStopWithStyleChange if this
   // is the root of the restyle.  (Otherwise, we would need to track the
   // style changes of the ancestors we just restyled.)
   if (!mIsRootOfRestyle) {
     canStopWithStyleChange = false;
   }
 
   // Look at the frame and its current style context for conditions
   // that would change our RestyleResult.
@@ -3886,17 +3886,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     // The provider's new context becomes the parent context of
     // aSelf's context.
     parentContext = providerFrame->StyleContext();
     // Set |mResolvedChild| so we don't bother resolving the
     // provider again.
     mResolvedChild = providerFrame;
     LOG_RESTYLE_CONTINUE("we had a provider frame");
     // Continue restyling past the odd style context inheritance.
-    result = eRestyleResult_Continue;
+    result = RestyleResult::eContinue;
     canStopWithStyleChange = false;
   }
 
   if (providerFrame != aSelf->GetParent()) {
     // We don't actually know what the parent style context's
     // non-inherited hints were, so assume the worst.
     mParentFrameHintsNotHandledForDescendants =
       nsChangeHint_Hints_NotHandledForDescendants;
@@ -4020,51 +4020,51 @@ ElementRestyler::RestyleSelf(nsIFrame* a
         oldContext->RelevantLinkVisited() ==
           newContext->RelevantLinkVisited()) {
       // We're the root of the style context tree and the new style
       // context returned has the same rule node.  This means that
       // we can use FindChildWithRules to keep a lot of the old
       // style contexts around.  However, we need to start from the
       // same root.
       LOG_RESTYLE("restyling root and keeping old context");
-      LOG_RESTYLE_IF(this, result != eRestyleResult_Continue,
+      LOG_RESTYLE_IF(this, result != RestyleResult::eContinue,
                      "continuing restyle since this is the root");
       newContext = oldContext;
       // Never consider stopping restyling at the root.
-      result = eRestyleResult_Continue;
+      result = RestyleResult::eContinue;
       canStopWithStyleChange = false;
     }
   }
 
   LOG_RESTYLE("oldContext = %p, newContext = %p%s",
               oldContext.get(), newContext.get(),
               oldContext == newContext ? (const char*) " (same)" :
                                          (const char*) "");
 
   if (newContext != oldContext) {
     if (oldContext->IsShared()) {
       // If the old style context was shared, then we can't return
-      // eRestyleResult_Stop and patch its parent to point to the
+      // RestyleResult::eStop and patch its parent to point to the
       // new parent style context, as that change might not be valid
       // for the other frames sharing the style context.
       LOG_RESTYLE_CONTINUE("the old style context is shared");
-      result = eRestyleResult_Continue;
-
-      // It is not safe to return eRestyleResult_StopWithStyleChange
+      result = RestyleResult::eContinue;
+
+      // It is not safe to return RestyleResult::eStopWithStyleChange
       // when oldContext is shared and newContext has different
       // inherited style data, regardless of whether the oldContext has
       // that inherited style data cached.  We can't simply rely on the
       // samePointerStructs check later on, as the descendent style
       // contexts just might not have had their inherited style data
       // requested yet (which is possible for example if we flush style
       // between resolving an initial style context for a frame and
       // building its display list items).  Therefore we must compare
       // the rule nodes of oldContext and newContext to see if the
       // restyle results in new inherited style data.  If not, then
-      // we can continue assuming that eRestyleResult_StopWithStyleChange
+      // we can continue assuming that RestyleResult::eStopWithStyleChange
       // is safe.  Without this check, we could end up with style contexts
       // shared between elements which should have different styles.
       if (!CommonInheritedStyleData(oldContext->RuleNode(),
                                     newContext->RuleNode())) {
         canStopWithStyleChange = false;
       }
     }
 
@@ -4078,71 +4078,71 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     uint32_t samePointerStructs = 0;
 
     if (copyFromContinuation) {
       // In theory we should know whether there was any style data difference,
       // since we would have calculated that in the previous call to
       // RestyleSelf, so until we perform only one restyling per chain-of-
       // same-style continuations (bug 918064), we need to check again here to
       // determine whether it is safe to stop restyling.
-      if (result == eRestyleResult_Stop) {
+      if (result == RestyleResult::eStop) {
         oldContext->CalcStyleDifference(newContext, nsChangeHint(0),
                                         &equalStructs,
                                         &samePointerStructs);
         if (equalStructs != NS_STYLE_INHERIT_MASK) {
           // At least one struct had different data in it, so we must
           // continue restyling children.
           LOG_RESTYLE_CONTINUE("there is different style data: %s",
                       RestyleManager::StructNamesToString(
                         ~equalStructs & NS_STYLE_INHERIT_MASK).get());
-          result = eRestyleResult_Continue;
+          result = RestyleResult::eContinue;
         }
       }
     } else {
       bool changedStyle =
         RestyleManager::TryStartingTransition(mPresContext, aSelf->GetContent(),
                                               oldContext, &newContext);
       if (changedStyle) {
         LOG_RESTYLE_CONTINUE("TryStartingTransition changed the new style context");
-        result = eRestyleResult_Continue;
+        result = RestyleResult::eContinue;
         canStopWithStyleChange = false;
       }
       CaptureChange(oldContext, newContext, assumeDifferenceHint,
                     &equalStructs, &samePointerStructs);
       if (equalStructs != NS_STYLE_INHERIT_MASK) {
         // At least one struct had different data in it, so we must
         // continue restyling children.
         LOG_RESTYLE_CONTINUE("there is different style data: %s",
                     RestyleManager::StructNamesToString(
                       ~equalStructs & NS_STYLE_INHERIT_MASK).get());
-        result = eRestyleResult_Continue;
+        result = RestyleResult::eContinue;
       }
     }
 
     if (canStopWithStyleChange) {
       // If any inherited struct pointers are different, or if any
       // reset struct pointers are different and we have descendants
       // that rely on those reset struct pointers, we can't return
-      // eRestyleResult_StopWithStyleChange.
+      // RestyleResult::eStopWithStyleChange.
       if ((samePointerStructs & NS_STYLE_INHERITED_STRUCT_MASK) !=
             NS_STYLE_INHERITED_STRUCT_MASK) {
-        LOG_RESTYLE("can't return eRestyleResult_StopWithStyleChange since "
+        LOG_RESTYLE("can't return RestyleResult::eStopWithStyleChange since "
                     "there is different inherited data");
         canStopWithStyleChange = false;
       } else if ((samePointerStructs & NS_STYLE_RESET_STRUCT_MASK) !=
                    NS_STYLE_RESET_STRUCT_MASK &&
                  oldContext->HasChildThatUsesResetStyle()) {
-        LOG_RESTYLE("can't return eRestyleResult_StopWithStyleChange since "
+        LOG_RESTYLE("can't return RestyleResult::eStopWithStyleChange since "
                     "there is different reset data and descendants use it");
         canStopWithStyleChange = false;
       }
     }
 
-    if (result == eRestyleResult_Stop) {
-      // Since we currently have eRestyleResult_Stop, we know at this
+    if (result == RestyleResult::eStop) {
+      // Since we currently have RestyleResult::eStop, we know at this
       // point that all of our style structs are equal in terms of styles.
       // However, some of them might be different pointers.  Since our
       // descendants might share those pointers, we have to continue to
       // restyling our descendants.
       //
       // However, because of the swapping of equal structs we've done on
       // ancestors (later in this function), we've ensured that for structs
       // that cannot be stored in the rule tree, we keep the old equal structs
@@ -4159,63 +4159,63 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       // FIXME This loop could be rewritten as bit operations on
       //       oldContext->mBits and samePointerStructs.
       for (nsStyleStructID sid = nsStyleStructID(0);
            sid < nsStyleStructID_Length;
            sid = nsStyleStructID(sid + 1)) {
         if (oldContext->HasCachedDependentStyleData(sid) &&
             !(samePointerStructs & nsCachedStyleData::GetBitForSID(sid))) {
           LOG_RESTYLE_CONTINUE("there are different struct pointers");
-          result = eRestyleResult_Continue;
+          result = RestyleResult::eContinue;
           break;
         }
       }
     }
 
     // From this point we no longer do any assignments of
-    // eRestyleResult_Continue to |result|.  If canStopWithStyleChange is true,
+    // RestyleResult::eContinue to |result|.  If canStopWithStyleChange is true,
     // it means that we can convert |result| (whether it is
-    // eRestyleResult_Continue or eRestyleResult_Stop) into
-    // eRestyleResult_StopWithStyleChange.
+    // RestyleResult::eContinue or RestyleResult::eStop) into
+    // RestyleResult::eStopWithStyleChange.
     if (canStopWithStyleChange) {
-      LOG_RESTYLE("converting %s into eRestyleResult_StopWithStyleChange",
+      LOG_RESTYLE("converting %s into RestyleResult::eStopWithStyleChange",
                   RestyleResultToString(result).get());
-      result = eRestyleResult_StopWithStyleChange;
+      result = RestyleResult::eStopWithStyleChange;
     }
 
     if (aRestyleHint & eRestyle_ForceDescendants) {
-      result = eRestyleResult_ContinueAndForceDescendants;
+      result = RestyleResult::eContinueAndForceDescendants;
     }
 
     if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
       // If the frame gets regenerated, let it keep its old context,
       // which is important to maintain various invariants about
       // frame types matching their style contexts.
       // Note that this check even makes sense if we didn't call
       // CaptureChange because of copyFromContinuation being true,
       // since we'll have copied the existing context from the
       // previous continuation, so newContext == oldContext.
 
-      if (result != eRestyleResult_Stop) {
+      if (result != RestyleResult::eStop) {
         if (copyFromContinuation) {
           LOG_RESTYLE("not swapping style structs, since we copied from a "
                       "continuation");
         } else if (oldContext->IsShared() && newContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since both old and contexts "
                       "are shared");
         } else if (oldContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since the old context is "
                       "shared");
         } else if (newContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since the new context is "
                       "shared");
         } else {
-          if (result == eRestyleResult_StopWithStyleChange) {
+          if (result == RestyleResult::eStopWithStyleChange) {
             LOG_RESTYLE("recording a style struct swap between %p and %p to "
-                        "do if eRestyleResult_StopWithStyleChange fails",
+                        "do if RestyleResult::eStopWithStyleChange fails",
                         oldContext.get(), newContext.get());
             SwapInstruction* swap = aSwaps.AppendElement();
             swap->mOldContext = oldContext;
             swap->mNewContext = newContext;
             swap->mStructsToSwap = equalStructs;
           } else {
             LOG_RESTYLE("swapping style structs between %p and %p",
                         oldContext.get(), newContext.get());
@@ -4245,17 +4245,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       // mSwappedStructOwners.
       //
       // We really only need to do this if we did swap structs on the
       // parent, but we don't have that information here.
       mSwappedStructOwners.AppendElement(newContext->GetParent());
     }
   } else {
     if (aRestyleHint & eRestyle_ForceDescendants) {
-      result = eRestyleResult_ContinueAndForceDescendants;
+      result = RestyleResult::eContinueAndForceDescendants;
     }
   }
   oldContext = nullptr;
 
   // do additional contexts
   // XXXbz might be able to avoid selector matching here in some
   // cases; won't worry about it for now.
   int32_t contextIndex = 0;
@@ -4497,17 +4497,17 @@ ElementRestyler::ComputeStyleChangeFor(n
       aRestyleHintData.mSelectorsForDescendants);
   nsTArray<nsIContent*> visibleKidsOfHiddenElement;
   nsIFrame* nextIBSibling;
   for (nsIFrame* ibSibling = aFrame; ibSibling; ibSibling = nextIBSibling) {
     nextIBSibling = GetNextBlockInInlineSibling(propTable, ibSibling);
 
     if (nextIBSibling) {
       // Don't allow some ib-split siblings to be processed with
-      // eRestyleResult_StopWithStyleChange and others not.
+      // RestyleResult::eStopWithStyleChange and others not.
       aRestyleHint |= eRestyle_Force;
     }
 
     // Outer loop over ib-split siblings
     for (nsIFrame* cont = ibSibling; cont; cont = cont->GetNextContinuation()) {
       if (GetPrevContinuationWithSameStyle(cont)) {
         // We already handled this element when dealing with its earlier
         // continuation.
@@ -5176,28 +5176,29 @@ RestyleManager::StructNamesToString(uint
   return result;
 }
 
 /* static */ nsCString
 ElementRestyler::RestyleResultToString(RestyleResult aRestyleResult)
 {
   nsCString result;
   switch (aRestyleResult) {
-    case eRestyleResult_Stop:
-      result.AssignLiteral("eRestyleResult_Stop");
+    case RestyleResult::eStop:
+      result.AssignLiteral("RestyleResult::eStop");
       break;
-    case eRestyleResult_StopWithStyleChange:
-      result.AssignLiteral("eRestyleResult_StopWithStyleChange");
+    case RestyleResult::eStopWithStyleChange:
+      result.AssignLiteral("RestyleResult::eStopWithStyleChange");
       break;
-    case eRestyleResult_Continue:
-      result.AssignLiteral("eRestyleResult_Continue");
+    case RestyleResult::eContinue:
+      result.AssignLiteral("RestyleResult::eContinue");
       break;
-    case eRestyleResult_ContinueAndForceDescendants:
-      result.AssignLiteral("eRestyleResult_ContinueAndForceDescendants");
+    case RestyleResult::eContinueAndForceDescendants:
+      result.AssignLiteral("RestyleResult::eContinueAndForceDescendants");
       break;
     default:
-      result.AppendPrintf("RestyleResult(%d)", aRestyleResult);
+      MOZ_ASSERT(aRestyleResult == RestyleResult::eNone,
+                 "Unexpected RestyleResult");
   }
   return result;
 }
 #endif
 
 } // namespace mozilla
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -660,35 +660,37 @@ public:
   bool ShouldLogRestyle() {
     return RestyleManager::ShouldLogRestyle(mPresContext);
   }
 #endif
 
 private:
   inline nsStyleSet* StyleSet() const;
 
-  // Enum for the result of RestyleSelf, which indicates whether the
+  // Enum class for the result of RestyleSelf, which indicates whether the
   // restyle procedure should continue to the children, and how.
   //
   // These values must be ordered so that later values imply that all
   // the work of the earlier values is also done.
-  enum RestyleResult {
+  enum class RestyleResult : uint8_t {
+    // default initial value
+    eNone,
 
     // we left the old style context on the frame; do not restyle children
-    eRestyleResult_Stop = 1,
+    eStop,
 
     // we got a new style context on this frame, but we know that children
     // do not depend on the changed values; do not restyle children
-    eRestyleResult_StopWithStyleChange,
+    eStopWithStyleChange,
 
     // continue restyling children
-    eRestyleResult_Continue,
+    eContinue,
 
     // continue restyling children with eRestyle_ForceDescendants set
-    eRestyleResult_ContinueAndForceDescendants
+    eContinueAndForceDescendants
   };
 
   struct SwapInstruction
   {
     RefPtr<nsStyleContext> mOldContext;
     RefPtr<nsStyleContext> mNewContext;
     uint32_t mStructsToSwap;
   };
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -444,17 +444,17 @@ RestyleTracker::GetRestyleData(Element* 
 
 void
 RestyleTracker::AddRestyleRootsIfAwaitingRestyle(
                                    const nsTArray<RefPtr<Element>>& aElements)
 {
   // The RestyleData for a given element has stored in mDescendants
   // the list of descendants we need to end up restyling.  Since we
   // won't necessarily end up restyling them, due to the restyle
-  // process finishing early (see how eRestyleResult_Stop is handled
+  // process finishing early (see how RestyleResult::eStop is handled
   // in ElementRestyler::Restyle), we add them to the list of restyle
   // roots to handle the next time around the
   // RestyleTracker::DoProcessRestyles loop.
   //
   // Note that aElements must maintain the same invariant
   // that mRestyleRoots does, i.e. that ancestors appear after descendants.
   // Since we call AddRestyleRootsIfAwaitingRestyle only after we have
   // removed the restyle root we are currently processing from the end of
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -342,17 +342,17 @@ public:
   }
 
   /**
    * For each element in aElements, appends it to mRestyleRoots if it
    * has its restyle bit set.  This is used to ensure we restyle elements
    * that we did not add as restyle roots initially (due to there being
    * an ancestor with the restyle root bit set), but which we might
    * not have got around to restyling due to the restyle process
-   * terminating early with eRestyleResult_Stop (see ElementRestyler::Restyle).
+   * terminating early with RestyleResul::eStop (see ElementRestyler::Restyle).
    *
    * This function must be called with elements in order such that
    * appending them to mRestyleRoots maintains its ordering invariant that
    * ancestors appear after descendants.
    */
   void AddRestyleRootsIfAwaitingRestyle(
                                   const nsTArray<RefPtr<Element>>& aElements);
 
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -973,18 +973,20 @@ nsHTMLFramesetFrame::Reflow(nsPresContex
         if (eFrameborder_Yes == mChildFrameborder[childX]) {
           childVis = ALL_VIS;
         } else if (eFrameborder_No == mChildFrameborder[childX]) {
           childVis = NONE_VIS;
         } else {  // notset
           childVis = (eFrameborder_No == frameborder) ? NONE_VIS : ALL_VIS;
         }
       } else {  // blank
-        DebugOnly<nsHTMLFramesetBlankFrame*> blank;
-        MOZ_ASSERT(blank = do_QueryFrame(child), "unexpected child frame type");
+#ifdef DEBUG
+        nsHTMLFramesetBlankFrame* blank = do_QueryFrame(child);
+        MOZ_ASSERT(blank, "unexpected child frame type");
+#endif
         childVis = NONE_VIS;
       }
       nsBorderColor childColors = mChildBorderColors[childX];
       // set the visibility, color of our edge borders based on children
       if (0 == cellIndex.x) {
         if (!(mEdgeVisibility & LEFT_VIS)) {
           mEdgeVisibility |= (LEFT_VIS & childVis);
         }
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4797,18 +4797,26 @@ nsDisplayText::Paint(nsDisplayListBuilde
       gfxMatrix mat = ctx->CurrentMatrix()
         .Translate(pt).Scale(scaleFactor, 1.0).Translate(-pt);
       ctx->SetMatrix(mat);
     }
   }
   nsTextFrame::PaintTextParams params(aCtx->ThebesContext());
   params.framePt = gfxPoint(framePt.x, framePt.y);
   params.dirtyRect = extraVisible;
-  params.generateTextMask = aBuilder->IsForGenerateGlyphMask();
-  params.paintSelectionBackground = aBuilder->IsForPaintingSelectionBG();
+
+  if (aBuilder->IsForGenerateGlyphMask()) {
+    MOZ_ASSERT(!aBuilder->IsForPaintingSelectionBG());
+    params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
+  } else if (aBuilder->IsForPaintingSelectionBG()) {
+    params.state = nsTextFrame::PaintTextParams::PaintTextBGColor;
+  } else {
+    params.state = nsTextFrame::PaintTextParams::PaintText;
+  }
+
   f->PaintText(params, *this, mOpacity);
 }
 
 void
 nsTextFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists)
 {
@@ -5998,18 +6006,17 @@ nsTextFrame::PaintTextWithSelectionColor
   bool vertical = mTextRun->IsVertical();
   const gfxFloat startIOffset = vertical ?
     aParams.textBaselinePt.y - aParams.framePt.y :
     aParams.textBaselinePt.x - aParams.framePt.x;
   gfxFloat iOffset, hyphenWidth;
   Range range; // in transformed string
   TextRangeStyle rangeStyle;
   // Draw background colors
-  if (anyBackgrounds && (!aParams.generateTextMask ||
-                         aParams.paintSelectionBackground)) {
+  if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
     int32_t appUnitsPerDevPixel =
       aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
     SelectionIterator iterator(prevailingSelections, contentRange,
                                *aParams.provider, mTextRun, startIOffset);
     SelectionType selectionType;
     while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                    &selectionType, &rangeStyle)) {
       nscolor foreground, background;
@@ -6032,17 +6039,17 @@ nsTextFrame::PaintTextWithSelectionColor
           *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
           LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel),
           aParams.callbacks);
       }
       iterator.UpdateWithAdvance(advance);
     }
   }
 
-  if (aParams.paintSelectionBackground) {
+  if (aParams.IsPaintBGColor()) {
     return true;
   }
 
   gfxFloat advance;
   DrawTextParams params(aParams.context);
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt;
   params.provider = aParams.provider;
@@ -6058,17 +6065,17 @@ nsTextFrame::PaintTextWithSelectionColor
   // Draw text
   const nsStyleText* textStyle = StyleText();
   SelectionIterator iterator(prevailingSelections, contentRange,
                              *aParams.provider, mTextRun, startIOffset);
   SelectionType selectionType;
   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &selectionType, &rangeStyle)) {
     nscolor foreground, background;
-    if (aParams.generateTextMask) {
+    if (aParams.IsGenerateTextMask()) {
       foreground = NS_RGBA(0, 0, 0, 255);
     } else {
       GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
                              rangeStyle, &foreground, &background);
     }
 
     gfxPoint textBaselinePt = vertical ?
       gfxPoint(aParams.textBaselinePt.x, aParams.framePt.y + iOffset) :
@@ -6591,57 +6598,56 @@ nsTextFrame::PaintText(const PaintTextPa
   }
   nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedStartEdge,
                                              snappedEndEdge);
   nsTextPaintStyle textPaintStyle(this);
   textPaintStyle.SetResolveColors(!aParams.callbacks);
 
   // Fork off to the (slower) paint-with-selection path if necessary.
   if (aItem.mIsFrameSelected.value() &&
-      (aParams.paintSelectionBackground ||
-       ShouldDrawSelection(this->GetParent()))) {
+      (aParams.IsPaintBGColor() || ShouldDrawSelection(this->GetParent()))) {
     MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
     gfxSkipCharsIterator tmp(provider.GetStart());
     Range contentRange(
       uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
       uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
     PaintTextSelectionParams params(aParams);
     params.textBaselinePt = textBaselinePt;
     params.provider = &provider;
     params.contentRange = contentRange;
     params.textPaintStyle = &textPaintStyle;
     if (PaintTextWithSelection(params, clipEdges)) {
       return;
     }
   }
 
-  if (aParams.paintSelectionBackground) {
+  if (aParams.IsPaintBGColor()) {
     return;
   }
 
-  nscolor foregroundColor = aParams.generateTextMask
+  nscolor foregroundColor = aParams.IsGenerateTextMask()
                             ? NS_RGBA(0, 0, 0, 255)
                             : textPaintStyle.GetTextColor();
   if (aOpacity != 1.0f) {
     gfx::Color gfxColor = gfx::Color::FromABGR(foregroundColor);
     gfxColor.a *= aOpacity;
     foregroundColor = gfxColor.ToABGR();
   }
 
-  nscolor textStrokeColor = aParams.generateTextMask
+  nscolor textStrokeColor = aParams.IsGenerateTextMask()
                             ? NS_RGBA(0, 0, 0, 255)
                             : textPaintStyle.GetWebkitTextStrokeColor();
   if (aOpacity != 1.0f) {
     gfx::Color gfxColor = gfx::Color::FromABGR(textStrokeColor);
     gfxColor.a *= aOpacity;
     textStrokeColor = gfxColor.ToABGR();
   }
 
   range = Range(startOffset, startOffset + maxLength);
-  if (!aParams.callbacks) {
+  if (!aParams.callbacks && aParams.IsPaintText()) {
     const nsStyleText* textStyle = StyleText();
     PaintShadowParams shadowParams(aParams);
     shadowParams.range = range;
     shadowParams.textBaselinePt = textBaselinePt;
     shadowParams.leftSideOffset = snappedStartEdge;
     shadowParams.provider = &provider;
     shadowParams.foregroundColor = foregroundColor;
     shadowParams.clipEdges = &clipEdges;
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -390,19 +390,31 @@ public:
 
   struct PaintTextParams
   {
     gfxContext* context;
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     gfxTextContextPaint* contextPaint = nullptr;
     DrawPathCallbacks* callbacks = nullptr;
-    bool generateTextMask = false;
-    bool paintSelectionBackground = false;
+    enum {
+      PaintText,           // Normal text painting.
+      PaintTextBGColor,    // Only paint background color of the selected text
+                           // range in this state.
+      GenerateTextMask     // To generate a mask from a text frame. Should
+                           // only paint text itself with opaque color.
+                           // Text shadow, text selection color and text
+                           // decoration are all discarded in this state.
+    };
+    uint8_t state = PaintText;
     explicit PaintTextParams(gfxContext* aContext) : context(aContext) {}
+
+    bool IsPaintText() const { return state == PaintText; }
+    bool IsGenerateTextMask() const { return state == GenerateTextMask; }
+    bool IsPaintBGColor() const { return state == PaintTextBGColor; }
   };
 
   struct PaintTextSelectionParams : PaintTextParams
   {
     gfxPoint textBaselinePt;
     PropertyProvider* provider = nullptr;
     Range contentRange;
     nsTextPaintStyle* textPaintStyle = nullptr;
--- a/layout/printing/PrintTranslator.cpp
+++ b/layout/printing/PrintTranslator.cpp
@@ -52,17 +52,20 @@ PrintTranslator::TranslateRecording(std:
       RecordedEvent::LoadEventFromStream(aRecording,
       static_cast<RecordedEvent::EventType>(eventType)));
 
     // Make sure that the whole event was read from the stream successfully.
     if (!aRecording.good() || !recordedEvent) {
       return false;
     }
 
-    recordedEvent->PlayEvent(this);
+    if (!recordedEvent->PlayEvent(this)) {
+      return false;
+    }
+
     ReadElement(aRecording, eventType);
   }
 
   return true;
 }
 
 already_AddRefed<DrawTarget>
 PrintTranslator::CreateDrawTarget(ReferencePtr aRefPtr,
@@ -94,9 +97,9 @@ PrintTranslator::GetDesiredFontType()
     case BackendType::COREGRAPHICS_ACCELERATED:
       return FontType::COREGRAPHICS;
     default:
       return FontType::CAIRO;
   }
 }
 
 } // namespace layout
-} // namespace mozilla
\ No newline at end of file
+} // namespace mozilla
--- a/layout/printing/ipc/RemotePrintJobParent.cpp
+++ b/layout/printing/ipc/RemotePrintJobParent.cpp
@@ -107,17 +107,19 @@ RemotePrintJobParent::PrintPage(const Sh
 
   nsresult rv = mPrintDeviceContext->BeginPage();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   std::istringstream recording(std::string(aStoredPage.get<char>(),
                                            aStoredPage.Size<char>()));
-  mPrintTranslator->TranslateRecording(recording);
+  if (!mPrintTranslator->TranslateRecording(recording)) {
+    return NS_ERROR_FAILURE;
+  }
 
   rv = mPrintDeviceContext->EndPage();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/backgrounds/background-clip-text-2-ref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+  <head>
+    <title>background-clip: text reference</title>
+    <style>
+      div.out {
+        width: 500px;
+        height: 300px;
+        margin: 0px;
+        color: white;
+        font-size: 50px;
+        font-family: serif;
+        text-shadow: 0px 60px 5px red;
+        -moz-osx-font-smoothing: grayscale;
+      }
+    </style>
+  </head>
+  <body style="margin: 0px;">
+    <div class="out">
+      Text Shadow
+      <div style="display:inline-block; width:0px; height:100px;"/>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/backgrounds/background-clip-text-2.html
@@ -0,0 +1,28 @@
+<!doctype HTML>
+<html>
+  <head>
+    <title>background-clip: text shadow</title>
+    <style>
+      div.out {
+        width: 500px;
+        height: 300px;
+        margin: 0px;
+        background-image: linear-gradient(green, green);
+        background-clip: text;
+        color: transparent;
+        font-size: 50px;
+        font-family: serif;
+        text-shadow: 0px 60px 5px red;
+        -moz-osx-font-smoothing: grayscale;
+      }
+    </style>
+  </head>
+  <body style="margin: 0px;">
+    <div class="out">
+      Text Shadow
+      <div style="display:inline-block; width:0px; height:100px;"/>
+    </div>
+    <!-- A white div which is used to cover on text -->
+    <div style="position: absolute; top: 0px; left:0px; width:500px; height:110px;background-color:white;"/>
+  </body>
+</html>
--- a/layout/reftests/backgrounds/reftest.list
+++ b/layout/reftests/backgrounds/reftest.list
@@ -180,8 +180,10 @@ fuzzy(30,474) fuzzy-if(skiaContent,31,47
 skip-if(!cocoaWidget) == background-repeat-resampling.html background-repeat-resampling-ref.html
 
 pref(layout.css.background-clip-text.enabled,true) fuzzy-if(winWidget,102,2032) fuzzy-if(skiaContent,102,2595) == background-clip-text-1a.html background-clip-text-1-ref.html
 pref(layout.css.background-clip-text.enabled,true) fuzzy-if(winWidget,102,2032) fuzzy-if(skiaContent,102,2595) == background-clip-text-1b.html background-clip-text-1-ref.html
 pref(layout.css.background-clip-text.enabled,true) fuzzy-if(winWidget,102,2032) fuzzy-if(skiaContent,102,2595) == background-clip-text-1c.html background-clip-text-1-ref.html
 pref(layout.css.background-clip-text.enabled,true) fuzzy-if(winWidget,102,2032) fuzzy-if(skiaContent,102,2595) == background-clip-text-1d.html background-clip-text-1-ref.html
 pref(layout.css.background-clip-text.enabled,true) fuzzy-if(winWidget,102,2032) fuzzy-if(skiaContent,102,2595) == background-clip-text-1e.html background-clip-text-1-ref.html
 pref(layout.css.background-clip-text.enabled,false) != background-clip-text-1a.html background-clip-text-1-ref.html
+
+pref(layout.css.background-clip-text.enabled,true) == background-clip-text-2.html background-clip-text-2-ref.html
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -72,17 +72,17 @@ public class MediaControlService extends
         PrefsHelper.removeObserver(mPrefsObserver);
 
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         handleIntent(intent);
-        return super.onStartCommand(intent, flags, startId);
+        return START_NOT_STICKY;
     }
 
     @Override
     public IBinder onBind(Intent intent) {
         return null;
     }
 
     @Override
@@ -262,32 +262,38 @@ public class MediaControlService extends
     }
 
     private void updateNotification(Tab tab, String action) {
         ThreadUtils.assertNotOnUiThread();
 
         final Notification.MediaStyle style = new Notification.MediaStyle();
         style.setShowActionsInCompactView(0);
 
+        final boolean isMediaPlaying = action.equals(ACTION_PAUSE);
         final Notification notification = new Notification.Builder(this)
             .setSmallIcon(R.drawable.flat_icon)
             .setLargeIcon(generateCoverArt(tab))
             .setContentTitle(tab.getTitle())
             .setContentText(tab.getURL())
             .setContentIntent(createContentIntent())
             .setDeleteIntent(createDeleteIntent())
             .setStyle(style)
             .addAction(createNotificationAction(action))
-            .setOngoing(action.equals(ACTION_PAUSE))
+            .setOngoing(isMediaPlaying)
             .setShowWhen(false)
             .setWhen(0)
             .build();
 
-        NotificationManagerCompat.from(this)
+        if (isMediaPlaying) {
+            startForeground(MEDIA_CONTROL_ID, notification);
+        } else {
+            stopForeground(false);
+            NotificationManagerCompat.from(this)
                 .notify(MEDIA_CONTROL_ID, notification);
+        }
     }
 
     private Notification.Action createNotificationAction(String action) {
         boolean isPlayAction = action.equals(ACTION_PLAY);
 
         int icon = isPlayAction ? R.drawable.ic_media_play : R.drawable.ic_media_pause;
         String title = getString(isPlayAction ? R.string.media_play : R.string.media_pause);
 
--- a/mobile/android/components/build/nsAndroidHistory.cpp
+++ b/mobile/android/components/build/nsAndroidHistory.cpp
@@ -282,16 +282,20 @@ nsAndroidHistory::SetURITitle(nsIURI *aU
   if (IsEmbedURI(aURI)) {
     return NS_OK;
   }
 
   if (jni::IsAvailable()) {
     nsAutoCString uri;
     nsresult rv = aURI->GetSpec(uri);
     if (NS_FAILED(rv)) return rv;
+    if (RemovePendingVisitURI(aURI)) {
+      // We have a title, so aURI isn't a redirect, so save the visit now before setting the title.
+      SaveVisitURI(aURI);
+    }
     NS_ConvertUTF8toUTF16 uriString(uri);
     widget::GeckoAppShell::SetURITitle(uriString, aTitle);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAndroidHistory::NotifyVisited(nsIURI *aURI)
--- a/mobile/locales/en-US/searchplugins/qwant.xml
+++ b/mobile/locales/en-US/searchplugins/qwant.xml
@@ -1,16 +1,16 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Qwant</ShortName>
 <InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">data:image/png;base64,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</Image>
+<Image width="16" height="16">data:image/png;base64,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</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://api.qwant.com/api/suggest/">
   <Param name="q" value="{searchTerms}"/>
   <Param name="client" value="ff_android"/>
 </Url>
 <Url type="text/html" method="GET" template="https://www.qwant.com/">
   <Param name="q" value="{searchTerms}"/>
   <Param name="client" value="ff_android"/>
 </Url>
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -101,17 +101,17 @@ class VCFiles(object):
             cmd = ['hg', 'status', '-amn']
         elif self.is_git(self):
             cmd = ['git', 'diff', '--name-only']
         else:
             return []
         return subprocess.check_output(cmd).split()
 
 
-def find_linters(self, linters=None):
+def find_linters(linters=None):
     lints = []
     for search_path in SEARCH_PATHS:
         if not os.path.isdir(search_path):
             continue
 
         files = os.listdir(search_path)
         for f in files:
             name, ext = os.path.splitext(f)
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/COPYING
@@ -0,0 +1,25 @@
+Copyright (c) 2010, Alec Thomas
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+ - Neither the name of SwapOff.org nor the names of its contributors may
+   be used to endorse or promote products derived from this software without
+   specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.md
+include COPYING
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/PKG-INFO
@@ -0,0 +1,611 @@
+Metadata-Version: 1.1
+Name: voluptuous
+Version: 0.8.11
+Summary: Voluptuous is a Python data validation library
+Home-page: https://github.com/alecthomas/voluptuous
+Author: Alec Thomas
+Author-email: alec@swapoff.org
+License: BSD
+Download-URL: https://pypi.python.org/pypi/voluptuous
+Description: Voluptuous is a Python data validation library
+        ==============================================
+        
+        |Build Status| |Stories in Ready|
+        
+        Voluptuous, *despite* the name, is a Python data validation library. It
+        is primarily intended for validating data coming into Python as JSON,
+        YAML, etc.
+        
+        It has three goals:
+        
+        1. Simplicity.
+        2. Support for complex data structures.
+        3. Provide useful error messages.
+        
+        Contact
+        -------
+        
+        Voluptuous now has a mailing list! Send a mail to
+        ` <mailto:voluptuous@librelist.com>`__ to subscribe. Instructions will
+        follow.
+        
+        You can also contact me directly via `email <mailto:alec@swapoff.org>`__
+        or `Twitter <https://twitter.com/alecthomas>`__.
+        
+        To file a bug, create a `new
+        issue <https://github.com/alecthomas/voluptuous/issues/new>`__ on GitHub
+        with a short example of how to replicate the issue.
+        
+        Show me an example
+        ------------------
+        
+        Twitter's `user search
+        API <https://dev.twitter.com/docs/api/1/get/users/search>`__ accepts
+        query URLs like:
+        
+        ::
+        
+            $ curl 'http://api.twitter.com/1/users/search.json?q=python&per_page=20&page=1
+        
+        To validate this we might use a schema like:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Schema
+            >>> schema = Schema({
+            ...   'q': str,
+            ...   'per_page': int,
+            ...   'page': int,
+            ... })
+        
+        This schema very succinctly and roughly describes the data required by
+        the API, and will work fine. But it has a few problems. Firstly, it
+        doesn't fully express the constraints of the API. According to the API,
+        ``per_page`` should be restricted to at most 20, defaulting to 5, for
+        example. To describe the semantics of the API more accurately, our
+        schema will need to be more thoroughly defined:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Required, All, Length, Range
+            >>> schema = Schema({
+            ...   Required('q'): All(str, Length(min=1)),
+            ...   Required('per_page', default=5): All(int, Range(min=1, max=20)),
+            ...   'page': All(int, Range(min=0)),
+            ... })
+        
+        This schema fully enforces the interface defined in Twitter's
+        documentation, and goes a little further for completeness.
+        
+        "q" is required:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import MultipleInvalid, Invalid
+            >>> try:
+            ...   schema({})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "required key not provided @ data['q']"
+            True
+        
+        ...must be a string:
+        
+        .. code:: pycon
+        
+            >>> try:
+            ...   schema({'q': 123})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "expected str for dictionary value @ data['q']"
+            True
+        
+        ...and must be at least one character in length:
+        
+        .. code:: pycon
+        
+            >>> try:
+            ...   schema({'q': ''})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
+            True
+            >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
+            True
+        
+        "per\_page" is a positive integer no greater than 20:
+        
+        .. code:: pycon
+        
+            >>> try:
+            ...   schema({'q': '#topic', 'per_page': 900})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
+            True
+            >>> try:
+            ...   schema({'q': '#topic', 'per_page': -10})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
+            True
+        
+        "page" is an integer >= 0:
+        
+        .. code:: pycon
+        
+            >>> try:
+            ...   schema({'q': '#topic', 'per_page': 'one'})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc)
+            "expected int for dictionary value @ data['per_page']"
+            >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
+            True
+        
+        Defining schemas
+        ----------------
+        
+        Schemas are nested data structures consisting of dictionaries, lists,
+        scalars and *validators*. Each node in the input schema is pattern
+        matched against corresponding nodes in the input data.
+        
+        Literals
+        ~~~~~~~~
+        
+        Literals in the schema are matched using normal equality checks:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema(1)
+            >>> schema(1)
+            1
+            >>> schema = Schema('a string')
+            >>> schema('a string')
+            'a string'
+        
+        Types
+        ~~~~~
+        
+        Types in the schema are matched by checking if the corresponding value
+        is an instance of the type:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema(int)
+            >>> schema(1)
+            1
+            >>> try:
+            ...   schema('one')
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "expected int"
+            True
+        
+        URL's
+        ~~~~~
+        
+        URL's in the schema are matched by using ``urlparse`` library.
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Url
+            >>> schema = Schema(Url())
+            >>> schema('http://w3.org')
+            'http://w3.org'
+            >>> try:
+            ...   schema('one')
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "expected a URL"
+            True
+        
+        Lists
+        ~~~~~
+        
+        Lists in the schema are treated as a set of valid values. Each element
+        in the schema list is compared to each value in the input data:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema([1, 'a', 'string'])
+            >>> schema([1])
+            [1]
+            >>> schema([1, 1, 1])
+            [1, 1, 1]
+            >>> schema(['a', 1, 'string', 1, 'string'])
+            ['a', 1, 'string', 1, 'string']
+        
+        Validation functions
+        ~~~~~~~~~~~~~~~~~~~~
+        
+        Validators are simple callables that raise an ``Invalid`` exception when
+        they encounter invalid data. The criteria for determining validity is
+        entirely up to the implementation; it may check that a value is a valid
+        username with ``pwd.getpwnam()``, it may check that a value is of a
+        specific type, and so on.
+        
+        The simplest kind of validator is a Python function that raises
+        ValueError when its argument is invalid. Conveniently, many builtin
+        Python functions have this property. Here's an example of a date
+        validator:
+        
+        .. code:: pycon
+        
+            >>> from datetime import datetime
+            >>> def Date(fmt='%Y-%m-%d'):
+            ...   return lambda v: datetime.strptime(v, fmt)
+        
+        .. code:: pycon
+        
+            >>> schema = Schema(Date())
+            >>> schema('2013-03-03')
+            datetime.datetime(2013, 3, 3, 0, 0)
+            >>> try:
+            ...   schema('2013-03')
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "not a valid value"
+            True
+        
+        In addition to simply determining if a value is valid, validators may
+        mutate the value into a valid form. An example of this is the
+        ``Coerce(type)`` function, which returns a function that coerces its
+        argument to the given type:
+        
+        .. code:: python
+        
+            def Coerce(type, msg=None):
+                """Coerce a value to a type.
+        
+                If the type constructor throws a ValueError, the value will be marked as
+                Invalid.
+                """
+                def f(v):
+                    try:
+                        return type(v)
+                    except ValueError:
+                        raise Invalid(msg or ('expected %s' % type.__name__))
+                return f
+        
+        This example also shows a common idiom where an optional human-readable
+        message can be provided. This can vastly improve the usefulness of the
+        resulting error messages.
+        
+        Dictionaries
+        ~~~~~~~~~~~~
+        
+        Each key-value pair in a schema dictionary is validated against each
+        key-value pair in the corresponding data dictionary:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema({1: 'one', 2: 'two'})
+            >>> schema({1: 'one'})
+            {1: 'one'}
+        
+        Extra dictionary keys
+        ^^^^^^^^^^^^^^^^^^^^^
+        
+        By default any additional keys in the data, not in the schema will
+        trigger exceptions:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema({2: 3})
+            >>> try:
+            ...   schema({1: 2, 2: 3})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "extra keys not allowed @ data[1]"
+            True
+        
+        This behaviour can be altered on a per-schema basis. To allow additional
+        keys use ``Schema(..., extra=ALLOW_EXTRA)``:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import ALLOW_EXTRA
+            >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
+            >>> schema({1: 2, 2: 3})
+            {1: 2, 2: 3}
+        
+        To remove additional keys use ``Schema(..., extra=REMOVE_EXTRA)``:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import REMOVE_EXTRA
+            >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
+            >>> schema({1: 2, 2: 3})
+            {2: 3}
+        
+        It can also be overridden per-dictionary by using the catch-all marker
+        token ``extra`` as a key:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Extra
+            >>> schema = Schema({1: {Extra: object}})
+            >>> schema({1: {'foo': 'bar'}})
+            {1: {'foo': 'bar'}}
+        
+        Required dictionary keys
+        ^^^^^^^^^^^^^^^^^^^^^^^^
+        
+        By default, keys in the schema are not required to be in the data:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema({1: 2, 3: 4})
+            >>> schema({3: 4})
+            {3: 4}
+        
+        Similarly to how extra\_ keys work, this behaviour can be overridden
+        per-schema:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema({1: 2, 3: 4}, required=True)
+            >>> try:
+            ...   schema({3: 4})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "required key not provided @ data[1]"
+            True
+        
+        And per-key, with the marker token ``Required(key)``:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema({Required(1): 2, 3: 4})
+            >>> try:
+            ...   schema({3: 4})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "required key not provided @ data[1]"
+            True
+            >>> schema({1: 2})
+            {1: 2}
+        
+        Optional dictionary keys
+        ^^^^^^^^^^^^^^^^^^^^^^^^
+        
+        If a schema has ``required=True``, keys may be individually marked as
+        optional using the marker token ``Optional(key)``:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Optional
+            >>> schema = Schema({1: 2, Optional(3): 4}, required=True)
+            >>> try:
+            ...   schema({})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "required key not provided @ data[1]"
+            True
+            >>> schema({1: 2})
+            {1: 2}
+            >>> try:
+            ...   schema({1: 2, 4: 5})
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "extra keys not allowed @ data[4]"
+            True
+        
+        .. code:: pycon
+        
+            >>> schema({1: 2, 3: 4})
+            {1: 2, 3: 4}
+        
+        Recursive schema
+        ~~~~~~~~~~~~~~~~
+        
+        There is no syntax to have a recursive schema. The best way to do it is
+        to have a wrapper like this:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Schema, Any
+            >>> def s2(v):
+            ...     return s1(v)
+            ...
+            >>> s1 = Schema({"key": Any(s2, "value")})
+            >>> s1({"key": {"key": "value"}})
+            {'key': {'key': 'value'}}
+        
+        Extending an existing Schema
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        
+        Often it comes handy to have a base ``Schema`` that is extended with
+        more requirements. In that case you can use ``Schema.extend`` to create
+        a new ``Schema``:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Schema
+            >>> person = Schema({'name': str})
+            >>> person_with_age = person.extend({'age': int})
+            >>> sorted(list(person_with_age.schema.keys()))
+            ['age', 'name']
+        
+        The original ``Schema`` remains unchanged.
+        
+        Objects
+        ~~~~~~~
+        
+        Each key-value pair in a schema dictionary is validated against each
+        attribute-value pair in the corresponding object:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Object
+            >>> class Structure(object):
+            ...     def __init__(self, q=None):
+            ...         self.q = q
+            ...     def __repr__(self):
+            ...         return '<Structure(q={0.q!r})>'.format(self)
+            ...
+            >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
+            >>> schema(Structure(q='one'))
+            <Structure(q='one')>
+        
+        Allow None values
+        ~~~~~~~~~~~~~~~~~
+        
+        To allow value to be None as well, use Any:
+        
+        .. code:: pycon
+        
+            >>> from voluptuous import Any
+        
+            >>> schema = Schema(Any(None, int))
+            >>> schema(None)
+            >>> schema(5)
+            5
+        
+        Error reporting
+        ---------------
+        
+        Validators must throw an ``Invalid`` exception if invalid data is passed
+        to them. All other exceptions are treated as errors in the validator and
+        will not be caught.
+        
+        Each ``Invalid`` exception has an associated ``path`` attribute
+        representing the path in the data structure to our currently validating
+        value, as well as an ``error_message`` attribute that contains the
+        message of the original exception. This is especially useful when you
+        want to catch ``Invalid`` exceptions and give some feedback to the user,
+        for instance in the context of an HTTP API.
+        
+        .. code:: pycon
+        
+            >>> def validate_email(email):
+            ...     """Validate email."""
+            ...     if not "@" in email:
+            ...         raise Invalid("This email is invalid.")
+            ...     return email
+            >>> schema = Schema({"email": validate_email})
+            >>> exc = None
+            >>> try:
+            ...     schema({"email": "whatever"})
+            ... except MultipleInvalid as e:
+            ...     exc = e
+            >>> str(exc)
+            "This email is invalid. for dictionary value @ data['email']"
+            >>> exc.path
+            ['email']
+            >>> exc.msg
+            'This email is invalid.'
+            >>> exc.error_message
+            'This email is invalid.'
+        
+        The ``path`` attribute is used during error reporting, but also during
+        matching to determine whether an error should be reported to the user or
+        if the next match should be attempted. This is determined by comparing
+        the depth of the path where the check is, to the depth of the path where
+        the error occurred. If the error is more than one level deeper, it is
+        reported.
+        
+        The upshot of this is that *matching is depth-first and fail-fast*.
+        
+        To illustrate this, here is an example schema:
+        
+        .. code:: pycon
+        
+            >>> schema = Schema([[2, 3], 6])
+        
+        Each value in the top-level list is matched depth-first in-order. Given
+        input data of ``[[6]]``, the inner list will match the first element of
+        the schema, but the literal ``6`` will not match any of the elements of
+        that list. This error will be reported back to the user immediately. No
+        backtracking is attempted:
+        
+        .. code:: pycon
+        
+            >>> try:
+            ...   schema([[6]])
+            ...   raise AssertionError('MultipleInvalid not raised')
+            ... except MultipleInvalid as e:
+            ...   exc = e
+            >>> str(exc) == "not a valid value @ data[0][0]"
+            True
+        
+        If we pass the data ``[6]``, the ``6`` is not a list type and so will
+        not recurse into the first element of the schema. Matching will continue
+        on to the second element in the schema, and succeed:
+        
+        .. code:: pycon
+        
+            >>> schema([6])
+            [6]
+        
+        Running tests.
+        --------------
+        
+        Voluptuous is using nosetests:
+        
+        ::
+        
+            $ nosetests
+        
+        Why use Voluptuous over another validation library?
+        ---------------------------------------------------
+        
+        **Validators are simple callables**
+            No need to subclass anything, just use a function.
+        **Errors are simple exceptions.**
+            A validator can just ``raise Invalid(msg)`` and expect the user to
+            get useful messages.
+        **Schemas are basic Python data structures.**
+            Should your data be a dictionary of integer keys to strings?
+            ``{int: str}`` does what you expect. List of integers, floats or
+            strings? ``[int, float, str]``.
+        **Designed from the ground up for validating more than just forms.**
+            Nested data structures are treated in the same way as any other
+            type. Need a list of dictionaries? ``[{}]``
+        **Consistency.**
+            Types in the schema are checked as types. Values are compared as
+            values. Callables are called to validate. Simple.
+        
+        Other libraries and inspirations
+        --------------------------------
+        
+        Voluptuous is heavily inspired by
+        `Validino <http://code.google.com/p/validino/>`__, and to a lesser
+        extent, `jsonvalidator <http://code.google.com/p/jsonvalidator/>`__ and
+        `json\_schema <http://blog.sendapatch.se/category/json_schema.html>`__.
+        
+        I greatly prefer the light-weight style promoted by these libraries to
+        the complexity of libraries like FormEncode.
+        
+        .. |Build Status| image:: https://travis-ci.org/alecthomas/voluptuous.png
+           :target: https://travis-ci.org/alecthomas/voluptuous
+        .. |Stories in Ready| image:: https://badge.waffle.io/alecthomas/voluptuous.png?label=ready&title=Ready
+           :target: https://waffle.io/alecthomas/voluptuous
+        
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.1
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/README.md
@@ -0,0 +1,596 @@
+# Voluptuous is a Python data validation library
+
+[![Build Status](https://travis-ci.org/alecthomas/voluptuous.png)](https://travis-ci.org/alecthomas/voluptuous) [![Stories in Ready](https://badge.waffle.io/alecthomas/voluptuous.png?label=ready&title=Ready)](https://waffle.io/alecthomas/voluptuous)
+
+Voluptuous, *despite* the name, is a Python data validation library. It
+is primarily intended for validating data coming into Python as JSON,
+YAML, etc.
+
+It has three goals:
+
+1.  Simplicity.
+2.  Support for complex data structures.
+3.  Provide useful error messages.
+
+## Contact
+
+Voluptuous now has a mailing list! Send a mail to
+[<voluptuous@librelist.com>](mailto:voluptuous@librelist.com) to subscribe. Instructions
+will follow.
+
+You can also contact me directly via [email](mailto:alec@swapoff.org) or
+[Twitter](https://twitter.com/alecthomas).
+
+To file a bug, create a [new issue](https://github.com/alecthomas/voluptuous/issues/new) on GitHub with a short example of how to replicate the issue.
+
+## Show me an example
+
+Twitter's [user search API](https://dev.twitter.com/docs/api/1/get/users/search) accepts
+query URLs like:
+
+```
+$ curl 'http://api.twitter.com/1/users/search.json?q=python&per_page=20&page=1
+```
+
+To validate this we might use a schema like:
+
+```pycon
+>>> from voluptuous import Schema
+>>> schema = Schema({
+...   'q': str,
+...   'per_page': int,
+...   'page': int,
+... })
+
+```
+
+This schema very succinctly and roughly describes the data required by
+the API, and will work fine. But it has a few problems. Firstly, it
+doesn't fully express the constraints of the API. According to the API,
+`per_page` should be restricted to at most 20, defaulting to 5, for
+example. To describe the semantics of the API more accurately, our
+schema will need to be more thoroughly defined:
+
+```pycon
+>>> from voluptuous import Required, All, Length, Range
+>>> schema = Schema({
+...   Required('q'): All(str, Length(min=1)),
+...   Required('per_page', default=5): All(int, Range(min=1, max=20)),
+...   'page': All(int, Range(min=0)),
+... })
+
+```
+
+This schema fully enforces the interface defined in Twitter's
+documentation, and goes a little further for completeness.
+
+"q" is required:
+
+```pycon
+>>> from voluptuous import MultipleInvalid, Invalid
+>>> try:
+...   schema({})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "required key not provided @ data['q']"
+True
+
+```
+
+...must be a string:
+
+```pycon
+>>> try:
+...   schema({'q': 123})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "expected str for dictionary value @ data['q']"
+True
+
+```
+
+...and must be at least one character in length:
+
+```pycon
+>>> try:
+...   schema({'q': ''})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
+True
+>>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
+True
+
+```
+
+"per\_page" is a positive integer no greater than 20:
+
+```pycon
+>>> try:
+...   schema({'q': '#topic', 'per_page': 900})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
+True
+>>> try:
+...   schema({'q': '#topic', 'per_page': -10})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
+True
+
+```
+
+"page" is an integer \>= 0:
+
+```pycon
+>>> try:
+...   schema({'q': '#topic', 'per_page': 'one'})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc)
+"expected int for dictionary value @ data['per_page']"
+>>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
+True
+
+```
+
+## Defining schemas
+
+Schemas are nested data structures consisting of dictionaries, lists,
+scalars and *validators*. Each node in the input schema is pattern
+matched against corresponding nodes in the input data.
+
+### Literals
+
+Literals in the schema are matched using normal equality checks:
+
+```pycon
+>>> schema = Schema(1)
+>>> schema(1)
+1
+>>> schema = Schema('a string')
+>>> schema('a string')
+'a string'
+
+```
+
+### Types
+
+Types in the schema are matched by checking if the corresponding value
+is an instance of the type:
+
+```pycon
+>>> schema = Schema(int)
+>>> schema(1)
+1
+>>> try:
+...   schema('one')
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "expected int"
+True
+
+```
+
+### URL's
+
+URL's in the schema are matched by using `urlparse` library.
+
+```pycon
+>>> from voluptuous import Url
+>>> schema = Schema(Url())
+>>> schema('http://w3.org')
+'http://w3.org'
+>>> try:
+...   schema('one')
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "expected a URL"
+True
+
+```
+
+### Lists
+
+Lists in the schema are treated as a set of valid values. Each element
+in the schema list is compared to each value in the input data:
+
+```pycon
+>>> schema = Schema([1, 'a', 'string'])
+>>> schema([1])
+[1]
+>>> schema([1, 1, 1])
+[1, 1, 1]
+>>> schema(['a', 1, 'string', 1, 'string'])
+['a', 1, 'string', 1, 'string']
+
+```
+
+### Validation functions
+
+Validators are simple callables that raise an `Invalid` exception when
+they encounter invalid data. The criteria for determining validity is
+entirely up to the implementation; it may check that a value is a valid
+username with `pwd.getpwnam()`, it may check that a value is of a
+specific type, and so on.
+
+The simplest kind of validator is a Python function that raises
+ValueError when its argument is invalid. Conveniently, many builtin
+Python functions have this property. Here's an example of a date
+validator:
+
+```pycon
+>>> from datetime import datetime
+>>> def Date(fmt='%Y-%m-%d'):
+...   return lambda v: datetime.strptime(v, fmt)
+
+```
+
+```pycon
+>>> schema = Schema(Date())
+>>> schema('2013-03-03')
+datetime.datetime(2013, 3, 3, 0, 0)
+>>> try:
+...   schema('2013-03')
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "not a valid value"
+True
+
+```
+
+In addition to simply determining if a value is valid, validators may
+mutate the value into a valid form. An example of this is the
+`Coerce(type)` function, which returns a function that coerces its
+argument to the given type:
+
+```python
+def Coerce(type, msg=None):
+    """Coerce a value to a type.
+
+    If the type constructor throws a ValueError, the value will be marked as
+    Invalid.
+    """
+    def f(v):
+        try:
+            return type(v)
+        except ValueError:
+            raise Invalid(msg or ('expected %s' % type.__name__))
+    return f
+
+```
+
+This example also shows a common idiom where an optional human-readable
+message can be provided. This can vastly improve the usefulness of the
+resulting error messages.
+
+### Dictionaries
+
+Each key-value pair in a schema dictionary is validated against each
+key-value pair in the corresponding data dictionary:
+
+```pycon
+>>> schema = Schema({1: 'one', 2: 'two'})
+>>> schema({1: 'one'})
+{1: 'one'}
+
+```
+
+#### Extra dictionary keys
+
+By default any additional keys in the data, not in the schema will
+trigger exceptions:
+
+```pycon
+>>> schema = Schema({2: 3})
+>>> try:
+...   schema({1: 2, 2: 3})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "extra keys not allowed @ data[1]"
+True
+
+```
+
+This behaviour can be altered on a per-schema basis. To allow
+additional keys use
+`Schema(..., extra=ALLOW_EXTRA)`:
+
+```pycon
+>>> from voluptuous import ALLOW_EXTRA
+>>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
+>>> schema({1: 2, 2: 3})
+{1: 2, 2: 3}
+
+```
+
+To remove additional keys use
+`Schema(..., extra=REMOVE_EXTRA)`:
+
+```pycon
+>>> from voluptuous import REMOVE_EXTRA
+>>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
+>>> schema({1: 2, 2: 3})
+{2: 3}
+
+```
+
+It can also be overridden per-dictionary by using the catch-all marker
+token `extra` as a key:
+
+```pycon
+>>> from voluptuous import Extra
+>>> schema = Schema({1: {Extra: object}})
+>>> schema({1: {'foo': 'bar'}})
+{1: {'foo': 'bar'}}
+
+```
+
+#### Required dictionary keys
+
+By default, keys in the schema are not required to be in the data:
+
+```pycon
+>>> schema = Schema({1: 2, 3: 4})
+>>> schema({3: 4})
+{3: 4}
+
+```
+
+Similarly to how extra\_ keys work, this behaviour can be overridden
+per-schema:
+
+```pycon
+>>> schema = Schema({1: 2, 3: 4}, required=True)
+>>> try:
+...   schema({3: 4})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "required key not provided @ data[1]"
+True
+
+```
+
+And per-key, with the marker token `Required(key)`:
+
+```pycon
+>>> schema = Schema({Required(1): 2, 3: 4})
+>>> try:
+...   schema({3: 4})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "required key not provided @ data[1]"
+True
+>>> schema({1: 2})
+{1: 2}
+
+```
+
+#### Optional dictionary keys
+
+If a schema has `required=True`, keys may be individually marked as
+optional using the marker token `Optional(key)`:
+
+```pycon
+>>> from voluptuous import Optional
+>>> schema = Schema({1: 2, Optional(3): 4}, required=True)
+>>> try:
+...   schema({})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "required key not provided @ data[1]"
+True
+>>> schema({1: 2})
+{1: 2}
+>>> try:
+...   schema({1: 2, 4: 5})
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "extra keys not allowed @ data[4]"
+True
+
+```
+
+```pycon
+>>> schema({1: 2, 3: 4})
+{1: 2, 3: 4}
+
+```
+
+### Recursive schema
+
+There is no syntax to have a recursive schema. The best way to do it is to have a wrapper like this:
+
+```pycon
+>>> from voluptuous import Schema, Any
+>>> def s2(v):
+...     return s1(v)
+...
+>>> s1 = Schema({"key": Any(s2, "value")})
+>>> s1({"key": {"key": "value"}})
+{'key': {'key': 'value'}}
+
+```
+
+### Extending an existing Schema
+
+Often it comes handy to have a base `Schema` that is extended with more
+requirements. In that case you can use `Schema.extend` to create a new
+`Schema`:
+
+```pycon
+>>> from voluptuous import Schema
+>>> person = Schema({'name': str})
+>>> person_with_age = person.extend({'age': int})
+>>> sorted(list(person_with_age.schema.keys()))
+['age', 'name']
+
+```
+
+The original `Schema` remains unchanged.
+
+### Objects
+
+Each key-value pair in a schema dictionary is validated against each
+attribute-value pair in the corresponding object:
+
+```pycon
+>>> from voluptuous import Object
+>>> class Structure(object):
+...     def __init__(self, q=None):
+...         self.q = q
+...     def __repr__(self):
+...         return '<Structure(q={0.q!r})>'.format(self)
+...
+>>> schema = Schema(Object({'q': 'one'}, cls=Structure))
+>>> schema(Structure(q='one'))
+<Structure(q='one')>
+
+```
+
+### Allow None values
+
+To allow value to be None as well, use Any:
+
+```pycon
+>>> from voluptuous import Any
+
+>>> schema = Schema(Any(None, int))
+>>> schema(None)
+>>> schema(5)
+5
+
+```
+
+## Error reporting
+
+Validators must throw an `Invalid` exception if invalid data is passed
+to them. All other exceptions are treated as errors in the validator and
+will not be caught.
+
+Each `Invalid` exception has an associated `path` attribute representing
+the path in the data structure to our currently validating value, as well
+as an `error_message` attribute that contains the message of the original
+exception. This is especially useful when you want to catch `Invalid`
+exceptions and give some feedback to the user, for instance in the context of
+an HTTP API.
+
+
+```pycon
+>>> def validate_email(email):
+...     """Validate email."""
+...     if not "@" in email:
+...         raise Invalid("This email is invalid.")
+...     return email
+>>> schema = Schema({"email": validate_email})
+>>> exc = None
+>>> try:
+...     schema({"email": "whatever"})
+... except MultipleInvalid as e:
+...     exc = e
+>>> str(exc)
+"This email is invalid. for dictionary value @ data['email']"
+>>> exc.path
+['email']
+>>> exc.msg
+'This email is invalid.'
+>>> exc.error_message
+'This email is invalid.'
+
+```
+
+The `path` attribute is used during error reporting, but also during matching
+to determine whether an error should be reported to the user or if the next
+match should be attempted. This is determined by comparing the depth of the
+path where the check is, to the depth of the path where the error occurred. If
+the error is more than one level deeper, it is reported.
+
+The upshot of this is that *matching is depth-first and fail-fast*.
+
+To illustrate this, here is an example schema:
+
+```pycon
+>>> schema = Schema([[2, 3], 6])
+
+```
+
+Each value in the top-level list is matched depth-first in-order. Given
+input data of `[[6]]`, the inner list will match the first element of
+the schema, but the literal `6` will not match any of the elements of
+that list. This error will be reported back to the user immediately. No
+backtracking is attempted:
+
+```pycon
+>>> try:
+...   schema([[6]])
+...   raise AssertionError('MultipleInvalid not raised')
+... except MultipleInvalid as e:
+...   exc = e
+>>> str(exc) == "not a valid value @ data[0][0]"
+True
+
+```
+
+If we pass the data `[6]`, the `6` is not a list type and so will not
+recurse into the first element of the schema. Matching will continue on
+to the second element in the schema, and succeed:
+
+```pycon
+>>> schema([6])
+[6]
+
+```
+
+## Running tests.
+
+Voluptuous is using nosetests:
+
+    $ nosetests
+
+
+## Why use Voluptuous over another validation library?
+
+**Validators are simple callables**
+:   No need to subclass anything, just use a function.
+
+**Errors are simple exceptions.**
+:   A validator can just `raise Invalid(msg)` and expect the user to get
+useful messages.
+
+**Schemas are basic Python data structures.**
+:   Should your data be a dictionary of integer keys to strings?
+`{int: str}` does what you expect. List of integers, floats or
+strings? `[int, float, str]`.
+
+**Designed from the ground up for validating more than just forms.**
+:   Nested data structures are treated in the same way as any other
+type. Need a list of dictionaries? `[{}]`
+
+**Consistency.**
+:   Types in the schema are checked as types. Values are compared as
+values. Callables are called to validate. Simple.
+
+## Other libraries and inspirations
+
+Voluptuous is heavily inspired by
+[Validino](http://code.google.com/p/validino/), and to a lesser extent,
+[jsonvalidator](http://code.google.com/p/jsonvalidator/) and
+[json\_schema](http://blog.sendapatch.se/category/json_schema.html).
+
+I greatly prefer the light-weight style promoted by these libraries to
+the complexity of libraries like FormEncode.
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/README.rst
@@ -0,0 +1,589 @@
+Voluptuous is a Python data validation library
+==============================================
+
+|Build Status| |Stories in Ready|
+
+Voluptuous, *despite* the name, is a Python data validation library. It
+is primarily intended for validating data coming into Python as JSON,
+YAML, etc.
+
+It has three goals:
+
+1. Simplicity.
+2. Support for complex data structures.
+3. Provide useful error messages.
+
+Contact
+-------
+
+Voluptuous now has a mailing list! Send a mail to
+` <mailto:voluptuous@librelist.com>`__ to subscribe. Instructions will
+follow.
+
+You can also contact me directly via `email <mailto:alec@swapoff.org>`__
+or `Twitter <https://twitter.com/alecthomas>`__.
+
+To file a bug, create a `new
+issue <https://github.com/alecthomas/voluptuous/issues/new>`__ on GitHub
+with a short example of how to replicate the issue.
+
+Show me an example
+------------------
+
+Twitter's `user search
+API <https://dev.twitter.com/docs/api/1/get/users/search>`__ accepts
+query URLs like:
+
+::
+
+    $ curl 'http://api.twitter.com/1/users/search.json?q=python&per_page=20&page=1
+
+To validate this we might use a schema like:
+
+.. code:: pycon
+
+    >>> from voluptuous import Schema
+    >>> schema = Schema({
+    ...   'q': str,
+    ...   'per_page': int,
+    ...   'page': int,
+    ... })
+
+This schema very succinctly and roughly describes the data required by
+the API, and will work fine. But it has a few problems. Firstly, it
+doesn't fully express the constraints of the API. According to the API,
+``per_page`` should be restricted to at most 20, defaulting to 5, for
+example. To describe the semantics of the API more accurately, our
+schema will need to be more thoroughly defined:
+
+.. code:: pycon
+
+    >>> from voluptuous import Required, All, Length, Range
+    >>> schema = Schema({
+    ...   Required('q'): All(str, Length(min=1)),
+    ...   Required('per_page', default=5): All(int, Range(min=1, max=20)),
+    ...   'page': All(int, Range(min=0)),
+    ... })
+
+This schema fully enforces the interface defined in Twitter's
+documentation, and goes a little further for completeness.
+
+"q" is required:
+
+.. code:: pycon
+
+    >>> from voluptuous import MultipleInvalid, Invalid
+    >>> try:
+    ...   schema({})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "required key not provided @ data['q']"
+    True
+
+...must be a string:
+
+.. code:: pycon
+
+    >>> try:
+    ...   schema({'q': 123})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "expected str for dictionary value @ data['q']"
+    True
+
+...and must be at least one character in length:
+
+.. code:: pycon
+
+    >>> try:
+    ...   schema({'q': ''})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "length of value must be at least 1 for dictionary value @ data['q']"
+    True
+    >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
+    True
+
+"per\_page" is a positive integer no greater than 20:
+
+.. code:: pycon
+
+    >>> try:
+    ...   schema({'q': '#topic', 'per_page': 900})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "value must be at most 20 for dictionary value @ data['per_page']"
+    True
+    >>> try:
+    ...   schema({'q': '#topic', 'per_page': -10})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
+    True
+
+"page" is an integer >= 0:
+
+.. code:: pycon
+
+    >>> try:
+    ...   schema({'q': '#topic', 'per_page': 'one'})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "expected int for dictionary value @ data['per_page']"
+    >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
+    True
+
+Defining schemas
+----------------
+
+Schemas are nested data structures consisting of dictionaries, lists,
+scalars and *validators*. Each node in the input schema is pattern
+matched against corresponding nodes in the input data.
+
+Literals
+~~~~~~~~
+
+Literals in the schema are matched using normal equality checks:
+
+.. code:: pycon
+
+    >>> schema = Schema(1)
+    >>> schema(1)
+    1
+    >>> schema = Schema('a string')
+    >>> schema('a string')
+    'a string'
+
+Types
+~~~~~
+
+Types in the schema are matched by checking if the corresponding value
+is an instance of the type:
+
+.. code:: pycon
+
+    >>> schema = Schema(int)
+    >>> schema(1)
+    1
+    >>> try:
+    ...   schema('one')
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "expected int"
+    True
+
+URL's
+~~~~~
+
+URL's in the schema are matched by using ``urlparse`` library.
+
+.. code:: pycon
+
+    >>> from voluptuous import Url
+    >>> schema = Schema(Url())
+    >>> schema('http://w3.org')
+    'http://w3.org'
+    >>> try:
+    ...   schema('one')
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "expected a URL"
+    True
+
+Lists
+~~~~~
+
+Lists in the schema are treated as a set of valid values. Each element
+in the schema list is compared to each value in the input data:
+
+.. code:: pycon
+
+    >>> schema = Schema([1, 'a', 'string'])
+    >>> schema([1])
+    [1]
+    >>> schema([1, 1, 1])
+    [1, 1, 1]
+    >>> schema(['a', 1, 'string', 1, 'string'])
+    ['a', 1, 'string', 1, 'string']
+
+Validation functions
+~~~~~~~~~~~~~~~~~~~~
+
+Validators are simple callables that raise an ``Invalid`` exception when
+they encounter invalid data. The criteria for determining validity is
+entirely up to the implementation; it may check that a value is a valid
+username with ``pwd.getpwnam()``, it may check that a value is of a
+specific type, and so on.
+
+The simplest kind of validator is a Python function that raises
+ValueError when its argument is invalid. Conveniently, many builtin
+Python functions have this property. Here's an example of a date
+validator:
+
+.. code:: pycon
+
+    >>> from datetime import datetime
+    >>> def Date(fmt='%Y-%m-%d'):
+    ...   return lambda v: datetime.strptime(v, fmt)
+
+.. code:: pycon
+
+    >>> schema = Schema(Date())
+    >>> schema('2013-03-03')
+    datetime.datetime(2013, 3, 3, 0, 0)
+    >>> try:
+    ...   schema('2013-03')
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "not a valid value"
+    True
+
+In addition to simply determining if a value is valid, validators may
+mutate the value into a valid form. An example of this is the
+``Coerce(type)`` function, which returns a function that coerces its
+argument to the given type:
+
+.. code:: python
+
+    def Coerce(type, msg=None):
+        """Coerce a value to a type.
+
+        If the type constructor throws a ValueError, the value will be marked as
+        Invalid.
+        """
+        def f(v):
+            try:
+                return type(v)
+            except ValueError:
+                raise Invalid(msg or ('expected %s' % type.__name__))
+        return f
+
+This example also shows a common idiom where an optional human-readable
+message can be provided. This can vastly improve the usefulness of the
+resulting error messages.
+
+Dictionaries
+~~~~~~~~~~~~
+
+Each key-value pair in a schema dictionary is validated against each
+key-value pair in the corresponding data dictionary:
+
+.. code:: pycon
+
+    >>> schema = Schema({1: 'one', 2: 'two'})
+    >>> schema({1: 'one'})
+    {1: 'one'}
+
+Extra dictionary keys
+^^^^^^^^^^^^^^^^^^^^^
+
+By default any additional keys in the data, not in the schema will
+trigger exceptions:
+
+.. code:: pycon
+
+    >>> schema = Schema({2: 3})
+    >>> try:
+    ...   schema({1: 2, 2: 3})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "extra keys not allowed @ data[1]"
+    True
+
+This behaviour can be altered on a per-schema basis. To allow additional
+keys use ``Schema(..., extra=ALLOW_EXTRA)``:
+
+.. code:: pycon
+
+    >>> from voluptuous import ALLOW_EXTRA
+    >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
+    >>> schema({1: 2, 2: 3})
+    {1: 2, 2: 3}
+
+To remove additional keys use ``Schema(..., extra=REMOVE_EXTRA)``:
+
+.. code:: pycon
+
+    >>> from voluptuous import REMOVE_EXTRA
+    >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
+    >>> schema({1: 2, 2: 3})
+    {2: 3}
+
+It can also be overridden per-dictionary by using the catch-all marker
+token ``extra`` as a key:
+
+.. code:: pycon
+
+    >>> from voluptuous import Extra
+    >>> schema = Schema({1: {Extra: object}})
+    >>> schema({1: {'foo': 'bar'}})
+    {1: {'foo': 'bar'}}
+
+Required dictionary keys
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, keys in the schema are not required to be in the data:
+
+.. code:: pycon
+
+    >>> schema = Schema({1: 2, 3: 4})
+    >>> schema({3: 4})
+    {3: 4}
+
+Similarly to how extra\_ keys work, this behaviour can be overridden
+per-schema:
+
+.. code:: pycon
+
+    >>> schema = Schema({1: 2, 3: 4}, required=True)
+    >>> try:
+    ...   schema({3: 4})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "required key not provided @ data[1]"
+    True
+
+And per-key, with the marker token ``Required(key)``:
+
+.. code:: pycon
+
+    >>> schema = Schema({Required(1): 2, 3: 4})
+    >>> try:
+    ...   schema({3: 4})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "required key not provided @ data[1]"
+    True
+    >>> schema({1: 2})
+    {1: 2}
+
+Optional dictionary keys
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+If a schema has ``required=True``, keys may be individually marked as
+optional using the marker token ``Optional(key)``:
+
+.. code:: pycon
+
+    >>> from voluptuous import Optional
+    >>> schema = Schema({1: 2, Optional(3): 4}, required=True)
+    >>> try:
+    ...   schema({})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "required key not provided @ data[1]"
+    True
+    >>> schema({1: 2})
+    {1: 2}
+    >>> try:
+    ...   schema({1: 2, 4: 5})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "extra keys not allowed @ data[4]"
+    True
+
+.. code:: pycon
+
+    >>> schema({1: 2, 3: 4})
+    {1: 2, 3: 4}
+
+Recursive schema
+~~~~~~~~~~~~~~~~
+
+There is no syntax to have a recursive schema. The best way to do it is
+to have a wrapper like this:
+
+.. code:: pycon
+
+    >>> from voluptuous import Schema, Any
+    >>> def s2(v):
+    ...     return s1(v)
+    ...
+    >>> s1 = Schema({"key": Any(s2, "value")})
+    >>> s1({"key": {"key": "value"}})
+    {'key': {'key': 'value'}}
+
+Extending an existing Schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Often it comes handy to have a base ``Schema`` that is extended with
+more requirements. In that case you can use ``Schema.extend`` to create
+a new ``Schema``:
+
+.. code:: pycon
+
+    >>> from voluptuous import Schema
+    >>> person = Schema({'name': str})
+    >>> person_with_age = person.extend({'age': int})
+    >>> sorted(list(person_with_age.schema.keys()))
+    ['age', 'name']
+
+The original ``Schema`` remains unchanged.
+
+Objects
+~~~~~~~
+
+Each key-value pair in a schema dictionary is validated against each
+attribute-value pair in the corresponding object:
+
+.. code:: pycon
+
+    >>> from voluptuous import Object
+    >>> class Structure(object):
+    ...     def __init__(self, q=None):
+    ...         self.q = q
+    ...     def __repr__(self):
+    ...         return '<Structure(q={0.q!r})>'.format(self)
+    ...
+    >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
+    >>> schema(Structure(q='one'))
+    <Structure(q='one')>
+
+Allow None values
+~~~~~~~~~~~~~~~~~
+
+To allow value to be None as well, use Any:
+
+.. code:: pycon
+
+    >>> from voluptuous import Any
+
+    >>> schema = Schema(Any(None, int))
+    >>> schema(None)
+    >>> schema(5)
+    5
+
+Error reporting
+---------------
+
+Validators must throw an ``Invalid`` exception if invalid data is passed
+to them. All other exceptions are treated as errors in the validator and
+will not be caught.
+
+Each ``Invalid`` exception has an associated ``path`` attribute
+representing the path in the data structure to our currently validating
+value, as well as an ``error_message`` attribute that contains the
+message of the original exception. This is especially useful when you
+want to catch ``Invalid`` exceptions and give some feedback to the user,
+for instance in the context of an HTTP API.
+
+.. code:: pycon
+
+    >>> def validate_email(email):
+    ...     """Validate email."""
+    ...     if not "@" in email:
+    ...         raise Invalid("This email is invalid.")
+    ...     return email
+    >>> schema = Schema({"email": validate_email})
+    >>> exc = None
+    >>> try:
+    ...     schema({"email": "whatever"})
+    ... except MultipleInvalid as e:
+    ...     exc = e
+    >>> str(exc)
+    "This email is invalid. for dictionary value @ data['email']"
+    >>> exc.path
+    ['email']
+    >>> exc.msg
+    'This email is invalid.'
+    >>> exc.error_message
+    'This email is invalid.'
+
+The ``path`` attribute is used during error reporting, but also during
+matching to determine whether an error should be reported to the user or
+if the next match should be attempted. This is determined by comparing
+the depth of the path where the check is, to the depth of the path where
+the error occurred. If the error is more than one level deeper, it is
+reported.
+
+The upshot of this is that *matching is depth-first and fail-fast*.
+
+To illustrate this, here is an example schema:
+
+.. code:: pycon
+
+    >>> schema = Schema([[2, 3], 6])
+
+Each value in the top-level list is matched depth-first in-order. Given
+input data of ``[[6]]``, the inner list will match the first element of
+the schema, but the literal ``6`` will not match any of the elements of
+that list. This error will be reported back to the user immediately. No
+backtracking is attempted:
+
+.. code:: pycon
+
+    >>> try:
+    ...   schema([[6]])
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == "not a valid value @ data[0][0]"
+    True
+
+If we pass the data ``[6]``, the ``6`` is not a list type and so will
+not recurse into the first element of the schema. Matching will continue
+on to the second element in the schema, and succeed:
+
+.. code:: pycon
+
+    >>> schema([6])
+    [6]
+
+Running tests.
+--------------
+
+Voluptuous is using nosetests:
+
+::
+
+    $ nosetests
+
+Why use Voluptuous over another validation library?
+---------------------------------------------------
+
+**Validators are simple callables**
+    No need to subclass anything, just use a function.
+**Errors are simple exceptions.**
+    A validator can just ``raise Invalid(msg)`` and expect the user to
+    get useful messages.
+**Schemas are basic Python data structures.**
+    Should your data be a dictionary of integer keys to strings?
+    ``{int: str}`` does what you expect. List of integers, floats or
+    strings? ``[int, float, str]``.
+**Designed from the ground up for validating more than just forms.**
+    Nested data structures are treated in the same way as any other
+    type. Need a list of dictionaries? ``[{}]``
+**Consistency.**
+    Types in the schema are checked as types. Values are compared as
+    values. Callables are called to validate. Simple.
+
+Other libraries and inspirations
+--------------------------------
+
+Voluptuous is heavily inspired by
+`Validino <http://code.google.com/p/validino/>`__, and to a lesser
+extent, `jsonvalidator <http://code.google.com/p/jsonvalidator/>`__ and
+`json\_schema <http://blog.sendapatch.se/category/json_schema.html>`__.
+
+I greatly prefer the light-weight style promoted by these libraries to
+the complexity of libraries like FormEncode.
+
+.. |Build Status| image:: https://travis-ci.org/alecthomas/voluptuous.png
+   :target: https://travis-ci.org/alecthomas/voluptuous
+.. |Stories in Ready| image:: https://badge.waffle.io/alecthomas/voluptuous.png?label=ready&title=Ready
+   :target: https://waffle.io/alecthomas/voluptuous
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/setup.cfg
@@ -0,0 +1,10 @@
+[nosetests]
+doctest-extension = md
+with-doctest = 1
+where = .
+
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/setup.py
@@ -0,0 +1,54 @@
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
+import sys
+import os
+import atexit
+sys.path.insert(0, '.')
+version = __import__('voluptuous').__version__
+
+try:
+    import pypandoc
+    long_description = pypandoc.convert('README.md', 'rst')
+    with open('README.rst', 'w') as f:
+        f.write(long_description)
+    atexit.register(lambda: os.unlink('README.rst'))
+except (ImportError, OSError):
+    print('WARNING: Could not locate pandoc, using Markdown long_description.')
+    with open('README.md') as f:
+        long_description = f.read()
+
+description = long_description.splitlines()[0].strip()
+
+
+setup(
+    name='voluptuous',
+    url='https://github.com/alecthomas/voluptuous',
+    download_url='https://pypi.python.org/pypi/voluptuous',
+    version=version,
+    description=description,
+    long_description=long_description,
+    license='BSD',
+    platforms=['any'],
+    py_modules=['voluptuous'],
+    author='Alec Thomas',
+    author_email='alec@swapoff.org',
+    classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.1',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+    ],
+    install_requires=[
+        'setuptools >= 0.6b1',
+    ],
+)
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/tests.md
@@ -0,0 +1,268 @@
+Error reporting should be accurate:
+
+    >>> from voluptuous import *
+    >>> schema = Schema(['one', {'two': 'three', 'four': ['five'],
+    ...                          'six': {'seven': 'eight'}}])
+    >>> schema(['one'])
+    ['one']
+    >>> schema([{'two': 'three'}])
+    [{'two': 'three'}]
+
+It should show the exact index and container type, in this case a list
+value:
+
+    >>> try:
+    ...   schema(['one', 'two'])
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc) == 'expected a dictionary @ data[1]'
+    True
+
+It should also be accurate for nested values:
+
+    >>> try:
+    ...   schema([{'two': 'nine'}])
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "not a valid value for dictionary value @ data[0]['two']"
+
+    >>> try:
+    ...   schema([{'four': ['nine']}])
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "not a valid value @ data[0]['four'][0]"
+
+    >>> try:
+    ...   schema([{'six': {'seven': 'nine'}}])
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "not a valid value for dictionary value @ data[0]['six']['seven']"
+
+Errors should be reported depth-first:
+
+    >>> validate = Schema({'one': {'two': 'three', 'four': 'five'}})
+    >>> try:
+    ...   validate({'one': {'four': 'six'}})
+    ... except Invalid as e:
+    ...   print(e)
+    ...   print(e.path)
+    not a valid value for dictionary value @ data['one']['four']
+    ['one', 'four']
+
+Voluptuous supports validation when extra fields are present in the
+data:
+
+    >>> schema = Schema({'one': 1, Extra: object})
+    >>> schema({'two': 'two', 'one': 1}) == {'two': 'two', 'one': 1}
+    True
+    >>> schema = Schema({'one': 1})
+    >>> try:
+    ...   schema({'two': 2})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "extra keys not allowed @ data['two']"
+
+dict, list, and tuple should be available as type validators:
+
+    >>> Schema(dict)({'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
+    True
+    >>> Schema(list)([1,2,3])
+    [1, 2, 3]
+    >>> Schema(tuple)((1,2,3))
+    (1, 2, 3)
+
+Validation should return instances of the right types when the types are
+subclasses of dict or list:
+
+    >>> class Dict(dict):
+    ...   pass
+    >>>
+    >>> d = Schema(dict)(Dict(a=1, b=2))
+    >>> d == {'a': 1, 'b': 2}
+    True
+    >>> type(d) is Dict
+    True
+    >>> class List(list):
+    ...   pass
+    >>>
+    >>> l = Schema(list)(List([1,2,3]))
+    >>> l
+    [1, 2, 3]
+    >>> type(l) is List
+    True
+
+Multiple errors are reported:
+
+    >>> schema = Schema({'one': 1, 'two': 2})
+    >>> try:
+    ...   schema({'one': 2, 'two': 3, 'three': 4})
+    ... except MultipleInvalid as e:
+    ...   errors = sorted(e.errors, key=lambda k: str(k))
+    ...   print([str(i) for i in errors])  # doctest: +NORMALIZE_WHITESPACE
+    ["extra keys not allowed @ data['three']",
+     "not a valid value for dictionary value @ data['one']",
+     "not a valid value for dictionary value @ data['two']"]
+    >>> schema = Schema([[1], [2], [3]])
+    >>> try:
+    ...   schema([1, 2, 3])
+    ... except MultipleInvalid as e:
+    ...   print([str(i) for i in e.errors])  # doctest: +NORMALIZE_WHITESPACE
+    ['expected a list @ data[0]',
+     'expected a list @ data[1]',
+     'expected a list @ data[2]']
+
+Required fields in dictionary which are invalid should not have required :
+
+    >>> from voluptuous import *
+    >>> schema = Schema({'one': {'two': 3}}, required=True)
+    >>> try:
+    ...   schema({'one': {'two': 2}})
+    ... except MultipleInvalid as e:
+    ...   errors = e.errors
+    >>> 'required' in ' '.join([x.msg for x in errors])
+    False
+
+Multiple errors for nested fields in dicts and objects:
+
+> \>\>\> from collections import namedtuple \>\>\> validate = Schema({
+> ... 'anobject': Object({ ... 'strfield': str, ... 'intfield': int ...
+> }) ... }) \>\>\> try: ... SomeObj = namedtuple('SomeObj', ('strfield',
+> 'intfield')) ... validate({'anobject': SomeObj(strfield=123,
+> intfield='one')}) ... except MultipleInvalid as e: ...
+> print(sorted(str(i) for i in e.errors)) \# doctest:
+> +NORMALIZE\_WHITESPACE ["expected int for object value @
+> data['anobject']['intfield']", "expected str for object value @
+> data['anobject']['strfield']"]
+
+Custom classes validate as schemas:
+
+    >>> class Thing(object):
+    ...     pass
+    >>> schema = Schema(Thing)
+    >>> t = schema(Thing())
+    >>> type(t) is Thing
+    True
+
+Classes with custom metaclasses should validate as schemas:
+
+    >>> class MyMeta(type):
+    ...     pass
+    >>> class Thing(object):
+    ...     __metaclass__ = MyMeta
+    >>> schema = Schema(Thing)
+    >>> t = schema(Thing())
+    >>> type(t) is Thing
+    True
+
+Schemas built with All() should give the same error as the original
+validator (Issue \#26):
+
+    >>> schema = Schema({
+    ...   Required('items'): All([{
+    ...     Required('foo'): str
+    ...   }])
+    ... })
+
+    >>> try:
+    ...   schema({'items': [{}]})
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "required key not provided @ data['items'][0]['foo']"
+
+Validator should return same instance of the same type for object:
+
+    >>> class Structure(object):
+    ...     def __init__(self, q=None):
+    ...         self.q = q
+    ...     def __repr__(self):
+    ...         return '{0.__name__}(q={1.q!r})'.format(type(self), self)
+    ...
+    >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
+    >>> type(schema(Structure(q='one'))) is Structure
+    True
+
+Object validator should treat cls argument as optional. In this case it
+shouldn't check object type:
+
+    >>> from collections import namedtuple
+    >>> NamedTuple = namedtuple('NamedTuple', ('q',))
+    >>> schema = Schema(Object({'q': 'one'}))
+    >>> named = NamedTuple(q='one')
+    >>> schema(named) == named
+    True
+    >>> schema(named)
+    NamedTuple(q='one')
+
+If cls argument passed to object validator we should check object type:
+
+    >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
+    >>> schema(NamedTuple(q='one'))  # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    MultipleInvalid: expected a <class 'Structure'>
+    >>> schema = Schema(Object({'q': 'one'}, cls=NamedTuple))
+    >>> schema(NamedTuple(q='one'))
+    NamedTuple(q='one')
+
+Ensure that objects with \_\_slots\_\_ supported properly:
+
+    >>> class SlotsStructure(Structure):
+    ...     __slots__ = ['q']
+    ...
+    >>> schema = Schema(Object({'q': 'one'}))
+    >>> schema(SlotsStructure(q='one'))
+    SlotsStructure(q='one')
+    >>> class DictStructure(object):
+    ...     __slots__ = ['q', '__dict__']
+    ...     def __init__(self, q=None, page=None):
+    ...         self.q = q
+    ...         self.page = page
+    ...     def __repr__(self):
+    ...         return '{0.__name__}(q={1.q!r}, page={1.page!r})'.format(type(self), self)
+    ...
+    >>> structure = DictStructure(q='one')
+    >>> structure.page = 1
+    >>> try:
+    ...   schema(structure)
+    ...   raise AssertionError('MultipleInvalid not raised')
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> str(exc)
+    "extra keys not allowed @ data['page']"
+
+    >>> schema = Schema(Object({'q': 'one', Extra: object}))
+    >>> schema(structure)
+    DictStructure(q='one', page=1)
+
+Ensure that objects can be used with other validators:
+
+    >>> schema = Schema({'meta': Object({'q': 'one'})})
+    >>> schema({'meta': Structure(q='one')})
+    {'meta': Structure(q='one')}
+
+Ensure that subclasses of Invalid of are raised as is.
+
+    >>> class SpecialInvalid(Invalid):
+    ...   pass
+    ...
+    >>> def custom_validator(value):
+    ...   raise SpecialInvalid('boom')
+    ...
+    >>> schema = Schema({'thing': custom_validator})
+    >>> try:
+    ...   schema({'thing': 'not an int'})
+    ... except MultipleInvalid as e:
+    ...   exc = e
+    >>> exc.errors[0].__class__.__name__
+    'SpecialInvalid'
new file mode 100644
--- /dev/null
+++ b/python/voluptuous/voluptuous.py
@@ -0,0 +1,1954 @@
+# encoding: utf-8
+#
+# Copyright (C) 2010-2013 Alec Thomas <alec@swapoff.org>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution.
+#
+# Author: Alec Thomas <alec@swapoff.org>
+
+"""Schema validation for Python data structures.
+
+Given eg. a nested data structure like this:
+
+    {
+        'exclude': ['Users', 'Uptime'],
+        'include': [],
+        'set': {
+            'snmp_community': 'public',
+            'snmp_timeout': 15,
+            'snmp_version': '2c',
+        },
+        'targets': {
+            'localhost': {
+                'exclude': ['Uptime'],
+                'features': {
+                    'Uptime': {
+                        'retries': 3,
+                    },
+                    'Users': {
+                        'snmp_community': 'monkey',
+                        'snmp_port': 15,
+                    },
+                },
+                'include': ['Users'],
+                'set': {
+                    'snmp_community': 'monkeys',
+                },
+            },
+        },
+    }
+
+A schema like this:
+
+    >>> settings = {
+    ...   'snmp_community': str,
+    ...   'retries': int,
+    ...   'snmp_version': All(Coerce(str), Any('3', '2c', '1')),
+    ... }
+    >>> features = ['Ping', 'Uptime', 'Http']
+    >>> schema = Schema({
+    ...    'exclude': features,
+    ...    'include': features,
+    ...    'set': settings,
+    ...    'targets': {
+    ...      'exclude': features,
+    ...      'include': features,
+    ...      'features': {
+    ...        str: settings,
+    ...      },
+    ...    },
+    ... })
+
+Validate like so:
+
+    >>> schema({
+    ...   'set': {
+    ...     'snmp_community': 'public',
+    ...     'snmp_version': '2c',
+    ...   },
+    ...   'targets': {
+    ...     'exclude': ['Ping'],
+    ...     'features': {
+    ...       'Uptime': {'retries': 3},
+    ...       'Users': {'snmp_community': 'monkey'},
+    ...     },
+    ...   },
+    ... }) == {
+    ...   'set': {'snmp_version': '2c', 'snmp_community': 'public'},
+    ...   'targets': {
+    ...     'exclude': ['Ping'],
+    ...     'features': {'Uptime': {'retries': 3},
+    ...                  'Users': {'snmp_community': 'monkey'}}}}
+    True
+"""
+import collections
+import datetime
+import inspect
+import os
+import re
+import sys
+from contextlib import contextmanager
+from functools import wraps
+
+
+if sys.version_info >= (3,):
+    import urllib.parse as urlparse
+    long = int
+    unicode = str
+    basestring = str
+    ifilter = filter
+    iteritems = lambda d: d.items()
+else:
+    from itertools import ifilter
+    import urlparse
+    iteritems = lambda d: d.iteritems()
+
+
+__author__ = 'Alec Thomas <alec@swapoff.org>'
+__version__ = '0.8.11'
+
+
+@contextmanager
+def raises(exc, msg=None, regex=None):
+    try:
+        yield
+    except exc as e:
+        if msg is not None:
+            assert str(e) == msg, '%r != %r' % (str(e), msg)
+        if regex is not None:
+            assert re.search(regex, str(e)), '%r does not match %r' % (str(e), regex)
+
+
+class Undefined(object):
+    def __nonzero__(self):
+        return False
+
+    def __repr__(self):
+        return '...'
+
+
+UNDEFINED = Undefined()
+
+
+def default_factory(value):
+    if value is UNDEFINED or callable(value):
+        return value
+    return lambda: value
+
+
+# options for extra keys
+PREVENT_EXTRA = 0  # any extra key not in schema will raise an error
+ALLOW_EXTRA = 1    # extra keys not in schema will be included in output
+REMOVE_EXTRA = 2   # extra keys not in schema will be excluded from output
+
+
+class Error(Exception):
+    """Base validation exception."""
+
+
+class SchemaError(Error):
+    """An error was encountered in the schema."""
+
+
+class Invalid(Error):
+    """The data was invalid.
+
+    :attr msg: The error message.
+    :attr path: The path to the error, as a list of keys in the source data.
+    :attr error_message: The actual error message that was raised, as a
+        string.
+
+    """
+
+    def __init__(self, message, path=None, error_message=None, error_type=None):
+        Error.__init__(self, message)
+        self.path = path or []
+        self.error_message = error_message or message
+        self.error_type = error_type
+
+    @property
+    def msg(self):
+        return self.args[0]
+
+    def __str__(self):
+        path = ' @ data[%s]' % ']['.join(map(repr, self.path)) \
+            if self.path else ''
+        output = Exception.__str__(self)
+        if self.error_type:
+            output += ' for ' + self.error_type
+        return output + path
+
+    def prepend(self, path):
+        self.path = path + self.path
+
+
+class MultipleInvalid(Invalid):
+    def __init__(self, errors=None):
+        self.errors = errors[:] if errors else []
+
+    def __repr__(self):
+        return 'MultipleInvalid(%r)' % self.errors
+
+    @property
+    def msg(self):
+        return self.errors[0].msg
+
+    @property
+    def path(self):
+        return self.errors[0].path
+
+    @property
+    def error_message(self):
+        return self.errors[0].error_message
+
+    def add(self, error):
+        self.errors.append(error)
+
+    def __str__(self):
+        return str(self.errors[0])
+
+    def prepend(self, path):
+        for error in self.errors:
+            error.prepend(path)
+
+
+class RequiredFieldInvalid(Invalid):
+    """Required field was missing."""
+
+
+class ObjectInvalid(Invalid):
+    """The value we found was not an object."""
+
+
+class DictInvalid(Invalid):
+    """The value found was not a dict."""
+
+
+class ExclusiveInvalid(Invalid):
+    """More than one value found in exclusion group."""
+
+
+class InclusiveInvalid(Invalid):
+    """Not all values found in inclusion group."""
+
+
+class SequenceTypeInvalid(Invalid):
+    """The type found is not a sequence type."""
+
+
+class TypeInvalid(Invalid):
+    """The value was not of required type."""
+
+
+class ValueInvalid(Invalid):
+    """The value was found invalid by evaluation function."""
+
+
+class ScalarInvalid(Invalid):
+    """Scalars did not match."""
+
+
+class CoerceInvalid(Invalid):
+    """Impossible to coerce value to type."""
+
+
+class AnyInvalid(Invalid):
+    """The value did not pass any validator."""
+
+
+class AllInvalid(Invalid):
+    """The value did not pass all validators."""
+
+
+class MatchInvalid(Invalid):
+    """The value does not match the given regular expression."""
+
+
+class RangeInvalid(Invalid):
+    """The value is not in given range."""
+
+
+class TrueInvalid(Invalid):
+    """The value is not True."""
+
+
+class FalseInvalid(Invalid):
+    """The value is not False."""
+
+
+class BooleanInvalid(Invalid):
+    """The value is not a boolean."""
+
+
+class UrlInvalid(Invalid):
+    """The value is not a url."""
+
+
+class FileInvalid(Invalid):
+    """The value is not a file."""
+
+
+class DirInvalid(Invalid):
+    """The value is not a directory."""
+
+
+class PathInvalid(Invalid):
+    """The value is not a path."""
+
+
+class LiteralInvalid(Invalid):
+    """The literal values do not match."""
+
+
+class VirtualPathComponent(str):
+    def __str__(self):
+        return '<' + self + '>'
+
+    def __repr__(self):
+        return self.__str__()
+
+
+class Schema(object):
+    """A validation schema.
+
+    The schema is a Python tree-like structure where nodes are pattern
+    matched against corresponding trees of values.
+
+    Nodes can be values, in which case a direct comparison is used, types,
+    in which case an isinstance() check is performed, or callables, which will
+    validate and optionally convert the value.
+    """
+
+    _extra_to_name = {
+        REMOVE_EXTRA: 'REMOVE_EXTRA',
+        ALLOW_EXTRA: 'ALLOW_EXTRA',
+        PREVENT_EXTRA: 'PREVENT_EXTRA',
+    }
+
+    def __init__(self, schema, required=False, extra=PREVENT_EXTRA):
+        """Create a new Schema.
+
+        :param schema: Validation schema. See :module:`voluptuous` for details.
+        :param required: Keys defined in the schema must be in the data.
+        :param extra: Specify how extra keys in the data are treated:
+            - :const:`~voluptuous.PREVENT_EXTRA`: to disallow any undefined
+              extra keys (raise ``Invalid``).
+            - :const:`~voluptuous.ALLOW_EXTRA`: to include undefined extra
+              keys in the output.
+            - :const:`~voluptuous.REMOVE_EXTRA`: to exclude undefined extra keys
+              from the output.
+            - Any value other than the above defaults to
+              :const:`~voluptuous.PREVENT_EXTRA`
+        """
+        self.schema = schema
+        self.required = required
+        self.extra = int(extra)  # ensure the value is an integer
+        self._compiled = self._compile(schema)
+
+    def __repr__(self):
+        return "<Schema(%s, extra=%s, required=%s) object at 0x%x>" % (
+            self.schema, self._extra_to_name.get(self.extra, '??'),
+            self.required, id(self))
+
+    def __call__(self, data):
+        """Validate data against this schema."""
+        try:
+            return self._compiled([], data)
+        except MultipleInvalid:
+            raise
+        except Invalid as e:
+            raise MultipleInvalid([e])
+        # return self.validate([], self.schema, data)
+
+    def _compile(self, schema):
+        if schema is Extra:
+            return lambda _, v: v
+        if isinstance(schema, Object):
+            return self._compile_object(schema)
+        if isinstance(schema, collections.Mapping):
+            return self._compile_dict(schema)
+        elif isinstance(schema, list):
+            return self._compile_list(schema)
+        elif isinstance(schema, tuple):
+            return self._compile_tuple(schema)
+        type_ = type(schema)
+        if type_ is type:
+            type_ = schema
+        if type_ in (bool, int, long, str, unicode, float, complex, object,
+                     list, dict, type(None)) or callable(schema):
+            return _compile_scalar(schema)
+        raise SchemaError('unsupported schema data type %r' %
+                          type(schema).__name__)
+
+    def _compile_mapping(self, schema, invalid_msg=None):
+        """Create validator for given mapping."""
+        invalid_msg = invalid_msg or 'mapping value'
+
+        # Keys that may be required
+        all_required_keys = set(key for key in schema
+                                if key is not Extra
+                                and ((self.required and not isinstance(key, (Optional, Remove)))
+                                     or isinstance(key, Required)))
+
+        # Keys that may have defaults
+        all_default_keys = set(key for key in schema
+                               if isinstance(key, Required)
+                               or isinstance(key, Optional))
+
+        _compiled_schema = {}
+        for skey, svalue in iteritems(schema):
+            new_key = self._compile(skey)
+            new_value = self._compile(svalue)
+            _compiled_schema[skey] = (new_key, new_value)
+
+        candidates = list(_iterate_mapping_candidates(_compiled_schema))
+
+        def validate_mapping(path, iterable, out):
+            required_keys = all_required_keys.copy()
+            # keeps track of all default keys that haven't been filled
+            default_keys = all_default_keys.copy()
+            error = None
+            errors = []
+            for key, value in iterable:
+                key_path = path + [key]
+                remove_key = False
+
+                # compare each given key/value against all compiled key/values
+                # schema key, (compiled key, compiled value)
+                for skey, (ckey, cvalue) in candidates:
+                    try:
+                        new_key = ckey(key_path, key)
+                    except Invalid as e:
+                        if len(e.path) > len(key_path):
+                            raise
+                        if not error or len(e.path) > len(error.path):
+                            error = e
+                        continue
+                    # Backtracking is not performed once a key is selected, so if
+                    # the value is invalid we immediately throw an exception.
+                    exception_errors = []
+                    # check if the key is marked for removal
+                    is_remove = new_key is Remove
+                    try:
+                        cval = cvalue(key_path, value)
+                        # include if it's not marked for removal
+                        if not is_remove:
+                            out[new_key] = cval
+                        else:
+                            remove_key = True
+                            continue
+                    except MultipleInvalid as e:
+                        exception_errors.extend(e.errors)
+                    except Invalid as e:
+                        exception_errors.append(e)
+
+                    if exception_errors:
+                        if is_remove or remove_key:
+                            continue
+                        for err in exception_errors:
+                            if len(err.path) <= len(key_path):
+                                err.error_type = invalid_msg
+                            errors.append(err)
+                        # If there is a validation error for a required
+                        # key, this means that the key was provided.
+                        # Discard the required key so it does not
+                        # create an additional, noisy exception.
+                        required_keys.discard(skey)
+                        break
+
+                    # Key and value okay, mark any Required() fields as found.
+                    required_keys.discard(skey)
+
+                    # No need for a default if it was filled
+                    default_keys.discard(skey)
+
+                    break
+                else:
+                    if remove_key:
+                        # remove key
+                        continue
+                    elif self.extra == ALLOW_EXTRA:
+                        out[key] = value
+                    elif self.extra != REMOVE_EXTRA:
+                        errors.append(Invalid('extra keys not allowed', key_path))
+                    # else REMOVE_EXTRA: ignore the key so it's removed from output
+
+            # set defaults for any that can have defaults
+            for key in default_keys:
+                if not isinstance(key.default, Undefined):  # if the user provides a default with the node
+                    out[key.schema] = key.default()
+                    if key in required_keys:
+                        required_keys.discard(key)
+
+            # for any required keys left that weren't found and don't have defaults:
+            for key in required_keys:
+                msg = key.msg if hasattr(key, 'msg') and key.msg else 'required key not provided'
+                errors.append(RequiredFieldInvalid(msg, path + [key]))
+            if errors:
+                raise MultipleInvalid(errors)
+
+            return out
+
+        return validate_mapping
+
+    def _compile_object(self, schema):
+        """Validate an object.
+
+        Has the same behavior as dictionary validator but work with object
+        attributes.
+
+        For example:
+
+            >>> class Structure(object):
+            ...     def __init__(self, one=None, three=None):
+            ...         self.one = one
+            ...         self.three = three
+            ...
+            >>> validate = Schema(Object({'one': 'two', 'three': 'four'}, cls=Structure))
+            >>> with raises(MultipleInvalid, "not a valid value for object value @ data['one']"):
+            ...   validate(Structure(one='three'))
+
+        """
+        base_validate = self._compile_mapping(
+            schema, invalid_msg='object value')
+
+        def validate_object(path, data):
+            if (schema.cls is not UNDEFINED
+                    and not isinstance(data, schema.cls)):
+                raise ObjectInvalid('expected a {0!r}'.format(schema.cls), path)
+            iterable = _iterate_object(data)
+            iterable = ifilter(lambda item: item[1] is not None, iterable)
+            out = base_validate(path, iterable, {})
+            return type(data)(**out)
+
+        return validate_object
+
+    def _compile_dict(self, schema):
+        """Validate a dictionary.
+
+        A dictionary schema can contain a set of values, or at most one
+        validator function/type.
+
+        A dictionary schema will only validate a dictionary:
+
+            >>> validate = Schema({})
+            >>> with raises(MultipleInvalid, 'expected a dictionary'):
+            ...   validate([])
+
+        An invalid dictionary value:
+
+            >>> validate = Schema({'one': 'two', 'three': 'four'})
+            >>> with raises(MultipleInvalid, "not a valid value for dictionary value @ data['one']"):
+            ...   validate({'one': 'three'})
+
+        An invalid key:
+
+            >>> with raises(MultipleInvalid, "extra keys not allowed @ data['two']"):
+            ...   validate({'two': 'three'})
+
+
+        Validation function, in this case the "int" type:
+
+            >>> validate = Schema({'one': 'two', 'three': 'four', int: str})
+
+        Valid integer input:
+
+            >>> validate({10: 'twenty'})
+            {10: 'twenty'}
+
+        By default, a "type" in the schema (in this case "int") will be used
+        purely to validate that the corresponding value is of that type. It
+        will not Coerce the value:
+
+            >>> with raises(MultipleInvalid, "extra keys not allowed @ data['10']"):
+            ...   validate({'10': 'twenty'})
+
+        Wrap them in the Coerce() function to achieve this:
+
+            >>> validate = Schema({'one': 'two', 'three': 'four',
+            ...                    Coerce(int): str})
+            >>> validate({'10': 'twenty'})
+            {10: 'twenty'}
+
+        Custom message for required key
+
+            >>> validate = Schema({Required('one', 'required'): 'two'})
+            >>> with raises(MultipleInvalid, "required @ data['one']"):
+            ...   validate({})
+
+        (This is to avoid unexpected surprises.)
+
+        Multiple errors for nested field in a dict:
+
+        >>> validate = Schema({
+        ...     'adict': {
+        ...         'strfield': str,
+        ...         'intfield': int
+        ...     }
+        ... })
+        >>> try:
+        ...     validate({
+        ...         'adict': {
+        ...             'strfield': 123,
+        ...             'intfield': 'one'
+        ...         }
+        ...     })
+        ... except MultipleInvalid as e:
+        ...     print(sorted(str(i) for i in e.errors)) # doctest: +NORMALIZE_WHITESPACE
+        ["expected int for dictionary value @ data['adict']['intfield']",
+         "expected str for dictionary value @ data['adict']['strfield']"]
+
+        """
+        base_validate = self._compile_mapping(
+            schema, invalid_msg='dictionary value')
+
+        groups_of_exclusion = {}
+        groups_of_inclusion = {}
+        for node in schema:
+            if isinstance(node, Exclusive):
+                g = groups_of_exclusion.setdefault(node.group_of_exclusion, [])
+                g.append(node)
+            elif isinstance(node, Inclusive):
+                g = groups_of_inclusion.setdefault(node.group_of_inclusion, [])
+                g.append(node)
+
+        def validate_dict(path, data):
+            if not isinstance(data, dict):
+                raise DictInvalid('expected a dictionary', path)
+
+            errors = []
+            for label, group in groups_of_exclusion.items():
+                exists = False
+                for exclusive in group:
+                    if exclusive.schema in data:
+                        if exists:
+                            msg = exclusive.msg if hasattr(exclusive, 'msg') and exclusive.msg else \
+                                "two or more values in the same group of exclusion '%s'" % label
+                            next_path = path + [VirtualPathComponent(label)]
+                            errors.append(ExclusiveInvalid(msg, next_path))
+                            break
+                        exists = True
+
+            if errors:
+                raise MultipleInvalid(errors)
+
+            for label, group in groups_of_inclusion.items():
+                included = [node.schema in data for node in group]
+                if any(included) and not all(included):
+                    msg = "some but not all values in the same group of inclusion '%s'" % label
+                    for g in group:
+                        if hasattr(g, 'msg') and g.msg:
+                            msg = g.msg
+                            break
+                    next_path = path + [VirtualPathComponent(label)]
+                    errors.append(InclusiveInvalid(msg, next_path))
+                    break
+
+            if errors:
+                raise MultipleInvalid(errors)
+
+            out = {}
+            return base_validate(path, iteritems(data), out)
+
+        return validate_dict
+
+    def _compile_sequence(self, schema, seq_type):
+        """Validate a sequence type.
+
+        This is a sequence of valid values or validators tried in order.
+
+        >>> validator = Schema(['one', 'two', int])
+        >>> validator(['one'])
+        ['one']
+        >>> with raises(MultipleInvalid, 'expected int @ data[0]'):
+        ...   validator([3.5])
+        >>> validator([1])
+        [1]
+        """
+        _compiled = [self._compile(s) for s in schema]
+        seq_type_name = seq_type.__name__
+
+        def validate_sequence(path, data):
+            if not isinstance(data, seq_type):
+                raise SequenceTypeInvalid('expected a %s' % seq_type_name, path)
+
+            # Empty seq schema, allow any data.
+            if not schema:
+                return data
+
+            out = []
+            invalid = None
+            errors = []
+            index_path = UNDEFINED
+            for i, value in enumerate(data):
+                index_path = path + [i]
+                invalid = None
+                for validate in _compiled:
+                    try:
+                        cval = validate(index_path, value)
+                        if cval is not Remove:  # do not include Remove values
+                            out.append(cval)
+                        break
+                    except Invalid as e:
+                        if len(e.path) > len(index_path):
+                            raise
+                        invalid = e
+                else:
+                    errors.append(invalid)
+            if errors:
+                raise MultipleInvalid(errors)
+            return type(data)(out)
+        return validate_sequence
+
+    def _compile_tuple(self, schema):
+        """Validate a tuple.
+
+        A tuple is a sequence of valid values or validators tried in order.
+
+        >>> validator = Schema(('one', 'two', int))
+        >>> validator(('one',))
+        ('one',)
+        >>> with raises(MultipleInvalid, 'expected int @ data[0]'):
+        ...   validator((3.5,))
+        >>> validator((1,))
+        (1,)
+        """
+        return self._compile_sequence(schema, tuple)
+
+    def _compile_list(self, schema):
+        """Validate a list.
+
+        A list is a sequence of valid values or validators tried in order.
+
+        >>> validator = Schema(['one', 'two', int])
+        >>> validator(['one'])
+        ['one']
+        >>> with raises(MultipleInvalid, 'expected int @ data[0]'):
+        ...   validator([3.5])
+        >>> validator([1])
+        [1]
+        """
+        return self._compile_sequence(schema, list)
+
+    def extend(self, schema, required=None, extra=None):
+        """Create a new `Schema` by merging this and the provided `schema`.
+
+        Neither this `Schema` nor the provided `schema` are modified. The
+        resulting `Schema` inherits the `required` and `extra` parameters of
+        this, unless overridden.
+
+        Both schemas must be dictionary-based.
+
+        :param schema: dictionary to extend this `Schema` with
+        :param required: if set, overrides `required` of this `Schema`
+        :param extra: if set, overrides `extra` of this `Schema`
+        """
+
+        assert type(self.schema) == dict and type(schema) == dict, 'Both schemas must be dictionary-based'
+
+        result = self.schema.copy()
+        result.update(schema)
+
+        result_required = (required if required is not None else self.required)
+        result_extra = (extra if extra is not None else self.extra)
+        return Schema(result, required=result_required, extra=result_extra)
+
+
+def _compile_scalar(schema):
+    """A scalar value.
+
+    The schema can either be a value or a type.
+
+    >>> _compile_scalar(int)([], 1)
+    1
+    >>> with raises(Invalid, 'expected float'):
+    ...   _compile_scalar(float)([], '1')
+
+    Callables have
+    >>> _compile_scalar(lambda v: float(v))([], '1')
+    1.0
+
+    As a convenience, ValueError's are trapped:
+
+    >>> with raises(Invalid, 'not a valid value'):
+    ...   _compile_scalar(lambda v: float(v))([], 'a')
+    """
+    if isinstance(schema, type):
+        def validate_instance(path, data):
+            if isinstance(data, schema):
+                return data
+            else:
+                msg = 'expected %s' % schema.__name__
+                raise TypeInvalid(msg, path)
+        return validate_instance
+
+    if callable(schema):
+        def validate_callable(path, data):
+            try:
+                return schema(data)
+            except ValueError as e:
+                raise ValueInvalid('not a valid value', path)
+            except Invalid as e:
+                e.prepend(path)
+                raise
+        return validate_callable
+
+    def validate_value(path, data):
+        if data != schema:
+            raise ScalarInvalid('not a valid value', path)
+        return data
+
+    return validate_value
+
+
+def _compile_itemsort():
+    '''return sort function of mappings'''
+    def is_extra(key_):
+        return key_ is Extra
+
+    def is_remove(key_):
+        return isinstance(key_, Remove)
+
+    def is_marker(key_):
+        return isinstance(key_, Marker)
+
+    def is_type(key_):
+        return inspect.isclass(key_)
+
+    def is_callable(key_):
+        return callable(key_)
+
+    # priority list for map sorting (in order of checking)
+    # We want Extra to match last, because it's a catch-all. On the other hand,
+    # Remove markers should match first (since invalid values will not
+    # raise an Error, instead the validator will check if other schemas match
+    # the same value).
+    priority = [(1, is_remove),    # Remove highest priority after values
+                (2, is_marker),    # then other Markers
+                (4, is_type),      # types/classes lowest before Extra
+                (3, is_callable),  # callables after markers
+                (5, is_extra)]     # Extra lowest priority
+
+    def item_priority(item_):
+        key_ = item_[0]
+        for i, check_ in priority:
+            if check_(key_):
+                return i
+        # values have hightest priorities
+        return 0
+
+    return item_priority
+
+_sort_item = _compile_itemsort()
+
+
+def _iterate_mapping_candidates(schema):
+    """Iterate over schema in a meaningful order."""
+    # Without this, Extra might appear first in the iterator, and fail to
+    # validate a key even though it's a Required that has its own validation,
+    # generating a false positive.
+    return sorted(iteritems(schema), key=_sort_item)
+
+
+def _iterate_object(obj):
+    """Return iterator over object attributes. Respect objects with
+    defined __slots__.
+
+    """
+    d = {}
+    try:
+        d = vars(obj)
+    except TypeError:
+        # maybe we have named tuple here?
+        if hasattr(obj, '_asdict'):
+            d = obj._asdict()
+    for item in iteritems(d):
+        yield item
+    try:
+        slots = obj.__slots__
+    except AttributeError:
+        pass
+    else:
+        for key in slots:
+            if key != '__dict__':
+                yield (key, getattr(obj, key))
+    raise StopIteration()
+
+
+class Object(dict):
+    """Indicate that we should work with attributes, not keys."""
+
+    def __init__(self, schema, cls=UNDEFINED):
+        self.cls = cls
+        super(Object, self).__init__(schema)
+
+
+class Marker(object):
+    """Mark nodes for special treatment."""
+
+    def __init__(self, schema, msg=None):
+        self.schema = schema
+        self._schema = Schema(schema)
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            return self._schema(v)
+        except Invalid as e:
+            if not self.msg or len(e.path) > 1:
+                raise
+            raise Invalid(self.msg)
+
+    def __str__(self):
+        return str(self.schema)
+
+    def __repr__(self):
+        return repr(self.schema)
+
+    def __lt__(self, other):
+        return self.schema < other.schema
+
+
+class Optional(Marker):
+    """Mark a node in the schema as optional, and optionally provide a default
+
+    >>> schema = Schema({Optional('key'): str})
+    >>> schema({})
+    {}
+    >>> schema = Schema({Optional('key', default='value'): str})
+    >>> schema({})
+    {'key': 'value'}
+    >>> schema = Schema({Optional('key', default=list): list})
+    >>> schema({})
+    {'key': []}
+
+    If 'required' flag is set for an entire schema, optional keys aren't required
+
+    >>> schema = Schema({
+    ...    Optional('key'): str,
+    ...    'key2': str
+    ... }, required=True)
+    >>> schema({'key2':'value'})
+    {'key2': 'value'}
+    """
+    def __init__(self, schema, msg=None, default=UNDEFINED):
+        super(Optional, self).__init__(schema, msg=msg)
+        self.default = default_factory(default)
+
+
+class Exclusive(Optional):
+    """Mark a node in the schema as exclusive.
+
+    Exclusive keys inherited from Optional:
+
+    >>> schema = Schema({Exclusive('alpha', 'angles'): int, Exclusive('beta', 'angles'): int})
+    >>> schema({'alpha': 30})
+    {'alpha': 30}
+
+    Keys inside a same group of exclusion cannot be together, it only makes sense for dictionaries:
+
+    >>> with raises(MultipleInvalid, "two or more values in the same group of exclusion 'angles' @ data[<angles>]"):
+    ...   schema({'alpha': 30, 'beta': 45})
+
+    For example, API can provides multiple types of authentication, but only one works in the same time:
+
+    >>> msg = 'Please, use only one type of authentication at the same time.'
+    >>> schema = Schema({
+    ... Exclusive('classic', 'auth', msg=msg):{
+    ...     Required('email'): basestring,
+    ...     Required('password'): basestring
+    ...     },
+    ... Exclusive('internal', 'auth', msg=msg):{
+    ...     Required('secret_key'): basestring
+    ...     },
+    ... Exclusive('social', 'auth', msg=msg):{
+    ...     Required('social_network'): basestring,
+    ...     Required('token'): basestring
+    ...     }
+    ... })
+
+    >>> with raises(MultipleInvalid, "Please, use only one type of authentication at the same time. @ data[<auth>]"):
+    ...     schema({'classic': {'email': 'foo@example.com', 'password': 'bar'},
+    ...             'social': {'social_network': 'barfoo', 'token': 'tEMp'}})
+    """
+    def __init__(self, schema, group_of_exclusion, msg=None):
+        super(Exclusive, self).__init__(schema, msg=msg)
+        self.group_of_exclusion = group_of_exclusion
+
+
+class Inclusive(Optional):
+    """ Mark a node in the schema as inclusive.
+
+    Exclusive keys inherited from Optional:
+
+    >>> schema = Schema({
+    ...     Inclusive('filename', 'file'): str,
+    ...     Inclusive('mimetype', 'file'): str
+    ... })
+    >>> data = {'filename': 'dog.jpg', 'mimetype': 'image/jpeg'}
+    >>> data == schema(data)
+    True
+
+    Keys inside a same group of inclusive must exist together, it only makes sense for dictionaries:
+
+    >>> with raises(MultipleInvalid, "some but not all values in the same group of inclusion 'file' @ data[<file>]"):
+    ...     schema({'filename': 'dog.jpg'})
+
+    If none of the keys in the group are present, it is accepted:
+
+    >>> schema({})
+    {}
+
+    For example, API can return 'height' and 'width' together, but not separately.
+
+    >>> msg = "Height and width must exist together"
+    >>> schema = Schema({
+    ...     Inclusive('height', 'size', msg=msg): int,
+    ...     Inclusive('width', 'size', msg=msg): int
+    ... })
+
+    >>> with raises(MultipleInvalid, msg + " @ data[<size>]"):
+    ...     schema({'height': 100})
+
+    >>> with raises(MultipleInvalid, msg + " @ data[<size>]"):
+    ...     schema({'width': 100})
+
+    >>> data = {'height': 100, 'width': 100}
+    >>> data == schema(data)
+    True
+    """
+
+    def __init__(self, schema, group_of_inclusion, msg=None):
+        super(Inclusive, self).__init__(schema, msg=msg)
+        self.group_of_inclusion = group_of_inclusion
+
+
+class Required(Marker):
+    """Mark a node in the schema as being required, and optionally provide a default value.
+
+    >>> schema = Schema({Required('key'): str})
+    >>> with raises(MultipleInvalid, "required key not provided @ data['key']"):
+    ...   schema({})
+
+    >>> schema = Schema({Required('key', default='value'): str})
+    >>> schema({})
+    {'key': 'value'}
+    >>> schema = Schema({Required('key', default=list): list})
+    >>> schema({})
+    {'key': []}
+    """
+    def __init__(self, schema, msg=None, default=UNDEFINED):
+        super(Required, self).__init__(schema, msg=msg)
+        self.default = default_factory(default)
+
+
+class Remove(Marker):
+    """Mark a node in the schema to be removed and excluded from the validated
+    output. Keys that fail validation will not raise ``Invalid``. Instead, these
+    keys will be treated as extras.
+
+    >>> schema = Schema({str: int, Remove(int): str})
+    >>> with raises(MultipleInvalid, "extra keys not allowed @ data[1]"):
+    ...    schema({'keep': 1, 1: 1.0})
+    >>> schema({1: 'red', 'red': 1, 2: 'green'})
+    {'red': 1}
+    >>> schema = Schema([int, Remove(float), Extra])
+    >>> schema([1, 2, 3, 4.0, 5, 6.0, '7'])
+    [1, 2, 3, 5, '7']
+    """
+    def __call__(self, v):
+        super(Remove, self).__call__(v)
+        return self.__class__
+
+    def __repr__(self):
+        return "Remove(%r)" % (self.schema,)
+
+
+def Extra(_):
+    """Allow keys in the data that are not present in the schema."""
+    raise SchemaError('"Extra" should never be called')
+
+
+# As extra() is never called there's no way to catch references to the
+# deprecated object, so we just leave an alias here instead.
+extra = Extra
+
+class Msg(object):
+    """Report a user-friendly message if a schema fails to validate.
+
+    >>> validate = Schema(
+    ...   Msg(['one', 'two', int],
+    ...       'should be one of "one", "two" or an integer'))
+    >>> with raises(MultipleInvalid, 'should be one of "one", "two" or an integer'):
+    ...   validate(['three'])
+
+    Messages are only applied to invalid direct descendants of the schema:
+
+    >>> validate = Schema(Msg([['one', 'two', int]], 'not okay!'))
+    >>> with raises(MultipleInvalid, 'expected int @ data[0][0]'):
+    ...   validate([['three']])
+
+    The type which is thrown can be overridden but needs to be a subclass of Invalid
+
+    >>> with raises(SchemaError, 'Msg can only use subclases of Invalid as custom class'):
+    ...   validate = Schema(Msg([int], 'should be int', cls=KeyError))
+
+    If you do use a subclass of Invalid, that error will be thrown (wrapped in a MultipleInvalid)
+
+    >>> validate = Schema(Msg([['one', 'two', int]], 'not okay!', cls=RangeInvalid))
+    >>> try:
+    ...  validate(['three'])
+    ... except MultipleInvalid as e:
+    ...   assert isinstance(e.errors[0], RangeInvalid)
+    """
+
+    def __init__(self, schema, msg, cls=None):
+        if cls and not issubclass(cls, Invalid):
+            raise SchemaError("Msg can only use subclases of"
+                              " Invalid as custom class")
+        self._schema = schema
+        self.schema = Schema(schema)
+        self.msg = msg
+        self.cls = cls
+
+    def __call__(self, v):
+        try:
+            return self.schema(v)
+        except Invalid as e:
+            if len(e.path) > 1:
+                raise e
+            else:
+                raise (self.cls or Invalid)(self.msg)
+
+    def __repr__(self):
+        return 'Msg(%s, %s, cls=%s)' % (self._schema, self.msg, self.cls)
+
+
+def message(default=None, cls=None):
+    """Convenience decorator to allow functions to provide a message.
+
+    Set a default message:
+
+        >>> @message('not an integer')
+        ... def isint(v):
+        ...   return int(v)
+
+        >>> validate = Schema(isint())
+        >>> with raises(MultipleInvalid, 'not an integer'):
+        ...   validate('a')
+
+    The message can be overridden on a per validator basis:
+
+        >>> validate = Schema(isint('bad'))
+        >>> with raises(MultipleInvalid, 'bad'):
+        ...   validate('a')
+
+    The class thrown too:
+
+        >>> class IntegerInvalid(Invalid): pass
+        >>> validate = Schema(isint('bad', clsoverride=IntegerInvalid))
+        >>> try:
+        ...  validate('a')
+        ... except MultipleInvalid as e:
+        ...   assert isinstance(e.errors[0], IntegerInvalid)
+    """
+    if cls and not issubclass(cls, Invalid):
+        raise SchemaError("message can only use subclases of Invalid as custom class")
+
+    def decorator(f):
+        @wraps(f)
+        def check(msg=None, clsoverride=None):
+            @wraps(f)
+            def wrapper(*args, **kwargs):
+                try:
+                    return f(*args, **kwargs)
+                except ValueError:
+                    raise (clsoverride or cls or ValueInvalid)(msg or default or 'invalid value')
+            return wrapper
+        return check
+    return decorator
+
+
+def truth(f):
+    """Convenience decorator to convert truth functions into validators.
+
+        >>> @truth
+        ... def isdir(v):
+        ...   return os.path.isdir(v)
+        >>> validate = Schema(isdir)
+        >>> validate('/')
+        '/'
+        >>> with raises(MultipleInvalid, 'not a valid value'):
+        ...   validate('/notavaliddir')
+    """
+    @wraps(f)
+    def check(v):
+        t = f(v)
+        if not t:
+            raise ValueError
+        return v
+    return check
+
+
+class Coerce(object):
+    """Coerce a value to a type.
+
+    If the type constructor throws a ValueError or TypeError, the value
+    will be marked as Invalid.
+
+    Default behavior:
+
+        >>> validate = Schema(Coerce(int))
+        >>> with raises(MultipleInvalid, 'expected int'):
+        ...   validate(None)
+        >>> with raises(MultipleInvalid, 'expected int'):
+        ...   validate('foo')
+
+    With custom message:
+
+        >>> validate = Schema(Coerce(int, "moo"))
+        >>> with raises(MultipleInvalid, 'moo'):
+        ...   validate('foo')
+    """
+
+    def __init__(self, type, msg=None):
+        self.type = type
+        self.msg = msg
+        self.type_name = type.__name__
+
+    def __call__(self, v):
+        try:
+            return self.type(v)
+        except (ValueError, TypeError):
+            msg = self.msg or ('expected %s' % self.type_name)
+            raise CoerceInvalid(msg)
+
+    def __repr__(self):
+        return 'Coerce(%s, msg=%r)' % (self.type_name, self.msg)
+
+
+@message('value was not true', cls=TrueInvalid)
+@truth
+def IsTrue(v):
+    """Assert that a value is true, in the Python sense.
+
+    >>> validate = Schema(IsTrue())
+
+    "In the Python sense" means that implicitly false values, such as empty
+    lists, dictionaries, etc. are treated as "false":
+
+    >>> with raises(MultipleInvalid, "value was not true"):
+    ...   validate([])
+    >>> validate([1])
+    [1]
+    >>> with raises(MultipleInvalid, "value was not true"):
+    ...   validate(False)
+
+    ...and so on.
+
+    >>> try:
+    ...  validate([])
+    ... except MultipleInvalid as e:
+    ...   assert isinstance(e.errors[0], TrueInvalid)
+    """
+    return v
+
+
+@message('value was not false', cls=FalseInvalid)
+def IsFalse(v):
+    """Assert that a value is false, in the Python sense.
+
+    (see :func:`IsTrue` for more detail)
+
+    >>> validate = Schema(IsFalse())
+    >>> validate([])
+    []
+    >>> with raises(MultipleInvalid, "value was not false"):
+    ...   validate(True)
+
+    >>> try:
+    ...  validate(True)
+    ... except MultipleInvalid as e:
+    ...   assert isinstance(e.errors[0], FalseInvalid)
+    """
+    if v:
+        raise ValueError
+    return v
+
+
+@message('expected boolean', cls=BooleanInvalid)
+def Boolean(v):
+    """Convert human-readable boolean values to a bool.
+
+    Accepted values are 1, true, yes, on, enable, and their negatives.
+    Non-string values are cast to bool.
+
+    >>> validate = Schema(Boolean())
+    >>> validate(True)
+    True
+    >>> validate("1")
+    True
+    >>> validate("0")
+    False
+    >>> with raises(MultipleInvalid, "expected boolean"):
+    ...   validate('moo')
+    >>> try:
+    ...  validate('moo')
+    ... except MultipleInvalid as e:
+    ...   assert isinstance(e.errors[0], BooleanInvalid)
+    """
+    if isinstance(v, basestring):
+        v = v.lower()
+        if v in ('1', 'true', 'yes', 'on', 'enable'):
+            return True
+        if v in ('0', 'false', 'no', 'off', 'disable'):
+            return False
+        raise ValueError
+    return bool(v)
+
+
+class Any(object):
+    """Use the first validated value.
+
+    :param msg: Message to deliver to user if validation fails.
+    :param kwargs: All other keyword arguments are passed to the sub-Schema constructors.
+    :returns: Return value of the first validator that passes.
+
+    >>> validate = Schema(Any('true', 'false',
+    ...                       All(Any(int, bool), Coerce(bool))))
+    >>> validate('true')
+    'true'
+    >>> validate(1)
+    True
+    >>> with raises(MultipleInvalid, "not a valid value"):
+    ...   validate('moo')
+
+    msg argument is used
+
+    >>> validate = Schema(Any(1, 2, 3, msg="Expected 1 2 or 3"))
+    >>> validate(1)
+    1
+    >>> with raises(MultipleInvalid, "Expected 1 2 or 3"):
+    ...   validate(4)
+    """
+
+    def __init__(self, *validators, **kwargs):
+        self.validators = validators
+        self.msg = kwargs.pop('msg', None)
+        self._schemas = [Schema(val, **kwargs) for val in validators]
+
+    def __call__(self, v):
+        error = None
+        for schema in self._schemas:
+            try:
+                return schema(v)
+            except Invalid as e:
+                if error is None or len(e.path) > len(error.path):
+                    error = e
+        else:
+            if error:
+                raise error if self.msg is None else AnyInvalid(self.msg)
+            raise AnyInvalid(self.msg or 'no valid value found')
+
+    def __repr__(self):
+        return 'Any([%s])' % (", ".join(repr(v) for v in self.validators))
+
+
+# Convenience alias
+Or = Any
+
+
+class All(object):
+    """Value must pass all validators.
+
+    The output of each validator is passed as input to the next.
+
+    :param msg: Message to deliver to user if validation fails.
+    :param kwargs: All other keyword arguments are passed to the sub-Schema constructors.
+
+    >>> validate = Schema(All('10', Coerce(int)))
+    >>> validate('10')
+    10
+    """
+
+    def __init__(self, *validators, **kwargs):
+        self.validators = validators
+        self.msg = kwargs.pop('msg', None)
+        self._schemas = [Schema(val, **kwargs) for val in validators]
+
+    def __call__(self, v):
+        try:
+            for schema in self._schemas:
+                v = schema(v)
+        except Invalid as e:
+            raise e if self.msg is None else AllInvalid(self.msg)
+        return v
+
+    def __repr__(self):
+        return 'All(%s, msg=%r)' % (
+            ", ".join(repr(v) for v in self.validators),
+            self.msg
+        )
+
+
+# Convenience alias
+And = All
+
+
+class Match(object):
+    """Value must be a string that matches the regular expression.
+
+    >>> validate = Schema(Match(r'^0x[A-F0-9]+$'))
+    >>> validate('0x123EF4')
+    '0x123EF4'
+    >>> with raises(MultipleInvalid, "does not match regular expression"):
+    ...   validate('123EF4')
+
+    >>> with raises(MultipleInvalid, 'expected string or buffer'):
+    ...   validate(123)
+
+    Pattern may also be a _compiled regular expression:
+
+    >>> validate = Schema(Match(re.compile(r'0x[A-F0-9]+', re.I)))
+    >>> validate('0x123ef4')
+    '0x123ef4'
+    """
+
+    def __init__(self, pattern, msg=None):
+        if isinstance(pattern, basestring):
+            pattern = re.compile(pattern)
+        self.pattern = pattern
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            match = self.pattern.match(v)
+        except TypeError:
+            raise MatchInvalid("expected string or buffer")
+        if not match:
+            raise MatchInvalid(self.msg or 'does not match regular expression')
+        return v
+
+    def __repr__(self):
+        return 'Match(%r, msg=%r)' % (self.pattern.pattern, self.msg)
+
+
+class Replace(object):
+    """Regex substitution.
+
+    >>> validate = Schema(All(Replace('you', 'I'),
+    ...                       Replace('hello', 'goodbye')))
+    >>> validate('you say hello')
+    'I say goodbye'
+    """
+
+    def __init__(self, pattern, substitution, msg=None):
+        if isinstance(pattern, basestring):
+            pattern = re.compile(pattern)
+        self.pattern = pattern
+        self.substitution = substitution
+        self.msg = msg
+
+    def __call__(self, v):
+        return self.pattern.sub(self.substitution, v)
+
+    def __repr__(self):
+        return 'Replace(%r, %r, msg=%r)' % (self.pattern.pattern,
+                                            self.substitution,
+                                            self.msg)
+
+
+def _url_validation(v):
+    parsed = urlparse.urlparse(v)
+    if not parsed.scheme or not parsed.netloc:
+        raise UrlInvalid("must have a URL scheme and host")
+    return parsed
+
+
+@message('expected a Fully qualified domain name URL', cls=UrlInvalid)
+def FqdnUrl(v):
+    """Verify that the value is a Fully qualified domain name URL.
+
+    >>> s = Schema(FqdnUrl())
+    >>> with raises(MultipleInvalid, 'expected a Fully qualified domain name URL'):
+    ...   s("http://localhost/")
+    >>> s('http://w3.org')
+    'http://w3.org'
+    """
+    try:
+        parsed_url = _url_validation(v)
+        if "." not in parsed_url.netloc:
+            raise UrlInvalid("must have a domain name in URL")
+        return v
+    except:
+        raise ValueError
+
+
+@message('expected a URL', cls=UrlInvalid)
+def Url(v):
+    """Verify that the value is a URL.
+
+    >>> s = Schema(Url())
+    >>> with raises(MultipleInvalid, 'expected a URL'):
+    ...   s(1)
+    >>> s('http://w3.org')
+    'http://w3.org'
+    """
+    try:
+        _url_validation(v)
+        return v
+    except:
+        raise ValueError
+
+
+@message('not a file', cls=FileInvalid)
+@truth
+def IsFile(v):
+    """Verify the file exists.
+
+    >>> os.path.basename(IsFile()(__file__)).startswith('voluptuous.py')
+    True
+    >>> with raises(FileInvalid, 'not a file'):
+    ...   IsFile()("random_filename_goes_here.py")
+    """
+    return os.path.isfile(v)
+
+
+@message('not a directory', cls=DirInvalid)
+@truth
+def IsDir(v):
+    """Verify the directory exists.
+
+    >>> IsDir()('/')
+    '/'
+    """
+    return os.path.isdir(v)
+
+
+@message('path does not exist', cls=PathInvalid)
+@truth
+def PathExists(v):
+    """Verify the path exists, regardless of its type.
+
+    >>> os.path.basename(PathExists()(__file__)).startswith('voluptuous.py')
+    True
+    >>> with raises(Invalid, 'path does not exist'):
+    ...   PathExists()("random_filename_goes_here.py")
+    """
+    return os.path.exists(v)
+
+
+class Range(object):
+    """Limit a value to a range.
+
+    Either min or max may be omitted.
+    Either min or max can be excluded from the range of accepted values.
+
+    :raises Invalid: If the value is outside the range.
+
+    >>> s = Schema(Range(min=1, max=10, min_included=False))
+    >>> s(5)
+    5
+    >>> s(10)
+    10
+    >>> with raises(MultipleInvalid, 'value must be at most 10'):
+    ...   s(20)
+    >>> with raises(MultipleInvalid, 'value must be higher than 1'):
+    ...   s(1)
+    >>> with raises(MultipleInvalid, 'value must be lower than 10'):
+    ...   Schema(Range(max=10, max_included=False))(20)
+    """
+
+    def __init__(self, min=None, max=None, min_included=True,
+                 max_included=True, msg=None):
+        self.min = min
+        self.max = max
+        self.min_included = min_included
+        self.max_included = max_included
+        self.msg = msg
+
+    def __call__(self, v):
+        if self.min_included:
+            if self.min is not None and v < self.min:
+                raise RangeInvalid(
+                    self.msg or 'value must be at least %s' % self.min)
+        else:
+            if self.min is not None and v <= self.min:
+                raise RangeInvalid(
+                    self.msg or 'value must be higher than %s' % self.min)
+        if self.max_included:
+            if self.max is not None and v > self.max:
+                raise RangeInvalid(
+                    self.msg or 'value must be at most %s' % self.max)
+        else:
+            if self.max is not None and v >= self.max:
+                raise RangeInvalid(
+                    self.msg or 'value must be lower than %s' % self.max)
+        return v
+
+    def __repr__(self):
+        return ('Range(min=%r, max=%r, min_included=%r,'
+                ' max_included=%r, msg=%r)' % (self.min, self.max,
+                                               self.min_included,
+                                               self.max_included,
+                                               self.msg))
+
+
+class Clamp(object):
+    """Clamp a value to a range.
+
+    Either min or max may be omitted.
+    >>> s = Schema(Clamp(min=0, max=1))
+    >>> s(0.5)
+    0.5
+    >>> s(5)
+    1
+    >>> s(-1)
+    0
+    """
+
+    def __init__(self, min=None, max=None, msg=None):
+        self.min = min
+        self.max = max
+        self.msg = msg
+
+    def __call__(self, v):
+        if self.min is not None and v < self.min:
+            v = self.min
+        if self.max is not None and v > self.max:
+            v = self.max
+        return v
+
+    def __repr__(self):
+        return 'Clamp(min=%s, max=%s)' % (self.min, self.max)
+
+
+class LengthInvalid(Invalid):
+    pass
+
+
+class Length(object):
+    """The length of a value must be in a certain range."""
+
+    def __init__(self, min=None, max=None, msg=None):
+        self.min = min
+        self.max = max
+        self.msg = msg
+
+    def __call__(self, v):
+        if self.min is not None and len(v) < self.min:
+            raise LengthInvalid(
+                self.msg or 'length of value must be at least %s' % self.min)
+        if self.max is not None and len(v) > self.max:
+            raise LengthInvalid(
+                self.msg or 'length of value must be at most %s' % self.max)
+        return v
+
+    def __repr__(self):
+        return 'Length(min=%s, max=%s)' % (self.min, self.max)
+
+
+class DatetimeInvalid(Invalid):
+    """The value is not a formatted datetime string."""
+
+
+class Datetime(object):
+    """Validate that the value matches the datetime format."""
+
+    DEFAULT_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
+
+    def __init__(self, format=None, msg=None):
+        self.format = format or self.DEFAULT_FORMAT
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            datetime.datetime.strptime(v, self.format)
+        except (TypeError, ValueError):
+            raise DatetimeInvalid(
+                self.msg or 'value does not match'
+                            ' expected format %s' % self.format)
+        return v
+
+    def __repr__(self):
+        return 'Datetime(format=%s)' % self.format
+
+
+class InInvalid(Invalid):
+    pass
+
+
+class In(object):
+    """Validate that a value is in a collection."""
+
+    def __init__(self, container, msg=None):
+        self.container = container
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            check = v not in self.container
+        except TypeError:
+            check = True
+        if check:
+            raise InInvalid(self.msg or 'value is not allowed')
+        return v
+
+    def __repr__(self):
+        return 'In(%s)' % (self.container,)
+
+
+class NotInInvalid(Invalid):
+    pass
+
+
+class NotIn(object):
+    """Validate that a value is not in a collection."""
+
+    def __init__(self, container, msg=None):
+        self.container = container
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            check = v in self.container
+        except TypeError:
+            check = True
+        if check:
+            raise NotInInvalid(self.msg or 'value is not allowed')
+        return v
+
+    def __repr__(self):
+        return 'NotIn(%s)' % (self.container,)
+
+
+def Lower(v):
+    """Transform a string to lower case.
+
+    >>> s = Schema(Lower)
+    >>> s('HI')
+    'hi'
+    """
+    return str(v).lower()
+
+
+def Upper(v):
+    """Transform a string to upper case.
+
+    >>> s = Schema(Upper)
+    >>> s('hi')
+    'HI'
+    """
+    return str(v).upper()
+
+
+def Capitalize(v):
+    """Capitalise a string.
+
+    >>> s = Schema(Capitalize)
+    >>> s('hello world')
+    'Hello world'
+    """
+    return str(v).capitalize()
+
+
+def Title(v):
+    """Title case a string.
+
+    >>> s = Schema(Title)
+    >>> s('hello world')
+    'Hello World'
+    """
+    return str(v).title()
+
+
+def Strip(v):
+    """Strip whitespace from a string.
+
+    >>> s = Schema(Strip)
+    >>> s('  hello world  ')
+    'hello world'
+    """
+    return str(v).strip()
+
+
+class DefaultTo(object):
+    """Sets a value to default_value if none provided.
+
+    >>> s = Schema(DefaultTo(42))
+    >>> s(None)
+    42
+    >>> s = Schema(DefaultTo(list))
+    >>> s(None)
+    []
+    """
+
+    def __init__(self, default_value, msg=None):
+        self.default_value = default_factory(default_value)
+        self.msg = msg
+
+    def __call__(self, v):
+        if v is None:
+            v = self.default_value()
+        return v
+
+    def __repr__(self):
+        return 'DefaultTo(%s)' % (self.default_value(),)
+
+
+class SetTo(object):
+    """Set a value, ignoring any previous value.
+
+    >>> s = Schema(Any(int, SetTo(42)))
+    >>> s(2)
+    2
+    >>> s("foo")
+    42
+    """
+
+    def __init__(self, value):
+        self.value = default_factory(value)
+
+    def __call__(self, v):
+        return self.value()
+
+    def __repr__(self):
+        return 'SetTo(%s)' % (self.value(),)
+
+
+class ExactSequenceInvalid(Invalid):
+    pass
+
+
+class ExactSequence(object):
+    """Matches each element in a sequence against the corresponding element in
+    the validators.
+
+    :param msg: Message to deliver to user if validation fails.
+    :param kwargs: All other keyword arguments are passed to the sub-Schema
+        constructors.
+
+    >>> from voluptuous import *
+    >>> validate = Schema(ExactSequence([str, int, list, list]))
+    >>> validate(['hourly_report', 10, [], []])
+    ['hourly_report', 10, [], []]
+    >>> validate(('hourly_report', 10, [], []))
+    ('hourly_report', 10, [], [])
+    """
+
+    def __init__(self, validators, **kwargs):
+        self.validators = validators
+        self.msg = kwargs.pop('msg', None)
+        self._schemas = [Schema(val, **kwargs) for val in validators]
+
+    def __call__(self, v):
+        if not isinstance(v, (list, tuple)):
+            raise ExactSequenceInvalid(self.msg)
+        try:
+            v = type(v)(schema(x) for x, schema in zip(v, self._schemas))
+        except Invalid as e:
+            raise e if self.msg is None else ExactSequenceInvalid(self.msg)
+        return v
+
+    def __repr__(self):
+        return 'ExactSequence([%s])' % (", ".join(repr(v)
+                                                  for v in self.validators))
+
+
+class Literal(object):
+    def __init__(self, lit):
+        self.lit = lit
+
+    def __call__(self, value, msg=None):
+        if self.lit != value:
+            raise LiteralInvalid(
+                msg or '%s not match for %s' % (value, self.lit)
+            )
+        else:
+            return self.lit
+
+    def __str__(self):
+        return str(self.lit)
+
+    def __repr__(self):
+        return repr(self.lit)
+
+
+class Unique(object):
+    """Ensure an iterable does not contain duplicate items.
+
+    Only iterables convertable to a set are supported (native types and
+    objects with correct __eq__).
+
+    JSON does not support set, so they need to be presented as arrays.
+    Unique allows ensuring that such array does not contain dupes.
+
+    >>> s = Schema(Unique())
+    >>> s([])
+    []
+    >>> s([1, 2])
+    [1, 2]
+    >>> with raises(Invalid, 'contains duplicate items: [1]'):
+    ...   s([1, 1, 2])
+    >>> with raises(Invalid, "contains duplicate items: ['one']"):
+    ...   s(['one', 'two', 'one'])
+    >>> with raises(Invalid, regex="^contains unhashable elements: "):
+    ...   s([set([1, 2]), set([3, 4])])
+    >>> s('abc')
+    'abc'
+    >>> with raises(Invalid, regex="^contains duplicate items: "):
+    ...   s('aabbc')
+    """
+
+    def __init__(self, msg=None):
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            set_v = set(v)
+        except TypeError as e:
+            raise TypeInvalid(
+                self.msg or 'contains unhashable elements: {0}'.format(e))
+        if len(set_v) != len(v):
+            seen = set()
+            dupes = list(set(x for x in v if x in seen or seen.add(x)))
+            raise Invalid(
+                self.msg or 'contains duplicate items: {0}'.format(dupes))
+        return v
+
+    def __repr__(self):
+        return 'Unique()'
+
+
+class Set(object):
+    """Convert a list into a set.
+
+    >>> s = Schema(Set())
+    >>> s([]) == set([])
+    True
+    >>> s([1, 2]) == set([1, 2])
+    True
+    >>> with raises(Invalid, regex="^cannot be presented as set: "):
+    ...   s([set([1, 2]), set([3, 4])])
+    """
+
+    def __init__(self, msg=None):
+        self.msg = msg
+
+    def __call__(self, v):
+        try:
+            set_v = set(v)
+        except Exception as e:
+            raise TypeInvalid(
+                self.msg or 'cannot be presented as set: {0}'.format(e))
+        return set_v
+
+    def __repr__(self):
+        return 'Set()'
+
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -1169,16 +1169,81 @@ static struct nsMyTrustedEVInfo myTruste
       0xCD, 0x98, 0xB6, 0x21, 0x49, 0xE5, 0x49, 0x4A, 0x67, 0xF5, 0x84,
       0x5E, 0x7B, 0xD1, 0xED, 0x01, 0x9F, 0x27, 0xB8, 0x6B, 0xD6 },
     "MG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNU"
     "RSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds"
     "b2JhbCBSb290IEdCIENB",
     "drEgUnTwhYdGs/gjGvbCwA==",
     nullptr
   },
+  {
+    // CN=Certplus Root CA G1,O=Certplus,C=FR
+    "1.3.6.1.4.1.22234.3.5.3.1",
+    "DocuSign EV OID 1",
+    SEC_OID_UNKNOWN,
+    { 0x15, 0x2A, 0x40, 0x2B, 0xFC, 0xDF, 0x2C, 0xD5, 0x48, 0x05, 0x4D,
+      0x22, 0x75, 0xB3, 0x9C, 0x7F, 0xCA, 0x3E, 0xC0, 0x97, 0x80, 0x78,
+      0xB0, 0xF0, 0xEA, 0x76, 0xE5, 0x61, 0xA6, 0xC7, 0x43, 0x3E },
+    "MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy"
+    "dHBsdXMgUm9vdCBDQSBHMQ==",
+    "ESBVg+QtPlRWhS2DN7cs3EYR",
+    nullptr
+  },
+  {
+    // CN=Certplus Root CA G2,O=Certplus,C=FR
+    "1.3.6.1.4.1.22234.3.5.3.2",
+    "DocuSign EV OID 2",
+    SEC_OID_UNKNOWN,
+    { 0x6C, 0xC0, 0x50, 0x41, 0xE6, 0x44, 0x5E, 0x74, 0x69, 0x6C, 0x4C,
+      0xFB, 0xC9, 0xF8, 0x0F, 0x54, 0x3B, 0x7E, 0xAB, 0xBB, 0x44, 0xB4,
+      0xCE, 0x6F, 0x78, 0x7C, 0x6A, 0x99, 0x71, 0xC4, 0x2F, 0x17 },
+    "MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy"
+    "dHBsdXMgUm9vdCBDQSBHMg==",
+    "ESDZkc6uo+jF5//pAq/Pc7xV",
+    nullptr
+  },
+  {
+    // CN=OpenTrust Root CA G1,O=OpenTrust,C=FR
+    "1.3.6.1.4.1.22234.2.14.3.11",
+    "DocuSign EV OID 3",
+    SEC_OID_UNKNOWN,
+    { 0x56, 0xC7, 0x71, 0x28, 0xD9, 0x8C, 0x18, 0xD9, 0x1B, 0x4C, 0xFD,
+      0xFF, 0xBC, 0x25, 0xEE, 0x91, 0x03, 0xD4, 0x75, 0x8E, 0xA2, 0xAB,
+      0xAD, 0x82, 0x6A, 0x90, 0xF3, 0x45, 0x7D, 0x46, 0x0E, 0xB4 },
+    "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w"
+    "ZW5UcnVzdCBSb290IENBIEcx",
+    "ESCzkFU5fX82bWTCp59rY45n",
+    nullptr
+  },
+  {
+    // CN=OpenTrust Root CA G2,O=OpenTrust,C=FR
+    "1.3.6.1.4.1.22234.2.14.3.11",
+    "DocuSign EV OID 3",
+    SEC_OID_UNKNOWN,
+    { 0x27, 0x99, 0x58, 0x29, 0xFE, 0x6A, 0x75, 0x15, 0xC1, 0xBF, 0xE8,
+      0x48, 0xF9, 0xC4, 0x76, 0x1D, 0xB1, 0x6C, 0x22, 0x59, 0x29, 0x25,
+      0x7B, 0xF4, 0x0D, 0x08, 0x94, 0xF2, 0x9E, 0xA8, 0xBA, 0xF2 },
+    "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w"
+    "ZW5UcnVzdCBSb290IENBIEcy",
+    "ESChaRu/vbm9UpaPI+hIvyYR",
+    nullptr
+  },
+  {
+    // CN=OpenTrust Root CA G3,O=OpenTrust,C=FR
+    "1.3.6.1.4.1.22234.2.14.3.11",
+    "DocuSign EV OID 3",
+    SEC_OID_UNKNOWN,
+    { 0xB7, 0xC3, 0x62, 0x31, 0x70, 0x6E, 0x81, 0x07, 0x8C, 0x36, 0x7C,
+      0xB8, 0x96, 0x19, 0x8F, 0x1E, 0x32, 0x08, 0xDD, 0x92, 0x69, 0x49,
+      0xDD, 0x8F, 0x57, 0x09, 0xA4, 0x10, 0xF7, 0x5B, 0x62, 0x92 },
+    "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w"
+    "ZW5UcnVzdCBSb290IENBIEcz",
+    "ESDm+Ez8JLC+BUCs2oMbNGA/",
+    nullptr
+  },
 };
 
 static SECOidTag
 register_oid(const SECItem* oid_item, const char* oid_name)
 {
   if (!oid_item)
     return SEC_OID_UNKNOWN;
 
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/android-test/kind.yml
@@ -0,0 +1,12 @@
+implementation: taskgraph.task.test:TestTask
+
+kind-dependencies:
+    - legacy
+
+transforms:
+   - taskgraph.transforms.tests.test_description:validate
+   - taskgraph.transforms.tests.android_test:transforms
+   - taskgraph.transforms.tests.all_kinds:transforms
+   - taskgraph.transforms.tests.test_description:validate
+   - taskgraph.transforms.tests.make_task_description:transforms
+   - taskgraph.transforms.make_task:transforms
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/android-test/test-platforms.yml
@@ -0,0 +1,19 @@
+# This file maps build platforms to test platforms.  In some cases, a
+# single build may be tested on multiple test platforms, but a single test
+# platform can only link to one build platform.  Both build and test platforms
+# are represented as <platform>/<type>, where <type> is what Treeherder calls a
+# collection.
+#
+# Each test platform further specifies the set of tests that will be scheduled
+# for the platform, referring to tests defined in test-sets.yml.
+#
+# Note that set does not depend on the tree; tree-dependent job selection
+# should be performed in the target task selection phase of task-graph
+# generation.
+
+android-4.3-arm7-api-15/debug:
+    build-platform: android-api-15/debug
+    test-set: debug-tests
+android-4.3-arm7-api-15/opt:
+    build-platform: android-api-15/opt
+    test-set: opt-tests
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/android-test/test-sets.yml
@@ -0,0 +1,37 @@
+# Each key in this file specifies a set of tests to run.  Different test sets
+# may, for example, be bound to different test platforms.
+#
+# Note that set does not depend on the tree; tree-dependent job selection
+# should be performed in the target task selection phase of task-graph
+# generation.
+#
+# A test set has a name, and a list of tests that it contains.
+#
+# Test names given here reference tests.yml.
+
+debug-tests:
+    - cppunit
+    - crashtest
+    - jsreftest
+    - mochitest
+    - mochitest-chrome
+    - mochitest-clipboard
+    - mochitest-gpu
+    - mochitest-media
+    - mochitest-webgl
+    - reftest
+    - xpcshell
+
+opt-tests:
+    - cppunit
+    - crashtest
+    - jsreftest
+    - mochitest
+    - mochitest-chrome
+    - mochitest-clipboard
+    - mochitest-gpu
+    - mochitest-media
+    - mochitest-webgl
+    - reftest
+    - robocop
+    - xpcshell
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/android-test/tests.yml
@@ -0,0 +1,248 @@
+# Each stanza here describes a particular test suite or sub-suite.  These are
+# processed through the transformations described in kind.yml to produce a
+# bunch of tasks.  See the schema in `test-descriptions.py` for a description
+# of the fields used here.
+
+# The Android tests have separate test definitions from desktop because,
+# despite sharing test names, the invocation of these test suites differ
+# substantially from desktop.
+
+# Note that these are in lexical order
+
+cppunit:
+    description: "CPP Unit Tests"
+    suite: cppunittest
+    treeherder-symbol: tc(Cpp)
+    e10s: false
+    loopback-video: true
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=cppunittest
+
+crashtest:
+    description: "Crashtest run"
+    suite: crashtest
+    treeherder-symbol: tc-R(C)
+    instance-size: xlarge
+    chunks:
+        by-test-platform:
+            android-4.3-arm7-api-15/debug: 10
+            android-4.3-arm7-api-15/opt: 4
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=crashtest
+
+jsreftest:
+    description: "JS Reftest run"
+    suite: reftest/jsreftest
+    treeherder-symbol: tc-R(J)
+    instance-size: xlarge
+    chunks:
+        by-test-platform:
+            android-4.3-arm7-api-15/debug: 20
+            android-4.3-arm7-api-15/opt: 6
+    loopback-video: true
+    max-run-time: 7200
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=jsreftest
+
+mochitest:
+    description: "Mochitest plain run"
+    suite: mochitest/plain-chunked
+    treeherder-symbol: tc-M()
+    instance-size: xlarge
+    chunks: 20
+    loopback-video: true
+    e10s: false
+    max-run-time:
+        by-test-platform:
+            android-4.3-arm7-api-15/debug: 10800
+            android-4.3-arm7-api-15/opt: 3600
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=mochitest
+
+mochitest-chrome:
+    description: "Mochitest chrome run"
+    suite: mochitest/chrome
+    treeherder-symbol: tc-M(c)
+    instance-size: xlarge
+    loopback-video: true
+    e10s: false
+    max-run-time: 5400
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=mochitest-chrome
+
+mochitest-clipboard:
+    description: "Mochitest clipboard run"
+    suite: mochitest/plain-clipboard
+    treeherder-symbol: tc-M(cl)
+    instance-size: xlarge
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=mochitest-plain-clipboard
+
+mochitest-gpu:
+    description: "Mochitest gpu run"
+    suite: mochitest/plain-gpu
+    treeherder-symbol: tc-M(gpu)
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=mochitest-plain-gpu
+
+mochitest-media:
+    description: "Mochitest media run"
+    suite: mochitest/mochitest-media
+    treeherder-symbol: tc-M(mda)
+    instance-size: xlarge
+    chunks: 2
+    loopback-video: true
+    e10s: false
+    max-run-time:
+        by-test-platform:
+            android-4.3-arm7-api-15/debug: 5400
+            android-4.3-arm7-api-15/opt: 3600
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=mochitest-media
+
+mochitest-webgl:
+    description: "Mochitest webgl run"
+    suite: mochitest/mochitest-gl
+    treeherder-symbol: tc-M(gl)
+    chunks: 10
+    loopback-video: true
+    e10s: false
+    max-run-time: 7200
+    instance-size:
+        by-test-platform:
+            android-4.3-arm7-api-15/opt: default
+            android-4.3-arm7-api-15/debug: xlarge
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=mochitest-gl
+
+reftest:
+    description: "Reftest run"
+    suite: reftest/reftest
+    treeherder-symbol: tc-R(R)
+    chunks:
+        by-test-platform:
+            android-4.3-arm7-api-15/debug: 48
+            android-4.3-arm7-api-15/opt: 16
+    instance-size: xlarge
+    max-run-time: 10800
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=reftest
+
+robocop:
+    description: "Robocop run"
+    suite: robocop
+    treeherder-symbol: tc-M(rc)
+    instance-size: xlarge
+    chunks:
+        by-test-platform:
+            # android-4.3-arm7-api-15/debug -- not run
+            android-4.3-arm7-api-15/opt: 4
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=robocop
+
+xpcshell:
+    description: "xpcshell test run"
+    suite: xpcshell
+    treeherder-symbol: tc-X()
+    chunks: 3
+    instance-size: xlarge
+    max-run-time: 7200
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/android_emulator_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/android/androidarm_4_3.py
+            - mozharness/configs/remove_executables.py
+            - mozharness/configs/android/androidarm_4_3-tc.py
+        extra-options:
+            - --test-suite=xpcshell
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/desktop-test/kind.yml
@@ -0,0 +1,12 @@
+implementation: taskgraph.task.test:TestTask
+
+kind-dependencies:
+    - legacy
+
+transforms:
+   - taskgraph.transforms.tests.test_description:validate
+   - taskgraph.transforms.tests.desktop_test:transforms
+   - taskgraph.transforms.tests.all_kinds:transforms
+   - taskgraph.transforms.tests.test_description:validate
+   - taskgraph.transforms.tests.make_task_description:transforms
+   - taskgraph.transforms.make_task:transforms
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/desktop-test/test-platforms.yml
@@ -0,0 +1,27 @@
+# This file maps build platforms to test platforms.  In some cases, a
+# single build may be tested on multiple test platforms, but a single test
+# platform can only link to one build platform.  Both build and test platforms
+# are represented as <platform>/<type>, where <type> is what Treeherder calls a
+# collection.
+#
+# Each test platform further specifies the set of tests that will be scheduled
+# for the platform, referring to tests defined in test-sets.yml.
+#
+# Note that set does not depend on the tree; tree-dependent job selection
+# should be performed in the target task selection phase of task-graph
+# generation.
+
+linux64/debug:
+    build-platform: linux64/debug
+    test-set: all-tests
+linux64/opt:
+    build-platform: linux64/opt
+    test-set: all-tests
+
+# TODO: use 'pgo' and 'asan' labels here, instead of -pgo/opt
+linux64-pgo/opt:
+    build-platform: linux64-pgo/opt
+    test-set: all-tests
+linux64-asan/opt:
+    build-platform: linux64-asan/opt
+    test-set: asan-tests
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/desktop-test/test-sets.yml
@@ -0,0 +1,54 @@
+# Each key in this file specifies a set of tests to run.  Different test sets
+# may, for example, be bound to different test platforms.
+#
+# Note that set does not depend on the tree; tree-dependent job selection
+# should be performed in the target task selection phase of task-graph
+# generation.
+#
+# A test set has a name, and a list of tests that it contains.
+#
+# Test names given here reference tests.yml.
+
+all-tests:
+    - cppunit
+    - crashtest
+    - external-media-tests
+    - firefox-ui-functional-local
+    - firefox-ui-functional-remote
+    - gtest
+    - jittests
+    - jsreftest
+    - marionette
+    - mochitest
+    - mochitest-a11y
+    - mochitest-browser-chrome
+    - mochitest-chrome
+    - mochitest-clipboard
+    - mochitest-devtools-chrome
+    - mochitest-gpu
+    - mochitest-jetpack
+    - mochitest-media
+    - mochitest-webgl
+    - reftest
+    - reftest-no-accel
+    - web-platform-tests
+    - web-platform-tests-reftests
+    - xpcshell
+
+asan-tests:
+    - cppunit
+    - crashtest
+    - gtest
+    - jittests
+    - jsreftest
+    - marionette
+    - mochitest
+    - mochitest-browser-chrome
+    - mochitest-chrome
+    - mochitest-clipboard
+    - mochitest-devtools-chrome
+    - mochitest-gpu
+    - mochitest-jetpack
+    - mochitest-media
+    - mochitest-webgl
+    - xpcshell
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/desktop-test/tests.yml
@@ -0,0 +1,378 @@
+# Each stanza here describes a particular test suite or sub-suite.  These are
+# processed through the transformations described in kind.yml to produce a
+# bunch of tasks.  See the schema in `test-descriptions.py` for a description
+# of the fields used here.
+
+# Note that these are in lexical order
+
+cppunit:
+    description: "CPP Unit Tests"
+    suite: cppunittest
+    treeherder-symbol: tc(Cpp)
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --cppunittest-suite=cppunittest
+
+crashtest:
+    description: "Crashtest run"
+    suite: reftest/crashtest
+    treeherder-symbol: tc-R(C)
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        chunked: true
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --reftest-suite=crashtest
+
+external-media-tests:
+    description: "External Media Test run"
+    suite: external-media-tests
+    treeherder-symbol: tc-VP(b-m)
+    e10s: false
+    tier: 2
+    max-run-time: 5400
+    mozharness:
+        script: mozharness/scripts/firefox_media_tests_buildbot.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/mediatests/buildbot_posix_config.py
+            - mozharness/configs/remove_executables.py
+
+firefox-ui-functional-local:
+    description: "Firefox-ui-tests functional run"
+    suite: "firefox-ui/functional local"
+    treeherder-symbol: tc-Fxfn-l(en-US)
+    max-run-time: 5400
+    tier: 1
+    mozharness:
+        script: mozharness/scripts/firefox_ui_tests/functional.py
+        config:
+            - mozharness/configs/firefox_ui_tests/taskcluster.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - "--tag local"
+
+firefox-ui-functional-remote:
+    description: "Firefox-ui-tests functional run"
+    suite: "firefox-ui/functional remote"
+    treeherder-symbol: tc-Fxfn-r(en-US)
+    max-run-time: 5400
+    tier: 2
+    mozharness:
+        script: mozharness/scripts/firefox_ui_tests/functional.py
+        config:
+            - mozharness/configs/firefox_ui_tests/taskcluster.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - "--tag remote"
+
+gtest:
+    description: "GTests run"
+    suite: gtest
+    treeherder-symbol: tc(GTest)
+    e10s: false
+    instance-size: xlarge
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --gtest-suite=gtest
+
+jittests:
+    description: "JIT Test run"
+    suite: jittest/jittest-chunked
+    treeherder-symbol: tc(Jit)
+    e10s: false
+    chunks: 6
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --jittest-suite=jittest-chunked
+
+jsreftest:
+    description: "JS Reftest run"
+    suite: reftest/jsreftest
+    treeherder-symbol: tc-R(J)
+    chunks: 2
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --reftest-suite=jsreftest
+
+marionette:
+    description: "Marionette unittest run"
+    suite: marionette
+    treeherder-symbol: tc(Mn)
+    max-run-time: 5400
+    mozharness:
+        script: mozharness/scripts/marionette.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/marionette/prod_config.py
+            - mozharness/configs/remove_executables.py
+
+mochitest:
+    description: "Mochitest plain run"
+    suite: mochitest/plain-chunked
+    treeherder-symbol: tc-M()
+    loopback-video: true
+    chunks: 10
+    max-run-time: 5400
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=plain-chunked
+
+mochitest-a11y:
+    description: "Mochitest a11y run"
+    suite: mochitest/a11y
+    treeherder-symbol: tc-M(a11y)
+    loopback-video: true
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=a11y
+
+mochitest-browser-chrome:
+    description: "Mochitest browser-chrome run"
+    suite: mochitest/browser-chrome-chunked
+    treeherder-symbol: tc-M(bc)
+    loopback-video: true
+    chunks: 7
+    max-run-time:
+        by-test-platform:
+            linux64/debug: 5400
+            default: 3600
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=browser-chrome-chunked
+
+mochitest-chrome:
+    description: "Mochitest chrome run"
+    suite: mochitest/chrome
+    treeherder-symbol: tc-M(c)
+    loopback-video: true
+    chunks: 3
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=chrome
+
+mochitest-clipboard:
+    description: "Mochitest clipboard run"
+    suite: mochitest/plain-clipboard,chrome-clipboard,browser-chrome-clipboard,jetpack-package-clipboard
+    treeherder-symbol: tc-M(cl)
+    loopback-video: true
+    instance-size: xlarge
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=plain-clipboard,chrome-clipboard,browser-chrome-clipboard,jetpack-package-clipboard
+
+mochitest-devtools-chrome:
+    description: "Mochitest devtools-chrome run"
+    suite: mochitest/mochitest-devtools-chrome-chunked
+    treeherder-symbol: tc-M(dt)
+    loopback-video: true
+    max-run-time: 5400
+    chunks: 10
+    e10s:
+        by-test-platform:
+            # Bug 1242986: linux64/debug mochitest-devtools-chrome e10s is not greened up yet
+            linux64/debug: false
+            default: both
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=mochitest-devtools-chrome-chunked
+
+mochitest-gpu:
+    description: "Mochitest GPU run"
+    suite: mochitest/plain-gpu,chrome-gpu,browser-chrome-gpu
+    treeherder-symbol: tc-M(gpu)
+    loopback-video: true
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=plain-gpu,chrome-gpu,browser-chrome-gpu
+
+mochitest-jetpack:
+    description: "Mochitest jetpack run"
+    suite: mochitest/jetpack-package
+    treeherder-symbol: tc-M(JP)
+    loopback-video: true
+    e10s: false
+    max-run-time: 5400
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=jetpack-package
+
+mochitest-media:
+    description: "Mochitest media run"
+    suite: mochitest/mochitest-media
+    treeherder-symbol: tc-M(mda)
+    max-run-time: 5400
+    loopback-video: true
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=mochitest-media
+
+mochitest-webgl:
+    description: "Mochitest webgl run"
+    suite: mochitest/mochitest-gl
+    treeherder-symbol: tc-M(gl)
+    loopback-video: true
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        chunked: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --mochitest-suite=mochitest-gl
+
+reftest:
+    description: "Reftest run"
+    suite: reftest/reftest
+    treeherder-symbol: tc-R(R)
+    chunks: 8
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --reftest-suite=reftest
+
+reftest-no-accel:
+    description: "Reftest not accelerated run"
+    suite: reftest/reftest-no-accel
+    treeherder-symbol: tc-R(Ru)
+    chunks: 8
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --reftest-suite=reftest-no-accel
+
+web-platform-tests:
+    description: "Web platform test run"
+    suite: web-platform-tests
+    treeherder-symbol: tc-W()
+    chunks: 12
+    max-run-time: 7200
+    instance-size: xlarge
+    mozharness:
+        script: mozharness/scripts/web_platform_tests.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/web_platform_tests/prod_config.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --test-type=testharness
+
+web-platform-tests-reftests:
+    description: "Web platform reftest run"
+    suite: web-platform-tests-reftests
+    treeherder-symbol: tc-W(Wr)
+    max-run-time: 5400
+    instance-size: xlarge
+    mozharness:
+        script: mozharness/scripts/web_platform_tests.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/web_platform_tests/prod_config.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --test-type=reftest
+
+xpcshell:
+    description: "xpcshell test run"
+    suite: xpcshell
+    treeherder-symbol: tc-X()
+    chunks:
+        by-test-platform:
+            linux64/debug: 10
+            default: 8
+    max-run-time: 5400
+    e10s: false
+    mozharness:
+        script: mozharness/scripts/desktop_unittest.py
+        no-read-buildbot-config: true
+        config:
+            - mozharness/configs/unittests/linux_unittest.py
+            - mozharness/configs/remove_executables.py
+        extra-options:
+            - --xpcshell-suite=xpcshell
--- a/taskcluster/ci/docker-image/kind.yml
+++ b/taskcluster/ci/docker-image/kind.yml
@@ -1,13 +1,13 @@
 # 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/.
 
-implementation: 'taskgraph.kind.docker_image:DockerImageTask'
+implementation: 'taskgraph.task.docker_image:DockerImageTask'
 images_path: '../../../testing/docker'
 
 # make a task for each docker-image we might want.  For the moment, since we
 # write artifacts for each, these are whitelisted, but ideally that will change
 # (to use subdirectory clones of the proper directory), at which point we can
 # generate tasks for every docker image in the directory, secure in the
 # knowledge that unnecessary images will be omitted from the target task graph
 images:
--- a/taskcluster/ci/legacy/kind.yml
+++ b/taskcluster/ci/legacy/kind.yml
@@ -1,6 +1,6 @@
 # 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/.
 
-implementation: 'taskgraph.kind.legacy:LegacyTask'
+implementation: 'taskgraph.task.legacy:LegacyTask'
 legacy_path: '.'
--- a/taskcluster/ci/legacy/tasks/branches/alder/job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/alder/job_flags.yml
@@ -7,15 +7,8 @@
 
 builds:
   linux64-mulet:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/mulet_linux.yml
-
-tests:
-  mochitest:
-    allowed_build_tasks:
-      tasks/builds/mulet_linux.yml:
-        task: tasks/tests/mulet_mochitests.yml
-        chunks: 5
--- a/taskcluster/ci/legacy/tasks/branches/ash/job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/ash/job_flags.yml
@@ -23,142 +23,8 @@ builds:
       debug:
         task: tasks/builds/dbg_linux64-asan.yml
   linux64-pgo:
     platforms:
       - Linux64 PGO
     types:
       opt:
         task: tasks/builds/opt_linux64_pgo.yml
-
-tests:
-  crashtest-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_opt.yml
-  jsreftest-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_jsreftest_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_jsreftest_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_jsreftest_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_jsreftest_e10s_opt.yml
-  marionette-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_marionette_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_marionette_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_marionette_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_marionette_e10s_opt.yml
-  mochitest-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_plain_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_plain_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_plain_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_plain_e10s_opt.yml
-  mochitest-browser-chrome-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_bc_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_bc_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_bc_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_bc_e10s_opt.yml
-  mochitest-clipboard-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_clipboard_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_clipboard_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_clipboard_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_clipboard_e10s_opt.yml
-  mochitest-devtools-chrome-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_dt_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_dt_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_dt_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_dt_e10s_opt.yml
-  mochitest-gpu-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_gpu_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_gpu_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_gpu_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_gpu_e10s_opt.yml
-  mochitest-media-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_media_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_media_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_media_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_media_e10s_opt.yml
-  mochitest-webgl-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_gl_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_mochitest_gl_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_mochitest_gl_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_mochitest_gl_e10s_opt.yml
-  reftest-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_reftest_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_reftest_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_reftest_e10s_opt.yml
-  reftest-no-accel-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_reftest_not_accelerated_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_reftest_not_accelerated_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_reftest_not_accelerated_e10s_opt.yml
-  web-platform-tests-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_web_platform_tests_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_web_platform_tests_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_web_platform_tests_e10s_opt.yml
-  web-platform-tests-reftests-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_web_platform_tests_reftests_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_web_platform_tests_reftests_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_web_platform_tests_reftests_e10s_opt.yml
--- a/taskcluster/ci/legacy/tasks/branches/base_job_flags.yml
+++ b/taskcluster/ci/legacy/tasks/branches/base_job_flags.yml
@@ -35,22 +35,16 @@ flags:
     - cppunit
     - crashtest
     - crashtest-e10s
     - external-media-tests
     - firefox-ui-functional-local
     - firefox-ui-functional-local-e10s
     - firefox-ui-functional-remote
     - firefox-ui-functional-remote-e10s
-    - gaia-build
-    - gaia-build-unit
-    - gaia-js-integration
-    - gaia-linter
-    - gaia-unit
-    - gaia-unit-oop
     - gtest
     - jittests
     - jsreftest
     - jsreftest-e10s
     - luciddream
     - marionette
     - marionette-e10s
     - marionette-webapi
--- a/taskcluster/ci/legacy/tasks/branches/base_jobs.yml
+++ b/taskcluster/ci/legacy/tasks/branches/base_jobs.yml
@@ -233,480 +233,16 @@ builds:
     platforms:
       - win64
     types:
       opt:
         task: tasks/builds/opt_win64.yml
       debug:
         task: tasks/builds/dbg_win64.yml
 
-tests:
-  cppunit:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_cppunit_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_cppunit_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_cppunit_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_cppunit_opt.yml
-      tasks/builds/android_api_15.yml:
-        task: tasks/tests/fx_android-api-15_cppunit_opt.yml
-      tasks/builds/android_api_15_debug.yml:
-        task: tasks/tests/fx_android-api-15_cppunit_dbg.yml
-  crashtest:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_crashtest_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_crashtest_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_crashtest_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_crashtest_opt.yml
-      tasks/builds/android_api_15.yml:
-        task: tasks/tests/fx_android-api-15_crashtest_opt.yml
-      tasks/builds/android_api_15_debug.yml:
-        task: tasks/tests/fx_android-api-15_crashtest_dbg.yml
-  crashtest-e10s:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_opt.yml
-      tasks/builds/opt_linux64-asan.yml:
-        task: tasks/tests/fx_linux64_crashtest_e10s_opt.yml
-  external-media-tests:
-    allowed_build_tasks:
-      tasks/builds/opt_linux64.yml:
-        task: tasks/tests/fx_linux64_external_media_tests_opt.yml
-      tasks/builds/dbg_linux64.yml:
-        task: tasks/tests/fx_linux64_external_media_tests_dbg.yml
-      tasks/builds/opt_linux64_pgo.yml:
-        task: tasks/tests/fx_linux64_external_media_tests_opt.yml
-  firefox-ui-functional-local: