Merge latest green b2g-inbound changeset and mozilla-central
authorEd Morley <emorley@mozilla.com>
Fri, 20 Sep 2013 10:20:58 +0100
changeset 148065 8f8a683dfc42d4ab81b904ddbcb157f15fd189a5
parent 148064 9e5d17cb786d642d9ee87031d386b1c21f6a0540 (current diff)
parent 148045 d923570ed7206648ecf5aa9c9317def697de4266 (diff)
child 148068 7e8d5be4898d4493e334254345211af502e40087
child 148078 3ca60aa3a003f64c9b73b0db4d2851a6bfa7a21f
child 148136 9ba8efe588db0edc1d1374d9bbcf2464985bac95
child 155770 548ddf4f9c90208200725d374fdfbc5292e3a117
push id25322
push useremorley@mozilla.com
push dateFri, 20 Sep 2013 09:21:16 +0000
treeherdermozilla-central@8f8a683dfc42 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
8f8a683dfc42 / 27.0a1 / 20130920030204 / files
nightly linux64
8f8a683dfc42 / 27.0a1 / 20130920030204 / files
nightly mac
8f8a683dfc42 / 27.0a1 / 20130920030204 / files
nightly win32
8f8a683dfc42 / 27.0a1 / 20130920030204 / files
nightly win64
8f8a683dfc42 / 27.0a1 / 20130920030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge latest green b2g-inbound changeset and mozilla-central
browser/base/content/test/newtab/moz.build
browser/devtools/shared/css-color.js
content/media/WebVTTLoadListener.cpp
content/media/WebVTTLoadListener.h
python/mozbuild/mozbuild/action/purge_manifests.py
testing/marionette/client/marionette/test_emulator.py
toolkit/components/osfile/modules/ospath_unix_back.jsm
toolkit/components/osfile/modules/ospath_win_back.jsm
--- a/Makefile.in
+++ b/Makefile.in
@@ -26,42 +26,19 @@ include $(topsrcdir)/config/config.mk
 GARBAGE_DIRS += dist _javagen _profile _tests staticlib
 DIST_GARBAGE = config.cache config.log config.status* config-defs.h \
    config/autoconf.mk \
    mozilla-config.h \
    netwerk/necko-config.h xpcom/xpcom-config.h xpcom/xpcom-private.h \
    .mozconfig.mk
 
 ifndef MOZ_PROFILE_USE
-# One of the first things we do in the build is purge "unknown" files
-# from the object directory. This serves two purposes:
-#
-#   1) Remove files from a previous build no longer accounted for in
-#      this build configuration.
-#
-#   2) Work around poor build system dependencies by forcing some
-#      rebuilds.
-#
-# Ideally #2 does not exist. Our reliance on this aspect should diminish
-# over time.
-#
-# moz.build backend generation simply installs a set of "manifests" into
-# a common directory. Each manifest is responsible for defining files in
-# a specific subdirectory of the object directory. The invoked Python
-# script simply iterates over all the manifests, purging files as
-# necessary. To manage new directories or add files to the manifests,
-# modify the backend generator.
-#
 # We need to explicitly put backend.RecursiveMakeBackend.built here
 # otherwise the rule in rules.mk doesn't run early enough.
-libs export tools:: CLOBBER $(topsrcdir)/configure config.status backend.RecursiveMakeBackend.built
-	$(call SUBMAKE,backend.RecursiveMakeBackend.built,js/src,1)
-
-export::
-	$(call py_action,purge_manifests,-d _build_manifests/purge .)
+libs export tools:: CLOBBER $(topsrcdir)/configure config.status backend.RecursiveMakeBackend.built js-config-status
 endif
 
 CLOBBER: $(topsrcdir)/CLOBBER
 	@echo "STOP!  The CLOBBER file has changed."
 	@echo "Please run the build through a sanctioned build wrapper, such as"
 	@echo "'mach build' or client.mk."
 	@exit 1
 
@@ -74,27 +51,63 @@ CLOBBER: $(topsrcdir)/CLOBBER
 
 config.status: $(topsrcdir)/configure
 	@echo "STOP!  configure has changed and needs to be run in this build directory."
 	@echo "Please rerun configure."
 	@echo "To ignore this message, touch 'config.status' in the build directory,"
 	@echo "but your build might not succeed."
 	@exit 1
 
-export::
-	$(RM) -r $(DIST)/sdk
+.PHONY: js-config-status
+js-config-status:
+	$(call SUBMAKE,backend.RecursiveMakeBackend.built,js/src,1)
+
+install_manifests := bin idl include public private sdk
+install_manifest_depends = \
+  CLOBBER \
+  $(topsrcdir)/configure \
+  config.status \
+  backend.RecursiveMakeBackend.built \
+  js-config-status \
+  $(NULL)
+
+.PHONY: install-manifests
+install-manifests: $(addprefix install-dist-,$(install_manifests))
+
+.PHONY: $(addprefix install-dist-,$(install_manifests))
+$(addprefix install-dist-,$(install_manifests)): install-dist-%: $(install_manifest_depends)
+	$(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )$(DIST)/$* _build_manifests/install/dist_$* js/src/_build_manifests/install/dist_$*)
+
+.PHONY: install-tests
+install-manifests: install-tests
+install-tests: $(install_manifest_depends)
+	$(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )_tests _build_manifests/install/tests js/src/_build_manifests/install/tests)
+
+# Windows PGO builds don't perform a clean before the 2nd pass. So, we want
+# to preserve content for the 2nd pass on Windows. Everywhere else, we always
+# process the install manifests as part of export.
+ifdef MOZ_PROFILE_USE
+ifndef NO_PROFILE_GUIDED_OPTIMIZE
+ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
+export:: install-manifests
+endif
+endif
+else # !MOZ_PROFILE_USE (normal build)
+export:: install-manifests
+endif
+
+# For historical reasons that are unknown, $(DIST)/sdk is always blown away
+# with no regard for PGO passes. This decision could probably be revisited.
+export:: install-dist-sdk
 
 ifdef ENABLE_TESTS
 # Additional makefile targets to call automated test suites
 include $(topsrcdir)/testing/testsuite-targets.mk
 endif
 
-export::
-	$(call py_action,process_install_manifest,$(DIST)/include _build_manifests/install/dist_include js/src/_build_manifests/install/dist_include)
-
 default all::
 	$(call BUILDSTATUS,TIERS export compile libs tools)
 
 include $(topsrcdir)/config/rules.mk
 
 distclean::
 	$(RM) $(DIST_GARBAGE)
 
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -2015,20 +2015,18 @@ Accessible::RelationByType(uint32_t aTyp
       }
 
       return rel;
     }
 
     case nsIAccessibleRelation::RELATION_LABEL_FOR: {
       Relation rel(new RelatedAccIterator(Document(), mContent,
                                           nsGkAtoms::aria_labelledby));
-      if (mContent->Tag() == nsGkAtoms::label)
-        rel.AppendIter(new IDRefsIterator(mDoc, mContent, mContent->IsHTML() ?
-          nsGkAtoms::_for :
-          nsGkAtoms::control));
+      if (mContent->Tag() == nsGkAtoms::label && mContent->IsXUL())
+        rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
 
       return rel;
     }
 
     case nsIAccessibleRelation::RELATION_DESCRIBED_BY: {
       Relation rel(new IDRefsIterator(mDoc, mContent,
                                       nsGkAtoms::aria_describedby));
       if (mContent->IsXUL())
--- a/accessible/src/html/HTMLElementAccessibles.cpp
+++ b/accessible/src/html/HTMLElementAccessibles.cpp
@@ -9,16 +9,18 @@
 #include "nsAccUtils.h"
 #include "nsIAccessibleRelation.h"
 #include "nsIPersistentProperties2.h"
 #include "nsTextEquivUtils.h"
 #include "Relation.h"
 #include "Role.h"
 #include "States.h"
 
+#include "mozilla/dom/HTMLLabelElement.h"
+
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLHRAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 role
 HTMLHRAccessible::NativeRole()
@@ -57,16 +59,28 @@ NS_IMPL_ISUPPORTS_INHERITED0(HTMLLabelAc
 
 ENameValueFlag
 HTMLLabelAccessible::NativeName(nsString& aName)
 {
   nsTextEquivUtils::GetNameFromSubtree(this, aName);
   return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
 }
 
+Relation
+HTMLLabelAccessible::RelationByType(uint32_t aType)
+{
+  Relation rel = AccessibleWrap::RelationByType(aType);
+  if (aType == nsIAccessibleRelation::RELATION_LABEL_FOR) {
+    nsRefPtr<dom::HTMLLabelElement> label = dom::HTMLLabelElement::FromContent(mContent);
+    rel.AppendTarget(mDoc, label->GetControl());
+  }
+
+  return rel;
+}
+
 role
 HTMLLabelAccessible::NativeRole()
 {
   return roles::LABEL;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLOuputAccessible
--- a/accessible/src/html/HTMLElementAccessibles.h
+++ b/accessible/src/html/HTMLElementAccessibles.h
@@ -56,16 +56,17 @@ public:
 
   HTMLLabelAccessible(nsIContent* aContent, DocAccessible* aDoc) :
     HyperTextAccessibleWrap(aContent, aDoc) {}
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // Accessible
   virtual a11y::role NativeRole();
+  virtual Relation RelationByType(uint32_t aType) MOZ_OVERRIDE;
 
 protected:
   virtual ENameValueFlag NativeName(nsString& aName) MOZ_OVERRIDE;
 };
 
 /**
  * Used for HTML output element.
  */
--- a/accessible/tests/mochitest/relations/test_general.html
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -23,27 +23,34 @@
 
       // html:label@for, multiple
       testRelation("label1_2", RELATION_LABEL_FOR, "control1_2");
       testRelation("label1_3", RELATION_LABEL_FOR, "control1_2");
       testRelation("control1_2", RELATION_LABELLED_BY,
                    [ "label1_2", "label1_3" ]);
 
       // ancestor html:label (implicit association)
-      todo(false, "no label_for relation on implicitly associated label, bug 687414");
-      //testRelation("label1_4", RELATION_LABEL_FOR, "control1_4");
+      testRelation("label1_4", RELATION_LABEL_FOR, "control1_4");
       testRelation("control1_4", RELATION_LABELLED_BY, "label1_4");
       testRelation("control1_4_option1", RELATION_LABELLED_BY, null);
+      testRelation("label1_5", RELATION_LABEL_FOR, "control1_5");
       testRelation("control1_5", RELATION_LABELLED_BY, "label1_5");
+      testRelation("label1_6", RELATION_LABEL_FOR, "control1_6");
       testRelation("control1_6", RELATION_LABELLED_BY, "label1_6");
+      testRelation("label1_7", RELATION_LABEL_FOR, "control1_7");
       testRelation("control1_7", RELATION_LABELLED_BY, "label1_7");
+      testRelation("label1_8", RELATION_LABEL_FOR, "control1_8");
       testRelation("control1_8", RELATION_LABELLED_BY, "label1_8");
+      testRelation("label1_9", RELATION_LABEL_FOR, "control1_9");
       testRelation("control1_9", RELATION_LABELLED_BY, "label1_9");
+      testRelation("label1_10", RELATION_LABEL_FOR, "control1_10");
       testRelation("control1_10", RELATION_LABELLED_BY, "label1_10");
+      testRelation("label1_11", RELATION_LABEL_FOR, "control1_11");
       testRelation("control1_11", RELATION_LABELLED_BY, "label1_11");
+      testRelation("label1_12", RELATION_LABEL_FOR, "control1_12");
       testRelation("control1_12", RELATION_LABELLED_BY, "label1_12");
 
       testRelation("label1_13", RELATION_LABEL_FOR, null);
       testRelation("control1_13", RELATION_LABELLED_BY, null);
       testRelation("control1_14", RELATION_LABELLED_BY, "label1_14");
 
       // aria-labelledby
       testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -447,16 +447,19 @@
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/SettingsService.js
 @BINPATH@/components/SettingsService.manifest
+@BINPATH@/components/webvtt.xpt
+@BINPATH@/components/WebVTT.manifest
+@BINPATH@/components/WebVTTParserWrapper.js
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
 @BINPATH@/components/MmsService.manifest
 @BINPATH@/components/MmsService.js
 @BINPATH@/components/RILContentHelper.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -53,17 +53,17 @@ pref("extensions.blocklist.level", 2);
 pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/");
 pref("extensions.blocklist.detailsURL", "https://www.mozilla.org/%LOCALE%/blocklist/");
 pref("extensions.blocklist.itemURL", "https://addons.mozilla.org/%LOCALE%/%APP%/blocked/%blockID%");
 
 pref("extensions.update.autoUpdateDefault", true);
 
 pref("extensions.hotfix.id", "firefox-hotfix@mozilla.org");
 pref("extensions.hotfix.cert.checkAttributes", true);
-pref("extensions.hotfix.certs.1.sha1Fingerprint", "CA:C4:7D:BF:63:4D:24:E9:DC:93:07:2F:E3:C8:EA:6D:C3:94:6E:89");
+pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35:22:9E:11:C9:A7:31:04:49:A1:AA");
 
 // Disable add-ons that are not installed by the user in all scopes by default.
 // See the SCOPE constants in AddonManager.jsm for values to use here.
 pref("extensions.autoDisableScopes", 15);
 
 // Dictionary download preference
 pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
 
@@ -855,16 +855,18 @@ pref("browser.sessionstore.restore_hidde
 // If restore_on_demand is set, pinned tabs are restored on startup by default.
 // When set to true, this pref overrides that behavior, and pinned tabs will only
 // be restored when they are focused.
 pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
 // The version at which we performed the latest upgrade backup
 pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
+// Enable asynchronous data collection by default.
+pref("browser.sessionstore.async", true);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
 
 // the (maximum) number of the recent visits to sample
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -519,17 +519,18 @@
                   <menuseparator id="menu_devtools_separator"/>
                   <menuitem id="menu_devToolbar"
                             observes="devtoolsMenuBroadcaster_DevToolbar"
                             accesskey="&devToolbarMenu.accesskey;"/>
                   <menuitem id="menu_devAppMgr"
                             observes="devtoolsMenuBroadcaster_DevAppMgr"
                             accesskey="&devAppMgrMenu.accesskey;"/>
                   <menuitem id="menu_chromeDebugger"
-                            observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+                            observes="devtoolsMenuBroadcaster_ChromeDebugger"
+                            accesskey="&chromeDebuggerMenu.accesskey;"/>
                   <menuitem id="menu_browserConsole"
                             observes="devtoolsMenuBroadcaster_BrowserConsole"
                             accesskey="&browserConsoleCmd.accesskey;"/>
                   <menuitem id="menu_responsiveUI"
                             observes="devtoolsMenuBroadcaster_ResponsiveUI"
                             accesskey="&responsiveDesignTool.accesskey;"/>
                   <menuitem id="menu_scratchpad"
                             observes="devtoolsMenuBroadcaster_Scratchpad"
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -210,23 +210,30 @@ SocialUI = {
 
     let toggleCommand = document.getElementById("Social:Toggle");
     toggleCommand.setAttribute("hidden", enabled ? "false" : "true");
 
     if (enabled) {
       // enabled == true means we at least have a defaultProvider
       let provider = Social.provider || Social.defaultProvider;
       // We only need to update the command itself - all our menu items use it.
-      let label = gNavigatorBundle.getFormattedString(Social.provider ?
-                                                        "social.turnOff.label" :
-                                                        "social.turnOn.label",
-                                                      [provider.name]);
-      let accesskey = gNavigatorBundle.getString(Social.provider ?
-                                                   "social.turnOff.accesskey" :
-                                                   "social.turnOn.accesskey");
+      let label;
+      if (Social.providers.length == 1) {
+        label = gNavigatorBundle.getFormattedString(Social.provider
+                                                    ? "social.turnOff.label"
+                                                    : "social.turnOn.label",
+                                                    [provider.name]);
+      } else {
+        label = gNavigatorBundle.getString(Social.provider
+                                           ? "social.turnOffAll.label"
+                                           : "social.turnOnAll.label");
+      }
+      let accesskey = gNavigatorBundle.getString(Social.provider
+                                                 ? "social.turnOff.accesskey"
+                                                 : "social.turnOn.accesskey");
       toggleCommand.setAttribute("label", label);
       toggleCommand.setAttribute("accesskey", accesskey);
     }
   },
 
   _updateMenuItems: function () {
     let provider = Social.provider || Social.defaultProvider;
     if (!provider)
--- a/browser/base/content/test/social/browser_social_multiworker.js
+++ b/browser/base/content/test/social/browser_social_multiworker.js
@@ -2,16 +2,17 @@
  * 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/. */
 
 function test() {
   waitForExplicitFinish();
 
   Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
   runSocialTestWithProvider(gProviders, function (finishcb) {
+    Social.enabled = true;
     runSocialTests(tests, undefined, undefined, function() {
       Services.prefs.clearUserPref("social.allowMultipleWorkers");
       finishcb();
     });
   });
 }
 
 let gProviders = [
--- a/browser/components/build/Makefile.in
+++ b/browser/components/build/Makefile.in
@@ -1,14 +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/.
 
 SHORT_LIBNAME = brwsrcmp
-IS_COMPONENT = 1
 MODULE_NAME = nsBrowserCompsModule
 FORCE_SHARED_LIB = 1
 
 USE_STATIC_LIBS = 1
 
 ifeq ($(OS_ARCH),WINNT)
 OS_LIBS	+= $(call EXPAND_LIBNAME,ole32 shell32 shlwapi)
 endif
--- a/browser/components/build/moz.build
+++ b/browser/components/build/moz.build
@@ -11,8 +11,10 @@ EXPORTS += [
 ]
 
 CPP_SOURCES += [
     'nsModule.cpp',
 ]
 
 LIBRARY_NAME = 'browsercomps'
 
+IS_COMPONENT = True
+
--- a/browser/components/privatebrowsing/test/browser/Makefile.in
+++ b/browser/components/privatebrowsing/test/browser/Makefile.in
@@ -1,17 +1,19 @@
 # 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/.
 
+# Disabled for too many intermittent failures (bug 895390)
+#                browser_privatebrowsing_cache.js \
+
 MOCHITEST_BROWSER_FILES =  \
 		head.js \
                 browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js \
                 browser_privatebrowsing_aboutSessionRestore.js \
-                browser_privatebrowsing_cache.js \
 		browser_privatebrowsing_certexceptionsui.js \
 		browser_privatebrowsing_concurrent.js \
 		browser_privatebrowsing_concurrent_page.html \
 		browser_privatebrowsing_cookieacceptdialog.js \
 		browser_privatebrowsing_cookieacceptdialog.html \
 		browser_privatebrowsing_crh.js \
 		browser_privatebrowsing_downloadLastDir.js \
 		browser_privatebrowsing_downloadLastDir_c.js \
--- a/browser/components/safebrowsing/content/test/Makefile.in
+++ b/browser/components/safebrowsing/content/test/Makefile.in
@@ -3,15 +3,18 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MOCHITEST_BROWSER_FILES := \
   head.js \
   browser_bug400731.js \
   $(NULL)
 
-# The browser chrome test for bug 415846 doesn't run on Mac because of its
-# bizarre special-and-unique snowflake of a help menu.
-ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
-MOCHITEST_BROWSER_FILES += \
-  browser_bug415846.js \
-  $(NULL)
-endif
+
+# browser_bug415846.js disabled for too many intermittent failures (bug 546169)
+#
+# # The browser chrome test for bug 415846 doesn't run on Mac because of its
+# # bizarre special-and-unique snowflake of a help menu.
+# ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+# MOCHITEST_BROWSER_FILES += \
+#   browser_bug415846.js \
+#   $(NULL)
+# endif
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -19,17 +19,21 @@
   <binding id="searchbar">
     <resources>
       <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
       <stylesheet src="chrome://browser/skin/searchbar.css"/>
     </resources>
     <content>
       <xul:stringbundle src="chrome://browser/locale/search.properties"
                         anonid="searchbar-stringbundle"/>
-
+      <!--
+      There is a dependency between "maxrows" attribute and
+      "SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing
+      one of them requires changing the other one.
+      -->
       <xul:textbox class="searchbar-textbox"
                    anonid="searchbar-textbox"
                    type="autocomplete"
                    flex="1"
                    autocompletepopup="PopupAutoComplete"
                    autocompletesearch="search-autocomplete"
                    autocompletesearchparam="searchbar-history"
                    timeout="250"
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -1,18 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 function debug(msg) {
   Services.console.logStringMessage("SessionStoreContent: " + msg);
 }
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
+XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
+  "resource:///modules/sessionstore/SessionHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
+  "resource:///modules/sessionstore/SessionStorage.jsm");
+
 /**
  * Listens for and handles content events that we need for the
  * session store service to be notified of state changes in content.
  */
 let EventListener = {
 
   DOM_EVENTS: [
     "pageshow", "change", "input", "MozStorageChanged"
@@ -50,17 +57,47 @@ let EventListener = {
         break;
       }
       default:
         debug("received unknown event '" + event.type + "'");
         break;
     }
   }
 };
-EventListener.init();
+
+/**
+ * Listens for and handles messages sent by the session store service.
+ */
+let MessageListener = {
+
+  MESSAGES: [
+    "SessionStore:collectSessionHistory",
+    "SessionStore:collectSessionStorage"
+  ],
+
+  init: function () {
+    this.MESSAGES.forEach(m => addMessageListener(m, this));
+  },
+
+  receiveMessage: function ({name, data: {id}}) {
+    switch (name) {
+      case "SessionStore:collectSessionHistory":
+        let history = SessionHistory.read(docShell);
+        sendAsyncMessage(name, {id: id, data: history});
+        break;
+      case "SessionStore:collectSessionStorage":
+        let storage = SessionStorage.serialize(docShell);
+        sendAsyncMessage(name, {id: id, data: storage});
+        break;
+      default:
+        debug("received unknown message '" + name + "'");
+        break;
+    }
+  }
+};
 
 let ProgressListener = {
   init: function() {
     let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebProgress);
     webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
   },
   onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
@@ -69,9 +106,12 @@ let ProgressListener = {
   },
   onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {},
   onProgressChange: function() {},
   onStatusChange: function() {},
   onSecurityChange: function() {},
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference])
 };
+
+EventListener.init();
+MessageListener.init();
 ProgressListener.init();
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/src/Messenger.jsm
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Messenger"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+
+/**
+ * The external API exported by this module.
+ */
+this.Messenger = Object.freeze({
+  send: function (tab, type, options = {}) {
+    return MessengerInternal.send(tab, type, options);
+  }
+});
+
+/**
+ * A module that handles communication between the main and content processes.
+ */
+let MessengerInternal = {
+  // The id of the last message we sent. This is used to assign a unique ID to
+  // every message we send to handle multiple responses from the same browser.
+  _latestMessageID: 0,
+
+  /**
+   * Sends a message to the given tab and waits for a response.
+   *
+   * @param tab
+   *        tabbrowser tab
+   * @param type
+   *        {string} the type of the message
+   * @param options (optional)
+   *        {timeout: int} to set the timeout in milliseconds
+   * @return {Promise} A promise that will resolve to the response message or
+   *                   be reject when timing out.
+   */
+  send: function (tab, type, options = {}) {
+    let browser = tab.linkedBrowser;
+    let mm = browser.messageManager;
+    let deferred = Promise.defer();
+    let id = ++this._latestMessageID;
+    let timeout;
+
+    function onMessage({data: {id: mid, data}}) {
+      if (mid == id) {
+        mm.removeMessageListener(type, onMessage);
+        clearTimeout(timeout);
+        deferred.resolve(data);
+      }
+    }
+
+    mm.addMessageListener(type, onMessage);
+    mm.sendAsyncMessage(type, {id: id});
+
+    function onTimeout() {
+      mm.removeMessageListener(type, onMessage);
+      deferred.reject(new Error("Timed out while waiting for a " + type + " " +
+                                "response message."));
+    }
+
+    let delay = (options && options.timeout) || 5000;
+    timeout = setTimeout(onTimeout, delay);
+    return deferred.promise;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/src/PrivacyLevel.jsm
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PrivacyLevel"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_NORMAL = "browser.sessionstore.privacy_level";
+const PREF_DEFERRED = "browser.sessionstore.privacy_level_deferred";
+
+// The following constants represent the different possible privacy levels that
+// can be set by the user and that we need to consider when collecting text
+// data, cookies, and POSTDATA.
+//
+// Collect data from all sites (http and https).
+const PRIVACY_NONE = 0;
+// Collect data from unencrypted sites (http), only.
+const PRIVACY_ENCRYPTED = 1;
+// Collect no data.
+const PRIVACY_FULL = 2;
+
+/**
+ * Returns whether we will resume the session automatically on next startup.
+ */
+function willResumeAutomatically() {
+  return Services.prefs.getIntPref("browser.startup.page") == 3 ||
+         Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+}
+
+/**
+ * Determines the current privacy level as set by the user.
+ *
+ * @param isPinned
+ *        Whether to return the privacy level for pinned tabs.
+ * @return {int} The privacy level as read from the user's preferences.
+ */
+function getCurrentLevel(isPinned) {
+  let pref = PREF_NORMAL;
+
+  // If we're in the process of quitting and we're not autoresuming the session
+  // then we will use the deferred privacy level for non-pinned tabs.
+  if (!isPinned && Services.startup.shuttingDown && !willResumeAutomatically()) {
+    pref = PREF_DEFERRED;
+  }
+
+  return Services.prefs.getIntPref(pref);
+}
+
+/**
+ * The external API as exposed by this module.
+ */
+let PrivacyLevel = Object.freeze({
+  /**
+   * Checks whether we're allowed to save data for a specific site.
+   *
+   * @param {isHttps: boolean, isPinned: boolean}
+   *        An object that must have two properties: 'isHttps' and 'isPinned'.
+   *        'isHttps' tells whether the site us secure communication (HTTPS).
+   *        'isPinned' tells whether the site is loaded in a pinned tab.
+   * @return {bool} Whether we can save data for the specified site.
+   */
+  canSave: function ({isHttps, isPinned}) {
+    let level = getCurrentLevel(isPinned);
+
+    // Never save any data when full privacy is requested.
+    if (level == PRIVACY_FULL) {
+      return false;
+    }
+
+    // Don't save data for encrypted sites when requested.
+    if (isHttps && level == PRIVACY_ENCRYPTED) {
+      return false;
+    }
+
+    return true;
+  }
+});
--- a/browser/components/sessionstore/src/SessionCookies.jsm
+++ b/browser/components/sessionstore/src/SessionCookies.jsm
@@ -7,18 +7,18 @@
 this.EXPORTED_SYMBOLS = ["SessionCookies"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
-  "resource:///modules/sessionstore/SessionStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
+  "resource:///modules/sessionstore/PrivacyLevel.jsm");
 
 // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
 const MAX_EXPIRY = Math.pow(2, 62);
 
 // Creates a new nsIURI object.
 function makeURI(uri) {
   return Services.io.newURI(uri, null, null);
 }
@@ -62,18 +62,18 @@ let SessionCookiesInternal = {
 
       // Collect all hosts for the current window.
       let hosts = this.getHostsForWindow(window, true);
 
       for (let [host, isPinned] in Iterator(hosts)) {
         for (let cookie of CookieStore.getCookiesForHost(host)) {
           // _getCookiesForHost() will only return hosts with the right privacy
           // rules, so there is no need to do anything special with this call
-          // to checkPrivacyLevel().
-          if (SessionStore.checkPrivacyLevel(cookie.secure, isPinned)) {
+          // to PrivacyLevel.canSave().
+          if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
             cookies.push(cookie);
           }
         }
       }
 
       // Don't include/keep empty cookie sections.
       if (cookies.length) {
         window.cookies = cookies;
@@ -204,17 +204,17 @@ let SessionCookiesInternal = {
    *        checkPrivacy
    */
   _extractHostsFromHostScheme:
     function (host, scheme, hosts, checkPrivacy, isPinned) {
     // host and scheme may not be set (for about: urls for example), in which
     // case testing scheme will be sufficient.
     if (/https?/.test(scheme) && !hosts[host] &&
         (!checkPrivacy ||
-         SessionStore.checkPrivacyLevel(scheme == "https", isPinned))) {
+         PrivacyLevel.canSave({isHttps: scheme == "https", isPinned: isPinned}))) {
       // By setting this to true or false, we can determine when looking at
       // the host in update() if we should check for privacy.
       hosts[host] = isPinned;
     } else if (scheme == "file") {
       hosts[host] = true;
     }
   },
 
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/src/SessionHistory.jsm
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SessionHistory"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
+  "resource:///modules/sessionstore/PrivacyLevel.jsm");
+
+function debug(msg) {
+  Services.console.logStringMessage("SessionHistory: " + msg);
+}
+
+// The preference value that determines how much post data to save.
+XPCOMUtils.defineLazyGetter(this, "gPostData", function () {
+  const PREF = "browser.sessionstore.postdata";
+
+  // Observer that updates the cached value when the preference changes.
+  Services.prefs.addObserver(PREF, () => {
+    this.gPostData = Services.prefs.getIntPref(PREF);
+  }, false);
+
+  return Services.prefs.getIntPref(PREF);
+});
+
+/**
+ * The external API exported by this module.
+ */
+this.SessionHistory = Object.freeze({
+  read: function (docShell, includePrivateData) {
+    return SessionHistoryInternal.read(docShell, includePrivateData);
+  }
+});
+
+/**
+ * The internal API for the SessionHistory module.
+ */
+let SessionHistoryInternal = {
+  /**
+   * Collects session history data for a given docShell.
+   *
+   * @param docShell
+   *        The docShell that owns the session history.
+   * @param includePrivateData (optional)
+   *        True to always include private data and skip any privacy checks.
+   */
+  read: function (docShell, includePrivateData = false) {
+    let data = {entries: []};
+    let isPinned = docShell.isAppTab;
+    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+    let history = webNavigation.sessionHistory;
+
+    if (history && history.count > 0) {
+      try {
+        for (let i = 0; i < history.count; i++) {
+          let shEntry = history.getEntryAtIndex(i, false);
+          let entry = this._serializeEntry(shEntry, includePrivateData, isPinned);
+          data.entries.push(entry);
+        }
+      } catch (ex) {
+        // In some cases, getEntryAtIndex will throw. This seems to be due to
+        // history.count being higher than it should be. By doing this in a
+        // try-catch, we'll update history to where it breaks, print an error
+        // message, and still save sessionstore.js.
+        debug("SessionStore failed gathering complete history " +
+              "for the focused window/tab. See bug 669196.");
+      }
+      data.index = history.index + 1;
+    } else {
+      let uri = webNavigation.currentURI.spec;
+      // We landed here because the history is inaccessible or there are no
+      // history entries. In that case we should at least record the docShell's
+      // current URL as a single history entry. If the URL is not about:blank
+      // or it's a blank tab that was modified (like a custom newtab page),
+      // record it. For about:blank we explicitly want an empty array without
+      // an 'index' property to denote that there are no history entries.
+      if (uri != "about:blank" || webNavigation.document.body.hasChildNodes()) {
+        data.entries.push({ url: uri });
+        data.index = 1;
+      }
+    }
+
+    return data;
+  },
+
+  /**
+   * Get an object that is a serialized representation of a History entry.
+   *
+   * @param shEntry
+   *        nsISHEntry instance
+   * @param includePrivateData
+   *        Always return privacy sensitive data (use with care).
+   * @param isPinned
+   *        The tab is pinned and should be treated differently for privacy.
+   * @return object
+   */
+  _serializeEntry: function (shEntry, includePrivateData, isPinned) {
+    let entry = { url: shEntry.URI.spec };
+
+    // Save some bytes and don't include the title property
+    // if that's identical to the current entry's URL.
+    if (shEntry.title && shEntry.title != entry.url) {
+      entry.title = shEntry.title;
+    }
+    if (shEntry.isSubFrame) {
+      entry.subframe = true;
+    }
+
+    let cacheKey = shEntry.cacheKey;
+    if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
+        cacheKey.data != 0) {
+      // XXXbz would be better to have cache keys implement
+      // nsISerializable or something.
+      entry.cacheKey = cacheKey.data;
+    }
+    entry.ID = shEntry.ID;
+    entry.docshellID = shEntry.docshellID;
+
+    // We will include the property only if it's truthy to save a couple of
+    // bytes when the resulting object is stringified and saved to disk.
+    if (shEntry.referrerURI)
+      entry.referrer = shEntry.referrerURI.spec;
+
+    if (shEntry.srcdocData)
+      entry.srcdocData = shEntry.srcdocData;
+
+    if (shEntry.isSrcdocEntry)
+      entry.isSrcdocEntry = shEntry.isSrcdocEntry;
+
+    if (shEntry.contentType)
+      entry.contentType = shEntry.contentType;
+
+    let x = {}, y = {};
+    shEntry.getScrollPosition(x, y);
+    if (x.value != 0 || y.value != 0)
+      entry.scroll = x.value + "," + y.value;
+
+    // Collect post data for the current history entry.
+    try {
+      let postdata = this._serializePostData(shEntry, isPinned);
+      if (postdata) {
+        entry.postdata_b64 = postdata;
+      }
+    } catch (ex) {
+      // POSTDATA is tricky - especially since some extensions don't get it right
+      debug("Failed serializing post data: " + ex);
+    }
+
+    // Collect owner data for the current history entry.
+    try {
+      let owner = this._serializeOwner(shEntry);
+      if (owner) {
+        entry.owner_b64 = owner;
+      }
+    } catch (ex) {
+      // Not catching anything specific here, just possible errors
+      // from writeCompoundObject() and the like.
+      debug("Failed serializing owner data: " + ex);
+    }
+
+    entry.docIdentifier = shEntry.BFCacheEntry.ID;
+
+    if (shEntry.stateData != null) {
+      entry.structuredCloneState = shEntry.stateData.getDataAsBase64();
+      entry.structuredCloneVersion = shEntry.stateData.formatVersion;
+    }
+
+    if (!(shEntry instanceof Ci.nsISHContainer)) {
+      return entry;
+    }
+
+    if (shEntry.childCount > 0) {
+      let children = [];
+      for (let i = 0; i < shEntry.childCount; i++) {
+        let child = shEntry.GetChildAt(i);
+
+        if (child) {
+          // Don't try to restore framesets containing wyciwyg URLs.
+          // (cf. bug 424689 and bug 450595)
+          if (child.URI.schemeIs("wyciwyg")) {
+            children.length = 0;
+            break;
+          }
+
+          children.push(this._serializeEntry(child, includePrivateData, isPinned));
+        }
+      }
+
+      if (children.length) {
+        entry.children = children;
+      }
+    }
+
+    return entry;
+  },
+
+  /**
+   * Serialize post data contained in the given session history entry.
+   *
+   * @param shEntry
+   *        The session history entry.
+   * @param isPinned
+   *        Whether the docShell is owned by a pinned tab.
+   * @return The base64 encoded post data.
+   */
+  _serializePostData: function (shEntry, isPinned) {
+    let isHttps = shEntry.URI.schemeIs("https");
+    if (!shEntry.postData || !gPostData ||
+        !PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
+      return null;
+    }
+
+    shEntry.postData.QueryInterface(Ci.nsISeekableStream)
+                    .seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+    let stream = Cc["@mozilla.org/binaryinputstream;1"]
+                   .createInstance(Ci.nsIBinaryInputStream);
+    stream.setInputStream(shEntry.postData);
+    let postBytes = stream.readByteArray(stream.available());
+    let postdata = String.fromCharCode.apply(null, postBytes);
+    if (gPostData != -1 &&
+        postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length > gPostData) {
+      return null;
+    }
+
+    // We can stop doing base64 encoding once our serialization into JSON
+    // is guaranteed to handle all chars in strings, including embedded
+    // nulls.
+    return btoa(postdata);
+  },
+
+  /**
+   * Serialize owner data contained in the given session history entry.
+   *
+   * @param shEntry
+   *        The session history entry.
+   * @return The base64 encoded owner data.
+   */
+  _serializeOwner: function (shEntry) {
+    if (!shEntry.owner) {
+      return null;
+    }
+
+    let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
+                       createInstance(Ci.nsIObjectOutputStream);
+    let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+    pipe.init(false, false, 0, 0xffffffff, null);
+    binaryStream.setOutputStream(pipe.outputStream);
+    binaryStream.writeCompoundObject(shEntry.owner, Ci.nsISupports, true);
+    binaryStream.close();
+
+    // Now we want to read the data from the pipe's input end and encode it.
+    let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
+                           createInstance(Ci.nsIBinaryInputStream);
+    scriptableStream.setInputStream(pipe.inputStream);
+    let ownerBytes =
+      scriptableStream.readByteArray(scriptableStream.available());
+
+    // We can stop doing base64 encoding once our serialization into JSON
+    // is guaranteed to handle all chars in strings, including embedded
+    // nulls.
+    return btoa(String.fromCharCode.apply(null, ownerBytes));
+  }
+};
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -146,17 +146,17 @@ let SessionSaverInternal = {
     if (this._timeoutID) {
       return;
     }
 
     // Interval until the next disk operation is allowed.
     delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0);
 
     // Schedule a state save.
-    this._timeoutID = setTimeout(() => this._saveState(), delay);
+    this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
   },
 
   /**
    * Sets the last save time to the current time. This will cause us to wait for
    * at least the configured interval when runDelayed() is called next.
    */
   updateLastSaveTime: function () {
     this._lastSaveTime = Date.now();
@@ -181,18 +181,17 @@ let SessionSaverInternal = {
   /**
    * Saves the current session state. Collects data and writes to disk.
    *
    * @param forceUpdateAllWindows (optional)
    *        Forces us to recollect data for all windows and will bypass and
    *        update the corresponding caches.
    */
   _saveState: function (forceUpdateAllWindows = false) {
-    // Cancel any pending timeouts or just clear
-    // the timeout if this is why we've been called.
+    // Cancel any pending timeouts.
     this.cancel();
 
     stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
 
     let state = SessionStore.getCurrentState(forceUpdateAllWindows);
     if (!state) {
       stopWatchCancel("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
       return;
@@ -242,16 +241,43 @@ let SessionSaverInternal = {
     }
 #endif
 
     stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
     this._writeState(state);
   },
 
   /**
+   * Saves the current session state. Collects data asynchronously and calls
+   * _saveState() to collect data again (with a cache hit rate of hopefully
+   * 100%) and write to disk afterwards.
+   */
+  _saveStateAsync: function () {
+    // Allow scheduling delayed saves again.
+    this._timeoutID = null;
+
+    // Check whether asynchronous data collection is disabled.
+    if (!Services.prefs.getBoolPref("browser.sessionstore.async")) {
+      this._saveState();
+      return;
+    }
+
+    // Update the last save time to make sure we wait at least another interval
+    // length until we call _saveStateAsync() again.
+    this.updateLastSaveTime();
+
+    // Save state synchronously after all tab caches have been filled. The data
+    // for the tab caches is collected asynchronously. We will reuse this
+    // cached data if the tab hasn't been invalidated in the meantime. In that
+    // case we will just fall back to synchronous data collection for single
+    // tabs.
+    SessionStore.fillTabCachesAsynchronously().then(() => this._saveState());
+  },
+
+  /**
    * Write the given state object to disk.
    */
   _writeState: function (state) {
     stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
     let data = JSON.stringify(state);
     stopWatchFinish("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
 
     // Give observers a chance to modify session data.
--- a/browser/components/sessionstore/src/SessionStorage.jsm
+++ b/browser/components/sessionstore/src/SessionStorage.jsm
@@ -2,22 +2,23 @@
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["SessionStorage"];
 
 const Cu = Components.utils;
+const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
-  "resource:///modules/sessionstore/SessionStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
+  "resource:///modules/sessionstore/PrivacyLevel.jsm");
 
 this.SessionStorage = {
   /**
    * Updates all sessionStorage "super cookies"
    * @param aDocShell
    *        That tab's docshell (containing the sessionStorage)
    * @param aFullData
    *        always return privacy sensitive data (use with care)
@@ -46,26 +47,27 @@ let DomStorage = {
    * @param aDocShell
    *        A tab's docshell (containing the sessionStorage)
    * @param aFullData
    *        Always return privacy sensitive data (use with care)
    */
   read: function DomStorage_read(aDocShell, aFullData) {
     let data = {};
     let isPinned = aDocShell.isAppTab;
-    let shistory = aDocShell.sessionHistory;
+    let webNavigation = aDocShell.QueryInterface(Ci.nsIWebNavigation);
+    let shistory = webNavigation.sessionHistory;
 
-    for (let i = 0; i < shistory.count; i++) {
+    for (let i = 0; shistory && i < shistory.count; i++) {
       let principal = History.getPrincipalForEntry(shistory, i, aDocShell);
       if (!principal)
         continue;
 
       // Check if we're allowed to store sessionStorage data.
-      let isHTTPS = principal.URI && principal.URI.schemeIs("https");
-      if (aFullData || SessionStore.checkPrivacyLevel(isHTTPS, isPinned)) {
+      let isHttps = principal.URI && principal.URI.schemeIs("https");
+      if (aFullData || PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
         let origin = principal.jarPrefix + principal.origin;
 
         // Don't read a host twice.
         if (!(origin in data)) {
           let originData = this._readEntry(principal, aDocShell);
           if (Object.keys(originData).length) {
             data[origin] = originData;
           }
@@ -85,18 +87,18 @@ let DomStorage = {
    */
   write: function DomStorage_write(aDocShell, aStorageData) {
     for (let [host, data] in Iterator(aStorageData)) {
       let uri = Services.io.newURI(host, null, null);
       let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
       let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
 
       // There is no need to pass documentURI, it's only used to fill documentURI property of
-			// domstorage event, which in this case has no consumer.  Prevention of events in case
-			// of missing documentURI will be solved in a followup bug to bug 600307.
+      // domstorage event, which in this case has no consumer. Prevention of events in case
+      // of missing documentURI will be solved in a followup bug to bug 600307.
       let storage = storageManager.createStorage(principal, "", aDocShell.usePrivateBrowsing);
 
       for (let [key, value] in Iterator(data)) {
         try {
           storage.setItem(key, value);
         } catch (e) {
           // throws e.g. for URIs that can't have sessionStorage
           Cu.reportError(e);
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -16,20 +16,16 @@ const STATE_RUNNING = 1;
 const STATE_QUITTING = -1;
 
 const STATE_STOPPED_STR = "stopped";
 const STATE_RUNNING_STR = "running";
 
 const TAB_STATE_NEEDS_RESTORE = 1;
 const TAB_STATE_RESTORING = 2;
 
-const PRIVACY_NONE = 0;
-const PRIVACY_ENCRYPTED = 1;
-const PRIVACY_FULL = 2;
-
 const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
 const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
 
 // Maximum number of tabs to restore simultaneously. Previously controlled by
 // the browser.sessionstore.max_concurrent_tabs pref.
 const MAX_CONCURRENT_TAB_RESTORES = 3;
 
 // global notifications observed
@@ -81,18 +77,16 @@ const TAB_EVENTS = [
 const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
 
 #ifndef XP_WIN
 #define BROKEN_WM_Z_ORDER
 #endif
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-// debug.js adds NS_ASSERT. cf. bug 669196
-Cu.import("resource://gre/modules/debug.js", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
@@ -112,26 +106,41 @@ let gDocShellCapabilities = (function ()
       let keys = Object.keys(docShell);
       caps = keys.filter(k => k.startsWith("allow")).map(k => k.slice(5));
     }
 
     return caps;
   };
 })();
 
+/**
+ * Get nsIURI from string
+ * @param string
+ * @returns nsIURI
+ */
+function makeURI(aString) {
+  return Services.io.newURI(aString, null, null);
+}
+
 XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   "resource:///modules/devtools/scratchpad-manager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
   "resource:///modules/sessionstore/DocumentUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
+  "resource:///modules/sessionstore/Messenger.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
+  "resource:///modules/sessionstore/PrivacyLevel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
   "resource:///modules/sessionstore/SessionSaver.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   "resource:///modules/sessionstore/SessionCookies.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
+  "resource:///modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
   "resource:///modules/sessionstore/_SessionFile.jsm");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
@@ -259,24 +268,24 @@ this.SessionStore = {
   persistTabAttribute: function ss_persistTabAttribute(aName) {
     SessionStoreInternal.persistTabAttribute(aName);
   },
 
   restoreLastSession: function ss_restoreLastSession() {
     SessionStoreInternal.restoreLastSession();
   },
 
-  checkPrivacyLevel: function ss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
-    return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref);
-  },
-
   getCurrentState: function (aUpdateAll) {
     return SessionStoreInternal.getCurrentState(aUpdateAll);
   },
 
+  fillTabCachesAsynchronously: function () {
+    return SessionStoreInternal.fillTabCachesAsynchronously();
+  },
+
   /**
    * Backstage pass to implementation details, used for testing purpose.
    * Controlled by preference "browser.sessionstore.testmode".
    */
   get _internal() {
     if (Services.prefs.getBoolPref("browser.sessionstore.debug")) {
       return SessionStoreInternal;
     }
@@ -387,20 +396,16 @@ let SessionStoreInternal = {
     TelemetryTimestamps.add("sessionRestoreInitialized");
     OBSERVING.forEach(function(aTopic) {
       Services.obs.addObserver(this, aTopic, true);
     }, this);
 
     this._initPrefs();
     this._initialized = true;
 
-    // this pref is only read at startup, so no need to observe it
-    this._sessionhistory_max_entries =
-      this._prefBranch.getIntPref("sessionhistory.max_entries");
-
     // Wait until nsISessionStartup has finished reading the session data.
     gSessionStartup.onceInitialized.then(() => {
       // Parse session data and start restoring.
       let initialState = this.initSession();
 
       // Start tracking the given (initial) browser window.
       if (!aWindow.closed) {
         this.onLoad(aWindow, initialState);
@@ -1074,18 +1079,19 @@ let SessionStoreInternal = {
    * On purge of domain data
    * @param aData
    *        String domain data
    */
   onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
     // does a session history entry contain a url for the given domain?
     function containsDomain(aEntry) {
       try {
-        if (this._getURIFromString(aEntry.url).host.hasRootDomain(aData))
+        if (makeURI(aEntry.url).host.hasRootDomain(aData)) {
           return true;
+        }
       }
       catch (ex) { /* url had no host at all */ }
       return aEntry.children && aEntry.children.some(containsDomain, this);
     }
     // remove all closed tabs containing a reference to the given domain
     for (let ix in this._windows) {
       let closedTabs = this._windows[ix]._closedTabs;
       for (let i = closedTabs.length - 1; i >= 0; i--) {
@@ -1224,17 +1230,17 @@ let SessionStoreInternal = {
     aTab.dispatchEvent(event);
 
     // don't update our internal state if we don't have to
     if (this._max_tabs_undo == 0) {
       return;
     }
 
     // Get the latest data for this tab (generally, from the cache)
-    let tabState = this._collectTabData(aTab);
+    let tabState = TabState.collectSync(aTab);
 
     // store closed-tab data for undo
     if (this._shouldSaveTabState(tabState)) {
       let tabTitle = aTab.label;
       let tabbrowser = aWindow.gBrowser;
       tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
 
       this._windows[aWindow.__SSi]._closedTabs.unshift({
@@ -1415,17 +1421,17 @@ let SessionStoreInternal = {
 
     this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
   },
 
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
-    let tabState = this._collectTabData(aTab);
+    let tabState = TabState.collectSync(aTab);
 
     return this._toJSONString(tabState);
   },
 
   setTabState: function ssi_setTabState(aTab, aState) {
     // Remove the tab state from the cache.
     // Note that we cannot simply replace the contents of the cache
     // as |aState| can be an incomplete state that will be completed
@@ -1460,17 +1466,17 @@ let SessionStoreInternal = {
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
         !aWindow.getBrowser)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     // Duplicate the tab state
-    let tabState = this._cloneFullTabData(aTab);
+    let tabState = TabState.clone(aTab);
 
     tabState.index += aDelta;
     tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
     tabState.pinned = false;
 
     this._setWindowStateBusy(aWindow);
     let newTab = aTab == aWindow.gBrowser.selectedTab ?
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
@@ -1876,457 +1882,79 @@ let SessionStoreInternal = {
       for (let i = removableTabs.length - 1; i >= 0; i--) {
         tabbrowser.removeTab(removableTabs.pop(), { animate: false });
       }
     }
 
     return [true, canOverwriteTabs];
   },
 
-  /* ........ Saving Functionality .............. */
-
-  /**
-   * Collect data related to a single tab
-   *
-   * @param aTab
-   *        tabbrowser tab
-   *
-   * @returns {TabData} An object with the data for this tab.  If the
-   * tab has not been invalidated since the last call to
-   * _collectTabData(aTab), the same object is returned.
-   */
-  _collectTabData: function ssi_collectTabData(aTab) {
-    if (!aTab) {
-      throw new TypeError("Expecting a tab");
-    }
-    let tabData;
-    if ((tabData = TabStateCache.get(aTab))) {
-      return tabData;
-    }
-    tabData = new TabData(this._collectBaseTabData(aTab));
-    if (this._updateTextAndScrollDataForTab(aTab, tabData)) {
-      TabStateCache.set(aTab, tabData);
-    }
-    return tabData;
-  },
-
-  /**
-   * Collect data related to a single tab, including private data.
-   * Use with caution.
-   *
-   * @param aTab
-   *        tabbrowser tab
-   *
-   * @returns {object} An object with the data for this tab. This object
-   * is recomputed at every call.
-   */
-  _cloneFullTabData: function ssi_cloneFullTabData(aTab) {
-    let options = { includePrivateData: true };
-    let tabData = this._collectBaseTabData(aTab, options);
-    this._updateTextAndScrollDataForTab(aTab, tabData, options);
-    return tabData;
-  },
-
-  _collectBaseTabData: function ssi_collectBaseTabData(aTab, aOptions = null) {
-    let includePrivateData = aOptions && aOptions.includePrivateData;
-    let tabData = {entries: [], lastAccessed: aTab.lastAccessed };
-    let browser = aTab.linkedBrowser;
-    if (!browser || !browser.currentURI) {
-      // can happen when calling this function right after .addTab()
-      return tabData;
-    }
-    if (browser.__SS_data && browser.__SS_tabStillLoading) {
-      // use the data to be restored when the tab hasn't been completely loaded
-      tabData = browser.__SS_data;
-      if (aTab.pinned)
-        tabData.pinned = true;
-      else
-        delete tabData.pinned;
-      tabData.hidden = aTab.hidden;
-
-      // If __SS_extdata is set then we'll use that since it might be newer.
-      if (aTab.__SS_extdata)
-        tabData.extData = aTab.__SS_extdata;
-      // If it exists but is empty then a key was likely deleted. In that case just
-      // delete extData.
-      if (tabData.extData && !Object.keys(tabData.extData).length)
-        delete tabData.extData;
-      return tabData;
-    }
-
-    var history = null;
-    try {
-      history = browser.sessionHistory;
-    }
-    catch (ex) { } // this could happen if we catch a tab during (de)initialization
-
-    // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
-    //           data even when we shouldn't (e.g. Back, different anchor)
-    if (history && browser.__SS_data &&
-        browser.__SS_data.entries[history.index] &&
-        browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
-        history.index < this._sessionhistory_max_entries - 1 && !includePrivateData) {
-      tabData = browser.__SS_data;
-      tabData.index = history.index + 1;
-    }
-    else if (history && history.count > 0) {
-      try {
-        for (var j = 0; j < history.count; j++) {
-          let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
-                                                  includePrivateData, aTab.pinned);
-          tabData.entries.push(entry);
-        }
-        // If we make it through the for loop, then we're ok and we should clear
-        // any indicator of brokenness.
-        delete aTab.__SS_broken_history;
-      }
-      catch (ex) {
-        // In some cases, getEntryAtIndex will throw. This seems to be due to
-        // history.count being higher than it should be. By doing this in a
-        // try-catch, we'll update history to where it breaks, assert for
-        // non-release builds, and still save sessionstore.js. We'll track if
-        // we've shown the assert for this tab so we only show it once.
-        // cf. bug 669196.
-        if (!aTab.__SS_broken_history) {
-          // First Focus the window & tab we're having trouble with.
-          aTab.ownerDocument.defaultView.focus();
-          aTab.ownerDocument.defaultView.gBrowser.selectedTab = aTab;
-          NS_ASSERT(false, "SessionStore failed gathering complete history " +
-                           "for the focused window/tab. See bug 669196.");
-          aTab.__SS_broken_history = true;
-        }
-      }
-      tabData.index = history.index + 1;
-
-      // make sure not to cache privacy sensitive data which shouldn't get out
-      if (!includePrivateData)
-        browser.__SS_data = tabData;
-    }
-    else if (browser.currentURI.spec != "about:blank" ||
-             browser.contentDocument.body.hasChildNodes()) {
-      tabData.entries[0] = { url: browser.currentURI.spec };
-      tabData.index = 1;
-    }
-
-    // If there is a userTypedValue set, then either the user has typed something
-    // in the URL bar, or a new tab was opened with a URI to load. userTypedClear
-    // is used to indicate whether the tab was in some sort of loading state with
-    // userTypedValue.
-    if (browser.userTypedValue) {
-      tabData.userTypedValue = browser.userTypedValue;
-      tabData.userTypedClear = browser.userTypedClear;
-    } else {
-      delete tabData.userTypedValue;
-      delete tabData.userTypedClear;
-    }
-
-    if (aTab.pinned)
-      tabData.pinned = true;
-    else
-      delete tabData.pinned;
-    tabData.hidden = aTab.hidden;
-
-    var disallow = [];
-    for (let cap of gDocShellCapabilities(browser.docShell))
-      if (!browser.docShell["allow" + cap])
-        disallow.push(cap);
-    if (disallow.length > 0)
-      tabData.disallow = disallow.join(",");
-    else if (tabData.disallow)
-      delete tabData.disallow;
-
-    // Save tab attributes.
-    tabData.attributes = TabAttributes.get(aTab);
-
-    // Store the tab icon.
-    let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
-    tabData.image = tabbrowser.getIcon(aTab);
-
-    if (aTab.__SS_extdata)
-      tabData.extData = aTab.__SS_extdata;
-    else if (tabData.extData)
-      delete tabData.extData;
-
-    if (history && browser.docShell instanceof Ci.nsIDocShell) {
-      let storageData = SessionStorage.serialize(browser.docShell, includePrivateData)
-      if (Object.keys(storageData).length)
-        tabData.storage = storageData;
-    }
-
-    return tabData;
-  },
+  /* ........ Async Data Collection .............. */
 
   /**
-   * Get an object that is a serialized representation of a History entry
-   * Used for data storage
-   * @param aEntry
-   *        nsISHEntry instance
-   * @param aIncludePrivateData
-   *        always return privacy sensitive data (use with care)
-   * @param aIsPinned
-   *        the tab is pinned and should be treated differently for privacy
-   * @returns object
+   * Kicks off asynchronous data collection for all tabs that do not have any
+   * cached data. The returned promise will only notify that the tab collection
+   * has been finished without resolving to any data. The tab collection for a
+   * a few or all tabs might have failed or timed out. By calling
+   * fillTabCachesAsynchronously() and waiting for the promise to be resolved
+   * before calling getCurrentState(), callers ensure that most of the data
+   * should have been collected asynchronously, without blocking the main
+   * thread.
+   *
+   * @return {Promise} the promise that is fulfilled when the tab data is ready
    */
-  _serializeHistoryEntry:
-    function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned) {
-    var entry = { url: aEntry.URI.spec };
-
-    if (aEntry.title && aEntry.title != entry.url) {
-      entry.title = aEntry.title;
-    }
-    if (aEntry.isSubFrame) {
-      entry.subframe = true;
-    }
-    if (!(aEntry instanceof Ci.nsISHEntry)) {
-      return entry;
-    }
-
-    var cacheKey = aEntry.cacheKey;
-    if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
-        cacheKey.data != 0) {
-      // XXXbz would be better to have cache keys implement
-      // nsISerializable or something.
-      entry.cacheKey = cacheKey.data;
-    }
-    entry.ID = aEntry.ID;
-    entry.docshellID = aEntry.docshellID;
-
-    if (aEntry.referrerURI)
-      entry.referrer = aEntry.referrerURI.spec;
-
-    if (aEntry.srcdocData)
-      entry.srcdocData = aEntry.srcdocData;
-
-    if (aEntry.isSrcdocEntry)
-      entry.isSrcdocEntry = aEntry.isSrcdocEntry;
-
-    if (aEntry.contentType)
-      entry.contentType = aEntry.contentType;
-
-    var x = {}, y = {};
-    aEntry.getScrollPosition(x, y);
-    if (x.value != 0 || y.value != 0)
-      entry.scroll = x.value + "," + y.value;
-
-    try {
-      var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
-      if (aEntry.postData && (aIncludePrivateData || prefPostdata &&
-            this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
-        aEntry.postData.QueryInterface(Ci.nsISeekableStream).
-                        seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
-        var stream = Cc["@mozilla.org/binaryinputstream;1"].
-                     createInstance(Ci.nsIBinaryInputStream);
-        stream.setInputStream(aEntry.postData);
-        var postBytes = stream.readByteArray(stream.available());
-        var postdata = String.fromCharCode.apply(null, postBytes);
-        if (aIncludePrivateData || prefPostdata == -1 ||
-            postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
-              prefPostdata) {
-          // We can stop doing base64 encoding once our serialization into JSON
-          // is guaranteed to handle all chars in strings, including embedded
-          // nulls.
-          entry.postdata_b64 = btoa(postdata);
-        }
+  fillTabCachesAsynchronously: function () {
+    let countdown = 0;
+    let deferred = Promise.defer();
+    let activeWindow = this._getMostRecentBrowserWindow();
+
+    // The callback that will be called when a promise has been resolved
+    // successfully, i.e. the tab data has been collected.
+    function done() {
+      if (--countdown === 0) {
+        deferred.resolve();
       }
     }
-    catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
-
-    if (aEntry.owner) {
-      // Not catching anything specific here, just possible errors
-      // from writeCompoundObject and the like.
-      try {
-        var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
-                           createInstance(Ci.nsIObjectOutputStream);
-        var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
-        pipe.init(false, false, 0, 0xffffffff, null);
-        binaryStream.setOutputStream(pipe.outputStream);
-        binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
-        binaryStream.close();
-
-        // Now we want to read the data from the pipe's input end and encode it.
-        var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
-                               createInstance(Ci.nsIBinaryInputStream);
-        scriptableStream.setInputStream(pipe.inputStream);
-        var ownerBytes =
-          scriptableStream.readByteArray(scriptableStream.available());
-        // We can stop doing base64 encoding once our serialization into JSON
-        // is guaranteed to handle all chars in strings, including embedded
-        // nulls.
-        entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
+
+    // The callback that will be called when a promise is rejected, i.e. we
+    // we couldn't collect the tab data because of a script error or a timeout.
+    function fail(reason) {
+      debug("Failed collecting tab data asynchronously: " + reason);
+      done();
+    }
+
+    this._forEachBrowserWindow(win => {
+      if (!this._isWindowLoaded(win)) {
+        // Bail out if the window hasn't even loaded, yet.
+        return;
       }
-      catch (ex) { debug(ex); }
-    }
-
-    entry.docIdentifier = aEntry.BFCacheEntry.ID;
-
-    if (aEntry.stateData != null) {
-      entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
-      entry.structuredCloneVersion = aEntry.stateData.formatVersion;
-    }
-
-    if (!(aEntry instanceof Ci.nsISHContainer)) {
-      return entry;
-    }
-
-    if (aEntry.childCount > 0) {
-      let children = [];
-      for (var i = 0; i < aEntry.childCount; i++) {
-        var child = aEntry.GetChildAt(i);
-
-        if (child) {
-          // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
-          if (child.URI.schemeIs("wyciwyg")) {
-            children = [];
-            break;
-          }
-
-          children.push(this._serializeHistoryEntry(child, aIncludePrivateData,
-                                                    aIsPinned));
+
+      if (!DirtyWindows.has(win) && win != activeWindow) {
+        // Bail out if the window is not dirty and inactive.
+        return;
+      }
+
+      for (let tab of win.gBrowser.tabs) {
+        if (!TabStateCache.has(tab)) {
+          countdown++;
+          TabState.collect(tab).then(done, fail);
         }
       }
-
-      if (children.length)
-        entry.children = children;
-    }
-
-    return entry;
-  },
-
-  /**
-   * Go through all frames and store the current scroll positions
-   * and innerHTML content of WYSIWYG editors
-   *
-   * @param aTab
-   *        tabbrowser tab
-   * @param aTabData
-   *        tabData object to add the information to
-   * @param options
-   *        An optional object that may contain the following field:
-   *        - includePrivateData: always return privacy sensitive data
-   *          (use with care)
-   * @return false if data should not be cached because the tab
-   *        has not been fully initialized yet.
-   */
-  _updateTextAndScrollDataForTab:
-    function ssi_updateTextAndScrollDataForTab(aTab, aTabData, aOptions = null) {
-    let includePrivateData = aOptions && aOptions.includePrivateData;
-    let window = aTab.ownerDocument.defaultView;
-    let browser = aTab.linkedBrowser;
-    // we shouldn't update data for incompletely initialized tabs
-    if (!browser.currentURI
-        || (browser.__SS_data && browser.__SS_tabStillLoading)) {
-      return false;
-    }
-
-    let tabIndex = (aTabData.index || aTabData.entries.length) - 1;
-    // entry data needn't exist for tabs just initialized with an incomplete session state
-    if (!aTabData.entries[tabIndex]) {
-      return false;
-    }
-
-    let selectedPageStyle = browser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
-                            this._getSelectedPageStyle(browser.contentWindow);
-    if (selectedPageStyle)
-      aTabData.pageStyle = selectedPageStyle;
-    else if (aTabData.pageStyle)
-      delete aTabData.pageStyle;
-
-    this._updateTextAndScrollDataForFrame(window, browser.contentWindow,
-                                          aTabData.entries[tabIndex],
-                                          includePrivateData,
-                                          !!aTabData.pinned);
-    if (browser.currentURI.spec == "about:config")
-      aTabData.entries[tabIndex].formdata = {
-        id: {
-          "textbox": browser.contentDocument.getElementById("textbox").value
-        },
-        xpath: {}
-      };
-      return true;
+    });
+
+    // If no dirty tabs were found, return a resolved
+    // promise because there is nothing to do here.
+    if (countdown == 0) {
+      return Promise.resolve();
+    }
+
+    return deferred.promise;
   },
 
-  /**
-   * go through all subframes and store all form data, the current
-   * scroll positions and innerHTML content of WYSIWYG editors
-   * @param aWindow
-   *        Window reference
-   * @param aContent
-   *        frame reference
-   * @param aData
-   *        part of a tabData object to add the information to
-   * @param aIncludePrivateData
-   *        always return privacy sensitive data (use with care)
-   * @param aIsPinned
-   *        the tab is pinned and should be treated differently for privacy
-   */
-  _updateTextAndScrollDataForFrame:
-    function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
-                                                 aIncludePrivateData, aIsPinned) {
-    for (var i = 0; i < aContent.frames.length; i++) {
-      if (aData.children && aData.children[i])
-        this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
-                                              aData.children[i],
-                                              aIncludePrivateData, aIsPinned);
-    }
-    var isHTTPS = this._getURIFromString((aContent.parent || aContent).
-                                         document.location.href).schemeIs("https");
-    let topURL = aContent.top.document.location.href;
-    let isAboutSR = topURL == "about:sessionrestore" || topURL == "about:welcomeback";
-    if (aIncludePrivateData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
-      let formData = DocumentUtils.getFormData(aContent.document);
-
-      // We want to avoid saving data for about:sessionrestore as a string.
-      // Since it's stored in the form as stringified JSON, stringifying further
-      // causes an explosion of escape characters. cf. bug 467409
-      if (formData && isAboutSR) {
-        formData.id["sessionData"] = JSON.parse(formData.id["sessionData"]);
-      }
-
-      if (Object.keys(formData.id).length ||
-          Object.keys(formData.xpath).length) {
-        aData.formdata = formData;
-      } else if (aData.formdata) {
-        delete aData.formdata;
-      }
-
-      // designMode is undefined e.g. for XUL documents (as about:config)
-      if ((aContent.document.designMode || "") == "on" && aContent.document.body)
-        aData.innerHTML = aContent.document.body.innerHTML;
-    }
-
-    // get scroll position from nsIDOMWindowUtils, since it allows avoiding a
-    // flush of layout
-    let domWindowUtils = aContent.QueryInterface(Ci.nsIInterfaceRequestor)
-                                 .getInterface(Ci.nsIDOMWindowUtils);
-    let scrollX = {}, scrollY = {};
-    domWindowUtils.getScrollXY(false, scrollX, scrollY);
-    aData.scroll = scrollX.value + "," + scrollY.value;
-  },
-
-  /**
-   * determine the title of the currently enabled style sheet (if any)
-   * and recurse through the frameset if necessary
-   * @param   aContent is a frame reference
-   * @returns the title style sheet determined to be enabled (empty string if none)
-   */
-  _getSelectedPageStyle: function ssi_getSelectedPageStyle(aContent) {
-    const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i;
-    for (let i = 0; i < aContent.document.styleSheets.length; i++) {
-      let ss = aContent.document.styleSheets[i];
-      let media = ss.media.mediaText;
-      if (!ss.disabled && ss.title && (!media || forScreen.test(media)))
-        return ss.title
-    }
-    for (let i = 0; i < aContent.frames.length; i++) {
-      let selectedPageStyle = this._getSelectedPageStyle(aContent.frames[i]);
-      if (selectedPageStyle)
-        return selectedPageStyle;
-    }
-    return "";
-  },
+  /* ........ Saving Functionality .............. */
 
   /**
    * Store window dimensions, visibility, sidebar
    * @param aWindow
    *        Window reference
    */
   _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
     var winData = this._windows[aWindow.__SSi];
@@ -2484,17 +2112,17 @@ let SessionStoreInternal = {
 
     let tabbrowser = aWindow.gBrowser;
     let tabs = tabbrowser.tabs;
     let winData = this._windows[aWindow.__SSi];
     let tabsData = winData.tabs = [];
 
     // update the internal state data for this window
     for (let tab of tabs) {
-      tabsData.push(this._collectTabData(tab));
+      tabsData.push(TabState.collectSync(tab));
     }
     winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
 
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
     // or leaving PB mode.
     if (aWindow.__SS_lastSessionWindowID)
@@ -2911,18 +2539,19 @@ let SessionStoreInternal = {
       let activeIndex = (tabData.index || tabData.entries.length) - 1;
       let activePageData = tabData.entries[activeIndex] || null;
       let uri = activePageData ? activePageData.url || null : null;
       browser.userTypedValue = uri;
 
       // Also make sure currentURI is set so that switch-to-tab works before
       // the tab is restored. We'll reset this to about:blank when we try to
       // restore the tab to ensure that docshell doeesn't get confused.
-      if (uri)
-        browser.docShell.setCurrentURI(this._getURIFromString(uri));
+      if (uri) {
+        browser.docShell.setCurrentURI(makeURI(uri));
+      }
 
       // If the page has a title, set it.
       if (activePageData) {
         if (activePageData.title) {
           tab.label = activePageData.title;
           tab.crop = "end";
         } else if (activePageData.url != "about:blank") {
           tab.label = activePageData.url;
@@ -3086,17 +2715,17 @@ let SessionStoreInternal = {
     this._removeSHistoryListener(aTab);
 
     let activeIndex = (tabData.index || tabData.entries.length) - 1;
     if (activeIndex >= tabData.entries.length)
       activeIndex = tabData.entries.length - 1;
     // Reset currentURI.  This creates a new session history entry with a new
     // doc identifier, so we need to explicitly save and restore the old doc
     // identifier (corresponding to the SHEntry at activeIndex) below.
-    browser.webNavigation.setCurrentURI(this._getURIFromString("about:blank"));
+    browser.webNavigation.setCurrentURI(makeURI("about:blank"));
     // Attach data that will be restored on "load" event, after tab is restored.
     if (activeIndex > -1) {
       // restore those aspects of the currently active documents which are not
       // preserved in the plain history entries (mainly scroll state and text data)
       browser.__SS_restore_data = tabData.entries[activeIndex] || {};
       browser.__SS_restore_pageStyle = tabData.pageStyle || "";
       browser.__SS_restore_tab = aTab;
       didStartLoad = true;
@@ -3179,25 +2808,25 @@ let SessionStoreInternal = {
    * @returns nsISHEntry
    */
   _deserializeHistoryEntry:
     function ssi_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
 
     var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
                   createInstance(Ci.nsISHEntry);
 
-    shEntry.setURI(this._getURIFromString(aEntry.url));
+    shEntry.setURI(makeURI(aEntry.url));
     shEntry.setTitle(aEntry.title || aEntry.url);
     if (aEntry.subframe)
       shEntry.setIsSubFrame(aEntry.subframe || false);
     shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
     if (aEntry.contentType)
       shEntry.contentType = aEntry.contentType;
     if (aEntry.referrer)
-      shEntry.referrerURI = this._getURIFromString(aEntry.referrer);
+      shEntry.referrerURI = makeURI(aEntry.referrer);
     if (aEntry.isSrcdocEntry)
       shEntry.srcdocData = aEntry.srcdocData;
 
     if (aEntry.cacheKey) {
       var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
                      createInstance(Ci.nsISupportsPRUint32);
       cacheKey.data = aEntry.cacheKey;
       shEntry.cacheKey = cacheKey;
@@ -3715,35 +3344,16 @@ let SessionStoreInternal = {
           aWindow.arguments[0] == defaultArgs)
         hasFirstArgument = false;
     }
 
     return !hasFirstArgument;
   },
 
   /**
-   * don't save sensitive data if the user doesn't want to
-   * (distinguishes between encrypted and non-encrypted sites)
-   * @param aIsHTTPS
-   *        Bool is encrypted
-   * @param aUseDefaultPref
-   *        don't do normal check for deferred
-   * @returns bool
-   */
-  checkPrivacyLevel: function ssi_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
-    let pref = "sessionstore.privacy_level";
-    // If we're in the process of quitting and we're not autoresuming the session
-    // then we should treat it as a deferred session. We have a different privacy
-    // pref for that case.
-    if (!aUseDefaultPref && this._loadState == STATE_QUITTING && !this._doResumeSession())
-      pref = "sessionstore.privacy_level_deferred";
-    return this._prefBranch.getIntPref(pref) < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
-  },
-
-  /**
    * on popup windows, the XULWindow's attributes seem not to be set correctly
    * we use thus JSDOMWindow attributes for sizemode and normal window attributes
    * (and hope for reasonable values when maximized/minimized - since then
    * outerWidth/outerHeight aren't the dimensions of the restored window)
    * @param aWindow
    *        Window reference
    * @param aAttribute
    *        String sizemode | width | height | other window attribute
@@ -4588,16 +4198,27 @@ function TabData(obj = null) {
  * Note that we should never cache private data, as:
  * - that data is used very seldom by SessionStore;
  * - caching private data in addition to public data is memory consuming.
  */
 let TabStateCache = {
   _data: new WeakMap(),
 
   /**
+   * Tells whether an entry is in the cache.
+   *
+   * @param {XULElement} aKey The tab or the associated browser.
+   * @return {bool} Whether there's a cached entry for the given tab.
+   */
+  has: function (aTab) {
+    let key = this._normalizeToBrowser(aTab);
+    return this._data.has(key);
+  },
+
+  /**
    * Add or replace an entry in the cache.
    *
    * @param {XULElement} aTab The key, which may be either a tab
    * or the corresponding browser. The binding will disappear
    * if the tab/browser is destroyed.
    * @param {TabData} aValue The data associated to |aTab|.
    */
   set: function(aTab, aValue) {
@@ -4683,16 +4304,415 @@ let TabStateCache = {
     }
     if (nodeName == "browser") {
       return aKey;
     }
     throw new TypeError("Key is neither a tab nor a browser: " + nodeName);
   }
 };
 
+/**
+ * Module that contains tab state collection methods.
+ */
+let TabState = {
+  // A map (xul:tab -> promise) that keeps track of tabs and
+  // their promises when collecting tab data asynchronously.
+  _pendingCollections: new WeakMap(),
+
+  /**
+   * Collect data related to a single tab, asynchronously.
+   *
+   * @param tab
+   *        tabbrowser tab
+   *
+   * @returns {Promise} A promise that will resolve to a TabData instance.
+   */
+  collect: function (tab) {
+    if (!tab) {
+      throw new TypeError("Expecting a tab");
+    }
+
+    // Don't collect if we don't need to.
+    if (TabStateCache.has(tab)) {
+      return Promise.resolve(TabStateCache.get(tab));
+    }
+
+    let promise = Task.spawn(function task() {
+      // Collected session history data asynchronously.
+      let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
+
+      // Collected session storage data asynchronously.
+      let storage = yield Messenger.send(tab, "SessionStore:collectSessionStorage");
+
+      // Collect basic tab data, without session history and storage.
+      let options = {omitSessionHistory: true, omitSessionStorage: true};
+      let tabData = new TabData(this._collectBaseTabData(tab, options));
+
+      // Apply collected data.
+      tabData.entries = history.entries;
+      tabData.index = history.index;
+
+      if (Object.keys(storage).length) {
+        tabData.storage = storage;
+      }
+
+      // Put tabData into cache when reading scroll and text data succeeds.
+      if (this._updateTextAndScrollDataForTab(tab, tabData)) {
+        // If we're still the latest async collection for the given tab and
+        // the cache hasn't been filled by collect() in the meantime, let's
+        // fill the cache with the data we received.
+        if (this._pendingCollections.get(tab) == promise) {
+          TabStateCache.set(tab, tabData);
+          this._pendingCollections.delete(tab);
+        }
+      }
+
+      throw new Task.Result(tabData);
+    }.bind(this));
+
+    // Save the current promise as the latest asynchronous collection that is
+    // running. This will be used to check whether the collected data is still
+    // valid and will be used to fill the tab state cache.
+    this._pendingCollections.set(tab, promise);
+
+    return promise;
+  },
+
+  /**
+   * Collect data related to a single tab, synchronously.
+   *
+   * @param tab
+   *        tabbrowser tab
+   *
+   * @returns {TabData} An object with the data for this tab.  If the
+   * tab has not been invalidated since the last call to
+   * collectSync(aTab), the same object is returned.
+   */
+  collectSync: function (tab) {
+    if (!tab) {
+      throw new TypeError("Expecting a tab");
+    }
+    if (TabStateCache.has(tab)) {
+      return TabStateCache.get(tab);
+    }
+
+    let tabData = new TabData(this._collectBaseTabData(tab));
+    if (this._updateTextAndScrollDataForTab(tab, tabData)) {
+      TabStateCache.set(tab, tabData);
+    }
+
+    // Prevent all running asynchronous collections from filling the cache.
+    // Every asynchronous data collection started before a collectSync() call
+    // can't expect to retrieve different data than the sync call. That's why
+    // we just fill the cache with the data collected from the sync call and
+    // discard any data collected asynchronously.
+    this._pendingCollections.delete(tab);
+
+    return tabData;
+  },
+
+  /**
+   * Collect data related to a single tab, including private data.
+   * Use with caution.
+   *
+   * @param tab
+   *        tabbrowser tab
+   *
+   * @returns {object} An object with the data for this tab. This data is never
+   *                   cached, it will always be read from the tab and thus be
+   *                   up-to-date.
+   */
+  clone: function (tab) {
+    let options = { includePrivateData: true };
+    let tabData = this._collectBaseTabData(tab, options);
+    this._updateTextAndScrollDataForTab(tab, tabData, options);
+    return tabData;
+  },
+
+  /**
+   * Collects basic tab data for a given tab.
+   *
+   * @param tab
+   *        tabbrowser tab
+   * @param options
+   *        An object that will be passed to session history and session
+   *        storage data collection methods.
+   *        {omitSessionHistory: true} to skip collecting session history data
+   *        {omitSessionStorage: true} to skip collecting session storage data
+   *
+   *        The omit* options have been introduced to enable us collecting
+   *        those parts of the tab data asynchronously. We will request basic
+   *        tabData without the parts to omit and fill those holes later when
+   *        the content script has responded.
+   *
+   * @returns {object} An object with the basic data for this tab.
+   */
+  _collectBaseTabData: function (tab, options = {}) {
+    let tabData = {entries: [], lastAccessed: tab.lastAccessed };
+    let browser = tab.linkedBrowser;
+    if (!browser || !browser.currentURI) {
+      // can happen when calling this function right after .addTab()
+      return tabData;
+    }
+    if (browser.__SS_data && browser.__SS_tabStillLoading) {
+      // use the data to be restored when the tab hasn't been completely loaded
+      tabData = browser.__SS_data;
+      if (tab.pinned)
+        tabData.pinned = true;
+      else
+        delete tabData.pinned;
+      tabData.hidden = tab.hidden;
+
+      // If __SS_extdata is set then we'll use that since it might be newer.
+      if (tab.__SS_extdata)
+        tabData.extData = tab.__SS_extdata;
+      // If it exists but is empty then a key was likely deleted. In that case just
+      // delete extData.
+      if (tabData.extData && !Object.keys(tabData.extData).length)
+        delete tabData.extData;
+      return tabData;
+    }
+
+    // Collection session history data.
+    if (!options || !options.omitSessionHistory) {
+      this._collectTabHistory(tab, tabData, options);
+    }
+
+    // If there is a userTypedValue set, then either the user has typed something
+    // in the URL bar, or a new tab was opened with a URI to load. userTypedClear
+    // is used to indicate whether the tab was in some sort of loading state with
+    // userTypedValue.
+    if (browser.userTypedValue) {
+      tabData.userTypedValue = browser.userTypedValue;
+      tabData.userTypedClear = browser.userTypedClear;
+    } else {
+      delete tabData.userTypedValue;
+      delete tabData.userTypedClear;
+    }
+
+    if (tab.pinned)
+      tabData.pinned = true;
+    else
+      delete tabData.pinned;
+    tabData.hidden = tab.hidden;
+
+    let disallow = [];
+    for (let cap of gDocShellCapabilities(browser.docShell))
+      if (!browser.docShell["allow" + cap])
+        disallow.push(cap);
+    if (disallow.length > 0)
+      tabData.disallow = disallow.join(",");
+    else if (tabData.disallow)
+      delete tabData.disallow;
+
+    // Save tab attributes.
+    tabData.attributes = TabAttributes.get(tab);
+
+    // Store the tab icon.
+    let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
+    tabData.image = tabbrowser.getIcon(tab);
+
+    if (tab.__SS_extdata)
+      tabData.extData = tab.__SS_extdata;
+    else if (tabData.extData)
+      delete tabData.extData;
+
+    // Collect DOMSessionStorage data.
+    if (!options || !options.omitSessionStorage) {
+      this._collectTabSessionStorage(tab, tabData, options);
+    }
+
+    return tabData;
+  },
+
+  /**
+   * Collects session history data for a given tab.
+   *
+   * @param tab
+   *        tabbrowser tab
+   * @param tabData
+   *        An object that the session history data will be added to.
+   * @param options
+   *        {includePrivateData: true} to always include private data
+   */
+  _collectTabHistory: function (tab, tabData, options = {}) {
+    let includePrivateData = options && options.includePrivateData;
+    let docShell = tab.linkedBrowser.docShell;
+
+    if (docShell instanceof Ci.nsIDocShell) {
+      let history = SessionHistory.read(docShell, includePrivateData);
+      tabData.entries = history.entries;
+
+      // For blank tabs without any history entries,
+      // there will not be an 'index' property.
+      if ("index" in history) {
+        tabData.index = history.index;
+      }
+    }
+  },
+
+  /**
+   * Collects session history data for a given tab.
+   *
+   * @param tab
+   *        tabbrowser tab
+   * @param tabData
+   *        An object that the session storage data will be added to.
+   * @param options
+   *        {includePrivateData: true} to always include private data
+   */
+  _collectTabSessionStorage: function (tab, tabData, options = {}) {
+    let includePrivateData = options && options.includePrivateData;
+    let docShell = tab.linkedBrowser.docShell;
+
+    if (docShell instanceof Ci.nsIDocShell) {
+      let storageData = SessionStorage.serialize(docShell, includePrivateData)
+      if (Object.keys(storageData).length) {
+        tabData.storage = storageData;
+      }
+    }
+  },
+
+  /**
+   * Go through all frames and store the current scroll positions
+   * and innerHTML content of WYSIWYG editors
+   *
+   * @param tab
+   *        tabbrowser tab
+   * @param tabData
+   *        tabData object to add the information to
+   * @param options
+   *        An optional object that may contain the following field:
+   *        - includePrivateData: always return privacy sensitive data
+   *          (use with care)
+   * @return false if data should not be cached because the tab
+   *        has not been fully initialized yet.
+   */
+  _updateTextAndScrollDataForTab: function (tab, tabData, options = null) {
+    let includePrivateData = options && options.includePrivateData;
+    let window = tab.ownerDocument.defaultView;
+    let browser = tab.linkedBrowser;
+    // we shouldn't update data for incompletely initialized tabs
+    if (!browser.currentURI
+        || (browser.__SS_data && browser.__SS_tabStillLoading)) {
+      return false;
+    }
+
+    let tabIndex = (tabData.index || tabData.entries.length) - 1;
+    // entry data needn't exist for tabs just initialized with an incomplete session state
+    if (!tabData.entries[tabIndex]) {
+      return false;
+    }
+
+    let selectedPageStyle = browser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
+                            this._getSelectedPageStyle(browser.contentWindow);
+    if (selectedPageStyle)
+      tabData.pageStyle = selectedPageStyle;
+    else if (tabData.pageStyle)
+      delete tabData.pageStyle;
+
+    this._updateTextAndScrollDataForFrame(window, browser.contentWindow,
+                                          tabData.entries[tabIndex],
+                                          includePrivateData,
+                                          !!tabData.pinned);
+    if (browser.currentURI.spec == "about:config") {
+      tabData.entries[tabIndex].formdata = {
+        id: {
+          "textbox": browser.contentDocument.getElementById("textbox").value
+        },
+        xpath: {}
+      };
+    }
+
+    return true;
+  },
+
+  /**
+   * Go through all subframes and store all form data, the current
+   * scroll positions and innerHTML content of WYSIWYG editors.
+   *
+   * @param window
+   *        Window reference
+   * @param content
+   *        frame reference
+   * @param data
+   *        part of a tabData object to add the information to
+   * @param includePrivateData
+   *        always return privacy sensitive data (use with care)
+   * @param isPinned
+   *        the tab is pinned and should be treated differently for privacy
+   */
+  _updateTextAndScrollDataForFrame:
+    function (window, content, data, includePrivateData, isPinned) {
+
+    for (let i = 0; i < content.frames.length; i++) {
+      if (data.children && data.children[i])
+        this._updateTextAndScrollDataForFrame(window, content.frames[i],
+                                              data.children[i],
+                                              includePrivateData, isPinned);
+    }
+    let href = (content.parent || content).document.location.href;
+    let isHttps = makeURI(href).schemeIs("https");
+    let topURL = content.top.document.location.href;
+    let isAboutSR = topURL == "about:sessionrestore" || topURL == "about:welcomeback";
+    if (includePrivateData || isAboutSR ||
+        PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
+      let formData = DocumentUtils.getFormData(content.document);
+
+      // We want to avoid saving data for about:sessionrestore as a string.
+      // Since it's stored in the form as stringified JSON, stringifying further
+      // causes an explosion of escape characters. cf. bug 467409
+      if (formData && isAboutSR) {
+        formData.id["sessionData"] = JSON.parse(formData.id["sessionData"]);
+      }
+
+      if (Object.keys(formData.id).length ||
+          Object.keys(formData.xpath).length) {
+        data.formdata = formData;
+      } else if (data.formdata) {
+        delete data.formdata;
+      }
+
+      // designMode is undefined e.g. for XUL documents (as about:config)
+      if ((content.document.designMode || "") == "on" && content.document.body)
+        data.innerHTML = content.document.body.innerHTML;
+    }
+
+    // get scroll position from nsIDOMWindowUtils, since it allows avoiding a
+    // flush of layout
+    let domWindowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDOMWindowUtils);
+    let scrollX = {}, scrollY = {};
+    domWindowUtils.getScrollXY(false, scrollX, scrollY);
+    data.scroll = scrollX.value + "," + scrollY.value;
+  },
+
+  /**
+   * determine the title of the currently enabled style sheet (if any)
+   * and recurse through the frameset if necessary
+   * @param   content is a frame reference
+   * @returns the title style sheet determined to be enabled (empty string if none)
+   */
+  _getSelectedPageStyle: function (content) {
+    const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i;
+    for (let i = 0; i < content.document.styleSheets.length; i++) {
+      let ss = content.document.styleSheets[i];
+      let media = ss.media.mediaText;
+      if (!ss.disabled && ss.title && (!media || forScreen.test(media)))
+        return ss.title
+    }
+    for (let i = 0; i < content.frames.length; i++) {
+      let selectedPageStyle = this._getSelectedPageStyle(content.frames[i]);
+      if (selectedPageStyle)
+        return selectedPageStyle;
+    }
+    return "";
+  }
+};
+
 let TabStateCacheTelemetry = {
   // Total number of hits during the session
   _hits: 0,
   // Total number of misses during the session
   _misses: 0,
   // Total number of clears during the session
   _clears: 0,
   // |true| once we have been initialized
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -9,17 +9,20 @@ EXTRA_COMPONENTS += [
     'nsSessionStore.js',
     'nsSessionStore.manifest',
 ]
 
 JS_MODULES_PATH = 'modules/sessionstore'
 
 EXTRA_JS_MODULES = [
     'DocumentUtils.jsm',
+    'Messenger.jsm',
+    'PrivacyLevel.jsm',
     'SessionCookies.jsm',
+    'SessionHistory.jsm',
     'SessionMigration.jsm',
     'SessionStorage.jsm',
     'SessionWorker.js',
     'XPathGenerator.jsm',
     '_SessionFile.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
--- a/browser/components/sessionstore/test/browser_625257.js
+++ b/browser/components/sessionstore/test/browser_625257.js
@@ -53,17 +53,16 @@ function test() {
       // Open a new tab
       let tab = gBrowser.addTab("about:blank");
       yield waitForTabLoaded(tab);
 
       // Trigger a save state, to initialize any caches
       ss.getBrowserState();
 
       is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
-      ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
 
       // Start a load and interrupt it by closing the tab
       tab.linkedBrowser.loadURI(URI_TO_LOAD);
       let loaded = yield waitForLoadStarted(tab);
       ok(loaded, "Load started");
 
       let tabClosing = waitForTabClosed();
       gBrowser.removeTab(tab);
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -12,17 +12,17 @@ const PREVIEW_AREA = 700;
 const DEFAULT_MAX_CHILDREN = 100;
 const COLLAPSE_ATTRIBUTE_LENGTH = 120;
 const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
 const COLLAPSE_DATA_URL_LENGTH = 60;
 
 const {UndoStack} = require("devtools/shared/undo");
 const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
 const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
-const {colorUtils} = require("devtools/shared/css-color");
+const {colorUtils} = require("devtools/css-color");
 const promise = require("sdk/core/promise");
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
--- a/browser/devtools/shared/test/browser_css_color.js
+++ b/browser/devtools/shared/test/browser_css_color.js
@@ -1,25 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
 
 let origColorUnit;
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 let {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
-let {colorUtils} = devtools.require("devtools/shared/css-color");
+let {colorUtils} = devtools.require("devtools/css-color");
 
 function test() {
-  // FIXME: Enable this test on Linux once bug 916544 is fixed
-  if (Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS === "Linux") {
-    Services = colorUtils.CssColor = Loader = null;
-    return;
-  }
-
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(init, content);
   }, true);
 
@@ -297,14 +291,14 @@ function getTestData() {
     {authored: "whitesmoke", name: "whitesmoke", hex: "#F5F5F5", hsl: "hsl(0, 0%, 96%)", rgb: "rgb(245, 245, 245)"},
     {authored: "yellow", name: "yellow", hex: "#FF0", hsl: "hsl(60, 100%, 50%)", rgb: "rgb(255, 255, 0)"},
     {authored: "yellowgreen", name: "yellowgreen", hex: "#9ACD32", hsl: "hsl(79.742, 61%, 50%)", rgb: "rgb(154, 205, 50)"},
     {authored: "transparent", name: "transparent", hex: "transparent", hsl: "transparent", rgb: "transparent"},
     {authored: "rgba(0, 0, 0, 0)", name: "transparent", hex: "transparent", hsl: "transparent", rgb: "transparent"},
     {authored: "hsla(0, 0%, 0%, 0)", name: "transparent", hex: "transparent", hsl: "transparent", rgb: "transparent"},
     {authored: "rgba(50, 60, 70, 0.5)", name: "rgba(50, 60, 70, 0.5)", hex: "rgba(50, 60, 70, 0.5)", hsl: "hsla(210, 17%, 24%, 0.5)", rgb: "rgba(50, 60, 70, 0.5)"},
     {authored: "rgba(0, 0, 0, 0.3)", name: "rgba(0, 0, 0, 0.3)", hex: "rgba(0, 0, 0, 0.3)", hsl: "hsla(0, 0%, 0%, 0.3)", rgb: "rgba(0, 0, 0, 0.3)"},
-    {authored: "rgba(255, 255, 255, 0.7)", name: "rgba(255, 255, 255, 0.7)", hex: "rgba(255, 255, 255, 0.7)", hsl: "hsla(0, 0%, 100%, 0.7)", rgb: "rgba(255, 255, 255, 0.7)"},
+    {authored: "rgba(255, 255, 255, 0.6)", name: "rgba(255, 255, 255, 0.6)", hex: "rgba(255, 255, 255, 0.6)", hsl: "hsla(0, 0%, 100%, 0.6)", rgb: "rgba(255, 255, 255, 0.6)"},
     {authored: "rgba(127, 89, 45, 1)", name: "#7F592D", hex: "#7F592D", hsl: "hsl(32.195, 48%, 34%)", rgb: "rgb(127, 89, 45)"},
     {authored: "hsla(19.304, 56%, 40%, 1)", name: "#9F512C", hex: "#9F512C", hsl: "hsl(19.304, 57%, 40%)", rgb: "rgb(159, 81, 44)"},
     {authored: "invalidColor", name: "", hex: "", hsl: "", rgb: ""}
   ];
 }
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -6,17 +6,17 @@
 
 const {Cc, Ci, Cu} = require("chrome");
 
 let ToolDefinitions = require("main").Tools;
 let {CssLogic} = require("devtools/styleinspector/css-logic");
 let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
 let promise = require("sdk/core/promise");
 let {EventEmitter} = require("devtools/shared/event-emitter");
-let {colorUtils} = require("devtools/shared/css-color");
+let {colorUtils} = require("devtools/css-color");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PluralForm.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -7,17 +7,17 @@
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const promise = require("sdk/core/promise");
 
 let {CssLogic} = require("devtools/styleinspector/css-logic");
 let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
 let {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
-let {colorUtils} = require("devtools/shared/css-color");
+let {colorUtils} = require("devtools/css-color");
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
--- a/browser/devtools/webconsole/test/browser_webconsole_view_source.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
@@ -1,63 +1,87 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that source URLs in the Web Console can be clicked to display the
 // standard View Source window.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
 
+let containsValue;
+let Sources;
+let containsValueInvoked = false;
+
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testViewSource);
   }, true);
 }
 
 function testViewSource(hud) {
+  info("console opened");
+
   let button = content.document.querySelector("button");
   ok(button, "we have the button on the page");
 
   expectUncaughtException();
   EventUtils.sendMouseEvent({ type: "click" }, button, content);
 
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "fooBazBaz is not defined",
-      category: CATEGORY_JS,
-      severity: SEVERITY_ERROR,
-    }],
-  }).then(([result]) => {
-    Cu.forceGC();
+  openDebugger().then(({panelWin: { DebuggerView }}) => {
+    info("debugger openeed");
+    Sources = DebuggerView.Sources;
+    openConsole(null, (hud) => {
+      info("console opened again");
+
+      waitForMessages({
+        webconsole: hud,
+        messages: [{
+          text: "fooBazBaz is not defined",
+          category: CATEGORY_JS,
+          severity: SEVERITY_ERROR,
+        }],
+      }).then(onMessage);
+    });
+  });
+
+  function onMessage([result]) {
     let msg = [...result.matched][0];
     ok(msg, "error message");
     let locationNode = msg.querySelector(".location");
     ok(locationNode, "location node");
 
     Services.ww.registerNotification(observer);
 
+    containsValue = Sources.containsValue;
+    Sources.containsValue = () => {
+      containsValueInvoked = true;
+      return false;
+    };
+
     EventUtils.sendMouseEvent({ type: "click" }, locationNode);
-  });
+  }
 }
 
 let observer = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened") {
       return;
     }
 
     ok(true, "the view source window was opened in response to clicking " +
        "the location node");
 
     // executeSoon() is necessary to avoid crashing Firefox. See bug 611543.
     executeSoon(function() {
       aSubject.close();
+      ok(containsValueInvoked, "custom containsValue() was invoked");
+      Sources.containsValue = containsValue;
+      Sources = containsValue = null;
       finishTest();
     });
   }
 };
 
 registerCleanupFunction(function() {
   Services.ww.unregisterNotification(observer);
 });
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -370,16 +370,19 @@
 @BINPATH@/components/nsLoginInfo.js
 @BINPATH@/components/nsLoginManager.js
 @BINPATH@/components/nsLoginManagerPrompter.js
 @BINPATH@/components/storage-Legacy.js
 @BINPATH@/components/storage-mozStorage.js
 @BINPATH@/components/crypto-SDR.js
 @BINPATH@/components/jsconsole-clhandler.manifest
 @BINPATH@/components/jsconsole-clhandler.js
+@BINPATH@/components/webvtt.xpt
+@BINPATH@/components/WebVTT.manifest
+@BINPATH@/components/WebVTTParserWrapper.js
 #ifdef MOZ_GTK
 @BINPATH@/components/nsFilePicker.manifest
 @BINPATH@/components/nsFilePicker.js
 #endif
 @BINPATH@/components/nsHelperAppDlg.manifest
 @BINPATH@/components/nsHelperAppDlg.js
 @BINPATH@/components/nsDownloadManagerUI.manifest
 @BINPATH@/components/nsDownloadManagerUI.js
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -241,16 +241,17 @@ These should match what Safari and other
 <!ENTITY scratchpad.label             "Scratchpad">
 <!ENTITY scratchpad.accesskey         "s">
 <!ENTITY scratchpad.keycode           "VK_F4">
 <!ENTITY scratchpad.keytext           "F4">
 
 <!-- LOCALIZATION NOTE (chromeDebuggerMenu.label): This is the label for the
   -  application menu item that opens the browser debugger UI in the Tools menu. -->
 <!ENTITY chromeDebuggerMenu.label       "Browser Debugger">
+<!ENTITY chromeDebuggerMenu.accesskey   "e">
 
 <!ENTITY devToolbarCloseButton.tooltiptext "Close Developer Toolbar">
 <!ENTITY devToolbarMenu.label              "Developer Toolbar">
 <!ENTITY devToolbarMenu.accesskey          "v">
 <!ENTITY devAppMgrMenu.label               "App Manager">
 <!ENTITY devAppMgrMenu.accesskey           "a">
 <!ENTITY devToolbar.keycode                "VK_F2">
 <!ENTITY devToolbar.keytext                "F2">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -423,16 +423,18 @@ service.install.ok.accesskey=E
 service.install.learnmore=Learn Moreā€¦
 
 # LOCALIZATION NOTE (social.turnOff.label): %S is the name of the social provider
 social.turnOff.label=Turn off %S
 social.turnOff.accesskey=T
 # LOCALIZATION NOTE (social.turnOn.label): %S is the name of the social provider
 social.turnOn.label=Turn on %S
 social.turnOn.accesskey=T
+social.turnOffAll.label=Turn off all Services
+social.turnOnAll.label=Turn on all Services
 
 # LOCALIZATION NOTE (social.markpageMenu.label): %S is the name of the social provider
 social.markpageMenu.label=Save Page to %S
 # LOCALIZATION NOTE (social.marklinkMenu.label): %S is the name of the social provider
 social.marklinkMenu.label=Save Link to %S
 
 # LOCALIZATION NOTE (social.error.message): %1$S is brandShortName (e.g. Firefox), %2$S is the name of the social provider
 social.error.message=%1$S is unable to connect with %2$S right now.
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/searchplugins/bingmetrofx.xml
@@ -0,0 +1,20 @@
+<!-- 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>Bing</ShortName>
+    <Description>Bing. Search by Microsoft.</Description>
+    <InputEncoding>UTF-8</InputEncoding>
+    <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAD/AAAAJgAAACAgAAAAAAAA+AMAACUBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAADGSURBVDjLY/i/TPQ/JZiBOgZsd/z/f08AhCbLgJdH/4MBiKaaAWtUIK4i4DJUA95d/v//xsz//788+o8VPN4GMRCnATAAMuByF8IFx3NR1dxbjscAkCTI+dicDDIIBkAuxWoALs0wDLIABH5+QDIA5DeY0wmFPMhrMAA34Gw1QhAtkFAwyHWwAIZahkiJoBiAOQ1kILpmkMHIaqBRy4BiOihgYACkCBQ2IIwcrSBDkNIFZl7YaARxAShcYAaAMCxaaZOZKMAAOzkOfhyf540AAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAO/SURBVFjDxZRJTxRBFMfrJiC7enKJ28nEGL+AXo2J+gFcrkr0C4gLKjCAoILKIgKyjSZG40FFMephHERcLm4XVzTRi7I4DHRPL8/3qqq7q6d7ZIgRO/mFzlT1+/3fq6ZZ8lzB9smq3JGxQwtgPhmvyHk1XpO3kZF8/EgOjB2eX8hJbkY3/4WjOTBRkQuMbuaVCsHEsVwOoxTzhpROHs8TnMjDAMe8hX/KcQFJOZVIFQaYPEE//mMqBb9QyKlGInmQiFCASpkG0WPVoF7m5xio63OmSvCreqEgIkjUILVEPjCeRhIWQF2fCwmCZI5QSqeIunyYOilg7iZEf5QWYDQG6nrW1OQLaj08aQFM1RdAskHApmhRkopHAgHU9ayp84RJop6Q0lMFME2cRs4UYoA674HQAHIt2bgUtNt7+R76PYzUUAS0gb2QbF8npA35rjTpSKV4urEQZhopQL0YCUEFfAG+xEC7s4//netFz+iDZZA8XSiEUsrFTUUwc1bAnHRE6nEk6+K012U4AtbX8JD25Cjo17e6wplzRaAR55FmCnAG5RIqlOnihQb3wXTLMnf/tAp11roc9HtlfG/6lbpf5ko5LcWgI2xGngVhPKkJ7/hNP2hYXN3LaZLjVEZKaO0rwHwbDdQx7uzkUr2VKIFUWwkGOItFJMZIMID54SaoewRFvpH6xup2WQzWx1v+YtoE6G1CnLqAtGMA9yHEfBoMoPesB3WPdr5Y0OyhN4txujgdRjcEG7q3i4uNi6UcphYyn9YGHlDXOS0enkzSVsw71J0OkfTLelEHRkepoBMDqMnN+MHgBMJEUpZSueBJeYe8y5AAT8rBRDGniwLIxDx12MiGyr11pTNV5iLHysEOreHyYL2rG1G8CMxLApZe0PqU9uLoE2Bc2+TvTOlQFTpQd9aNzfxZ37/y6G0utYhuAfN1QHSvBPvHy0AI6kYIJYqQztJwxkrykfKg/OcrsC6vdsVWj4D5Cjn0rgL7Wzz4QUiMgv26FayBbWD2r+JnyOnHwgPbwX7TyvcEPmLf42BdWe1KrZ7FYPUKmJM+DCu2P7Rg1lfiC9jxA5641xPbRB8FwBeCMDNB5/VgN9jvLmcXhqTvr4D9cI9P6Ir7/DDnbcyEe2YOeI72XRz3YDo7cMxrgl2GSDn9AmZiUZWAsOcPhHWYSahIVRh/IWYjrKvZZBmlSwTRJQAISxdk7CobWYYuXXHUA5wARlfJkD1XyawyD1Bk6Rhdpc+Z1lG4Fm+G/1bkEhX8SczlnaXP8Juz5TddEmZvDz4eOQAAAABJRU5ErkJggg==</Image>
+    <Url type="application/x-suggestions+json" template="http://api.bing.com/osjson.aspx">
+        <Param name="query" value="{searchTerms}"/>
+        <Param name="form" value="MOZW"/>
+    </Url>
+    <Url type="text/html" method="GET" template="http://www.bing.com/search">
+        <Param name="q" value="{searchTerms}"/>
+        <MozParam name="pc" condition="pref" pref="ms-pc"/>
+        <Param name="form" value="MOZW"/>
+    </Url>
+    <SearchForm>http://www.bing.com/search</SearchForm>
+</SearchPlugin>
--- a/browser/locales/en-US/searchplugins/metrolist.txt
+++ b/browser/locales/en-US/searchplugins/metrolist.txt
@@ -1,4 +1,4 @@
-bing
+bingmetrofx
 googlemetrofx
 wikipedia
 yahoo
--- a/browser/metro/base/content/appbar.js
+++ b/browser/metro/base/content/appbar.js
@@ -83,22 +83,16 @@ var Appbar = {
    * has changed such that button states may need to be
    * updated.
    */
   update: function update() {
     this._updatePinButton();
     this._updateStarButton();
   },
 
-  onDownloadButton: function() {
-    let notificationBox = Browser.getNotificationBox();
-    notificationBox.notificationsHidden = !notificationBox.notificationsHidden;
-    ContextUI.dismiss();
-  },
-
   onPinButton: function() {
     if (this.pinButton.checked) {
       Browser.pinSite();
     } else {
       Browser.unpinSite();
     }
   },
 
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -249,17 +249,17 @@
                            command="cmd_stop"/>
           </hbox>
 
           <stack id="toolbar-contextual">
             <observes element="bcast_windowState" attribute="*"/>
             <observes element="bcast_urlbarState" attribute="*"/>
             <hbox id="toolbar-context-page" pack="end">
               <circularprogressindicator id="download-progress"
-                                     oncommand="Appbar.onDownloadButton()"/>
+                                     oncommand="Downloads.onDownloadButton()"/>
               <toolbarbutton id="star-button" class="appbar-primary"
                              type="checkbox"
                              oncommand="Appbar.onStarButton()"/>
               <toolbarbutton id="pin-button" class="appbar-primary"
                              type="checkbox"
                              oncommand="Appbar.onPinButton()"/>
               <toolbarbutton id="menu-button" class="appbar-primary"
                              oncommand="Appbar.onMenuButton(event)"/>
--- a/browser/metro/base/content/downloads.js
+++ b/browser/metro/base/content/downloads.js
@@ -83,17 +83,17 @@ var Downloads = {
     let activeDownloads = this.manager.activeDownloads;
 
     while (activeDownloads.hasMoreElements()) {
       let dl = activeDownloads.getNext();
       switch (dl.state) {
         case 0: // Downloading
         case 5: // Queued
           this.watchDownload(dl);
-          this.updateInfobar(dl);
+          this.updateInfobar();
           break;
       }
     }
     if (this.manager.activeDownloadCount) {
       Services.obs.notifyObservers(null, "dl-request", "");
     }
   },
 
@@ -189,17 +189,16 @@ var Downloads = {
 
     if (!aIcon)
       aIcon = TOAST_URI_GENERIC_ICON_DOWNLOAD;
 
     notifier.showAlertNotification(aIcon, aTitle, aMessage, true, "", aObserver, aName);
   },
 
   showNotification: function dh_showNotification(title, msg, buttons, priority) {
-    this._notificationBox.notificationsHidden = false;
     let notification = this._notificationBox.appendNotification(msg,
                                               title,
                                               URI_GENERIC_ICON_DOWNLOAD,
                                               priority,
                                               buttons);
     return notification;
   },
 
@@ -330,19 +329,20 @@ var Downloads = {
       // info.download => nsIDownload
       totPercent += info.download.percentComplete;
     }
 
     let percentComplete = totPercent / this._progressNotificationInfo.size;
     this._downloadProgressIndicator.updateProgress(percentComplete);
   },
 
-  _computeDownloadProgressString: function dv_computeDownloadProgressString(aDownload) {
+  _computeDownloadProgressString: function dv_computeDownloadProgressString() {
     let totTransferred = 0, totSize = 0, totSecondsLeft = 0;
-    for (let [guid, info] of this._progressNotificationInfo) {
+    let guid, info;
+    for ([guid, info] of this._progressNotificationInfo) {
       let size = info.download.size;
       let amountTransferred = info.download.amountTransferred;
       let speed = info.download.speed;
 
       totTransferred += amountTransferred;
       totSize += size;
       totSecondsLeft += ((size - amountTransferred) / speed);
     }
@@ -353,17 +353,17 @@ var Downloads = {
     let progress = amountTransferred + "/" + size;
 
     // Compute progress in time.;
     let [timeLeft, newLast] = DownloadUtils.getTimeLeft(totSecondsLeft, this._lastSec);
     this._lastSec = newLast;
 
     if (this._downloadCount == 1) {
       return Strings.browser.GetStringFromName("alertDownloadsStart2")
-        .replace("#1", aDownload.displayName)
+        .replace("#1", info.download.displayName)
         .replace("#2", progress)
         .replace("#3", timeLeft)
     }
 
     let numDownloads = this._downloadCount;
     return PluralForm.get(numDownloads,
                           Strings.browser.GetStringFromName("alertDownloadMultiple"))
                           .replace("#1", numDownloads)
@@ -375,23 +375,31 @@ var Downloads = {
     if (!this._progressNotificationInfo.get(aDownload.guid)) {
       this._progressNotificationInfo.set(aDownload.guid, {});
     }
     let infoObj = this._progressNotificationInfo.get(aDownload.guid);
     infoObj.download = aDownload;
     this._progressNotificationInfo.set(aDownload.guid, infoObj);
   },
 
-  updateInfobar: function dv_updateInfobar(aDownload) {
-    let message = this._computeDownloadProgressString(aDownload);
+  onDownloadButton: function dv_onDownloadButton() {
+    if (this._progressNotification) {
+      let progressBar = this._notificationBox.getNotificationWithValue("download-progress");
+      if (progressBar) {
+        this._notificationBox.removeNotification(progressBar);
+      } else {
+        this.updateInfobar();
+      }
+    }
+  },
+
+  updateInfobar: function dv_updateInfobar() {
+    let message = this._computeDownloadProgressString();
     this._updateCircularProgressMeter();
 
-    if (this._notificationBox && this._notificationBox.notificationsHidden)
-      this._notificationBox.notificationsHidden = false;
-
     if (this._progressNotification == null ||
         !this._notificationBox.getNotificationWithValue("download-progress")) {
       let cancelButtonText =
               Strings.browser.GetStringFromName("downloadCancel");
 
       let buttons = [
         {
           isDefault: false,
@@ -452,17 +460,17 @@ var Downloads = {
     switch (aTopic) {
       case "dl-run":
         let file = aSubject.QueryInterface(Ci.nsIFile);
         this._runDownloadBooleanMap.set(file.path, (aData == 'true'));
         break;
       case "dl-start":
         let download = aSubject.QueryInterface(Ci.nsIDownload);
         this.watchDownload(download);
-        this.updateInfobar(download);
+        this.updateInfobar();
         break;
       case "dl-done":
         this._downloadsInProgress--;
         download = aSubject.QueryInterface(Ci.nsIDownload);
         let runAfterDownload = this._runDownloadBooleanMap.get(download.targetFile.path);
         if (runAfterDownload) {
           this.openDownload(download);
         }
--- a/browser/metro/components/HelperAppDialog.js
+++ b/browser/metro/components/HelperAppDialog.js
@@ -119,17 +119,16 @@ HelperAppLauncherDialog.prototype = {
                         text: aLauncher.suggestedFileName,
                         className: "download-size-text"
                       },
                       {
                         text: aLauncher.source.host,
                         className: "download-host-text"
                       }
                     );
-    notificationBox.notificationsHidden = false;
     let newBar = notificationBox.appendNotification("",
                                                     "save-download",
                                                     URI_GENERIC_ICON_DOWNLOAD,
                                                     notificationBox.PRIORITY_WARNING_HIGH,
                                                     buttons);
     let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText");
     messageContainer.appendChild(fragment);
   },
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -29,19 +29,18 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIScriptableUnescapeHTML");
 
 // Add a pref observer for the enabled state
 function prefObserver(subject, topic, data) {
   let enable = Services.prefs.getBoolPref("social.enabled");
   if (enable && !Social.provider) {
     // this will result in setting Social.provider
     SocialService.getOrderedProviderList(function(providers) {
+      Social.enabled = true;
       Social._updateProviderCache(providers);
-      Social.enabled = true;
-      Services.obs.notifyObservers(null, "social:providers-changed", null);
     });
   } else if (!enable && Social.provider) {
     Social.provider = null;
   }
 }
 
 Services.prefs.addObserver("social.enabled", prefObserver, false);
 Services.obs.addObserver(function xpcomShutdown() {
@@ -163,58 +162,64 @@ this.Social = {
     }
     this.initialized = true;
     // if SocialService.hasEnabledProviders, retreive the providers so the
     // front-end can generate UI
     if (SocialService.hasEnabledProviders) {
       // Retrieve the current set of providers, and set the current provider.
       SocialService.getOrderedProviderList(function (providers) {
         Social._updateProviderCache(providers);
-        Social._updateWorkerState(true);
+        Social._updateWorkerState(SocialService.enabled);
       });
     }
 
     // Register an observer for changes to the provider list
     SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
       // An engine change caused by adding/removing a provider should notify.
       // any providers we receive are enabled in the AddonsManager
       if (topic == "provider-installed" || topic == "provider-uninstalled") {
         // installed/uninstalled do not send the providers param
         Services.obs.notifyObservers(null, "social:" + topic, origin);
         return;
       }
-      if (topic == "provider-enabled" || topic == "provider-disabled") {
+      if (topic == "provider-enabled") {
         Social._updateProviderCache(providers);
-        Social._updateWorkerState(true);
-        Services.obs.notifyObservers(null, "social:providers-changed", null);
+        Social._updateWorkerState(Social.enabled);
+        Services.obs.notifyObservers(null, "social:" + topic, origin);
+        return;
+      }
+      if (topic == "provider-disabled") {
+        // a provider was removed from the list of providers, that does not
+        // affect worker state for other providers
+        Social._updateProviderCache(providers);
         Services.obs.notifyObservers(null, "social:" + topic, origin);
         return;
       }
       if (topic == "provider-update") {
         // a provider has self-updated its manifest, we need to update our cache
         // and reload the provider.
         Social._updateProviderCache(providers);
         let provider = Social._getProviderFromOrigin(origin);
         provider.reload();
-        Services.obs.notifyObservers(null, "social:providers-changed", null);
       }
     });
   },
 
   _updateWorkerState: function(enable) {
     // ensure that our providers are all disabled, and enabled if we allow
     // multiple workers
     if (enable && !Social.allowMultipleWorkers)
       return;
     [p.enabled = enable for (p of Social.providers) if (p.enabled != enable)];
   },
 
   // Called to update our cache of providers and set the current provider
   _updateProviderCache: function (providers) {
     this.providers = providers;
+    Services.obs.notifyObservers(null, "social:providers-changed", null);
 
     // If social is currently disabled there's nothing else to do other than
     // to notify about the lack of a provider.
     if (!SocialService.enabled) {
       Services.obs.notifyObservers(null, "social:provider-set", null);
       return;
     }
     // Otherwise set the provider.
--- a/browser/modules/test/moz.build
+++ b/browser/modules/test/moz.build
@@ -1,7 +1,7 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += ['chrome']
+DIRS += ['chrome', 'unit']
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['social']
+
+XPCSHELL_TESTS_MANIFESTS += ['social/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/Makefile.in
@@ -0,0 +1,11 @@
+# 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/.
+
+XPCSHELL_RESOURCES = \
+  xpcshell.ini \
+  head.js \
+  blocklist.xml \
+  test_social.js \
+  test_socialDisabledStartup.js \
+  $(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/blocklist.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <emItems>
+    <emItem  blockID="s1" id="bad.com@services.mozilla.org"></emItem>
+  </emItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/head.js
@@ -0,0 +1,146 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var Social, SocialService;
+
+let manifests = [
+  {
+    name: "provider 1",
+    origin: "https://example1.com",
+    sidebarURL: "https://example1.com/sidebar/",
+  },
+  {
+    name: "provider 2",
+    origin: "https://example2.com",
+    sidebarURL: "https://example1.com/sidebar/",
+  }
+];
+
+const MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+
+// SocialProvider class relies on blocklisting being enabled.  To enable
+// blocklisting, we have to setup an app and initialize the blocklist (see
+// initApp below).
+const gProfD = do_get_profile();
+
+const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
+const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
+
+function createAppInfo(id, name, version, platformVersion) {
+  gAppInfo = {
+    // nsIXULAppInfo
+    vendor: "Mozilla",
+    name: name,
+    ID: id,
+    version: version,
+    appBuildID: "2007010101",
+    platformVersion: platformVersion ? platformVersion : "1.0",
+    platformBuildID: "2007010101",
+
+    // nsIXULRuntime
+    inSafeMode: false,
+    logConsoleErrors: true,
+    OS: "XPCShell",
+    XPCOMABI: "noarch-spidermonkey",
+    invalidateCachesOnRestart: function invalidateCachesOnRestart() {
+      // Do nothing
+    },
+
+    // nsICrashReporter
+    annotations: {},
+
+    annotateCrashReport: function(key, data) {
+      this.annotations[key] = data;
+    },
+
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo,
+                                           Ci.nsIXULRuntime,
+                                           Ci.nsICrashReporter,
+                                           Ci.nsISupports])
+  };
+
+  var XULAppInfoFactory = {
+    createInstance: function (outer, iid) {
+      if (outer != null)
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      return gAppInfo.QueryInterface(iid);
+    }
+  };
+  var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+  registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
+                            XULAPPINFO_CONTRACTID, XULAppInfoFactory);
+}
+
+function initApp() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+  // prepare a blocklist file for the blocklist service
+  var blocklistFile = gProfD.clone();
+  blocklistFile.append("blocklist.xml");
+  if (blocklistFile.exists())
+    blocklistFile.remove(false);
+  var source = do_get_file("blocklist.xml");
+  source.copyTo(gProfD, "blocklist.xml");
+}
+
+function setManifestPref(manifest) {
+  let string = Cc["@mozilla.org/supports-string;1"].
+               createInstance(Ci.nsISupportsString);
+  string.data = JSON.stringify(manifest);
+  Services.prefs.setComplexValue("social.manifest." + manifest.origin, Ci.nsISupportsString, string);
+}
+
+function do_wait_observer(topic, cb) {
+  function observer(subject, topic, data) {
+    Services.obs.removeObserver(observer, topic);
+    cb();
+  }
+  Services.obs.addObserver(observer, topic, false);
+}
+
+function do_initialize_social(enabledOnStartup, cb) {
+  initApp();
+
+  manifests.forEach(function (manifest) {
+    setManifestPref(manifest);
+  });
+  // Set both providers active and flag the first one as "current"
+  let activeVal = Cc["@mozilla.org/supports-string;1"].
+             createInstance(Ci.nsISupportsString);
+  let active = {};
+  for (let m of manifests)
+    active[m.origin] = 1;
+  activeVal.data = JSON.stringify(active);
+  Services.prefs.setComplexValue("social.activeProviders",
+                                 Ci.nsISupportsString, activeVal);
+  Services.prefs.setCharPref("social.provider.current", manifests[0].origin);
+  Services.prefs.setBoolPref("social.enabled", enabledOnStartup);
+
+  do_register_cleanup(function() {
+    manifests.forEach(function (manifest) {
+      Services.prefs.clearUserPref("social.manifest." + manifest.origin);
+    });
+    Services.prefs.clearUserPref("social.enabled");
+    Services.prefs.clearUserPref("social.provider.current");
+    Services.prefs.clearUserPref("social.activeProviders");
+  });
+
+  // expecting 2 providers installed
+  do_wait_observer("social:providers-changed", function() {
+    do_check_eq(Social.providers.length, 2, "2 providers installed");
+    cb();
+  });
+
+  // import and initialize everything
+  SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+  do_check_eq(SocialService.enabled, enabledOnStartup, "service is doing its thing");
+  do_check_true(SocialService.hasEnabledProviders, "Service has enabled providers");
+  Social = Cu.import("resource:///modules/Social.jsm", {}).Social;
+  do_check_false(Social.initialized, "Social is not initialized");
+  Social.init();
+  do_check_true(Social.initialized, "Social is initialized");
+}
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/test_social.js
@@ -0,0 +1,34 @@
+/* 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/. */
+
+function run_test() {
+  // we are testing worker startup specifically
+  Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("social.allowMultipleWorkers");
+  });
+  do_test_pending();
+  add_test(testStartupEnabled);
+  add_test(testDisableAfterStartup);
+  do_initialize_social(true, run_next_test);
+}
+
+function testStartupEnabled() {
+  // wait on startup before continuing
+  do_check_eq(Social.providers.length, 2, "two social providers enabled");
+  do_check_true(Social.providers[0].enabled, "provider is enabled");
+  do_check_true(Social.providers[1].enabled, "provider is enabled");
+  run_next_test();
+}
+
+function testDisableAfterStartup() {
+  do_wait_observer("social:provider-set", function() {
+    do_check_eq(Social.enabled, false, "Social is disabled");
+    do_check_false(Social.providers[0].enabled, "provider is enabled");
+    do_check_false(Social.providers[1].enabled, "provider is enabled");
+    do_test_finished();
+    run_next_test();
+  });
+  Social.enabled = false;
+}
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/test_socialDisabledStartup.js
@@ -0,0 +1,35 @@
+/* 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/. */
+
+function run_test() {
+  // we are testing worker startup specifically
+  Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("social.allowMultipleWorkers");
+  });
+  do_test_pending();
+  add_test(testStartupDisabled);
+  add_test(testEnableAfterStartup);
+  do_initialize_social(false, run_next_test);
+}
+
+function testStartupDisabled() {
+  // wait on startup before continuing
+  do_check_eq(Social.providers.length, 2, "two social providers available");
+  do_check_false(Social.providers[0].enabled, "provider is enabled");
+  do_check_false(Social.providers[1].enabled, "provider is enabled");
+  run_next_test();
+}
+
+function testEnableAfterStartup() {
+  do_wait_observer("social:provider-set", function() {
+    do_check_true(Social.enabled, "Social is enabled");
+    do_check_eq(Social.providers.length, 2, "two social providers available");
+    do_check_true(Social.providers[0].enabled, "provider is enabled");
+    do_check_true(Social.providers[1].enabled, "provider is enabled");
+    do_test_finished();
+    run_next_test();
+  });
+  Social.enabled = true;
+}
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/social/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+
+[test_social.js]
+
+[test_socialDisabledStartup.js]
--- a/build/mobile/robocop/Makefile.in
+++ b/build/mobile/robocop/Makefile.in
@@ -54,16 +54,17 @@ manifest_TARGET   := AndroidManifest.xml
 
 # Install robocop configs and helper
 INSTALL_TARGETS += robocop
 robocop_TARGET  := libs
 robocop_DEST    := $(CURDIR)
 robocop_FILES   := \
   $(TESTPATH)/robocop.ini \
   $(TESTPATH)/robocop_autophone.ini \
+  $(TESTPATH)/robocop_x86.ini \
   $(NULL)
 robocop-deps := $(notdir $(robocop_FILES))
 
 MOCHITEST_ROBOCOP_FILES := \
   $(wildcard $(TESTPATH)/*.html) \
   $(wildcard $(TESTPATH)/*.jpg) \
   $(wildcard $(TESTPATH)/*.sjs) \
   $(wildcard $(TESTPATH)/test*.js) \
--- a/build/win32/mozconfig.vs2010-win64
+++ b/build/win32/mozconfig.vs2010-win64
@@ -1,23 +1,30 @@
+
+if [ -d "/c/Program Files (x86)/Microsoft Visual Studio 10.0" ]; then
+  _VSPATH="/c/Program Files (x86)/Microsoft Visual Studio 10.0"
+else
+  _VSPATH="/c/tools/msvs10"
+fi
+
 ## SDK redist ##
-export WIN32_REDIST_DIR=/c/tools/msvs10/VC/redist/x86/Microsoft.VC100.CRT
+export WIN32_REDIST_DIR=${_VSPATH}/VC/redist/x86/Microsoft.VC100.CRT
 
 ## moz tools location for 64-bit builders ##
 export MOZ_TOOLS=C:/mozilla-build/moztools
 
 ## includes: win8 sdk includes, winrt headers for metro, msvc 10 std library, directx sdk for d3d9 ##
-export INCLUDE=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/shared:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/um:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl/wrappers:/c/tools/msvs10/vc/include:/c/tools/msvs10/vc/atlmfc/include:/c/tools/sdks/dx10/include
+export INCLUDE=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/shared:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/um:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl/wrappers:${_VSPATH}/vc/include:${_VSPATH}/vc/atlmfc/include:/c/tools/sdks/dx10/include
 
 ## libs: win8 sdk x86 (32-bit) libs, msvc 10 (32-bit) std library, msvc 10 atl libs, directx sdk (32-bit) for d3d9  ##
-export LIBPATH=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x86:/c/tools/msvs10/vc/lib:/c/tools/msvs10/vc/atlmfc/lib:/c/tools/sdks/dx10/lib
-export LIB=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x86:/c/tools/msvs10/vc/lib:/c/tools/msvs10/vc/atlmfc/lib:/c/tools/sdks/dx10/lib
+export LIBPATH=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x86:${_VSPATH}/vc/lib:${_VSPATH}/vc/atlmfc/lib:/c/tools/sdks/dx10/lib
+export LIB=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x86:${_VSPATH}/vc/lib:${_VSPATH}/vc/atlmfc/lib:/c/tools/sdks/dx10/lib
 
 ## paths: win8 sdk x86 (32-bit) tools, msvc 10 (32-bit) build toolchain, moz tools  ##
-export PATH="/c/Program Files (x86)/Windows Kits/8.0/bin/x86:/c/tools/msvs10/Common7/IDE:/c/tools/msvs10/VC/BIN:/c/tools/msvs10/Common7/Tools:/c/tools/msvs10/VC/VCPackages:/c/mozilla-build/moztools:${PATH}"
+export PATH="/c/Program Files (x86)/Windows Kits/8.0/bin/x86:${_VSPATH}/Common7/IDE:${_VSPATH}/VC/BIN:${_VSPATH}/Common7/Tools:${_VSPATH}/VC/VCPackages:/c/mozilla-build/moztools:${PATH}"
 
 . $topsrcdir/build/mozconfig.vs2010-common
 
 mk_export_correct_style LIB
 mk_export_correct_style LIBPATH
 mk_export_correct_style PATH
 mk_export_correct_style INCLUDE
 mk_export_correct_style WIN32_REDIST_DIR
--- a/build/win64/mozconfig.vs2010
+++ b/build/win64/mozconfig.vs2010
@@ -1,20 +1,27 @@
+
+if [ -d "/c/Program Files (x86)/Microsoft Visual Studio 10.0" ]; then
+  _VSPATH="/c/Program Files (x86)/Microsoft Visual Studio 10.0"
+else
+  _VSPATH="/c/tools/msvs10"
+fi
+
 ## SDK redist ##
-export WIN32_REDIST_DIR=/c/tools/msvs10/VC/redist/x64/Microsoft.VC100.CRT
+export WIN32_REDIST_DIR=${_VSPATH}/VC/redist/x64/Microsoft.VC100.CRT
 
 ## includes: win8 sdk includes, winrt headers for metro, msvc 10 std library, directx sdk for d3d9 ##
-export INCLUDE=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/shared:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/um:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl/wrappers:/c/tools/msvs10/vc/include:/c/tools/msvs10/vc/atlmfc/include:/c/tools/sdks/dx10/include
+export INCLUDE=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/shared:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/um:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl:/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/include/winrt/wrl/wrappers:${_VSPATH}/vc/include:${_VSPATH}/vc/atlmfc/include:/c/tools/sdks/dx10/include
 
 ## libs: win8 sdk x64 (64-bit) libs, msvc 10 (64-bit) std library, msvc 10 atl libs, directx sdk (64-bit) for d3d9  ##
-export LIBPATH=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x64:/c/tools/msvs10/vc/lib/amd64:/c/tools/msvs10/vc/atlmfc/lib/amd64:/c/tools/sdks/dx10/lib/x64
-export LIB=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x64:/c/tools/msvs10/vc/lib/amd64:/c/tools/msvs10/vc/atlmfc/lib/amd64:/c/tools/sdks/dx10/lib/x64
+export LIBPATH=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x64:${_VSPATH}/vc/lib/amd64:${_VSPATH}/vc/atlmfc/lib/amd64:/c/tools/sdks/dx10/lib/x64
+export LIB=/c/Program\ Files\ \(x86\)/Windows\ Kits/8.0/Lib/win8/um/x64:${_VSPATH}/vc/lib/amd64:${_VSPATH}/vc/atlmfc/lib/amd64:/c/tools/sdks/dx10/lib/x64
 
 ## paths: win8 sdk x64 (64-bit) tools, msvc 10 (64-bit) build toolchain, moz tools  ##
-export PATH="/c/Program Files (x86)/Windows Kits/8.0/bin/x64:/c/tools/msvs10/Common7/IDE:/c/tools/msvs10/VC/BIN/amd64:/c/tools/msvs10/VC/BIN/x86_amd64:/c/tools/msvs10/VC/BIN:/c/tools/msvs10/Common7/Tools:/c/tools/msvs10/VC/VCPackages:${PATH}"
+export PATH="/c/Program Files (x86)/Windows Kits/8.0/bin/x64:${_VSPATH}/Common7/IDE:${_VSPATH}/VC/BIN/amd64:${_VSPATH}/VC/BIN/x86_amd64:${_VSPATH}/VC/BIN:${_VSPATH}/Common7/Tools:${_VSPATH}/VC/VCPackages:${PATH}"
 
 # Use 32bit linker for PGO crash bug.
 # https://connect.microsoft.com/VisualStudio/feedback/details/686117/
 export LD=c:/tools/msvs10/VC/BIN/x86_amd64/link.exe
 
 . $topsrcdir/build/mozconfig.vs2010-common
 
 mk_export_correct_style LIB
--- a/caps/include/nsNullPrincipal.h
+++ b/caps/include/nsNullPrincipal.h
@@ -11,16 +11,17 @@
 
 #ifndef nsNullPrincipal_h__
 #define nsNullPrincipal_h__
 
 #include "nsIPrincipal.h"
 #include "nsJSPrincipals.h"
 #include "nsCOMPtr.h"
 #include "nsPrincipal.h"
+#include "nsIContentSecurityPolicy.h"
 
 class nsIURI;
 
 #define NS_NULLPRINCIPAL_CID \
 { 0xdd156d62, 0xd26f, 0x4441, \
  { 0x9c, 0xdb, 0xe8, 0xf0, 0x91, 0x07, 0xc2, 0x73 } }
 #define NS_NULLPRINCIPAL_CONTRACTID "@mozilla.org/nullprincipal;1"
 
@@ -48,11 +49,12 @@ public:
 #ifdef DEBUG
   virtual void dumpImpl() MOZ_OVERRIDE;
 #endif 
 
  protected:
   virtual ~nsNullPrincipal();
 
   nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<nsIContentSecurityPolicy> mCSP;
 };
 
 #endif // nsNullPrincipal_h__
--- a/caps/src/nsNullPrincipal.cpp
+++ b/caps/src/nsNullPrincipal.cpp
@@ -144,49 +144,51 @@ nsNullPrincipal::GetHashValue(uint32_t *
 {
   *aResult = (NS_PTR_TO_INT32(this) >> 2);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::GetSecurityPolicy(void** aSecurityPolicy)
 {
-  // We don't actually do security policy caching.  And it's not like anyone
-  // can set a security policy for us anyway.
+  // We don't actually do security policy caching.
   *aSecurityPolicy = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::SetSecurityPolicy(void* aSecurityPolicy)
 {
-  // We don't actually do security policy caching.  And it's not like anyone
-  // can set a security policy for us anyway.
+  // We don't actually do security policy caching.
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsNullPrincipal::GetURI(nsIURI** aURI)
 {
   return NS_EnsureSafeToReturn(mURI, aURI);
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::GetCsp(nsIContentSecurityPolicy** aCsp)
 {
-  // CSP on a null principal makes no sense
-  *aCsp = nullptr;
+  NS_IF_ADDREF(*aCsp = mCSP);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::SetCsp(nsIContentSecurityPolicy* aCsp)
 {
-  // CSP on a null principal makes no sense
-  return NS_ERROR_NOT_AVAILABLE;
+  // If CSP was already set, it should not be destroyed!  Instead, it should
+  // get set anew when a new principal is created.
+  if (mCSP)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  mCSP = aCsp;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::GetDomain(nsIURI** aDomain)
 {
   return NS_EnsureSafeToReturn(mURI, aDomain);
 }
 
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -26,16 +26,17 @@ INCLUDED_RULES_MK = 1
   DIRS \
   EXTRA_PP_COMPONENTS \
   EXTRA_PP_JS_MODULES \
   GTEST_CMMSRCS \
   GTEST_CPPSRCS \
   GTEST_CSRCS \
   HOST_CSRCS \
   HOST_LIBRARY_NAME \
+  IS_COMPONENT \
   LIBRARY_NAME \
   LIBXUL_LIBRARY \
   MODULE \
   MSVC_ENABLE_PGO \
   NO_DIST_INSTALL \
   PARALLEL_DIRS \
   SIMPLE_PROGRAMS \
   TEST_DIRS \
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -238,16 +238,18 @@ CSPRep.ALLOW_DIRECTIVE   = "allow";
   *        string rep of a CSP
   * @param self (optional)
   *        URI representing the "self" source
   * @param docRequest (optional)
   *        request for the parent document which may need to be suspended
   *        while the policy-uri is asynchronously fetched
   * @param csp (optional)
   *        the CSP object to update once the policy has been fetched
+  * @param reportOnly (optional)
+  *        whether or not this CSP is report-only (defaults to false)
   * @returns
   *        an instance of CSPRep
   */
 CSPRep.fromString = function(aStr, self, docRequest, csp, reportOnly) {
   if (typeof reportOnly === 'undefined') reportOnly = false;
   var SD = CSPRep.SRC_DIRECTIVES_OLD;
   var UD = CSPRep.URI_DIRECTIVES;
   var aCSPR = new CSPRep();
@@ -475,17 +477,18 @@ CSPRep.fromString = function(aStr, self,
                                              [dirname]));
 
   } // end directive: loop
 
   // the X-Content-Security-Policy syntax requires an allow or default-src
   // directive to be present.
   if (!aCSPR._directives[SD.DEFAULT_SRC]) {
     cspWarn(aCSPR, CSPLocalizer.getStr("allowOrDefaultSrcRequired"));
-    return CSPRep.fromString("default-src 'none'", selfUri);
+    return CSPRep.fromString("default-src 'none'", selfUri, docRequest, csp,
+                             reportOnly);
   }
   return aCSPR;
 };
 
 /**
   * Factory to create a new CSPRep, parsed from a string, compliant
   * with the CSP 1.0 spec.
   *
@@ -493,16 +496,18 @@ CSPRep.fromString = function(aStr, self,
   *        string rep of a CSP
   * @param self (optional)
   *        URI representing the "self" source
   * @param docRequest (optional)
   *        request for the parent document which may need to be suspended
   *        while the policy-uri is asynchronously fetched
   * @param csp (optional)
   *        the CSP object to update once the policy has been fetched
+  * @param reportOnly (optional)
+  *        whether or not this CSP is report-only (defaults to false)
   * @returns
   *        an instance of CSPRep
   */
 // When we deprecate our original CSP implementation, we rename this to
 // CSPRep.fromString and remove the existing CSPRep.fromString above.
 CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp, reportOnly) {
   if (typeof reportOnly === 'undefined') reportOnly = false;
 
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -44,19 +44,19 @@ Cu.import("resource://gre/modules/CSPUti
 function ContentSecurityPolicy() {
   CSPdebug("CSP CREATED");
   this._isInitialized = false;
 
   this._policies = [];
 
   this._request = "";
   this._requestOrigin = "";
-  this._requestPrincipal = "";
+  this._weakRequestPrincipal = null;
   this._referrer = "";
-  this._docRequest = null;
+  this._weakDocRequest = { get : function() { return null; } };
   CSPdebug("CSP object initialized, no policies to enforce yet");
 
   this._cache = { };
 }
 
 /*
  * Set up mappings from nsIContentPolicy content types to CSP directives.
  */
@@ -244,29 +244,30 @@ ContentSecurityPolicy.prototype = {
    * Given an nsIHttpChannel, fill out the appropriate data.
    */
   scanRequestData:
   function(aChannel) {
     if (!aChannel)
       return;
 
     // Save the docRequest for fetching a policy-uri
-    this._docRequest = aChannel;
+    this._weakDocRequest = Cu.getWeakReference(aChannel);
 
     // save the document URI (minus <fragment>) and referrer for reporting
     let uri = aChannel.URI.cloneIgnoringRef();
     try { // GetUserPass throws for some protocols without userPass
       uri.userPass = '';
     } catch (ex) {}
     this._request = uri.asciiSpec;
     this._requestOrigin = uri;
 
     //store a reference to the principal, that can later be used in shouldLoad
-    this._requestPrincipal = Components.classes["@mozilla.org/scriptsecuritymanager;1"].
-    getService(Components.interfaces.nsIScriptSecurityManager).getChannelPrincipal(aChannel);
+    this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
+                                                       .getService(Ci.nsIScriptSecurityManager)
+                                                       .getChannelPrincipal(aChannel));
 
     if (aChannel.referrer) {
       let referrer = aChannel.referrer.cloneIgnoringRef();
       try { // GetUserPass throws for some protocols without userPass
         referrer.userPass = '';
       } catch (ex) {}
       this._referrer = referrer.asciiSpec;
     }
@@ -305,23 +306,23 @@ ContentSecurityPolicy.prototype = {
     // CSPSource only the scheme, host, and port are kept.
 
     // If we want to be CSP 1.0 spec compliant, use the new parser.
     // The old one will be deprecated in the future and will be
     // removed at that time.
     if (aSpecCompliant) {
       newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
                                                  selfURI,
-                                                 this._docRequest,
+                                                 this._weakDocRequest.get(),
                                                  this,
                                                  aReportOnly);
     } else {
       newpolicy = CSPRep.fromString(aPolicy,
                                     selfURI,
-                                    this._docRequest,
+                                    this._weakDocRequest.get(),
                                     this,
                                     aReportOnly);
     }
 
     newpolicy._specCompliant = !!aSpecCompliant;
     newpolicy._isInitialized = true;
     this._policies.push(newpolicy);
     this._cache = {}; // reset cache since effective policy changes
@@ -429,18 +430,18 @@ ContentSecurityPolicy.prototype = {
 
           // make sure this is an anonymous request (no cookies) so in case the
           // policy URI is injected, it can't be abused for CSRF.
           chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
 
           // we need to set an nsIChannelEventSink on the channel object
           // so we can tell it to not follow redirects when posting the reports
           chan.notificationCallbacks = new CSPReportRedirectSink(policy);
-          if (this._docRequest) {
-            chan.loadGroup = this._docRequest.loadGroup;
+          if (this._weakDocRequest.get()) {
+            chan.loadGroup = this._weakDocRequest.get().loadGroup;
           }
 
           chan.QueryInterface(Ci.nsIUploadChannel)
               .setUploadStream(content, "application/json", content.available());
 
           try {
             // if this is an HTTP channel, set the request method to post
             chan.QueryInterface(Ci.nsIHttpChannel);
@@ -449,17 +450,17 @@ ContentSecurityPolicy.prototype = {
 
           // check with the content policy service to see if we're allowed to
           // send this request.
           try {
             var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
                                   .getService(Ci.nsIContentPolicy);
             if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
                                          chan.URI, this._requestOrigin,
-                                         null, null, null, this._requestPrincipal)
+                                         null, null, null, this._weakRequestPrincipal.get())
                 != Ci.nsIContentPolicy.ACCEPT) {
               continue; // skip unauthorized URIs
             }
           } catch(e) {
             continue; // refuse to load if we can't do a security check.
           }
 
           //send data (and set up error notifications)
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -30,16 +30,17 @@
 #include "nsHostObjectProtocolHandler.h"
 #include "nsStringStream.h"
 #include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Attributes.h"
+#include "nsThreadUtils.h"
 
 #include "mozilla/dom/FileListBinding.h"
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // XXXkhuey the input stream that we pass out of a DOMFile
 // can outlive the actual DOMFile object.  Thus, we must
 // ensure that the buffer underlying the stream we get
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -2676,17 +2676,18 @@ nsDocument::InitCSP(nsIChannel* aChannel
       // stop!  ERROR page!
       aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
     }
   }
 
   if (csp) {
     // Copy into principal
     nsIPrincipal* principal = GetPrincipal();
-    principal->SetCsp(csp);
+    rv = principal->SetCsp(csp);
+    NS_ENSURE_SUCCESS(rv, rv);
 #ifdef PR_LOGGING
     PR_LOG(gCspPRLog, PR_LOG_DEBUG,
            ("Inserted CSP into principal %p", principal));
 #endif
   }
 
   return NS_OK;
 }
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -48,17 +48,16 @@
 #include "nsContentList.h"
 #include "nsGkAtoms.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsStyleSet.h"
 #include "pldhash.h"
 #include "nsAttrAndChildArray.h"
 #include "nsDOMAttributeMap.h"
-#include "nsThreadUtils.h"
 #include "nsIContentViewer.h"
 #include "nsIDOMXPathNSResolver.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILoadContext.h"
 #include "nsIProgressEventSink.h"
 #include "nsISecurityEventSink.h"
 #include "nsIChannelEventSink.h"
 #include "imgIRequest.h"
--- a/content/base/src/nsInProcessTabChildGlobal.h
+++ b/content/base/src/nsInProcessTabChildGlobal.h
@@ -13,17 +13,17 @@
 #include "nsIScriptContext.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIClassInfo.h"
 #include "nsIDocShell.h"
 #include "nsIDOMElement.h"
 #include "nsCOMArray.h"
-#include "nsThreadUtils.h"
+#include "nsIRunnable.h"
 #include "nsIGlobalObject.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsWeakReference.h"
 
 class nsInProcessTabChildGlobal : public nsDOMEventTargetHelper,
                                   public nsFrameScriptExecutor,
                                   public nsIInProcessContentFrameMessageManager,
                                   public nsIGlobalObject,
--- a/content/base/test/csp/Makefile.in
+++ b/content/base/test/csp/Makefile.in
@@ -92,16 +92,32 @@ MOCHITEST_FILES := \
   test_CSP_bug888172.html \
   file_CSP_bug888172.html \
   file_CSP_bug888172.sjs \
   test_bug836922_npolicies.html \
   file_bug836922_npolicies.html \
   file_bug836922_npolicies.html^headers^ \
   file_bug836922_npolicies_violation.sjs \
   file_bug836922_npolicies_ro_violation.sjs \
+  test_bug886164.html \
+  file_bug886164.html \
+  file_bug886164.html^headers^ \
+  file_bug886164_2.html \
+  file_bug886164_2.html^headers^ \
+  file_bug886164_3.html \
+  file_bug886164_3.html^headers^ \
+  file_bug886164_4.html \
+  file_bug886164_4.html^headers^ \
+  file_bug886164_5.html \
+  file_bug886164_5.html^headers^ \
+  file_bug886164_6.html \
+  file_bug886164_6.html^headers^ \
+  test_CSP_bug916446.html \
+  file_CSP_bug916446.html \
+  file_CSP_bug916446.html^headers^ \
   $(NULL)
 
 MOCHITEST_CHROME_FILES := \
   test_csp_bug768029.html \
   test_csp_bug773891.html \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_CSP_bug916446.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+  <body>
+    <ol>
+      <li id="inline-script">Inline script (green if allowed, black if blocked)</li>
+    </ol>
+
+    <script>
+      // Use inline script to set a style attribute
+      document.getElementById("inline-script").style.color = "rgb(0, 128, 0)";
+    </script>
+
+    <img src="file_CSP.sjs?testid=img_bad&type=img/png"></img>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_CSP_bug916446.html^headers^
@@ -0,0 +1,1 @@
+x-content-security-policy-report-only: options eval-script; script-src 'self' ; report-uri /csp_report
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164.html
@@ -0,0 +1,15 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox="allow-same-origin" -->
+    <!-- Content-Security-Policy: default-src 'self' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img_bad&type=img/png"> </img>
+
+    <!-- these should load ok -->
+    <img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img_good&type=img/png" />
+    <script src='/tests/content/base/test/csp/file_CSP.sjs?testid=scripta_bad&type=text/javascript'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'self'
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_2.html
@@ -0,0 +1,14 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox -->
+    <!-- Content-Security-Policy: default-src 'self' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img2_bad&type=img/png"> </img>
+
+    <!-- these should load ok -->
+    <img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img2a_good&type=img/png" />
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_2.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'self'
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_3.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox -->
+    <!-- Content-Security-Policy: default-src 'none' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img3_bad&type=img/png"> </img>
+    <img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img3a_bad&type=img/png" />
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_3.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'none'
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_4.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox -->
+    <!-- Content-Security-Policy: default-src 'none' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img4_bad&type=img/png"> </img>
+    <img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img4a_bad&type=img/png" />
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_4.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'none'
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_5.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+
+  function doStuff() {
+    ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+  }
+</script>
+<script src='file_iframe_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+  I am sandboxed but with only inline "allow-scripts"
+
+ <!-- sandbox="allow-scripts" -->
+ <!-- Content-Security-Policy: default-src 'none' 'unsafe-inline'-->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img5_bad&type=img/png" />
+ <img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img5a_bad&type=img/png"> </img>
+ <script src='/tests/content/base/test/csp/file_CSP.sjs?testid=script5_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=script5a_bad&type=text/javascript'></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_5.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'none' 'unsafe-inline';
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_6.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+
+  function doStuff() {
+    ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+    document.getElementById('a_form').submit();
+
+    // trigger the javascript: url test
+    sendMouseEvent({type:'click'}, 'a_link');
+  }
+</script>
+<script src='file_iframe_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+  I am sandboxed but with "allow-scripts"
+  <img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img6_bad&type=img/png"> </img>
+  <script src='http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=script6_bad&type=text/javascript'></script>
+
+  <form method="get" action="file_iframe_sandbox_form_fail.html" id="a_form">
+    First name: <input type="text" name="firstname">
+    Last name: <input type="text" name="lastname">
+    <input type="submit" onclick="doSubmit()" id="a_button">
+  </form>
+
+  <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_bug886164_6.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'self' 'unsafe-inline';
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_CSP_bug916446.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 916446</title>
+   <!--
+   test that an invalid report-only policy (a stripped down version of what
+   web.tweetdeck.com was serving) defaults to "default-src 'none'" but only
+   sends reports and is not accidentally enforced
+   -->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:200px;height:200px;" id='testframe'></iframe>
+
+<script class="testbody" type="text/javascript">
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+  SpecialPowers.addObserver(this, "http-on-modify-request", false);
+}
+examiner.prototype  = {
+  completedTests: 0,
+  totalTests: 4,
+
+  observe: function(subject, topic, data) {
+    // subject should be an nsURI, and should be either allowed or blocked.
+    if (!SpecialPowers.can_QI(subject))
+      return;
+
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+    if (topic === "http-on-modify-request") {
+      //these things were allowed by CSP
+      var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
+      if (!testpat.test(asciiSpec)) return;
+      var testid = testpat.exec(asciiSpec)[1];
+      if (testid === "img_bad") {
+        // img_bad should be *allowed* because the policy is report-only
+        ok(true, "Inline scripts should execute (because the policy is report-only)");
+        this.completedTests++;
+      }
+    }
+
+    if(topic === "csp-on-violate-policy") {
+      // these were blocked
+      try {
+        var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+        if (!testpat.test(asciiSpec)) return;
+        var testid = testpat.exec(asciiSpec)[1];
+        if (testid === "img_bad") {
+          ok(true, "External loads should trigger a violation report (because the policy should fail closed to \"default-src 'none'\")");
+          this.completedTests++;
+        }
+      } catch (e) {
+        // if that fails, the subject is probably a string
+        violation_msg = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsISupportsCString"), "data");
+        if (/Inline Scripts will not execute/.test(violation_msg)) {
+          ok(true, "Inline scripts should trigger a violation report (because the policy should fail closed to \"default-src 'none'\")");
+          this.completedTests++;
+        }
+      }
+    }
+  },
+
+  // must eventually call this to remove the listener,
+  // or mochitests might get borked.
+  remove: function() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "http-on-modify-request");
+  }
+}
+
+window.examiner = new examiner();
+
+function checkInlineScriptExecuted() {
+  var green = 'rgb(0, 128, 0)';
+  var black = 'rgb(0, 0, 0)';
+  var that = this;
+  function getElementColorById(id) {
+    return window.getComputedStyle(that.contentDocument.getElementById(id)).color;
+  }
+  if (getElementColorById('inline-script') === green) {
+    ok(true, "Inline scripts should execute (because the policy is report-only)");
+    window.examiner.completedTests++;
+  }
+
+  waitToFinish();
+}
+
+function waitToFinish() {
+  setTimeout(function wait() {
+    if (window.examiner.completedTests < window.examiner.totalTests) {
+        waitToFinish();
+    } else {
+      // Cleanup
+      window.examiner.remove();
+      SimpleTest.finish();
+    }
+  }, 10);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+  {'set':[["security.csp.speccompliant", false]]},
+  function() {
+    var testframe = document.getElementById('testframe');
+    testframe.src = 'file_CSP_bug916446.html';
+    testframe.addEventListener('load', checkInlineScriptExecuted);
+  }
+);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_bug886164.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 886164 - Enforce CSP in sandboxed iframe</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'  sandbox="allow-same-origin"></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe2' sandbox></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe3' sandbox="allow-same-origin"></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe4' sandbox></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe5' sandbox="allow-scripts"></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe6' sandbox="allow-same-origin allow-scripts"></iframe>
+<script class="testbody" type="text/javascript">
+
+
+var path = "/tests/content/base/test/csp/";
+
+// These are test results: -1 means it hasn't run,
+// true/false is the pass/fail result.
+window.tests = {
+  // sandbox allow-same-origin; 'self'
+  img_good: -1, // same origin
+  img_bad: -1, //example.com
+
+  // sandbox; 'self'
+  img2_bad: -1, //example.com
+  img2a_good: -1, // same origin & is image
+
+  // sandbox allow-same-origin; 'none'
+  img3_bad: -1,
+  img3a_bad: -1,
+
+  // sandbox; 'none'
+  img4_bad: -1,
+  img4a_bad: -1,
+
+  // sandbox allow-scripts; 'none' 'unsafe-inline'
+  img5_bad: -1,
+  img5a_bad: -1,
+  script5_bad: -1,
+  script5a_bad: -1,
+
+  // sandbox allow-same-origin allow-scripts; 'self' 'unsafe-inline'
+  img6_bad: -1,
+  script6_bad: -1,
+};
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+  ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var cspTestsDone = false;
+var iframeSandboxTestsDone = false;
+
+// iframe related
+var completedTests = 0;
+var passedTests = 0;
+
+function ok_wrapper(result, desc) {
+  ok(result, desc);
+
+  completedTests++;
+
+  if (result) {
+    passedTests++;
+  }
+
+  if (completedTests === 5) {
+    iframeSandboxTestsDone = true;
+    if (cspTestsDone) {
+      SimpleTest.finish();
+    }
+  }
+}
+
+
+//csp related
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+  SpecialPowers.addObserver(this, "http-on-modify-request", false);
+}
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+    // subject should be an nsURI, and should be either allowed or blocked.
+    if (!SpecialPowers.can_QI(subject))
+      return;
+
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+    //_good things better be allowed!
+    //_bad things better be stopped!
+
+    if (topic === "http-on-modify-request") {
+      //these things were allowed by CSP
+      var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
+      if (!testpat.test(asciiSpec)) return;
+      var testid = testpat.exec(asciiSpec)[1];
+
+      window.testResult(testid,
+                        /_good/.test(testid),
+                        asciiSpec + " allowed by csp");
+    }
+
+    if(topic === "csp-on-violate-policy") {
+      //these were blocked... record that they were blocked
+      var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+      if (!testpat.test(asciiSpec)) return;
+      var testid = testpat.exec(asciiSpec)[1];
+      window.testResult(testid,
+                        /_bad/.test(testid),
+                        asciiSpec + " blocked by \"" + data + "\"");
+    }
+  },
+
+  // must eventually call this to remove the listener,
+  // or mochitests might get borked.
+  remove: function() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "http-on-modify-request");
+  }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+  //test already complete.... forget it... remember the first result.
+  if (window.tests[testname] != -1)
+    return;
+
+  window.tests[testname] = result;
+  is(result, true, testname + ' test: ' + msg);
+
+  // if any test is incomplete, keep waiting
+  for (var v in window.tests)
+    if(tests[v] == -1) {
+      console.log(v + " is not complete");
+      return;
+    }
+
+  // ... otherwise, finish
+  window.examiner.remove();
+  cspTestsDone = true;
+  if (iframeSandboxTestsDone) {
+    SimpleTest.finish();
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+  {'set':[["security.csp.speccompliant", true]]},
+  function() {
+    // save this for last so that our listeners are registered.
+    // ... this loads the testbed of good and bad requests.
+    document.getElementById('cspframe').src = 'file_bug886164.html';
+    document.getElementById('cspframe2').src = 'file_bug886164_2.html';
+    document.getElementById('cspframe3').src = 'file_bug886164_3.html';
+    document.getElementById('cspframe4').src = 'file_bug886164_4.html';
+    document.getElementById('cspframe5').src = 'file_bug886164_5.html';
+    document.getElementById('cspframe6').src = 'file_bug886164_6.html';
+  });
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/canvas/src/WebGL2Context.cpp
+++ b/content/canvas/src/WebGL2Context.cpp
@@ -69,16 +69,17 @@ WebGLContext::InitWebGL2()
         OES_texture_float,
         OES_texture_float_linear,
         OES_vertex_array_object,
         WEBGL_depth_texture,
         WEBGL_draw_buffers
     };
     const GLFeature::Enum sFeatureRequiredArr[] = {
         GLFeature::blend_minmax,
+        GLFeature::instanced_non_arrays,
         GLFeature::transform_feedback
     };
 
     // check WebGL extensions that are supposed to be natively supported
     for (size_t i = 0; i < size_t(MOZ_ARRAY_LENGTH(sExtensionNativelySupportedArr)); i++)
     {
         WebGLExtensionID extension = sExtensionNativelySupportedArr[i];
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -772,16 +772,17 @@ private:
         mBufferFetchingHasPerVertex = false;
         mMaxFetchedVertices = 0;
         mMaxFetchedInstances = 0;
     }
 
     bool DrawArrays_check(GLint first, GLsizei count, GLsizei primcount, const char* info);
     bool DrawElements_check(GLsizei count, GLenum type, WebGLintptr byteOffset,
                             GLsizei primcount, const char* info);
+    bool DrawInstanced_check(const char* info);
     void Draw_cleanup();
 
     void VertexAttrib1fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
     void VertexAttrib2fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
     void VertexAttrib3fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
     void VertexAttrib4fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
 
     bool ValidateBufferFetching(const char *info);
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -3013,16 +3013,25 @@ WebGLContext::CompileShader(WebGLShader 
 
         if (IsExtensionEnabled(WEBGL_draw_buffers))
             resources.EXT_draw_buffers = 1;
 
         // Tell ANGLE to allow highp in frag shaders. (unless disabled)
         // If underlying GLES doesn't have highp in frag shaders, it should complain anyways.
         resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
 
+        if (gl->WorkAroundDriverBugs()) {
+#ifdef XP_MACOSX
+            if (gl->Vendor() == gl::GLContext::VendorNVIDIA) {
+                // Work around bug 890432
+                resources.MaxExpressionComplexity = 1000;
+            }
+#endif
+        }
+
         // We're storing an actual instance of StripComments because, if we don't, the 
         // cleanSource nsAString instance will be destroyed before the reference is
         // actually used.
         StripComments stripComments(shader->Source());
         const nsAString& cleanSource = Substring(stripComments.result().Elements(), stripComments.length());
         if (!ValidateGLSLString(cleanSource, "compileShader"))
             return;
 
@@ -3093,25 +3102,30 @@ WebGLContext::CompileShader(WebGLShader 
         compiler = ShConstructCompiler((ShShaderType) shader->ShaderType(),
                                        SH_WEBGL_SPEC,
                                        targetShaderSourceLanguage,
                                        &resources);
 
         int compileOptions = SH_ATTRIBUTES_UNIFORMS |
                              SH_ENFORCE_PACKING_RESTRICTIONS;
 
+        if (resources.MaxExpressionComplexity > 0) {
+            compileOptions |= SH_LIMIT_EXPRESSION_COMPLEXITY;
+        }
+
         // We want to do this everywhere, but:
 #ifndef XP_MACOSX // To do this on Mac, we need to do it only on Mac OSX > 10.6 as this
                   // causes the shader compiler in 10.6 to crash
         compileOptions |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS;
 #endif
 
         if (useShaderSourceTranslation) {
             compileOptions |= SH_OBJECT_CODE
                             | SH_MAP_LONG_VARIABLE_NAMES;
+
 #ifdef XP_MACOSX
             if (gl->WorkAroundDriverBugs()) {
                 // Work around bug 665578 and bug 769810
                 if (gl->Vendor() == gl::GLContext::VendorATI) {
                     compileOptions |= SH_EMULATE_BUILT_IN_FUNCTIONS;
                 }
 
                 // Work around bug 735560
@@ -4236,9 +4250,9 @@ WebGLContext::PolygonOffset(GLfloat fact
 }
 
 void
 WebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
     if (IsContextLost())
         return;
     MakeContextCurrent();
     gl->fSampleCoverage(value, invert);
-}
\ No newline at end of file
+}
--- a/content/canvas/src/WebGLContextVertices.cpp
+++ b/content/canvas/src/WebGLContextVertices.cpp
@@ -240,20 +240,20 @@ WebGLContext::GetVertexAttrib(JSContext*
         {
             return JS::Int32Value(mBoundVertexArray->mAttribBuffers[index].stride);
         }
 
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE:
         {
             if (!ValidateAttribIndex(index, "getVertexAttrib"))
                 return JS::NullValue();
-            
+
             if (!mBoundVertexArray->mAttribBuffers[index].enabled)
                 return JS::Int32Value(4);
-            
+
             // Don't break; fall through.
         }
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE:
         {
             GLint i = 0;
             gl->fGetVertexAttribiv(index, pname, &i);
             if (pname == LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE)
                 return JS::Int32Value(i);
@@ -372,17 +372,17 @@ WebGLContext::VertexAttribPointer(GLuint
     if (stride & requiredAlignmentMask) {
         return ErrorInvalidOperation("vertexAttribPointer: stride doesn't satisfy the alignment "
                                      "requirement of given type");
     }
 
     if (byteOffset & requiredAlignmentMask) {
         return ErrorInvalidOperation("vertexAttribPointer: byteOffset doesn't satisfy the alignment "
                                      "requirement of given type");
-        
+
     }
 
     InvalidateBufferFetching();
 
     /* XXX make work with bufferSubData & heterogeneous types
      if (type != mBoundArrayBuffer->GLType())
      return ErrorInvalidOperation("vertexAttribPointer: type must match bound VBO type: %d != %d", type, mBoundArrayBuffer->GLType());
      */
@@ -418,16 +418,40 @@ WebGLContext::VertexAttribDivisor(GLuint
 
     InvalidateBufferFetching();
 
     MakeContextCurrent();
 
     gl->fVertexAttribDivisor(index, divisor);
 }
 
+bool
+WebGLContext::DrawInstanced_check(const char* info)
+{
+    // This restriction was removed in GLES3, so WebGL2 shouldn't have it.
+    if (!IsWebGL2() &&
+        IsExtensionEnabled(ANGLE_instanced_arrays) &&
+        !mBufferFetchingHasPerVertex)
+    {
+        /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt
+         *  If all of the enabled vertex attribute arrays that are bound to active
+         *  generic attributes in the program have a non-zero divisor, the draw
+         *  call should return INVALID_OPERATION.
+         *
+         * NB: This also appears to apply to NV_instanced_arrays, though the
+         * INVALID_OPERATION emission is not explicitly stated.
+         * ARB_instanced_arrays does not have this restriction.
+         */
+        ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
+        return false;
+    }
+
+    return true;
+}
+
 bool WebGLContext::DrawArrays_check(GLint first, GLsizei count, GLsizei primcount, const char* info)
 {
     if (first < 0 || count < 0) {
         ErrorInvalidValue("%s: negative first or count", info);
         return false;
     }
 
     if (primcount < 0) {
@@ -466,26 +490,16 @@ bool WebGLContext::DrawArrays_check(GLin
         return false;
     }
 
     if (uint32_t(primcount) > mMaxFetchedInstances) {
         ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
         return false;
     }
 
-    if (!mBufferFetchingHasPerVertex && !IsWebGL2()) {
-        /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt
-         *  If all of the enabled vertex attribute arrays that are bound to active
-         *  generic attributes in the program have a non-zero divisor, the draw
-         *  call should return INVALID_OPERATION.
-         */
-        ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
-        return false;
-    }
-
     MakeContextCurrent();
 
     if (mBoundFramebuffer) {
         if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers()) {
             ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
             return false;
         }
     }
@@ -523,16 +537,19 @@ WebGLContext::DrawArraysInstanced(GLenum
         return;
 
     if (!ValidateDrawModeEnum(mode, "drawArraysInstanced: mode"))
         return;
 
     if (!DrawArrays_check(first, count, primcount, "drawArraysInstanced"))
         return;
 
+    if (!DrawInstanced_check("drawArraysInstanced"))
+        return;
+
     SetupContextLossTimer();
     gl->fDrawArraysInstanced(mode, first, count, primcount);
 
     Draw_cleanup();
 }
 
 bool
 WebGLContext::DrawElements_check(GLsizei count, GLenum type, WebGLintptr byteOffset, GLsizei primcount, const char* info)
@@ -630,26 +647,16 @@ WebGLContext::DrawElements_check(GLsizei
         return false;
     }
 
     if (uint32_t(primcount) > mMaxFetchedInstances) {
         ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
         return false;
     }
 
-    if (!mBufferFetchingHasPerVertex && !IsWebGL2()) {
-        /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt
-         *  If all of the enabled vertex attribute arrays that are bound to active
-         *  generic attributes in the program have a non-zero divisor, the draw
-         *  call should return INVALID_OPERATION.
-         */
-        ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
-        return false;
-    }
-
     MakeContextCurrent();
 
     if (mBoundFramebuffer) {
         if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers()) {
             ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
             return false;
         }
     }
@@ -689,16 +696,19 @@ WebGLContext::DrawElementsInstanced(GLen
         return;
 
     if (!ValidateDrawModeEnum(mode, "drawElementsInstanced: mode"))
         return;
 
     if (!DrawElements_check(count, type, byteOffset, primcount, "drawElementsInstanced"))
         return;
 
+    if (!DrawInstanced_check("drawElementsInstanced"))
+        return;
+
     SetupContextLossTimer();
     gl->fDrawElementsInstanced(mode, count, type, reinterpret_cast<GLvoid*>(byteOffset), primcount);
 
     Draw_cleanup();
 }
 
 void WebGLContext::Draw_cleanup()
 {
@@ -709,17 +719,17 @@ void WebGLContext::Draw_cleanup()
         Invalidate();
         mShouldPresent = true;
         mIsScreenCleared = false;
     }
 
     if (gl->WorkAroundDriverBugs()) {
         if (gl->Renderer() == gl::GLContext::RendererTegra) {
             mDrawCallsSinceLastFlush++;
-            
+
             if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
                 gl->fFlush();
                 mDrawCallsSinceLastFlush = 0;
             }
         }
     }
 }
 
--- a/content/canvas/test/webgl/Makefile.in
+++ b/content/canvas/test/webgl/Makefile.in
@@ -12,17 +12,19 @@ MOCHITEST_FILES = \
   failing_tests_linux_nvidia.txt \
   failing_tests_windows.txt \
   skipped_tests_winxp.txt \
   skipped_tests_win_vista.txt \
   failing_tests_mac.txt \
   failing_tests_mac_mtnlion.txt \
   failing_tests_android.txt \
   failing_tests_android_nvidia.txt \
+  failing_tests_android_x86.txt \
   skipped_tests_android.txt \
+  skipped_tests_android_x86.txt \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 libs::
 	$(TAR) -cf - -C $(srcdir) \
 	  resources \
 	  conformance \
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/failing_tests_android_x86.txt
@@ -0,0 +1,9 @@
+# Failures for our x86 emulator test environment.
+
+conformance/extensions/oes-texture-float.html
+conformance/misc/error-reporting.html
+conformance/misc/object-deletion-behaviour.html
+conformance/misc/type-conversion-test.html
+conformance/programs/get-active-test.html
+conformance/textures/texture-npot.html
+conformance/textures/texture-size-cube-maps.html
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/skipped_tests_android_x86.txt
@@ -0,0 +1,39 @@
+conformance/glsl/functions/glsl-function-abs.html
+conformance/glsl/functions/glsl-function-atan-xy.html
+conformance/glsl/functions/glsl-function-ceil.html
+conformance/glsl/functions/glsl-function-clamp-float.html
+conformance/glsl/functions/glsl-function-clamp-gentype.html
+conformance/glsl/functions/glsl-function-cross.html
+conformance/glsl/functions/glsl-function-distance.html
+conformance/glsl/functions/glsl-function-dot.html
+conformance/glsl/functions/glsl-function-faceforward.html
+conformance/glsl/functions/glsl-function-floor.html
+conformance/glsl/functions/glsl-function-fract.html
+conformance/glsl/functions/glsl-function.html
+conformance/glsl/functions/glsl-function-length.html
+conformance/glsl/functions/glsl-function-max-float.html
+conformance/glsl/functions/glsl-function-max-gentype.html
+conformance/glsl/functions/glsl-function-min-float.html
+conformance/glsl/functions/glsl-function-min-gentype.html
+conformance/glsl/functions/glsl-function-mix-float.html
+conformance/glsl/functions/glsl-function-mix-gentype.html
+conformance/glsl/functions/glsl-function-mod-float.html
+conformance/glsl/functions/glsl-function-mod-gentype.html
+conformance/glsl/functions/glsl-function-normalize.html
+conformance/glsl/functions/glsl-function-reflect.html
+conformance/glsl/functions/glsl-function-sign.html
+conformance/glsl/functions/glsl-function-smoothstep-float.html
+conformance/glsl/functions/glsl-function-smoothstep-gentype.html
+conformance/glsl/functions/glsl-function-step-float.html
+conformance/glsl/functions/glsl-function-step-gentype.html
+conformance/more/conformance/quickCheckAPI-B2.html
+conformance/programs/gl-getshadersource.html
+conformance/reading/read-pixels-test.html
+conformance/renderbuffers/framebuffer-object-attachment.html
+conformance/textures/gl-teximage.html
+conformance/textures/tex-image-and-sub-image-2d-with-image.html
+conformance/textures/tex-image-and-sub-image-2d-with-video.html
+conformance/textures/tex-image-with-format-and-type.html
+conformance/textures/texture-mips.html
+conformance/textures/texture-npot-video.html
+conformance/textures/texture-size.html
--- a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html
+++ b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html
@@ -76,16 +76,17 @@ function detectDriverInfo() {
 function start() {
   var OS_WINDOWS = 'windows';
   var OS_MAC     = 'mac';
   var OS_LINUX   = 'linux';
   var OS_ANDROID = 'android';
   
   var GLDRIVER_MESA   = 'mesa';
   var GLDRIVER_NVIDIA = 'nvidia';
+  var GLDRIVER_X86EMULATOR = 'android x86 emulator';
 
   var kOS = null;
   var kOSVersion = null;
   var kGLDriver = null;
 
   if (navigator.platform.indexOf('Win') == 0) {
     kOS = OS_WINDOWS;
     
@@ -107,16 +108,18 @@ function start() {
   
   var glVendor, glRenderer;
   [glVendor, glRenderer] = detectDriverInfo();
   info('GL vendor: ' + glVendor);
   info('GL renderer: ' + glRenderer);
   
   if (glRenderer.contains('llvmpipe')) {
     kGLDriver = GLDRIVER_MESA;
+  } else if (glRenderer.contains('Android Emulator')) {
+    kGLDriver = GLDRIVER_X86EMULATOR;
   } else if (glVendor.contains('NVIDIA')) {
     kGLDriver = GLDRIVER_NVIDIA;
   }
   
   if (kOS) {
     info('OS detected as: ' + kOS);
     info('  Version: ' + kOSVersion);
   } else {
@@ -513,23 +516,28 @@ function start() {
           break;
         default:
           failingTestsFilename = 'failing_tests_linux.txt';
           break;
       }
       break;
     }
     case OS_ANDROID: {
-      skippedTestsFilename = 'skipped_tests_android.txt';
       switch (kGLDriver) {
         case GLDRIVER_NVIDIA:
           failingTestsFilename = 'failing_tests_android_nvidia.txt';
+          skippedTestsFilename = 'skipped_tests_android.txt';
+          break;
+        case GLDRIVER_X86EMULATOR:
+          failingTestsFilename = 'failing_tests_android_x86.txt';
+          skippedTestsFilename = 'skipped_tests_android_x86.txt';
           break;
         default:
           failingTestsFilename = 'failing_tests_android.txt';
+          skippedTestsFilename = 'skipped_tests_android.txt';
           break;
       }
       break;
     }
   }
   
   info('Failing tests file: ' + failingTestsFilename);
   info('Skipped tests file: ' + skippedTestsFilename);
--- a/content/events/src/nsDOMEventTargetHelper.h
+++ b/content/events/src/nsDOMEventTargetHelper.h
@@ -8,17 +8,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsGkAtoms.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsPIDOMWindow.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsEventListenerManager.h"
 #include "nsIScriptContext.h"
-#include "nsThreadUtils.h"
+#include "MainThreadUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/EventTarget.h"
 
 #define NS_DOMEVENTTARGETHELPER_IID \
 { 0xda0e6d40, 0xc17b, 0x4937, \
   { 0x8e, 0xa2, 0x99, 0xca, 0x1c, 0x81, 0xea, 0xbe } }
 
 class nsDOMEventTargetHelper : public mozilla::dom::EventTarget
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -6,17 +6,16 @@
 #ifndef mozilla_dom_HTMLMediaElement_h
 #define mozilla_dom_HTMLMediaElement_h
 
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "MediaDecoderOwner.h"
 #include "nsIChannel.h"
 #include "nsIHttpChannel.h"
-#include "nsThreadUtils.h"
 #include "nsIDOMRange.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsILoadGroup.h"
 #include "nsIObserver.h"
 #include "AudioStream.h"
 #include "VideoFrameContainer.h"
 #include "mozilla/CORSMode.h"
 #include "DOMMediaStream.h"
@@ -40,16 +39,17 @@ typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
 class MediaResource;
 class MediaDecoder;
 }
 
 class nsITimer;
 class nsRange;
+class nsIRunnable;
 
 namespace mozilla {
 namespace dom {
 
 class MediaError;
 class MediaSource;
 
 class HTMLMediaElement : public nsGenericHTMLElement,
--- a/content/html/content/src/HTMLTrackElement.cpp
+++ b/content/html/content/src/HTMLTrackElement.cpp
@@ -3,17 +3,17 @@
  * 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/. */
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/HTMLTrackElementBinding.h"
 #include "mozilla/dom/HTMLUnknownElement.h"
-#include "WebVTTLoadListener.h"
+#include "WebVTTListener.h"
 #include "nsAttrValueInlines.h"
 #include "nsCOMPtr.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
@@ -82,17 +82,17 @@ HTMLTrackElement::~HTMLTrackElement()
 
 NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
 
 NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED_4(HTMLTrackElement, nsGenericHTMLElement,
                                      mTrack, mChannel, mMediaParent,
-                                     mLoadListener)
+                                     mListener)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTrackElement)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 void
 HTMLTrackElement::GetKind(DOMString& aKind) const
 {
   GetEnumAttr(nsGkAtoms::kind, kKindTableDefaultString, aKind);
@@ -233,23 +233,23 @@ HTMLTrackElement::LoadResource()
                      uri,
                      nullptr,
                      loadGroup,
                      nullptr,
                      nsIRequest::LOAD_NORMAL,
                      channelPolicy);
   NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
 
-  mLoadListener = new WebVTTLoadListener(this);
-  rv = mLoadListener->LoadResource();
+  mListener = new WebVTTListener(this);
+  rv = mListener->LoadResource();
   NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
-  channel->SetNotificationCallbacks(mLoadListener);
+  channel->SetNotificationCallbacks(mListener);
 
   LOG(PR_LOG_DEBUG, ("opening webvtt channel"));
-  rv = channel->AsyncOpen(mLoadListener, nullptr);
+  rv = channel->AsyncOpen(mListener, nullptr);
   NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
 
   mChannel = channel;
 }
 
 nsresult
 HTMLTrackElement::BindToTree(nsIDocument* aDocument,
                              nsIContent* aParent,
--- a/content/html/content/src/HTMLTrackElement.h
+++ b/content/html/content/src/HTMLTrackElement.h
@@ -26,17 +26,17 @@ static const nsAttrValue::EnumTable kKin
   { "subtitles", static_cast<int16_t>(TextTrackKind::Subtitles) },
   { "captions", static_cast<int16_t>(TextTrackKind::Captions) },
   { "descriptions", static_cast<int16_t>(TextTrackKind::Descriptions) },
   { "chapters", static_cast<int16_t>(TextTrackKind::Chapters) },
   { "metadata", static_cast<int16_t>(TextTrackKind::Metadata) },
   { 0 }
 };
 
-class WebVTTLoadListener;
+class WebVTTListener;
 
 class HTMLTrackElement MOZ_FINAL : public nsGenericHTMLElement
 {
 public:
   HTMLTrackElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~HTMLTrackElement();
 
   // nsISupports
@@ -141,28 +141,27 @@ public:
   // Check enabling preference.
   static bool IsWebVTTEnabled();
 
 protected:
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
   void OnChannelRedirect(nsIChannel* aChannel, nsIChannel* aNewChannel,
                          uint32_t aFlags);
-  // Will open a new channel for the HTMLTrackElement's src attribute and load
-  // HTMLTrackElement's WebVTTLoadListener by calling WebVTTLoadListener's
-  // LoadResource().
+  // Open a new channel to the HTMLTrackElement's src attribute and call
+  // mListener's LoadResource().
   void LoadResource();
 
   friend class TextTrackCue;
-  friend class WebVTTLoadListener;
+  friend class WebVTTListener;
 
   nsRefPtr<TextTrack> mTrack;
   nsCOMPtr<nsIChannel> mChannel;
   nsRefPtr<HTMLMediaElement> mMediaParent;
-  nsRefPtr<WebVTTLoadListener> mLoadListener;
+  nsRefPtr<WebVTTListener> mListener;
   uint16_t mReadyState;
 
   void CreateTextTrack();
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -10,16 +10,17 @@
 #include "mozilla/dom/AudioNode.h"
 #include "mozilla/Mutex.h"
 
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
 class AudioParamTimeline;
+class DelayNodeEngine;
 }
 
 class AudioNodeStream;
 
 /**
  * This class holds onto a set of immutable channel buffers. The storage
  * for the buffers must be malloced, but the buffer pointers and the malloc
  * pointers can be different (e.g. if the buffers are contained inside
@@ -201,16 +202,18 @@ public:
     MOZ_COUNT_CTOR(AudioNodeEngine);
   }
   virtual ~AudioNodeEngine()
   {
     MOZ_ASSERT(!mNode, "The node reference must be already cleared");
     MOZ_COUNT_DTOR(AudioNodeEngine);
   }
 
+  virtual dom::DelayNodeEngine* AsDelayNodeEngine() { return nullptr; }
+
   virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
   {
     NS_ERROR("Invalid SetStreamTimeParameter index");
   }
   virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
   {
     NS_ERROR("Invalid SetDoubleParameter index");
   }
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -281,16 +281,30 @@ AudioNodeStream::ObtainInputBlock(AudioC
     }
     MediaStream* s = mInputs[i]->GetSource();
     AudioNodeStream* a = static_cast<AudioNodeStream*>(s);
     MOZ_ASSERT(a == s->AsAudioNodeStream());
     if (a->IsFinishedOnGraphThread() ||
         a->IsAudioParamStream()) {
       continue;
     }
+
+    // It is possible for mLastChunks to be empty here, because `a` might be a
+    // AudioNodeStream that has not been scheduled yet, because it is further
+    // down the graph _but_ as a connection to this node. Because we enforce the
+    // presence of at least one DelayNode, with at least one block of delay, and
+    // because the output of a DelayNode when it has been fed less that
+    // `delayTime` amount of audio is silence, we can simply continue here,
+    // because this input would not influence the output of this node. Next
+    // iteration, a->mLastChunks.IsEmpty() will be false, and everthing will
+    // work as usual.
+    if (a->mLastChunks.IsEmpty()) {
+      continue;
+    }
+
     AudioChunk* chunk = &a->mLastChunks[mInputs[i]->OutputNumber()];
     MOZ_ASSERT(chunk);
     if (chunk->IsNull() || chunk->mChannelData.IsEmpty()) {
       continue;
     }
 
     inputChunks.AppendElement(chunk);
     outputChannelCount =
@@ -407,18 +421,17 @@ AudioNodeStream::ProduceOutput(GraphTime
     FinishOutput();
   }
 
   EnsureTrack(AUDIO_NODE_STREAM_TRACK_ID, mSampleRate);
 
   uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount());
   mLastChunks.SetLength(outputCount);
 
-  if (mInCycle) {
-    // XXX DelayNode not supported yet so just produce silence
+  if (mMuted) {
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
   } else {
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(0);
     }
 
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -16,16 +16,17 @@
 #define LOG(type, msg)
 #endif
 
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
 class AudioParamTimeline;
+class DelayNodeEngine;
 }
 
 class ThreadSharedFloatArrayBufferList;
 class AudioNodeEngine;
 
 /**
  * An AudioNodeStream produces one audio track with ID AUDIO_TRACK.
  * The start time of the AudioTrack is aligned to the start time of the
@@ -49,17 +50,18 @@ public:
                   MediaStreamGraph::AudioNodeStreamKind aKind,
                   TrackRate aSampleRate)
     : ProcessedMediaStream(nullptr),
       mEngine(aEngine),
       mSampleRate(aSampleRate),
       mKind(aKind),
       mNumberOfInputChannels(2),
       mMarkAsFinishedAfterThisBlock(false),
-      mAudioParamStream(false)
+      mAudioParamStream(false),
+      mMuted(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     mChannelCountMode = dom::ChannelCountMode::Max;
     mChannelInterpretation = dom::ChannelInterpretation::Speakers;
     // AudioNodes are always producing data
     mHasCurrentData = true;
     MOZ_COUNT_CTOR(AudioNodeStream);
   }
@@ -98,16 +100,24 @@ public:
                                       dom::ChannelInterpretation aChannelInterpretation);
   virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
   TrackTicks GetCurrentPosition();
   bool AllInputsFinished() const;
   bool IsAudioParamStream() const
   {
     return mAudioParamStream;
   }
+  void Mute() {
+    mMuted = true;
+  }
+
+  void Unmute() {
+    mMuted = false;
+  }
+
   const OutputChunks& LastChunks() const
   {
     return mLastChunks;
   }
   virtual bool MainThreadNeedsUpdates() const MOZ_OVERRIDE
   {
     // Only source and external streams need updates on the main thread.
     return (mKind == MediaStreamGraph::SOURCE_STREAM && mFinished) ||
@@ -148,13 +158,15 @@ protected:
   // The mixing modes
   dom::ChannelCountMode mChannelCountMode;
   dom::ChannelInterpretation mChannelInterpretation;
   // Whether the stream should be marked as finished as soon
   // as the current time range has been computed block by block.
   bool mMarkAsFinishedAfterThisBlock;
   // Whether the stream is an AudioParamHelper stream.
   bool mAudioParamStream;
+  // Whether the stream is muted. Access only on the MediaStreamGraph thread.
+  bool mMuted;
 };
 
 }
 
 #endif /* MOZILLA_AUDIONODESTREAM_H_ */
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -467,22 +467,49 @@ MediaStreamGraphImpl::MarkConsumed(Media
 void
 MediaStreamGraphImpl::UpdateStreamOrderForStream(mozilla::LinkedList<MediaStream>* aStack,
                                                  already_AddRefed<MediaStream> aStream)
 {
   nsRefPtr<MediaStream> stream = aStream;
   NS_ASSERTION(!stream->mHasBeenOrdered, "stream should not have already been ordered");
   if (stream->mIsOnOrderingStack) {
     MediaStream* iter = aStack->getLast();
+    AudioNodeStream* ns = stream->AsAudioNodeStream();
+    bool delayNodePresent = ns ? ns->Engine()->AsDelayNodeEngine() != nullptr : false;
+    bool cycleFound = false;
     if (iter) {
       do {
+        cycleFound = true;
         iter->AsProcessedStream()->mInCycle = true;
+        AudioNodeStream* ns = iter->AsAudioNodeStream();
+        if (ns && ns->Engine()->AsDelayNodeEngine()) {
+          delayNodePresent = true;
+        }
         iter = iter->getPrevious();
       } while (iter && iter != stream);
     }
+    if (cycleFound && !delayNodePresent) {
+      // If we have detected a cycle, the previous loop should exit with stream
+      // == iter, or the node is connected to itself. Go back in the cycle and
+      // mute all nodes we find, or just mute the node itself.
+      if (!iter) {
+        // The node is connected to itself.
+        iter = aStack->getLast();
+        iter->AsAudioNodeStream()->Mute();
+      } else {
+        MOZ_ASSERT(iter);
+        do {
+          // There can't be non-AudioNodeStream here, MediaStreamAudio{Source,
+          // Destination}Node are connected to regular MediaStreams, but they can't be
+          // in a cycle (there is no content API to do so).
+          MOZ_ASSERT(iter->AsAudioNodeStream());
+          iter->AsAudioNodeStream()->Mute();
+        } while((iter = iter->getNext()));
+      }
+    }
     return;
   }
   ProcessedMediaStream* ps = stream->AsProcessedStream();
   if (ps) {
     aStack->insertBack(stream);
     stream->mIsOnOrderingStack = true;
     for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
       MediaStream* source = ps->mInputs[i]->mSource;
@@ -508,16 +535,20 @@ MediaStreamGraphImpl::UpdateStreamOrder(
     MediaStream* stream = mOldStreams[i];
     stream->mHasBeenOrdered = false;
     stream->mIsConsumed = false;
     stream->mIsOnOrderingStack = false;
     stream->mInBlockingSet = false;
     ProcessedMediaStream* ps = stream->AsProcessedStream();
     if (ps) {
       ps->mInCycle = false;
+      AudioNodeStream* ns = ps->AsAudioNodeStream();
+      if (ns) {
+        ns->Unmute();
+      }
     }
   }
 
   mozilla::LinkedList<MediaStream> stack;
   for (uint32_t i = 0; i < mOldStreams.Length(); ++i) {
     nsRefPtr<MediaStream>& s = mOldStreams[i];
     if (s->IsIntrinsicallyConsumed()) {
       MarkConsumed(s);
@@ -2288,16 +2319,17 @@ MediaStreamGraphImpl::MediaStreamGraphIm
   , mNeedAnotherIteration(false)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
   , mNonRealtimeIsRunning(false)
   , mDetectedNotRunning(false)
   , mPostedRunInStableState(false)
   , mRealtime(aRealtime)
   , mNonRealtimeProcessing(false)
+  , mStreamOrderDirty(false)
 {
 #ifdef PR_LOGGING
   if (!gMediaStreamGraphLog) {
     gMediaStreamGraphLog = PR_NewLogModule("MediaStreamGraph");
   }
 #endif
 
   mCurrentTimeStamp = mInitialTimeStamp = mLastMainThreadUpdate = TimeStamp::Now();
@@ -2441,9 +2473,16 @@ MediaStreamGraph::StartNonRealtimeProces
 
   if (graph->mNonRealtimeProcessing)
     return;
   graph->mNonRealtimeTicksToProcess = aTicksToProcess;
   graph->mNonRealtimeProcessing = true;
   graph->EnsureRunInStableState();
 }
 
+void
+ProcessedMediaStream::AddInput(MediaInputPort* aPort)
+{
+  mInputs.AppendElement(aPort);
+  GraphImpl()->SetStreamOrderDirty();
 }
+
+}
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -10,17 +10,19 @@
 #include "mozilla/LinkedList.h"
 #include "AudioStream.h"
 #include "nsTArray.h"
 #include "nsIRunnable.h"
 #include "StreamBuffer.h"
 #include "TimeVarying.h"
 #include "VideoFrameContainer.h"
 #include "VideoSegment.h"
-#include "nsThreadUtils.h"
+#include "MainThreadUtils.h"
+
+class nsIRunnable;
 
 namespace mozilla {
 
 class DOMMediaStream;
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaStreamGraphLog;
 #endif
@@ -912,20 +914,17 @@ public:
    */
   void SetAutofinish(bool aAutofinish);
 
   virtual ProcessedMediaStream* AsProcessedStream() { return this; }
 
   friend class MediaStreamGraphImpl;
 
   // Do not call these from outside MediaStreamGraph.cpp!
-  virtual void AddInput(MediaInputPort* aPort)
-  {
-    mInputs.AppendElement(aPort);
-  }
+  virtual void AddInput(MediaInputPort* aPort);
   virtual void RemoveInput(MediaInputPort* aPort)
   {
     mInputs.RemoveElement(aPort);
   }
   bool HasInputPort(MediaInputPort* aPort)
   {
     return mInputs.Contains(aPort);
   }
@@ -941,16 +940,19 @@ public:
   virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) = 0;
   void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; }
 
   /**
    * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID
    */
   virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {};
 
+  bool InCycle() const { return mInCycle; }
+
+
 protected:
   // This state is all accessed only on the media graph thread.
 
   // The list of all inputs that are currently enabled or waiting to be enabled.
   nsTArray<MediaInputPort*> mInputs;
   bool mAutofinish;
   // True if and only if this stream is in a cycle.
   // Updated by MediaStreamGraphImpl::UpdateStreamOrder.
--- a/content/media/MediaStreamGraphImpl.h
+++ b/content/media/MediaStreamGraphImpl.h
@@ -360,16 +360,23 @@ public:
    * Remove aStream from the graph. Ensures that pending messages about the
    * stream back to the main thread are flushed.
    */
   void RemoveStream(MediaStream* aStream);
   /**
    * Remove aPort from the graph and release it.
    */
   void DestroyPort(MediaInputPort* aPort);
+  /**
+   * Mark the media stream order as dirty.
+   */
+  void SetStreamOrderDirty()
+  {
+    mStreamOrderDirty = true;
+  }
 
   // Data members
 
   /**
    * Media graph thread.
    * Readonly after initialization on the main thread.
    */
   nsCOMPtr<nsIThread> mThread;
@@ -549,13 +556,18 @@ public:
    * audio.
    */
   bool mRealtime;
   /**
    * True when a non-realtime MediaStreamGraph has started to process input.  This
    * value is only accessed on the main thread.
    */
   bool mNonRealtimeProcessing;
+  /**
+   * True when a change has happened which requires us to recompute the stream
+   * blocking order.
+   */
+  bool mStreamOrderDirty;
 };
 
 }
 
 #endif /* MEDIASTREAMGRAPHIMPL_H_ */
--- a/content/media/TextTrackCue.cpp
+++ b/content/media/TextTrackCue.cpp
@@ -2,16 +2,17 @@
 /* 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/. */
 
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "nsIFrame.h"
 #include "nsVideoFrame.h"
+#include "nsComponentManagerUtils.h"
 
 // Alternate value for the 'auto' keyword.
 #define WEBVTT_AUTO -1
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED_4(TextTrackCue,
@@ -21,16 +22,18 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED_4(Tex
                                      mTrackElement,
                                      mDisplayState)
 
 NS_IMPL_ADDREF_INHERITED(TextTrackCue, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(TextTrackCue, nsDOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TextTrackCue)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
+StaticRefPtr<nsIWebVTTParserWrapper> TextTrackCue::sParserWrapper;
+
 // Set cue setting defaults based on step 19 & seq.
 // in http://dev.w3.org/html5/webvtt/#parsing
 void
 TextTrackCue::SetDefaultCueSettings()
 {
   mPosition = 50;
   mSize = 100;
   mPauseOnExit = false;
@@ -43,57 +46,46 @@ TextTrackCue::SetDefaultCueSettings()
 TextTrackCue::TextTrackCue(nsISupports* aGlobal,
                            double aStartTime,
                            double aEndTime,
                            const nsAString& aText,
                            ErrorResult& aRv)
   : mText(aText)
   , mStartTime(aStartTime)
   , mEndTime(aEndTime)
-  , mHead(nullptr)
   , mReset(false)
 {
   SetDefaultCueSettings();
   MOZ_ASSERT(aGlobal);
   SetIsDOMBinding();
   if (NS_FAILED(StashDocument(aGlobal))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 TextTrackCue::TextTrackCue(nsISupports* aGlobal,
                            double aStartTime,
                            double aEndTime,
                            const nsAString& aText,
                            HTMLTrackElement* aTrackElement,
-                           webvtt_node* head,
                            ErrorResult& aRv)
   : mText(aText)
   , mStartTime(aStartTime)
   , mEndTime(aEndTime)
   , mTrackElement(aTrackElement)
-  , mHead(head)
   , mReset(false)
 {
-  // Ref mHead here.
   SetDefaultCueSettings();
   MOZ_ASSERT(aGlobal);
   SetIsDOMBinding();
   if (NS_FAILED(StashDocument(aGlobal))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
-TextTrackCue::~TextTrackCue()
-{
-  if (mHead) {
-    // Release mHead here.
-  }
-}
-
 /** Save a reference to our creating document so it's available
  *  even when unlinked during discard/teardown.
  */
 nsresult
 TextTrackCue::StashDocument(nsISupports* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aGlobal));
   if (!window) {
@@ -156,61 +148,49 @@ TextTrackCue::RenderCue()
   mDisplayState->AppendChild(*frag, rv);
   overlay->AppendChild(*mDisplayState, rv);
 }
 
 already_AddRefed<DocumentFragment>
 TextTrackCue::GetCueAsHTML()
 {
   MOZ_ASSERT(mDocument);
-  nsRefPtr<DocumentFragment> frag = mDocument->CreateDocumentFragment();
-  ConvertNodeTreeToDOMTree(frag);
 
-  return frag.forget();
-}
+  if (!sParserWrapper) {
+    nsresult rv;
+    nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
+      do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+      return mDocument->CreateDocumentFragment();
+    }
+    sParserWrapper = parserWrapper;
+  }
 
-struct WebVTTNodeParentPair
-{
-  webvtt_node* mNode;
-  nsIContent* mParent;
+  nsPIDOMWindow* window = mDocument->GetWindow();
+  if (!window) {
+    return mDocument->CreateDocumentFragment();
+  }
 
-  WebVTTNodeParentPair(webvtt_node* aNode, nsIContent* aParent)
-    : mNode(aNode)
-    , mParent(aParent)
-  {}
-};
+  nsCOMPtr<nsIDOMHTMLElement> div;
+  sParserWrapper->ConvertCueToDOMTree(window, this,
+                                      getter_AddRefs(div));
+  if (!div) {
+    return mDocument->CreateDocumentFragment();
+  }
+  nsRefPtr<DocumentFragment> docFrag = mDocument->CreateDocumentFragment();
+  nsCOMPtr<nsIDOMNode> throwAway;
+  docFrag->AppendChild(div, getter_AddRefs(throwAway));
+
+  return docFrag.forget();
+}
 
 void
-TextTrackCue::ConvertNodeTreeToDOMTree(nsIContent* aParentContent)
-{
-  nsTArray<WebVTTNodeParentPair> nodeParentPairStack;
-
-  // mHead should actually be the head of a node tree.
-  // Seed the stack for traversal.
-}
-
-already_AddRefed<nsIContent>
-TextTrackCue::ConvertInternalNodeToContent(const webvtt_node* aWebVTTNode)
+TextTrackCue::SetTrackElement(HTMLTrackElement* aTrackElement)
 {
-  nsIAtom* atom = nsGkAtoms::span;
-
-  nsCOMPtr<nsIContent> cueTextContent;
-  mDocument->CreateElem(nsDependentAtomString(atom), nullptr,
-                        kNameSpaceID_XHTML,
-                        getter_AddRefs(cueTextContent));
-  return cueTextContent.forget();
-}
-
-already_AddRefed<nsIContent>
-TextTrackCue::ConvertLeafNodeToContent(const webvtt_node* aWebVTTNode)
-{
-  nsCOMPtr<nsIContent> cueTextContent;
-  // Use mDocument to create nodes on cueTextContent.
-
-  return cueTextContent.forget();
+  mTrackElement = aTrackElement;
 }
 
 JSObject*
 TextTrackCue::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return VTTCueBinding::Wrap(aCx, aScope, this);
 }
 
--- a/content/media/TextTrackCue.h
+++ b/content/media/TextTrackCue.h
@@ -3,23 +3,21 @@
 /* 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/. */
 
 #ifndef mozilla_dom_TextTrackCue_h
 #define mozilla_dom_TextTrackCue_h
 
 #include "mozilla/dom/DocumentFragment.h"
-#include "mozilla/dom/TextTrack.h"
 #include "mozilla/dom/VTTCueBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMEventTargetHelper.h"
-#include "nsIDocument.h"
-
-struct webvtt_node;
+#include "nsIWebVTTParserWrapper.h"
+#include "mozilla/StaticPtr.h"
 
 namespace mozilla {
 namespace dom {
 
 class HTMLTrackElement;
 class TextTrack;
 
 class TextTrackCue MOZ_FINAL : public nsDOMEventTargetHelper
@@ -41,19 +39,17 @@ public:
                                                     aEndTime, aText, aRv);
     return ttcue.forget();
   }
   TextTrackCue(nsISupports* aGlobal, double aStartTime, double aEndTime,
                const nsAString& aText, ErrorResult& aRv);
 
   TextTrackCue(nsISupports* aGlobal, double aStartTime, double aEndTime,
                const nsAString& aText, HTMLTrackElement* aTrackElement,
-               webvtt_node* head, ErrorResult& aRv);
-
-  ~TextTrackCue();
+               ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   nsINode* GetParentObject()
   {
     return mDocument;
   }
@@ -229,130 +225,101 @@ public:
     if (mText == aText)
       return;
 
     mReset = true;
     mText = aText;
     CueChanged();
   }
 
-  already_AddRefed<DocumentFragment> GetCueAsHTML() const
-  {
-    // XXXhumph: todo. Bug 868509.
-    return nullptr;
-  }
-
   IMPL_EVENT_HANDLER(enter)
   IMPL_EVENT_HANDLER(exit)
 
   // Helper functions for implementation.
   bool
   operator==(const TextTrackCue& rhs) const
   {
     return mId.Equals(rhs.mId);
   }
 
   const nsAString& Id() const
   {
     return mId;
   }
 
   /**
-   * Overview of WEBVTT cuetext and anonymous content setup.
+   * Overview of WebVTT cuetext and anonymous content setup.
    *
-   * webvtt_nodes are the parsed version of WEBVTT cuetext. WEBVTT cuetext is
-   * the portion of a WEBVTT cue that specifies what the caption will actually
+   * WebVTT nodes are the parsed version of WebVTT cuetext. WebVTT cuetext is
+   * the portion of a WebVTT cue that specifies what the caption will actually
    * show up as on screen.
    *
-   * WEBVTT cuetext can contain markup that loosely relates to HTML markup. It
+   * WebVTT cuetext can contain markup that loosely relates to HTML markup. It
    * can contain tags like <b>, <u>, <i>, <c>, <v>, <ruby>, <rt>, <lang>,
    * including timestamp tags.
    *
-   * When the caption is ready to be displayed the webvtt_nodes are converted
+   * When the caption is ready to be displayed the WebVTT nodes are converted
    * over to anonymous DOM content. <i>, <u>, <b>, <ruby>, and <rt> all become
    * HTMLElements of their corresponding HTML markup tags. <c> and <v> are
    * converted to <span> tags. Timestamp tags are converted to XML processing
    * instructions. Additionally, all cuetext tags support specifying of classes.
    * This takes the form of <foo.class.subclass>. These classes are then parsed
    * and set as the anonymous content's class attribute.
    *
-   * Rules on constructing DOM objects from webvtt_nodes can be found here
+   * Rules on constructing DOM objects from WebVTT nodes can be found here
    * http://dev.w3.org/html5/webvtt/#webvtt-cue-text-dom-construction-rules.
    * Current rules are taken from revision on April 15, 2013.
    */
 
   /**
    * Converts the TextTrackCue's cuetext into a tree of DOM objects and attaches
    * it to a div on it's owning TrackElement's MediaElement's caption overlay.
    */
   void RenderCue();
 
   /**
    * Produces a tree of anonymous content based on the tree of the processed
-   * cue text. This lives in a tree of C nodes whose head is mHead.
+   * cue text.
    *
    * Returns a DocumentFragment that is the head of the tree of anonymous
    * content.
    */
   already_AddRefed<DocumentFragment> GetCueAsHTML();
 
-  /**
-   * Converts mHead to a list of DOM elements and attaches it to aParentContent.
-   *
-   * Processes the C node tree in a depth-first pre-order traversal and creates
-   * a mirrored DOM tree. The conversion rules come from the webvtt DOM
-   * construction rules:
-   * http://dev.w3.org/html5/webvtt/#webvtt-cue-text-dom-construction-rules
-   * Current rules taken from revision on May 13, 2013.
-   */
-  void
-  ConvertNodeTreeToDOMTree(nsIContent* aParentContent);
-
-  /**
-   * Converts an internal webvtt node, i.e. one that has children, to an
-   * anonymous HTMLElement.
-   */
-  already_AddRefed<nsIContent>
-  ConvertInternalNodeToContent(const webvtt_node* aWebVTTNode);
-
-  /**
-   * Converts a leaf webvtt node, i.e. one that does not have children, to
-   * either a text node or processing instruction.
-   */
-  already_AddRefed<nsIContent>
-  ConvertLeafNodeToContent(const webvtt_node* aWebVTTNode);
+  void SetTrackElement(HTMLTrackElement* aTrackElement);
 
 private:
   void CueChanged();
   void SetDefaultCueSettings();
   void CreateCueOverlay();
   nsresult StashDocument(nsISupports* aGlobal);
 
   nsRefPtr<nsIDocument> mDocument;
   nsString mText;
   double mStartTime;
   double mEndTime;
 
   nsRefPtr<TextTrack> mTrack;
   nsRefPtr<HTMLTrackElement> mTrackElement;
-  webvtt_node *mHead;
   nsString mId;
   int32_t mPosition;
   int32_t mSize;
   bool mPauseOnExit;
   bool mSnapToLines;
   DirectionSetting mVertical;
   int mLine;
   TextTrackCueAlign mAlign;
 
   // Holds the computed DOM elements that represent the parsed cue text.
   // http://www.whatwg.org/specs/web-apps/current-work/#text-track-cue-display-state
   nsCOMPtr<nsIContent> mDisplayState;
   // Tells whether or not we need to recompute mDisplayState. This is set
   // anytime a property that relates to the display of the TextTrackCue is
   // changed.
   bool mReset;
+
+  static StaticRefPtr<nsIWebVTTParserWrapper> sParserWrapper;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TextTrackCue_h
rename from content/media/WebVTTLoadListener.cpp
rename to content/media/WebVTTListener.cpp
--- a/content/media/WebVTTLoadListener.cpp
+++ b/content/media/WebVTTListener.cpp
@@ -1,173 +1,180 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#include "WebVTTLoadListener.h"
+#include "WebVTTListener.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "nsIInputStream.h"
+#include "nsIWebVTTParserWrapper.h"
+#include "nsComponentManagerUtils.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_1(WebVTTLoadListener, mElement)
+NS_IMPL_CYCLE_COLLECTION_2(WebVTTListener, mElement, mParserWrapper)
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebVTTLoadListener)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebVTTListener)
+  NS_INTERFACE_MAP_ENTRY(nsIWebVTTListener)
   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebVTTListener)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebVTTLoadListener)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebVTTLoadListener)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebVTTListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebVTTListener)
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gTextTrackLog;
 # define LOG(...) PR_LOG(gTextTrackLog, PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 # define LOG(msg)
 #endif
 
-WebVTTLoadListener::WebVTTLoadListener(HTMLTrackElement* aElement)
+WebVTTListener::WebVTTListener(HTMLTrackElement* aElement)
   : mElement(aElement)
 {
   MOZ_ASSERT(mElement, "Must pass an element to the callback");
 #ifdef PR_LOGGING
   if (!gTextTrackLog) {
     gTextTrackLog = PR_NewLogModule("TextTrack");
   }
 #endif
-  LOG("WebVTTLoadListener created.");
+  LOG("WebVTTListener created.");
 }
 
-WebVTTLoadListener::~WebVTTLoadListener()
+WebVTTListener::~WebVTTListener()
 {
-  LOG("WebVTTLoadListener destroyed.");
+  LOG("WebVTTListener destroyed.");
+}
+
+NS_IMETHODIMP
+WebVTTListener::GetInterface(const nsIID &aIID,
+                             void** aResult)
+{
+  return QueryInterface(aIID, aResult);
 }
 
 nsresult
-WebVTTLoadListener::LoadResource()
+WebVTTListener::LoadResource()
 {
   if (!HTMLTrackElement::IsWebVTTEnabled()) {
     NS_WARNING("WebVTT support disabled."
                " See media.webvtt.enabled in about:config. ");
     return NS_ERROR_FAILURE;
   }
+  nsresult rv;
+  mParserWrapper = do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  LOG("Loading text track resource.");
-  webvtt_parser_t* parser = nullptr;
-  // Create parser here.
-  mParser.own(parser);
-  NS_ENSURE_TRUE(mParser != nullptr, NS_ERROR_FAILURE);
+  nsPIDOMWindow* window = mElement->OwnerDoc()->GetWindow();
+  rv = mParserWrapper->LoadParser(window);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mParserWrapper->Watch(this);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   mElement->mReadyState = HTMLTrackElement::LOADING;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebVTTLoadListener::OnStartRequest(nsIRequest* aRequest,
-                                   nsISupports* aContext)
+WebVTTListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+                                       nsIChannel* aNewChannel,
+                                       uint32_t aFlags,
+                                       nsIAsyncVerifyRedirectCallback* cb)
+{
+  if (mElement) {
+    mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebVTTListener::OnStartRequest(nsIRequest* aRequest,
+                               nsISupports* aContext)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebVTTLoadListener::OnStopRequest(nsIRequest* aRequest,
-                                  nsISupports* aContext,
-                                  nsresult aStatus)
+WebVTTListener::OnStopRequest(nsIRequest* aRequest,
+                              nsISupports* aContext,
+                              nsresult aStatus)
 {
-  // Flush parser here.
-  if(mElement->mReadyState != HTMLTrackElement::ERROR) {
+  if (mElement->mReadyState != HTMLTrackElement::ERROR) {
     mElement->mReadyState = HTMLTrackElement::LOADED;
   }
+  // Attempt to parse any final data the parser might still have.
+  mParserWrapper->Flush();
+  return NS_OK;
+}
+
+NS_METHOD
+WebVTTListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
+                           const char* aFromSegment, uint32_t aToOffset,
+                           uint32_t aCount, uint32_t* aWriteCount)
+{
+  nsCString buffer(aFromSegment, aCount);
+  WebVTTListener* listener = static_cast<WebVTTListener*>(aClosure);
+
+  if (NS_FAILED(listener->mParserWrapper->Parse(buffer))) {
+    LOG("Unable to parse chunk of WEBVTT text. Aborting.");
+    *aWriteCount = 0;
+    return NS_ERROR_FAILURE;
+  }
+
+  *aWriteCount = aCount;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebVTTLoadListener::OnDataAvailable(nsIRequest* aRequest,
-                                    nsISupports* aContext,
-                                    nsIInputStream* aStream,
-                                    uint64_t aOffset,
-                                    uint32_t aCount)
+WebVTTListener::OnDataAvailable(nsIRequest* aRequest,
+                                nsISupports* aContext,
+                                nsIInputStream* aStream,
+                                uint64_t aOffset,
+                                uint32_t aCount)
 {
   uint32_t count = aCount;
   while (count > 0) {
     uint32_t read;
     nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!read) {
       return NS_ERROR_FAILURE;
     }
     count -= read;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebVTTLoadListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
-                                           nsIChannel* aNewChannel,
-                                           uint32_t aFlags,
-                                           nsIAsyncVerifyRedirectCallback* cb)
+WebVTTListener::OnCue(const JS::Value &aCue, JSContext* aCx)
 {
-  if (mElement) {
-    mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
+  if (!aCue.isObject()) {
+    return NS_ERROR_FAILURE;
   }
+
+  TextTrackCue* cue;
+  nsresult rv = UNWRAP_OBJECT(VTTCue, aCx, &aCue.toObject(), cue);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  cue->SetTrackElement(mElement);
+  mElement->mTrack->AddCue(*cue);
+
   return NS_OK;
 }
 
+
 NS_IMETHODIMP
-WebVTTLoadListener::GetInterface(const nsIID &aIID,
-                                 void** aResult)
+WebVTTListener::OnRegion(const JS::Value &aRegion, JSContext* aCx)
 {
-  return QueryInterface(aIID, aResult);
-}
-
-NS_METHOD
-WebVTTLoadListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
-                               const char* aFromSegment, uint32_t aToOffset,
-                               uint32_t aCount, uint32_t* aWriteCount)
-{
-  //WebVTTLoadListener* loadListener = static_cast<WebVTTLoadListener*>(aClosure);
-  // Call parser incrementally on new data.
-  if (1) {
-    LOG("WebVTT parser disabled.");
-    LOG("Unable to parse chunk of WEBVTT text. Aborting.");
-    *aWriteCount = 0;
-    return NS_ERROR_FAILURE;
-  }
-  *aWriteCount = aCount;
+  // TODO: Implement VTTRegions see bug 897504
   return NS_OK;
 }
 
-void
-WebVTTLoadListener::OnParsedCue(webvtt_cue* aCue)
-{
-  nsRefPtr<TextTrackCue> textTrackCue;
-  // Create a new textTrackCue here.
-  // Copy settings from the parsed cue here.
-  mElement->mTrack->AddCue(*textTrackCue);
-}
-
-int
-WebVTTLoadListener::OnReportError(uint32_t aLine, uint32_t aCol,
-                                  webvtt_error aError)
-{
-#ifdef PR_LOGGING
-  // Get source webvtt file to display in the log
-  DOMString wideFile;
-  mElement->GetSrc(wideFile);
-
-  NS_ConvertUTF16toUTF8 file(wideFile);
-
-  const char* error = "parser error";
-
-  LOG("error: %s(%d:%d) - %s\n", file.get(), aLine, aCol, error);
-#endif
-  return 0;
-}
-
 } // namespace dom
 } // namespace mozilla
rename from content/media/WebVTTLoadListener.h
rename to content/media/WebVTTListener.h
--- a/content/media/WebVTTLoadListener.h
+++ b/content/media/WebVTTListener.h
@@ -1,90 +1,60 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #ifndef mozilla_dom_WebVTTLoadListener_h
 #define mozilla_dom_WebVTTLoadListener_h
 
+#include "nsIWebVTTListener.h"
 #include "nsIStreamListener.h"
 #include "nsIChannelEventSink.h"
+#include "nsAutoPtr.h"
 #include "nsIInterfaceRequestor.h"
-#include "nsAutoPtr.h"
-#include "nsAutoRef.h"
 #include "nsCycleCollectionParticipant.h"
 
-struct webvtt_parser_t;
-struct webvtt_cue;
-typedef int webvtt_error;
-
-template <>
-class nsAutoRefTraits<webvtt_parser_t> : public nsPointerRefTraits<webvtt_parser_t>
-{
-public:
-  static void Release(webvtt_parser_t* aParser) {
-    // Call parser dtor here.
-  }
-};
+class nsIWebVTTParserWrapper;
 
 namespace mozilla {
 namespace dom {
 
 class HTMLTrackElement;
 
-/**
- * Class that manages the libwebvtt parsing library and functions as an
- * interface between Gecko and libwebvtt.
- *
- * Currently it's only designed to work with an HTMLTrackElement. The
- * HTMLTrackElement controls the lifetime of the WebVTTLoadListener.
- *
- * The workflow of this class is as follows:
- *  - Gets Loaded via an HTMLTrackElement class.
- *  - As the HTMLTrackElement class gets new data WebVTTLoadListener's
- *    OnDataAvailable() function is called and data is passed to the libwebvtt
- *    parser.
- *  - When the libwebvtt parser has finished a cue it will call the callbacks
- *    that are exposed by the WebVTTLoadListener -- OnParsedCueWebVTTCallBack or
- *    OnReportErrorWebVTTCallBack if it has encountered an error.
- *  - When it has returned a cue successfully the WebVTTLoadListener will create
- *    a new TextTrackCue and add it to the HTMLTrackElement's TextTrack.
- */
-class WebVTTLoadListener MOZ_FINAL : public nsIStreamListener,
-                                     public nsIChannelEventSink,
-                                     public nsIInterfaceRequestor
+class WebVTTListener MOZ_FINAL : public nsIWebVTTListener,
+                                 public nsIStreamListener,
+                                 public nsIChannelEventSink,
+                                 public nsIInterfaceRequestor
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_NSIWEBVTTLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebVTTLoadListener,
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebVTTListener,
                                            nsIStreamListener)
 
 public:
-  WebVTTLoadListener(HTMLTrackElement* aElement);
-  ~WebVTTLoadListener();
+  WebVTTListener(HTMLTrackElement* aElement);
+  ~WebVTTListener();
 
-  // Loads the libwebvtt parser. Must call this function in order to the
-  // WebVTTLoadListener to be ready to accept data.
+  /**
+   * Loads the WebVTTListener. Must call this in order for the listener to be
+   * ready to parse data that is passed to it.
+   */
   nsresult LoadResource();
 
 private:
   static NS_METHOD ParseChunk(nsIInputStream* aInStream, void* aClosure,
                               const char* aFromSegment, uint32_t aToOffset,
                               uint32_t aCount, uint32_t* aWriteCount);
 
   nsRefPtr<HTMLTrackElement> mElement;
-  nsAutoRef<webvtt_parser_t> mParser;
-
-  void OnParsedCue(webvtt_cue* aCue);
-  int OnReportError(uint32_t aLine, uint32_t aCol, webvtt_error aError);
+  nsCOMPtr<nsIWebVTTParserWrapper> mParserWrapper;
 };
 
-
-
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_WebVTTLoadListener_h
+#endif // mozilla_dom_WebVTTListener_h
--- a/content/media/dash/DASHDecoder.h
+++ b/content/media/dash/DASHDecoder.h
@@ -13,17 +13,16 @@
  * see DASHDecoder.cpp for info on DASH interaction with the media engine.*/
 
 #if !defined(DASHDecoder_h_)
 #define DASHDecoder_h_
 
 #include "nsTArray.h"
 #include "nsIURI.h"
 #include "nsITimer.h"
-#include "nsThreadUtils.h"
 #include "MediaDecoder.h"
 #include "DASHReader.h"
 
 namespace mozilla {
 namespace net {
 class IMPDManager;
 class nsDASHMPDParser;
 class Representation;
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -1,19 +1,20 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-PARALLEL_DIRS += ['encoder']
-
-PARALLEL_DIRS += ['mediasource']
-
-PARALLEL_DIRS += ['webaudio']
+PARALLEL_DIRS += [
+  'encoder',
+  'mediasource',
+  'webaudio',
+  'webvtt'
+]
 
 if CONFIG['MOZ_RAW']:
     PARALLEL_DIRS += ['raw']
 
 if CONFIG['MOZ_OGG']:
     PARALLEL_DIRS += ['ogg']
 
 if CONFIG['MOZ_WAVE']:
@@ -135,17 +136,17 @@ CPP_SOURCES += [
     'TextTrackList.cpp',
     'TextTrackRegion.cpp',
     'TextTrackRegionList.cpp',
     'VideoFrameContainer.cpp',
     'VideoPlaybackQuality.cpp',
     'VideoSegment.cpp',
     'VideoStreamTrack.cpp',
     'VideoUtils.cpp',
-    'WebVTTLoadListener.cpp',
+    'WebVTTListener.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']:
     CPP_SOURCES += [
         'AudioNodeEngineNEON.cpp',
     ]
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -20,16 +20,20 @@
 # add it to gPlayTests in manifest.js. To test whether an invalid
 # resource correctly throws an error (and does not cause a crash or hang),
 # just add it to gErrorTests in manifest.js.
 
 # To test for a specific bug in handling a specific resource type,
 # make the test first check canPlayType for the type, and if it's not
 # supported, just do ok(true, "Type not supported") and stop the test.
 
+
+# Disabled for too many intermittent failures (bug 897108)
+#		test_playback_rate_playpause.html \
+
 MOCHITEST_FILES = \
 		allowed.sjs \
 		can_play_type_ogg.js \
 		can_play_type_wave.js \
 		can_play_type_webm.js \
 		can_play_type_dash.js \
 		can_play_type_mpeg.js \
 		cancellable_request.sjs \
@@ -136,18 +140,18 @@ MOCHITEST_FILES = \
 		$(filter disabled-for-intermittent-failures--bug-608634, test_error_in_video_document.html) \
 		test_texttrack.html \
 		test_texttrackcue.html \
 		test_timeupdate_small_files.html \
 		test_unseekable.html \
 		test_VideoPlaybackQuality.html \
 		test_VideoPlaybackQuality_disabled.html \
 		test_webvtt_disabled.html \
-		test_playback_rate_playpause.html \
 		test_bug895305.html \
+		test_bug895091.html \
 		$(NULL)
 
 # Don't run in suite
 ifndef MOZ_SUITE
 MOCHITEST_FILES += test_play_twice.html
 else
 $(filter disabled-pending-investigation--bug-598252, test_play_twice.html)
 endif
@@ -263,16 +267,17 @@ MOCHITEST_FILES += \
 		test-8-7.1.opus \
 		vbr.mp3 \
 		video-overhang.ogg \
 		file_a4_tone.ogg \
 		detodos.opus \
 		notags.mp3 \
 		id3tags.mp3 \
 		basic.vtt \
+		long.vtt \
 		$(NULL)
 
 # Wave sample files
 MOCHITEST_FILES += \
 		big.wav \
 		bogus.wav \
 		r11025_msadpcm_c1.wav \
 		r11025_s16_c1.wav \
new file mode 100644
--- /dev/null
+++ b/content/media/test/long.vtt
@@ -0,0 +1,8001 @@
+WEBVTT
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+
+1
+00:00.500 --> 00:00.700
+This
+
+2
+00:01.200 --> 00:02.400
+Is
+
+3
+00:02.710 --> 00:02.910
+A
+
+4
+00:03.217 --> 00:03.989
+Test
+