Import Cobalt 9.140119

Change-Id: Idfa97844364064c39ec81cbe3f998a22b7009be6
diff --git a/src/cobalt/base/c_val.cc b/src/cobalt/base/c_val.cc
index f0c9a84..d44f9fa 100644
--- a/src/cobalt/base/c_val.cc
+++ b/src/cobalt/base/c_val.cc
@@ -48,7 +48,9 @@
   delete registered_vars_;
 }
 
-void CValManager::RegisterCVal(const CValDetail::CValBase* cval) {
+void CValManager::RegisterCVal(
+    const CValDetail::CValBase* cval,
+    scoped_refptr<base::RefCountedThreadSafeLock>* value_lock) {
   base::AutoLock auto_lock(cvals_lock_);
 
   // CVals cannot share name.  If this assert is triggered, we are trying to
@@ -57,6 +59,7 @@
   DCHECK(registered_vars_->find(cval->GetName()) == registered_vars_->end());
 
   (*registered_vars_)[cval->GetName()] = cval;
+  *value_lock = value_lock_refptr_;
 }
 
 void CValManager::UnregisterCVal(const CValDetail::CValBase* cval) {
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h
index 92f8f6c..e85b935 100644
--- a/src/cobalt/base/c_val.h
+++ b/src/cobalt/base/c_val.h
@@ -500,8 +500,10 @@
   ~CValManager();
 
   // Called in CVal constructors to register/deregister themselves with the
-  // system.
-  void RegisterCVal(const CValDetail::CValBase* cval);
+  // system.  Registering a CVal will also provide that CVal with a value lock
+  // to lock when it modifies its value.
+  void RegisterCVal(const CValDetail::CValBase* cval,
+                    scoped_refptr<base::RefCountedThreadSafeLock>* value_lock);
   void UnregisterCVal(const CValDetail::CValBase* cval);
 
 #if defined(ENABLE_DEBUG_C_VAL)
@@ -586,7 +588,6 @@
     CommonConstructor();
   }
   virtual ~CValImpl() {
-    base::AutoLock auto_lock(value_lock_refptr_->GetLock());
     if (registered_) {
       CValManager::GetInstance()->UnregisterCVal(this);
     }
@@ -673,8 +674,7 @@
   void RegisterWithManager() {
     if (!registered_) {
       CValManager* manager = CValManager::GetInstance();
-      manager->RegisterCVal(this);
-      value_lock_refptr_ = manager->value_lock_refptr_;
+      manager->RegisterCVal(this, &value_lock_refptr_);
       registered_ = true;
     }
   }
diff --git a/src/cobalt/base/c_val_collection_timer_stats.h b/src/cobalt/base/c_val_collection_timer_stats.h
index eda96b9..4f21cb7 100644
--- a/src/cobalt/base/c_val_collection_timer_stats.h
+++ b/src/cobalt/base/c_val_collection_timer_stats.h
@@ -63,6 +63,9 @@
     start_time_ = base::TimeTicks();
   }
 
+  // Stops any started timer and this sample is ignored.
+  void Cancel() { start_time_ = base::TimeTicks(); }
+
   // Flush the collection. This causes |CValCollectionStats| to update its stats
   // and clear the entries.
   void Flush() { entry_stats_.Flush(); }
diff --git a/src/cobalt/base/c_val_test.cc b/src/cobalt/base/c_val_test.cc
index 3dc416d..6281766 100644
--- a/src/cobalt/base/c_val_test.cc
+++ b/src/cobalt/base/c_val_test.cc
@@ -13,7 +13,10 @@
 // limitations under the License.
 
 #include <limits>
+#include <utility>
 
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
 #include "base/time.h"
 #include "cobalt/base/c_val.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -584,4 +587,113 @@
   EXPECT_EQ(test_hook.value_changed_count_, 1);
 }
 
+namespace {
+// Helper class for the RemoveAndRead test defined below.
+class ReadCValThread : public base::SimpleThread {
+ public:
+  ReadCValThread(const char* test_cval_name, int num_read_cycles,
+                 std::pair<std::string, std::string> valid_values,
+                 base::WaitableEvent* thread_ready_event,
+                 base::WaitableEvent* start_processing_event)
+      : base::SimpleThread("ReadCValThread"),
+        test_cval_name_(test_cval_name),
+        num_read_cycles_(num_read_cycles),
+        valid_values_(valid_values),
+        thread_ready_event_(thread_ready_event),
+        start_processing_event_(start_processing_event) {}
+
+  void Run() OVERRIDE {
+    thread_ready_event_->Signal();
+    start_processing_event_->Wait();
+
+    base::CValManager* cvm = base::CValManager::GetInstance();
+    for (int i = 0; i < num_read_cycles_; ++i) {
+      base::optional<std::string> result =
+          cvm->GetValueAsString(test_cval_name_);
+      if (result) {
+        EXPECT_TRUE(*result == valid_values_.first ||
+                    *result == valid_values_.second);
+      }
+    }
+  }
+
+ private:
+  const char* test_cval_name_;
+  int num_read_cycles_;
+  std::pair<std::string, std::string> valid_values_;
+  base::WaitableEvent* thread_ready_event_;
+  base::WaitableEvent* start_processing_event_;
+};
+
+// Helper class for the RemoveAndRead test defined below.
+class CreateDestroyCValThread : public base::SimpleThread {
+ public:
+  CreateDestroyCValThread(const char* test_cval_name,
+                          int num_create_destroy_cycles,
+                          std::pair<std::string, std::string> valid_values,
+                          base::WaitableEvent* thread_ready_event,
+                          base::WaitableEvent* start_processing_event)
+      : base::SimpleThread("CreateDestroyCValThread"),
+        test_cval_name_(test_cval_name),
+        num_create_destroy_cycles_(num_create_destroy_cycles),
+        valid_values_(valid_values),
+        thread_ready_event_(thread_ready_event),
+        start_processing_event_(start_processing_event) {}
+
+  void Run() OVERRIDE {
+    thread_ready_event_->Signal();
+    start_processing_event_->Wait();
+
+    for (int i = 0; i < num_create_destroy_cycles_; ++i) {
+      base::CVal<std::string> test_cval(test_cval_name_, valid_values_.first,
+                                        "Description");
+      test_cval = valid_values_.second;
+    }
+  }
+
+ private:
+  const char* test_cval_name_;
+  int num_create_destroy_cycles_;
+  std::pair<std::string, std::string> valid_values_;
+  base::WaitableEvent* thread_ready_event_;
+  base::WaitableEvent* start_processing_event_;
+};
+
+}  // namespace
+
+// Tests that we can create and destroy cvals no problem while simultaneously
+// reading from them.  The test creates two threads, a reader thread and a
+// creater/destroyer (and writer) thread.  These both attempt to access the same
+// cval as fast as possible.
+TEST(CValTest, RemoveAndRead) {
+  const char* kTestCValName = "TestCVal";
+  const int kNumReadCycles = 10000;
+  const int kNuMCreateDestroyCycles = 10000;
+  const std::pair<std::string, std::string> valid_values("hello", "66");
+  base::WaitableEvent read_cval_thread_ready(true, false);
+  base::WaitableEvent create_destroy_cval_thread_ready(true, false);
+  base::WaitableEvent start_processing_event(true, false);
+
+  // Create and start both threads.
+  ReadCValThread read_cval_thread(kTestCValName, kNumReadCycles, valid_values,
+                                  &read_cval_thread_ready,
+                                  &start_processing_event);
+  CreateDestroyCValThread create_destroy_cval_thread(
+      kTestCValName, kNuMCreateDestroyCycles, valid_values,
+      &create_destroy_cval_thread_ready, &start_processing_event);
+  read_cval_thread.Start();
+  create_destroy_cval_thread.Start();
+
+  // Wait until both threads are initialized and ready.
+  read_cval_thread_ready.Wait();
+  create_destroy_cval_thread_ready.Wait();
+
+  // Signal for the processing/testing to begin.
+  start_processing_event.Signal();
+
+  // Wait for both threads to complete.
+  read_cval_thread.Join();
+  create_destroy_cval_thread.Join();
+}
+
 }  // namespace base
diff --git a/src/cobalt/base/clock.h b/src/cobalt/base/clock.h
index 01d5442..e2a4bbc 100644
--- a/src/cobalt/base/clock.h
+++ b/src/cobalt/base/clock.h
@@ -49,6 +49,31 @@
   ~SystemMonotonicClock() OVERRIDE {}
 };
 
+// The MinimumResolutionClock modifies the output of an existing clock by
+// clamping its minimum resolution to a predefined amount.  This is implemented
+// by rounding down the existing clock's time to the previous multiple of the
+// desired clock resolution.
+class MinimumResolutionClock : public Clock {
+ public:
+  MinimumResolutionClock(scoped_refptr<Clock> parent,
+                         const base::TimeDelta& min_resolution)
+      : parent_(parent),
+        min_resolution_in_microseconds_(min_resolution.InMicroseconds()) {
+    DCHECK(parent);
+  }
+
+  base::TimeDelta Now() OVERRIDE {
+    base::TimeDelta now = parent_->Now();
+    int64 microseconds = now.InMicroseconds();
+    return base::TimeDelta::FromMicroseconds(
+        microseconds - (microseconds % min_resolution_in_microseconds_));
+  }
+
+ private:
+  scoped_refptr<Clock> parent_;
+  const int64_t min_resolution_in_microseconds_;
+};
+
 // The OffsetClock takes a parent clock and an offset upon construction, and
 // when queried for the time it returns the time of the parent clock offset by
 // the specified offset.
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 236fcef..29b055b 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -317,6 +317,8 @@
     "h5vcc-location-src "
     "https://www.youtube.com/tv "
     "https://www.youtube.com/tv/ "
+    "https://web-green-qa.youtube.com/tv "
+    "https://web-green-qa.youtube.com/tv/ "
     "https://web-release-qa.youtube.com/tv "
     "https://web-release-qa.youtube.com/tv/ "
 #if defined(ENABLE_ABOUT_SCHEME)
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index b916179..b575c58 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -624,7 +624,7 @@
 
   web_module_stat_tracker_->OnEndInjectEvent(
       window_->HasPendingAnimationFrameCallbacks(),
-      layout_manager_->IsNewRenderTreePending());
+      layout_manager_->IsRenderTreePending());
 }
 
 void WebModule::Impl::ExecuteJavascript(
@@ -657,7 +657,7 @@
   // Notify the stat tracker that the animation frame callbacks have finished.
   // This may end the current event being tracked.
   web_module_stat_tracker_->OnRanAnimationFrameCallbacks(
-      layout_manager_->IsNewRenderTreePending());
+      layout_manager_->IsRenderTreePending());
 }
 
 void WebModule::Impl::OnRenderTreeProduced(
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 05f7d20..2f807b4 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-96542
\ No newline at end of file
+140119
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index ee9c278..da47e25 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -47,38 +47,22 @@
     # Contains the current font package selection.  This can be used to trade
     # font quality, coverage, and latency for different font package sizes.
     # The font package can be one of the following options:
-    #   'expanded' -- The largest package. It includes everything in the
-    #                 'standard' package, along with 'bold' weight CJK. It is
-    #                 recommended that 'local_font_cache_size_in_bytes' be
-    #                 increased to 24MB when using this package to account for
-    #                 the extra memory required by bold CJK. This package is
-    #                 ~48.7MB.
     #   'standard' -- The default package. It includes all sans-serif, serif,
     #                 and FCC fonts, non-CJK fallback fonts in both 'normal' and
-    #                 'bold' weights, and 'normal' weight CJK ('bold' weight CJK
-    #                 is synthesized from it). This package is ~29.4MB.
-    #   'limited_with_jp' -- A significantly smaller package than 'standard'.
-    #                 This package removes all but 'normal' and 'bold' weighted
+    #                 'bold' weights, and 'normal' weight CJK ('bold' weight
+    #                 CJK is synthesized from it). This package is ~29.4MB.
+    #   'limited'  -- A significantly smaller package than 'standard'. This
+    #                 package removes all but 'normal' and 'bold' weighted
     #                 sans-serif and serif, removes the FCC fonts (which must be
     #                 provided by the system or downloaded from the web),
-    #                 removes the 'bold' weighted non-CJK fallback fonts (the
-    #                 'normal' weight is still included and is used to
-    #                 synthesize bold), and replaces standard CJK with low
-    #                 quality CJK. However, higher quality Japanese is still
-    #                 included. Because low quality CJK cannot synthesize bold,
-    #                 bold glyphs are unavailable in Chinese and Korean. This
-    #                 package is ~10.9MB.
-    #   'limited'  -- A smaller package than 'limited_with_jp'. The two packages
-    #                 are identical with the exception that 'limited' does not
-    #                 include the higher quality Japanese font; instead it
-    #                 relies on low quality CJK for all CJK characters. Because
+    #                 and replaces standard CJK with low quality CJK. Because
     #                 low quality CJK cannot synthesize bold, bold glyphs are
-    #                 unavailable in Chinese, Japanese, and Korean. This package
+    #                 unavailable in Chinese, Japanese and Korean. This package
     #                 is ~7.7MB.
     #   'minimal'  -- The smallest possible font package. It only includes
     #                 Roboto's Basic Latin characters. Everything else must be
     #                 provided by the system or downloaded from the web. This
-    #                 package is ~16.4KB.
+    #                 package is ~35.4KB.
     # NOTE: When bold is needed, but unavailable, it is typically synthesized,
     #       resulting in lower quality glyphs than those generated directly from
     #       a bold font. However, this does not occur with low quality CJK,
@@ -111,7 +95,6 @@
     'cobalt_font_package_override_fallback_lang_non_cjk%': -1,
     'cobalt_font_package_override_fallback_lang_cjk%': -1,
     'cobalt_font_package_override_fallback_lang_cjk_low_quality%': -1,
-    'cobalt_font_package_override_fallback_lang_jp%': -1,
     'cobalt_font_package_override_fallback_emoji%': -1,
     'cobalt_font_package_override_fallback_symbols%': -1,
 
diff --git a/src/cobalt/content/fonts/README.md b/src/cobalt/content/fonts/README.md
index 1181646..0c2edfc 100644
--- a/src/cobalt/content/fonts/README.md
+++ b/src/cobalt/content/fonts/README.md
@@ -41,23 +41,6 @@
     }
 
 ### Package Profiles
-*  'expanded' -- The largest package. It includes everything in the 'standard'
-                 package, along with 'bold' weight CJK. It is recommended that
-                 'local_font_cache_size_in_bytes' be increased to 24MB when
-                 using this package to account for the extra memory required by
-                 bold CJK. This package is ~48.7MB.
-
-                 Package category values:
-                   'package_named_sans_serif': 4,
-                   'package_named_serif': 3,
-                   'package_named_fcc_fonts': 2,
-                   'package_fallback_lang_non_cjk': 2,
-                   'package_fallback_lang_cjk': 2,
-                   'package_fallback_lang_cjk_low_quality': 0,
-                   'package_fallback_lang_jp': 0,
-                   'package_fallback_emoji': 1,
-                   'package_fallback_symbols': 1,
-
 *  'standard' -- The default package. It includes all sans-serif, serif, and FCC
                  fonts, non-CJK fallback fonts in both 'normal' and 'bold'
                  weights, and 'normal' weight CJK ('bold' weight CJK is
@@ -70,20 +53,16 @@
                   'package_fallback_lang_non_cjk': 2,
                   'package_fallback_lang_cjk': 1,
                   'package_fallback_lang_cjk_low_quality': 0,
-                  'package_fallback_lang_jp': 0,
                   'package_fallback_emoji': 1,
                   'package_fallback_symbols': 1,
 
-*  'limited_with_jp' -- A significantly smaller package than 'standard'. This
-                 package removes all but 'normal' and 'bold' weighted sans-serif
-                 and serif, removes the FCC fonts (which must be provided by the
-                 system or downloaded from the web), removes the 'bold' weighted
-                 non-CJK fallback fonts (the 'normal' weight is still included
-                 and is used to synthesize bold), and replaces standard CJK with
-                 low quality CJK. However, higher quality Japanese is still
-                 included. Because low quality CJK cannot synthesize bold, bold
-                 glyphs are unavailable in Chinese and Korean. This package is
-                 ~10.9MB.
+*  'limited'  -- A significantly smaller package than 'standard'. This package
+                 removes all but 'normal' and 'bold' weighted sans-serif and
+                 serif, removes the FCC fonts (which must be provided by the
+                 system or downloaded from the web), and replaces standard CJK
+                 with low quality CJK. Because low quality CJK cannot synthesize
+                 bold, bold glyphs are unavailable in Chinese, Japanese and
+                 Korean. This package is ~7.7MB.
 
                  Package category values:
                   'package_named_sans_serif': 2,
@@ -92,31 +71,12 @@
                   'package_fallback_lang_non_cjk': 1,
                   'package_fallback_lang_cjk': 0,
                   'package_fallback_lang_cjk_low_quality': 1,
-                  'package_fallback_lang_jp': 1,
-                  'package_fallback_emoji': 1,
-                  'package_fallback_symbols': 1,
-
-*  'limited'  -- A smaller package than 'limited_with_jp'. The two packages are
-                 identical with the exception that 'limited' does not include
-                 the higher quality Japanese font; instead it relies on low
-                 quality CJK for all CJK characters. Because low quality CJK
-                 cannot synthesize bold, bold glyphs are unavailable in Chinese,
-                 Japanese, and Korean. This package is ~7.7MB.
-
-                 Package category values:
-                  'package_named_sans_serif': 2,
-                  'package_named_serif': 0,
-                  'package_named_fcc_fonts': 0,
-                  'package_fallback_lang_non_cjk': 1,
-                  'package_fallback_lang_cjk': 0,
-                  'package_fallback_lang_cjk_low_quality': 1,
-                  'package_fallback_lang_jp': 0,
                   'package_fallback_emoji': 1,
                   'package_fallback_symbols': 1,
 
 *  'minimal'  -- The smallest possible font package. It only includes Roboto's
                  Basic Latin characters. Everything else must be provided by the
-                 system or downloaded from the web. This package is ~16.4KB.
+                 system or downloaded from the web. This package is ~35.4KB.
 
                  Package category values:
                   'package_named_sans_serif': 0,
@@ -125,7 +85,6 @@
                   'package_fallback_lang_non_cjk': 0,
                   'package_fallback_lang_cjk': 0,
                   'package_fallback_lang_cjk_low_quality': 0,
-                  'package_fallback_lang_jp': 0,
                   'package_fallback_emoji': 0,
                   'package_fallback_symbols': 0,
 
@@ -160,10 +119,6 @@
        included when 'package_fallback_lang_cjk' has a value of '0'. This is the
        only category of fonts that is not synthetically boldable.
 
-  *  'package_fallback_lang_jp':
-       Higher quality Japanese language-specific fallback fonts. These should
-       only be included when 'package_fallback_lang_cjk' has a value of '0'.
-
   *  'package_fallback_emoji':
        Emoji-related fallback fonts.
 
@@ -219,8 +174,6 @@
        'package_fallback_lang_cjk'
   *  'cobalt_font_package_override_fallback_lang_cjk_low_quality' ==>
        'package_fallback_lang_cjk_low_quality'
-  *  'cobalt_font_package_override_fallback_lang_jp' ==>
-       'package_fallback_lang_jp'
   *  'cobalt_font_package_override_fallback_emoji' ==>
        'package_fallback_emoji'
   *  'cobalt_font_package_override_fallback_symbols' ==>
diff --git a/src/cobalt/content/fonts/config/common/fonts.xml b/src/cobalt/content/fonts/config/common/fonts.xml
index b60cc92..fc579cc 100644
--- a/src/cobalt/content/fonts/config/common/fonts.xml
+++ b/src/cobalt/content/fonts/config/common/fonts.xml
@@ -346,9 +346,6 @@
     <family lang="ko" pages="0-4,17,30,32-39,41,43,46-159,169,172-215,249-251,254-255,497-498,512-658,660-682,685,687,689,691-696,698-702,704-718,760-761">
         <font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
     </family>
-    <family lang="ja" pages="0,32,34-35,46-159,249-250,254-255,498,512-523,525-527,530-538,540-543,545-547,550,552,554-559,561,563-568,570,572-573,575-579,582-584,586-594,596-608,610-612,614-618,620,622-625,627-628,630-631,633-638,640,642-646,649-655,658,660-664,666,669-678,681,695-696,760-761">
-        <font weight="400" style="normal">NotoSansJP-Regular.otf</font>
-    </family>
     <family pages="0,32-33,35-39,41,43,48,50,254,496-502,4068,4072">
         <font weight="400" style="normal">NotoEmoji-Regular.ttf</font>
     </family>
diff --git a/src/cobalt/content/fonts/font_files/NotoSansCJK-Bold.ttc b/src/cobalt/content/fonts/font_files/NotoSansCJK-Bold.ttc
deleted file mode 100644
index 09707f9..0000000
--- a/src/cobalt/content/fonts/font_files/NotoSansCJK-Bold.ttc
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/content/fonts/font_files/NotoSansJP-Regular.otf b/src/cobalt/content/fonts/font_files/NotoSansJP-Regular.otf
deleted file mode 100644
index 40b064a..0000000
--- a/src/cobalt/content/fonts/font_files/NotoSansJP-Regular.otf
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 3ca1222..89dd4ff 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -66,6 +66,14 @@
   DISALLOW_COPY_AND_ASSIGN(RelayLoadEvent);
 };
 
+namespace {
+// Ensure that the timer resolution is at the lowest 20 microseconds in
+// order to mitigate potential Spectre-related attacks.  This is following
+// Mozilla's lead as described here:
+//   https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/
+const int64_t kPerformanceTimerMinResolutionInMicroseconds = 20;
+}  // namespace
+
 Window::Window(int width, int height, cssom::CSSParser* css_parser,
                Parser* dom_parser, loader::FetcherFactory* fetcher_factory,
                render_tree::ResourceProvider** resource_provider,
@@ -106,7 +114,10 @@
           media_source_registry, resource_provider, animated_image_tracker,
           image_cache, reduced_image_cache_capacity_manager,
           remote_typeface_cache, mesh_cache, dom_stat_tracker, language)),
-      performance_(new Performance(new base::SystemMonotonicClock())),
+      performance_(new Performance(new base::MinimumResolutionClock(
+          new base::SystemMonotonicClock(),
+              base::TimeDelta::FromMicroseconds(
+                  kPerformanceTimerMinResolutionInMicroseconds)))),
       ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document(
           html_element_context_.get(),
           Document::Options(
diff --git a/src/cobalt/layout/layout.cc b/src/cobalt/layout/layout.cc
index 2331e2e..1a26b41 100644
--- a/src/cobalt/layout/layout.cc
+++ b/src/cobalt/layout/layout.cc
@@ -127,20 +127,11 @@
   }
 }
 
-scoped_refptr<render_tree::Node> Layout(
-    const icu::Locale& locale, const scoped_refptr<dom::Document>& document,
-    int dom_max_element_depth, UsedStyleProvider* used_style_provider,
+scoped_refptr<render_tree::Node> GenerateRenderTreeFromBoxTree(
+    UsedStyleProvider* used_style_provider,
     LayoutStatTracker* layout_stat_tracker,
-    icu::BreakIterator* line_break_iterator,
-    icu::BreakIterator* character_break_iterator,
     scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block) {
-  TRACE_EVENT0("cobalt::layout", "Layout()");
-  UpdateComputedStylesAndLayoutBoxTree(
-      locale, document, dom_max_element_depth, used_style_provider,
-      layout_stat_tracker, line_break_iterator, character_break_iterator,
-      initial_containing_block);
-
-  // Add to render tree.
+  TRACE_EVENT0("cobalt::layout", "GenerateRenderTreeFromBoxTree()");
   render_tree::CompositionNode::Builder render_tree_root_builder;
   {
     TRACE_EVENT0("cobalt::layout", kBenchmarkStatRenderAndAnimate);
diff --git a/src/cobalt/layout/layout.h b/src/cobalt/layout/layout.h
index a777318..678fe54 100644
--- a/src/cobalt/layout/layout.h
+++ b/src/cobalt/layout/layout.h
@@ -38,7 +38,8 @@
 // (https://www.w3.org/TR/CSS2/visuren.html) as recommended by a newer draft
 // (http://dev.w3.org/csswg/css-box/) which is undergoing active changes.
 
-// Update the computed styles, then generate and layout the box tree.
+// Update the computed styles, then generate and layout the box tree produced
+// by the given document.
 void UpdateComputedStylesAndLayoutBoxTree(
     const icu::Locale& locale, const scoped_refptr<dom::Document>& document,
     int dom_max_element_depth, UsedStyleProvider* used_style_provider,
@@ -47,15 +48,11 @@
     icu::BreakIterator* character_break_iterator,
     scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block);
 
-// Main entry point to the layout engine.
-// Produces the render tree (along with corresponding animations) which is a
-// result of recursive layout of the given HTML element.
-scoped_refptr<render_tree::Node> Layout(
-    const icu::Locale& locale, const scoped_refptr<dom::Document>& document,
-    int dom_max_element_depth, UsedStyleProvider* used_style_provider,
+// Generates the render tree (along with corresponding animations) of the box
+// tree contained within the provided containing block.
+scoped_refptr<render_tree::Node> GenerateRenderTreeFromBoxTree(
+    UsedStyleProvider* used_style_provider,
     LayoutStatTracker* layout_stat_tracker,
-    icu::BreakIterator* line_break_iterator,
-    icu::BreakIterator* character_break_iterator,
     scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block);
 
 }  // namespace layout
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc
index 0ada5d3..36ddfc8 100644
--- a/src/cobalt/layout/layout_manager.cc
+++ b/src/cobalt/layout/layout_manager.cc
@@ -57,9 +57,10 @@
   void Suspend();
   void Resume();
 
-  bool IsNewRenderTreePending() const;
+  bool IsRenderTreePending() const;
 
  private:
+  void DirtyLayout();
   void StartLayoutTimer();
   void DoLayoutAndProduceRenderTree();
 
@@ -73,11 +74,13 @@
   const OnRenderTreeProducedCallback on_render_tree_produced_callback_;
   const LayoutTrigger layout_trigger_;
 
-  // This flag indicates whether or not we should do a re-layout.  The flag
-  // is checked at a regular interval (e.g. 60Hz) and if it is set to true,
-  // a layout is initiated and it is set back to false.  Events such as
-  // DOM mutations will set this flag back to true.
-  base::CVal<bool> layout_dirty_;
+  // Setting these flags triggers an update of the layout box tree and the
+  // generation of a new render tree at a regular interval (e.g. 60Hz). Events
+  // such as DOM mutations cause them to be set to true. While the render tree
+  // is excusively produced at the regular interval, the box tree can also be
+  // updated via a call to DoSynchronousLayout().
+  bool are_computed_styles_and_box_tree_dirty_;
+  base::CVal<bool> is_render_tree_pending_;
 
   // Construction of |BreakIterator| requires a disk read, so we cache them
   // in the layout manager in order to reuse them with all layouts happening
@@ -106,9 +109,7 @@
 void UpdateCamera(
     float width_to_height_aspect_ratio, scoped_refptr<dom::Camera3DImpl> camera,
     float max_horizontal_fov_rad, float max_vertical_fov_rad,
-    render_tree::MatrixTransform3DNode::Builder* transform_node_builder,
-    base::TimeDelta time) {
-  UNREFERENCED_PARAMETER(time);
+    render_tree::MatrixTransform3DNode::Builder* transform_node_builder) {
   transform_node_builder->transform = camera->QueryViewPerspectiveMatrix(
       width_to_height_aspect_ratio, max_horizontal_fov_rad,
       max_vertical_fov_rad);
@@ -151,9 +152,10 @@
           base::Bind(&AttachCameraNodes, window), enable_image_animations)),
       on_render_tree_produced_callback_(on_render_tree_produced),
       layout_trigger_(layout_trigger),
-      layout_dirty_(StringPrintf("%s.Layout.IsDirty", name.c_str()), true,
-                    "Non-zero when the layout is dirty and a new render tree "
-                    "is pending."),
+      are_computed_styles_and_box_tree_dirty_(true),
+      is_render_tree_pending_(
+          StringPrintf("%s.Layout.IsRenderTreePending", name.c_str()), true,
+          "Non-zero when a new render tree is pending."),
       layout_timer_(true, true, true),
       dom_max_element_depth_(dom_max_element_depth),
       layout_refresh_rate_(layout_refresh_rate),
@@ -197,7 +199,7 @@
 #if defined(ENABLE_TEST_RUNNER)
   if (layout_trigger_ == kTestRunnerMode &&
       !window_->test_runner()->should_wait()) {
-    layout_dirty_ = true;
+    DirtyLayout();
 
     // Run the |DoLayoutAndProduceRenderTree| task after onload event finished.
     MessageLoop::current()->PostTask(
@@ -210,7 +212,7 @@
 
 void LayoutManager::Impl::OnMutation() {
   if (layout_trigger_ == kOnDocumentMutation) {
-    layout_dirty_ = true;
+    DirtyLayout();
   }
 }
 
@@ -220,11 +222,14 @@
     return;
   }
 
-  layout::UpdateComputedStylesAndLayoutBoxTree(
-      locale_, window_->document(), dom_max_element_depth_,
-      used_style_provider_.get(), layout_stat_tracker_,
-      line_break_iterator_.get(), character_break_iterator_.get(),
-      &initial_containing_block_);
+  if (are_computed_styles_and_box_tree_dirty_) {
+    layout::UpdateComputedStylesAndLayoutBoxTree(
+        locale_, window_->document(), dom_max_element_depth_,
+        used_style_provider_.get(), layout_stat_tracker_,
+        line_break_iterator_.get(), character_break_iterator_.get(),
+        &initial_containing_block_);
+    are_computed_styles_and_box_tree_dirty_ = false;
+  }
 }
 
 void LayoutManager::Impl::Suspend() {
@@ -245,18 +250,18 @@
 void LayoutManager::Impl::Resume() {
   // Mark that we are no longer suspended and indicate that the layout is
   // dirty since when Suspend() was called we invalidated our previous layout.
-  layout_dirty_ = true;
+  DirtyLayout();
   suspended_ = false;
 }
 
-bool LayoutManager::Impl::IsNewRenderTreePending() const {
-  return layout_dirty_;
+bool LayoutManager::Impl::IsRenderTreePending() const {
+  return is_render_tree_pending_;
 }
 
 #if defined(ENABLE_TEST_RUNNER)
 void LayoutManager::Impl::DoTestRunnerLayoutCallback() {
   DCHECK_EQ(kTestRunnerMode, layout_trigger_);
-  layout_dirty_ = true;
+  DirtyLayout();
 
   if (layout_trigger_ == kTestRunnerMode &&
       window_->test_runner()->should_wait()) {
@@ -272,6 +277,11 @@
 }
 #endif  // ENABLE_TEST_RUNNER
 
+void LayoutManager::Impl::DirtyLayout() {
+  are_computed_styles_and_box_tree_dirty_ = true;
+  is_render_tree_pending_ = true;
+}
+
 void LayoutManager::Impl::StartLayoutTimer() {
   // TODO: Eventually we would like to instead base our layouts off of a
   //       "refresh" signal generated by the rasterizer, instead of trying to
@@ -304,7 +314,7 @@
 
   bool has_layout_processing_started = false;
   if (window_->HasPendingAnimationFrameCallbacks()) {
-    if (layout_dirty_) {
+    if (are_computed_styles_and_box_tree_dirty_) {
       has_layout_processing_started = true;
       TRACE_EVENT_BEGIN0("cobalt::layout", kBenchmarkStatLayout);
       // Update our computed style before running animation callbacks, so that
@@ -321,7 +331,10 @@
     window_->RunAnimationFrameCallbacks();
   }
 
-  if (layout_dirty_) {
+  // It should never be possible for for the computed styles and box tree to
+  // be dirty when a render tree is not pending.
+  DCHECK(is_render_tree_pending_ || !are_computed_styles_and_box_tree_dirty_);
+  if (is_render_tree_pending_) {
     if (!has_layout_processing_started) {
       // We want to catch the beginning of all layout processing.  If it didn't
       // begin before the call to RunAnimationFrameCallbacks(), then the flow
@@ -329,11 +342,19 @@
       TRACE_EVENT_BEGIN0("cobalt::layout", kBenchmarkStatLayout);
     }
 
-    scoped_refptr<render_tree::Node> render_tree_root = layout::Layout(
-        locale_, window_->document(), dom_max_element_depth_,
-        used_style_provider_.get(), layout_stat_tracker_,
-        line_break_iterator_.get(), character_break_iterator_.get(),
-        &initial_containing_block_);
+    if (are_computed_styles_and_box_tree_dirty_) {
+      layout::UpdateComputedStylesAndLayoutBoxTree(
+          locale_, window_->document(), dom_max_element_depth_,
+          used_style_provider_.get(), layout_stat_tracker_,
+          line_break_iterator_.get(), character_break_iterator_.get(),
+          &initial_containing_block_);
+      are_computed_styles_and_box_tree_dirty_ = false;
+    }
+
+    scoped_refptr<render_tree::Node> render_tree_root =
+        layout::GenerateRenderTreeFromBoxTree(used_style_provider_.get(),
+                                              layout_stat_tracker_,
+                                              &initial_containing_block_);
     bool run_on_render_tree_produced_callback = true;
 #if defined(ENABLE_TEST_RUNNER)
     if (layout_trigger_ == kTestRunnerMode &&
@@ -348,8 +369,7 @@
                                 *document->timeline()->current_time())));
     }
 
-    layout_dirty_ = false;
-
+    is_render_tree_pending_ = false;
     TRACE_EVENT_END0("cobalt::layout", kBenchmarkStatLayout);
   }
 }
@@ -368,8 +388,8 @@
 
 void LayoutManager::Suspend() { impl_->Suspend(); }
 void LayoutManager::Resume() { impl_->Resume(); }
-bool LayoutManager::IsNewRenderTreePending() const {
-  return impl_->IsNewRenderTreePending();
+bool LayoutManager::IsRenderTreePending() const {
+  return impl_->IsRenderTreePending();
 }
 
 }  // namespace layout
diff --git a/src/cobalt/layout/layout_manager.h b/src/cobalt/layout/layout_manager.h
index a2cde5c..799516b 100644
--- a/src/cobalt/layout/layout_manager.h
+++ b/src/cobalt/layout/layout_manager.h
@@ -71,7 +71,7 @@
   void Suspend();
   void Resume();
 
-  bool IsNewRenderTreePending() const;
+  bool IsRenderTreePending() const;
 
  private:
   class Impl;
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index 4811118..6cfe7bc 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -231,9 +231,7 @@
 }
 
 void AnimateVideoImage(const ReplacedBox::ReplaceImageCB& replace_image_cb,
-                       ImageNode::Builder* image_node_builder,
-                       base::TimeDelta time) {
-  UNREFERENCED_PARAMETER(time);
+                       ImageNode::Builder* image_node_builder) {
   DCHECK(!replace_image_cb.is_null());
   DCHECK(image_node_builder);
 
@@ -250,14 +248,27 @@
 void AnimateVideoWithLetterboxing(
     const ReplacedBox::ReplaceImageCB& replace_image_cb,
     math::SizeF destination_size,
-    CompositionNode::Builder* composition_node_builder, base::TimeDelta time) {
-  UNREFERENCED_PARAMETER(time);
-
+    CompositionNode::Builder* composition_node_builder) {
   DCHECK(!replace_image_cb.is_null());
   DCHECK(composition_node_builder);
 
   scoped_refptr<render_tree::Image> image = replace_image_cb.Run();
 
+  // If the image hasn't changed, then no need to change anything else.  The
+  // image should be the first child (see AddLetterboxedImageToRenderTree()).
+  if (!composition_node_builder->children().empty()) {
+    render_tree::ImageNode* existing_image_node =
+        base::polymorphic_downcast<render_tree::ImageNode*>(
+            composition_node_builder->GetChild(0)->get());
+    if (existing_image_node->data().source.get() == image.get()) {
+      return;
+    }
+  }
+
+  // Reset the composition node from whatever it was before; we will recreate
+  // it anew in each animation frame.
+  composition_node_builder->Reset();
+
   // TODO: Detect better when the intrinsic video size is used for the
   //   node size, and trigger a re-layout from the media element when the size
   //   changes.
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc
index ec0ce07..26b09b7 100644
--- a/src/cobalt/layout_tests/layout_tests.cc
+++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -90,7 +90,7 @@
   scoped_refptr<render_tree::animations::AnimateNode> animate_node =
       new render_tree::animations::AnimateNode(layout_results.render_tree);
   scoped_refptr<render_tree::Node> animated_tree =
-      animate_node->Apply(layout_results.layout_time).animated;
+      animate_node->Apply(layout_results.layout_time).animated->source();
 
   bool results =
       pixel_tester.TestTree(animated_tree, GetParam().base_file_path);
diff --git a/src/cobalt/loader/image/animated_webp_image.cc b/src/cobalt/loader/image/animated_webp_image.cc
index 123d945..6f12f2d 100644
--- a/src/cobalt/loader/image/animated_webp_image.cc
+++ b/src/cobalt/loader/image/animated_webp_image.cc
@@ -211,6 +211,10 @@
   *image_pointer = static_image->image();
 }
 
+void DecodeError(const std::string& error) {
+  LOG(ERROR) << error;
+}
+
 }  // namespace
 
 bool AnimatedWebPImage::DecodeOneFrame(int frame_index) {
@@ -233,7 +237,7 @@
 
     ImageDecoder image_decoder(
         resource_provider_, base::Bind(&RecordImage, &next_frame_image),
-        ImageDecoder::ErrorCallback(), ImageDecoder::kImageTypeWebP);
+        base::Bind(&DecodeError), ImageDecoder::kImageTypeWebP);
     image_decoder.DecodeChunk(
         reinterpret_cast<const char*>(webp_iterator.fragment.bytes),
         webp_iterator.fragment.size);
diff --git a/src/cobalt/loader/image/image.h b/src/cobalt/loader/image/image.h
index 2b72010..740c9b5 100644
--- a/src/cobalt/loader/image/image.h
+++ b/src/cobalt/loader/image/image.h
@@ -140,10 +140,7 @@
       scoped_refptr<FrameProvider> frame_provider,
       const math::RectF& destination_rect,
       const math::Matrix3F& local_transform,
-      render_tree::ImageNode::Builder* image_node_builder,
-      base::TimeDelta time) {
-    UNREFERENCED_PARAMETER(time);
-
+      render_tree::ImageNode::Builder* image_node_builder) {
     image_node_builder->source = frame_provider->GetFrame();
     image_node_builder->destination_rect = destination_rect;
     image_node_builder->local_transform = local_transform;
diff --git a/src/cobalt/loader/image/webp_image_decoder.cc b/src/cobalt/loader/image/webp_image_decoder.cc
index 5c803bc..f44986b 100644
--- a/src/cobalt/loader/image/webp_image_decoder.cc
+++ b/src/cobalt/loader/image/webp_image_decoder.cc
@@ -106,6 +106,7 @@
         DLOG(ERROR) << "WebPIAppend error, status code: " << status;
         DeleteInternalDecoder();
         set_state(kError);
+        return 0;
       }
     } else {
       animated_webp_image_->AppendChunk(data, input_byte);
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index 336d137..024cc55 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -510,10 +510,14 @@
 
   base::ThreadChecker resource_cache_thread_checker_;
 
-  base::CVal<base::cval::SizeInBytes, base::CValPublic> size_in_bytes_;
-  base::CVal<base::cval::SizeInBytes, base::CValPublic> capacity_in_bytes_;
-  base::CVal<int> count_requested_resources_;
-  base::CVal<int> count_loading_resources_;
+  base::CVal<base::cval::SizeInBytes, base::CValPublic> memory_size_in_bytes_;
+  base::CVal<base::cval::SizeInBytes, base::CValPublic>
+      memory_capacity_in_bytes_;
+  base::CVal<base::cval::SizeInBytes> memory_resources_loaded_in_bytes_;
+
+  base::CVal<int> count_resources_requested_;
+  base::CVal<int> count_resources_loading_;
+  base::CVal<int> count_resources_loaded_;
   base::CVal<int> count_pending_callbacks_;
 
   DISALLOW_COPY_AND_ASSIGN(ResourceCache);
@@ -532,20 +536,28 @@
       create_loader_function_(create_loader_function),
       is_processing_pending_callbacks_(false),
       are_callbacks_disabled_(false),
-      size_in_bytes_(base::StringPrintf("Memory.%s.Size", name_.c_str()), 0,
-                     "Total number of bytes currently used by the cache."),
-      capacity_in_bytes_(
+      memory_size_in_bytes_(
+          base::StringPrintf("Memory.%s.Size", name_.c_str()), 0,
+          "Total number of bytes currently used by the cache."),
+      memory_capacity_in_bytes_(
           base::StringPrintf("Memory.%s.Capacity", name_.c_str()),
           cache_capacity_,
           "The capacity, in bytes, of the resource cache.  "
           "Exceeding this results in *unused* resources being "
           "purged."),
-      count_requested_resources_(
-          base::StringPrintf("Count.%s.RequestedResources", name_.c_str()), 0,
+      memory_resources_loaded_in_bytes_(
+          base::StringPrintf("Memory.%s.Resource.Loaded", name_.c_str()), 0,
+          "Combined size in bytes of all resources that have been loaded by "
+          "the cache."),
+      count_resources_requested_(
+          base::StringPrintf("Count.%s.Resource.Requested", name_.c_str()), 0,
           "The total number of resources that have been requested."),
-      count_loading_resources_(
-          base::StringPrintf("Count.%s.LoadingResources", name_.c_str()), 0,
-          "The number of loading resources that are still outstanding."),
+      count_resources_loading_(
+          base::StringPrintf("Count.%s.Resource.Loading", name_.c_str()), 0,
+          "The number of resources that are currently loading."),
+      count_resources_loaded_(
+          base::StringPrintf("Count.%s.Resource.Loaded", name_.c_str()), 0,
+          "The total number of resources that have been successfully loaded."),
       count_pending_callbacks_(
           base::StringPrintf("Count.%s.PendingCallbacks", name_.c_str()), 0,
           "The number of loading completed resources that have pending "
@@ -580,7 +592,7 @@
   }
 
   // If we reach this point, then the resource doesn't exist yet.
-  ++count_requested_resources_;
+  ++count_resources_requested_;
 
   // Add the resource to a loading set. If no current resources have pending
   // callbacks, then this resource will block callbacks until it is decoded.
@@ -593,7 +605,7 @@
   } else {
     non_callback_blocking_loading_resource_set_.insert(url.spec());
   }
-  ++count_loading_resources_;
+  ++count_resources_loading_;
 
   // Create the cached resource and fetch its resource based on the url.
   scoped_refptr<CachedResourceType> cached_resource(new CachedResourceType(
@@ -613,7 +625,7 @@
 void ResourceCache<CacheType>::SetCapacity(uint32 capacity) {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
   cache_capacity_ = capacity;
-  capacity_in_bytes_ = capacity;
+  memory_capacity_in_bytes_ = capacity;
   ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_);
 }
 
@@ -637,8 +649,12 @@
   const std::string& url = cached_resource->url().spec();
 
   if (cached_resource->TryGetResource()) {
-    size_in_bytes_ +=
+    uint32 estimated_size_in_bytes =
         CacheType::GetEstimatedSizeInBytes(cached_resource->TryGetResource());
+    memory_size_in_bytes_ += estimated_size_in_bytes;
+    memory_resources_loaded_in_bytes_ += estimated_size_in_bytes;
+
+    ++count_resources_loaded_;
   }
 
   // Remove the resource from its loading set. It should exist in exactly one
@@ -659,7 +675,7 @@
   // incremented first to ensure that the total of the two counts always remains
   // above 0.
   ++count_pending_callbacks_;
-  --count_loading_resources_;
+  --count_resources_loading_;
 
   ProcessPendingCallbacksIfUnblocked();
   ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_);
@@ -690,10 +706,10 @@
     DCHECK(non_callback_blocking_loading_resource_set_.find(url) ==
            non_callback_blocking_loading_resource_set_.end());
     DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end());
-    --count_loading_resources_;
+    --count_resources_loading_;
   } else if (non_callback_blocking_loading_resource_set_.erase(url)) {
     DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end());
-    --count_loading_resources_;
+    --count_resources_loading_;
   } else if (pending_callback_map_.erase(url)) {
     --count_pending_callbacks_;
   }
@@ -717,7 +733,7 @@
   // pending callbacks and try again. References to the cached resources are
   // potentially being held until the callbacks run, so processing them may
   // enable more memory to be reclaimed.
-  if (size_in_bytes_ > bytes_to_reclaim_down_to) {
+  if (memory_size_in_bytes_ > bytes_to_reclaim_down_to) {
     ProcessPendingCallbacks();
     ReclaimMemory(bytes_to_reclaim_down_to, true /*log_warning_if_over*/);
   }
@@ -728,7 +744,7 @@
                                              bool log_warning_if_over) {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
 
-  while (size_in_bytes_ > bytes_to_reclaim_down_to &&
+  while (memory_size_in_bytes_ > bytes_to_reclaim_down_to &&
          !unreference_cached_resource_map_.empty()) {
     // The first element is the earliest-inserted element.
     scoped_refptr<ResourceType> resource =
@@ -739,15 +755,15 @@
     // in linked_hash_map. Add that function and related unit test.
     unreference_cached_resource_map_.erase(
         unreference_cached_resource_map_.begin());
-    size_in_bytes_ -= first_resource_size;
+    memory_size_in_bytes_ -= first_resource_size;
   }
 
   if (log_warning_if_over) {
     // Log a warning if we're still over |bytes_to_reclaim_down_to| after
     // attempting to reclaim memory. This can occur validly when the size of
     // the referenced images exceeds the target size.
-    DLOG_IF(WARNING, size_in_bytes_ > bytes_to_reclaim_down_to)
-        << "cached size: " << size_in_bytes_
+    DLOG_IF(WARNING, memory_size_in_bytes_ > bytes_to_reclaim_down_to)
+        << "cached size: " << memory_size_in_bytes_
         << ", target size: " << bytes_to_reclaim_down_to;
   }
 }
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index e783005..95f2b55 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -653,7 +653,7 @@
 void SbPlayerPipeline::OnDemuxerSeeked(PipelineStatus status) {
   DCHECK(message_loop_->BelongsToCurrentThread());
 
-  if (status == PIPELINE_OK) {
+  if (status == PIPELINE_OK && player_) {
     player_->Seek(seek_time_);
   }
 }
diff --git a/src/cobalt/render_tree/animations/animate_node.cc b/src/cobalt/render_tree/animations/animate_node.cc
index 58c11b4..dfdc7c1 100644
--- a/src/cobalt/render_tree/animations/animate_node.cc
+++ b/src/cobalt/render_tree/animations/animate_node.cc
@@ -66,7 +66,8 @@
                       TraverseList* traverse_list)
       : animation_map_(animation_map),
         traverse_list_(traverse_list),
-        expiry_(-base::TimeDelta::Max()) {}
+        expiry_(-base::TimeDelta::Max()),
+        depends_on_time_expiry_(-base::TimeDelta::Max()) {}
 
   void Visit(animations::AnimateNode* animate) OVERRIDE;
   void Visit(CompositionNode* composition) OVERRIDE { VisitNode(composition); }
@@ -117,6 +118,10 @@
   // The time after which all animations will have completed and be constant.
   base::TimeDelta expiry_;
 
+  // Similar to |expiry_| but accumulated only for animations whose callback
+  // depends on the time parameter.
+  base::TimeDelta depends_on_time_expiry_;
+
   friend class AnimateNode;
 };
 
@@ -146,6 +151,9 @@
 
   // Update our expiry in accordance with the sub-AnimateNode's expiry.
   expiry_ = std::max(expiry_, animate->expiry());
+
+  depends_on_time_expiry_ =
+      std::max(depends_on_time_expiry_, animate->depends_on_time_expiry());
 }
 
 template <typename T>
@@ -208,8 +216,10 @@
 void AnimateNode::TraverseListBuilder::AddToTraverseList(
     Node* node, AnimateNode::Builder::InternalMap::const_iterator found) {
   if (found != animation_map_.end()) {
-    traverse_list_->push_back(TraverseListEntry(node, found->second));
+    traverse_list_->push_back(TraverseListEntry(node, found->second, false));
     expiry_ = std::max(expiry_, found->second->GetExpiry());
+    depends_on_time_expiry_ = std::max(depends_on_time_expiry_,
+                                       found->second->GetDependsOnTimeExpiry());
   } else {
     traverse_list_->push_back(TraverseListEntry(node));
   }
@@ -304,7 +314,9 @@
   TraverseListEntry current_entry = AdvanceIterator(node);
 
   DCHECK(current_entry.animations);
-  ProcessAnimatedNodeBounds(current_entry, node);
+  if (current_entry.did_animate_previously) {
+    ProcessAnimatedNodeBounds(current_entry, node);
+  }
 }
 
 template <typename T>
@@ -336,7 +348,7 @@
   }
   transform_ = old_transform;
 
-  if (current_entry.animations) {
+  if (current_entry.did_animate_previously) {
     ProcessAnimatedNodeBounds(current_entry, node);
   }
 }
@@ -376,7 +388,8 @@
 // tree.
 class AnimateNode::ApplyVisitor : public NodeVisitor {
  public:
-  ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset);
+  ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset,
+               const base::optional<base::TimeDelta>& snapshot_time);
 
   void Visit(animations::AnimateNode* /* animate */) OVERRIDE {
     // An invariant of AnimateNodes is that they should never contain descendant
@@ -405,7 +418,9 @@
   // As we compute the animated nodes, we create a new traverse list that leads
   // to the newly created animated nodes.  This can be used afterwards to
   // calculate the bounding boxes around the active animated nodes.
-  TraverseList* animated_traverse_list() { return &animated_traverse_list_; }
+  const TraverseList& animated_traverse_list() const {
+    return animated_traverse_list_;
+  }
 
  private:
   template <typename T>
@@ -415,8 +430,8 @@
   typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode(
       T* node);
   template <typename T>
-  scoped_refptr<Node> ApplyAnimations(const TraverseListEntry& entry,
-                                      typename T::Builder* builder);
+  scoped_refptr<T> ApplyAnimations(const TraverseListEntry& entry,
+                                   typename T::Builder* builder);
   TraverseListEntry AdvanceIterator(Node* node);
 
   // The time offset to be passed in to individual animations.
@@ -435,11 +450,18 @@
 
   // An iterator pointing to the next valid render tree node to visit.
   TraverseList::const_iterator iterator_;
+
+  // Time at which the existing source render tree was created/last animated
+  // at.
+  base::optional<base::TimeDelta> snapshot_time_;
 };
 
-AnimateNode::ApplyVisitor::ApplyVisitor(const TraverseList& traverse_list,
-                                        base::TimeDelta time_offset)
-    : time_offset_(time_offset), traverse_list_(traverse_list) {
+AnimateNode::ApplyVisitor::ApplyVisitor(
+    const TraverseList& traverse_list, base::TimeDelta time_offset,
+    const base::optional<base::TimeDelta>& snapshot_time)
+    : time_offset_(time_offset),
+      traverse_list_(traverse_list),
+      snapshot_time_(snapshot_time) {
   animated_traverse_list_.reserve(traverse_list.size());
   iterator_ = traverse_list_.begin();
 }
@@ -452,9 +474,18 @@
   // have animations.
   DCHECK(current_entry.animations);
   typename T::Builder builder(node->data());
-  animated_ = ApplyAnimations<T>(current_entry, &builder);
+  scoped_refptr<T> animated = ApplyAnimations<T>(current_entry, &builder);
+  // If nothing ends up getting animated, then just re-use the existing node.
+  bool did_animate = false;
+  if (animated->data() == node->data()) {
+    animated_ = node;
+  } else {
+    animated_ = animated.get();
+    did_animate = true;
+  }
+
   animated_traverse_list_.push_back(
-      TraverseListEntry(animated_, current_entry.animations));
+      TraverseListEntry(animated_, current_entry.animations, did_animate));
 }
 
 template <typename T>
@@ -464,7 +495,7 @@
 
   size_t animated_traverse_list_index = animated_traverse_list_.size();
   animated_traverse_list_.push_back(
-      TraverseListEntry(NULL, current_entry.animations));
+      TraverseListEntry(NULL, current_entry.animations, false));
 
   // Traverse the child nodes, but only the ones that are on the
   // |traverse_list_|.  In particular, the next node we are allowed to visit
@@ -482,41 +513,60 @@
       // If one of our children is next up on the path to animation, traverse
       // into it.
       child->Accept(this);
-      // Traversing into the child means that it was animated, and so replaced
-      // by an animated node while it was visited.  Thus, replace it in the
-      // current node's child list with its animated version.
-      child_iterator.ReplaceCurrent(animated_);
-      children_modified = true;
+      if (animated_ != child) {
+        // Traversing into the child and seeing |animated_| emerge from the
+        // traversal equal to something other than |child| means that the child
+        // was animated, and so replaced by an animated node while it was
+        // visited.  Thus, replace it in the current node's child list with its
+        // animated version.
+        child_iterator.ReplaceCurrent(animated_);
+        children_modified = true;
+      }
     }
 
     child_iterator.Next();
   }
 
+  base::optional<typename T::Builder> builder;
+  if (children_modified) {
+    // Reuse the modified Builder object from child traversal if one of
+    // our children was animated.
+    builder.emplace(child_iterator.TakeReplacedChildrenBuilder());
+  }
+
+  bool did_animate = false;
   if (current_entry.animations) {
-    base::optional<typename T::Builder> builder;
-    if (children_modified) {
-      // Reuse the modified Builder object from child traversal if one of
-      // our children was animated.
-      builder.emplace(child_iterator.TakeReplacedChildrenBuilder());
-    } else {
+    if (!builder) {
       // Create a fresh copy of the Builder object for this animated node, to
       // be passed into the animations.
       builder.emplace(node->data());
     }
-    animated_ = ApplyAnimations<T>(current_entry, &(*builder));
+    typename T::Builder original_builder(*builder);
+    scoped_refptr<T> animated = ApplyAnimations<T>(current_entry, &(*builder));
+    if (!(original_builder == *builder)) {
+      did_animate = true;
+    }
+    // If the data didn't actually change, then no animation took place and
+    // so we should note this by not modifying the original render tree node.
+    animated_ = animated->data() == node->data() ? node : animated.get();
   } else {
-    // If there were no animations targeting this node directly, then its
-    // children must have been modified since otherwise it wouldn't be in
-    // the traverse list.
-    DCHECK(children_modified);
-    animated_ = new T(child_iterator.TakeReplacedChildrenBuilder());
+    // If there were no animations targeting this node directly, it may still
+    // need to be animated if its children are animated, which will be the
+    // case if |builder| is populated.
+    if (builder) {
+      animated_ = new T(*builder);
+    } else {
+      animated_ = node;
+    }
   }
 
   animated_traverse_list_[animated_traverse_list_index].node = animated_;
+  animated_traverse_list_[animated_traverse_list_index].did_animate_previously =
+      did_animate;
 }
 
 template <typename T>
-scoped_refptr<Node> AnimateNode::ApplyVisitor::ApplyAnimations(
+scoped_refptr<T> AnimateNode::ApplyVisitor::ApplyAnimations(
     const TraverseListEntry& entry, typename T::Builder* builder) {
   TRACE_EVENT0("cobalt::renderer",
                "AnimateNode::ApplyVisitor::ApplyAnimations()");
@@ -525,11 +575,16 @@
       base::polymorphic_downcast<const AnimationList<T>*>(
           entry.animations.get());
 
-  // Iterate through each animation applying them one at a time.
-  for (typename AnimationList<T>::InternalList::const_iterator iter =
-           typed_node_animations->data().animations.begin();
-       iter != typed_node_animations->data().animations.end(); ++iter) {
-    iter->Run(builder, time_offset_);
+  // Only execute the animation updates on nodes that have not expired.
+  if (!snapshot_time_ ||
+      typed_node_animations->data().expiry >= *snapshot_time_) {
+    TRACE_EVENT0("cobalt::renderer", "Running animation callbacks");
+    // Iterate through each animation applying them one at a time.
+    for (typename AnimationList<T>::InternalList::const_iterator iter =
+             typed_node_animations->data().animations.begin();
+         iter != typed_node_animations->data().animations.end(); ++iter) {
+      iter->Run(builder, time_offset_);
+    }
   }
 
   return new T(*builder);
@@ -559,9 +614,9 @@
 class AnimateNode::RefCountedTraversalList
     : public base::RefCounted<RefCountedTraversalList> {
  public:
-  explicit RefCountedTraversalList(TraverseList* to_swap_in) {
-    traverse_list_.swap(*to_swap_in);
-  }
+  explicit RefCountedTraversalList(const TraverseList& traverse_list)
+      : traverse_list_(traverse_list) {}
+
   const TraverseList& traverse_list() const { return traverse_list_; }
 
  private:
@@ -594,24 +649,41 @@
 
 AnimateNode::AnimateResults AnimateNode::Apply(base::TimeDelta time_offset) {
   TRACE_EVENT0("cobalt::renderer", "AnimateNode::Apply()");
+  if (snapshot_time_) {
+    // Assume we are always animating forward.
+    DCHECK_LE(*snapshot_time_, time_offset);
+  }
+
   AnimateResults results;
   if (traverse_list_.empty()) {
-    results.animated = source_;
+    results.animated = this;
     // There are no animations, so there is no bounding rectangle, so setup the
     // bounding box function to trivially return an empty rectangle.
     results.get_animation_bounds_since =
         base::Bind(&ReturnTrivialEmptyRectBound);
   } else {
-    ApplyVisitor apply_visitor(traverse_list_, time_offset);
+    ApplyVisitor apply_visitor(traverse_list_, time_offset, snapshot_time_);
     source_->Accept(&apply_visitor);
-    results.animated = apply_visitor.animated();
 
-    // Setup a function for returning
+    // Setup a function for returning the bounds on the regions modified by
+    // animations given a specific starting point in time ("since").  This
+    // can be used by rasterizers to determine which regions need to be
+    // re-drawn or not.
     results.get_animation_bounds_since = base::Bind(
         &GetAnimationBoundsSince,
         scoped_refptr<RefCountedTraversalList>(new RefCountedTraversalList(
             apply_visitor.animated_traverse_list())),
-        time_offset, results.animated);
+        time_offset, apply_visitor.animated());
+
+    if (apply_visitor.animated() == source()) {
+      // If no animations were actually applied, indicate this by returning
+      // this exact node as the animated node.
+      results.animated = this;
+    } else {
+      results.animated = new AnimateNode(apply_visitor.animated_traverse_list(),
+                                         apply_visitor.animated(), expiry_,
+                                         depends_on_time_expiry_, time_offset);
+    }
   }
   return results;
 }
@@ -638,6 +710,7 @@
   }
 
   expiry_ = traverse_list_builder.expiry_;
+  depends_on_time_expiry_ = traverse_list_builder.depends_on_time_expiry_;
 }
 
 }  // namespace animations
diff --git a/src/cobalt/render_tree/animations/animate_node.h b/src/cobalt/render_tree/animations/animate_node.h
index 70ed9ba..c71e620 100644
--- a/src/cobalt/render_tree/animations/animate_node.h
+++ b/src/cobalt/render_tree/animations/animate_node.h
@@ -20,6 +20,7 @@
 
 #include "base/containers/small_map.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "cobalt/math/rect_f.h"
 #include "cobalt/render_tree/animations/animation_list.h"
 #include "cobalt/render_tree/movable.h"
@@ -80,12 +81,30 @@
     }
 
     template <typename T>
+    void Add(
+        const scoped_refptr<T>& target_node,
+        const typename Animation<T>::TimeIndependentFunction& single_animation,
+        base::TimeDelta expiry) {
+      AddInternal(target_node,
+                  scoped_refptr<AnimationListBase>(
+                      new AnimationList<T>(single_animation, expiry)));
+    }
+
+    template <typename T>
     void Add(const scoped_refptr<T>& target_node,
              const typename Animation<T>::Function& single_animation) {
       AddInternal(target_node,
                   scoped_refptr<AnimationListBase>(new AnimationList<T>(
                       single_animation, base::TimeDelta::Max())));
     }
+    template <typename T>
+    void Add(const scoped_refptr<T>& target_node,
+             const typename Animation<T>::TimeIndependentFunction&
+                 single_animation) {
+      AddInternal(target_node,
+                  scoped_refptr<AnimationListBase>(new AnimationList<T>(
+                      single_animation, base::TimeDelta::Max())));
+    }
 
     // Merge all mappings from another AnimateNode::Builder into this one.
     // There cannot be any keys that are in both the merge target and source.
@@ -140,8 +159,11 @@
 
   struct AnimateResults {
     // The animated render tree, which is guaranteed to not contain any
-    // AnimateNodes.
-    scoped_refptr<Node> animated;
+    // AnimateNodes.  Note that it is returned as an AnimateNode...  The actual
+    // animated render tree can be obtained via |animated->source()|, the
+    // AnimateNode however allows one to animate the animated node so that
+    // it can be checked if anything has actually been animated or not.
+    scoped_refptr<AnimateNode> animated;
 
     // Can be called in order to return a bounding rectangle around all
     // nodes that are actively animated.  The parameter specifies a "since"
@@ -152,6 +174,9 @@
     base::Callback<math::RectF(base::TimeDelta)> get_animation_bounds_since;
   };
   // Apply the animations to the sub render tree with the given |time_offset|.
+  // Note that if |animated| in the results is equivalent to |this| on which
+  // Apply() is called, then no animations have actually taken place, and a
+  // re-render can be avoided.
   AnimateResults Apply(base::TimeDelta time_offset);
 
   // Returns the sub-tree for which the animations apply to.
@@ -163,6 +188,12 @@
   // all x, y >= expiry().
   const base::TimeDelta& expiry() const { return expiry_; }
 
+  // Similar to |expiry()|, but returns the expiration for all animations whose
+  // callback function actually depends on the time parameter passed into them.
+  const base::TimeDelta& depends_on_time_expiry() const {
+    return depends_on_time_expiry_;
+  }
+
  private:
   // The compiled node animation list is a sequence of nodes that are either
   // animated themselves, or on the path to an animated node.  Only nodes in
@@ -170,12 +201,19 @@
   // list, complete with animations to be applied to the given node, if any.
   struct TraverseListEntry {
     TraverseListEntry(Node* node,
-                      const scoped_refptr<AnimationListBase>& animations)
-        : node(node), animations(animations) {}
-    explicit TraverseListEntry(Node* node) : node(node) {}
+                      const scoped_refptr<AnimationListBase>& animations,
+                      bool did_animate_previously)
+        : node(node),
+          animations(animations),
+          did_animate_previously(did_animate_previously) {}
+    explicit TraverseListEntry(Node* node)
+        : node(node), did_animate_previously(false) {}
 
     Node* node;
     scoped_refptr<AnimationListBase> animations;
+    // Used when checking animation bounds to see which nodes were actually
+    // animated and whose bounds we need to accumulate.
+    bool did_animate_previously;
   };
   typedef std::vector<TraverseListEntry> TraverseList;
 
@@ -192,6 +230,18 @@
   // rectangle for all active animations.
   class BoundsVisitor;
 
+  // This private constructor is used by AnimateNode::Apply() to create a new
+  // AnimateNode that matches the resulting animated render tree.
+  AnimateNode(const TraverseList& traverse_list, scoped_refptr<Node> source,
+              const base::TimeDelta& expiry,
+              const base::TimeDelta& depends_on_time_expiry,
+              const base::TimeDelta& snapshot_time)
+      : traverse_list_(traverse_list),
+        source_(source),
+        expiry_(expiry),
+        depends_on_time_expiry_(depends_on_time_expiry),
+        snapshot_time_(snapshot_time) {}
+
   void CommonInit(const Builder::InternalMap& node_animation_map,
                   const scoped_refptr<Node>& source);
 
@@ -205,6 +255,12 @@
   TraverseList traverse_list_;
   scoped_refptr<Node> source_;
   base::TimeDelta expiry_;
+  base::TimeDelta depends_on_time_expiry_;
+  // Time when the |source_| render tree was last animated, if known.  This
+  // will get set when Apply() is called to produce a new AnimateNode that can
+  // then be Apply()d again.  It can be used during the second apply to check
+  // if some animations have expired.
+  base::optional<base::TimeDelta> snapshot_time_;
 };
 
 }  // namespace animations
diff --git a/src/cobalt/render_tree/animations/animate_node_test.cc b/src/cobalt/render_tree/animations/animate_node_test.cc
index 7f539b9..b19733e 100644
--- a/src/cobalt/render_tree/animations/animate_node_test.cc
+++ b/src/cobalt/render_tree/animations/animate_node_test.cc
@@ -81,10 +81,10 @@
   scoped_refptr<AnimateNode> with_animations =
       CreateSingleAnimation(text_node, base::Bind(&AnimateText));
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   TextNode* animated_text_node =
-      dynamic_cast<TextNode*>(animated_render_tree.get());
+      dynamic_cast<TextNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_text_node);
   EXPECT_EQ(ColorRGBA(.5f, .5f, .5f), animated_text_node->data().color);
 }
@@ -101,10 +101,10 @@
   scoped_refptr<AnimateNode> with_animations =
       CreateSingleAnimation(image_node, base::Bind(&AnimateImage));
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   ImageNode* animated_image_node =
-      dynamic_cast<ImageNode*>(animated_render_tree.get());
+      dynamic_cast<ImageNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
   EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect);
@@ -122,10 +122,10 @@
   scoped_refptr<AnimateNode> with_animations =
       CreateSingleAnimation(rect_node, base::Bind(&AnimateRect));
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   RectNode* animated_rect_node =
-      dynamic_cast<RectNode*>(animated_render_tree.get());
+      dynamic_cast<RectNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_rect_node);
   EXPECT_TRUE(animated_rect_node);
   EXPECT_EQ(RectF(2.0f, 2.0f), animated_rect_node->data().rect);
@@ -147,26 +147,29 @@
   scoped_refptr<AnimateNode> with_animations =
       CreateSingleAnimation(image_node, base::Bind(&AnimateImageAddWithTime));
 
-  scoped_refptr<Node> animated_render_tree;
+  scoped_refptr<AnimateNode> animated_render_tree;
   ImageNode* animated_image_node;
 
   animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
-  animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
+  animated_image_node =
+      dynamic_cast<ImageNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
   EXPECT_FLOAT_EQ(3.0f, animated_image_node->data().destination_rect.width());
 
   animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(2)).animated;
-  animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
+  animated_image_node =
+      dynamic_cast<ImageNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
   EXPECT_FLOAT_EQ(5.0f, animated_image_node->data().destination_rect.width());
 
   animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(4)).animated;
-  animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
+  animated_image_node =
+      dynamic_cast<ImageNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
   EXPECT_FLOAT_EQ(9.0f, animated_image_node->data().destination_rect.width());
@@ -202,11 +205,11 @@
   scoped_refptr<AnimateNode> with_animations(
       new AnimateNode(animations_builder, image_node));
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(3)).animated;
 
   ImageNode* animated_image_node =
-      dynamic_cast<ImageNode*>(animated_render_tree.get());
+      dynamic_cast<ImageNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_image_node);
 
   EXPECT_FLOAT_EQ(21.0f, animated_image_node->data().destination_rect.width());
@@ -228,11 +231,11 @@
   scoped_refptr<AnimateNode> with_animations =
       CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform));
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   MatrixTransformNode* animated_transform_node =
-      dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
+      dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_transform_node);
 
   EXPECT_EQ(math::TranslateMatrix(2.0f, 2.0f),
@@ -260,11 +263,11 @@
   scoped_refptr<AnimateNode> with_animations =
       new AnimateNode(animation_builder, composition_node);
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   CompositionNode* animated_composition_node =
-      dynamic_cast<CompositionNode*>(animated_render_tree.get());
+      dynamic_cast<CompositionNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_composition_node);
 
   ImageNode* animated_image_node_a = dynamic_cast<ImageNode*>(
@@ -291,11 +294,11 @@
   scoped_refptr<AnimateNode> with_animations =
       CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform));
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   MatrixTransformNode* animated_transform_node =
-      dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
+      dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_transform_node);
 
   // Check that the matrix transform node is animated.
@@ -328,11 +331,11 @@
   scoped_refptr<AnimateNode> with_animations =
       new AnimateNode(transform_node_b);
 
-  scoped_refptr<Node> animated_render_tree =
+  scoped_refptr<AnimateNode> animated_render_tree =
       with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   MatrixTransformNode* animated_transform_node =
-      dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
+      dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get());
   EXPECT_TRUE(animated_transform_node);
 
   // Check that the image node is animated.
diff --git a/src/cobalt/render_tree/animations/animation_list.h b/src/cobalt/render_tree/animations/animation_list.h
index 78f1e65..b51144d 100644
--- a/src/cobalt/render_tree/animations/animation_list.h
+++ b/src/cobalt/render_tree/animations/animation_list.h
@@ -17,6 +17,7 @@
 
 #include <list>
 
+#include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/time.h"
@@ -68,6 +69,16 @@
   // and ultimately add that to a AnimateNode::Builder object so that it can be
   // mapped to a specific TextNode that it should be applied to.
   typedef base::Callback<void(typename T::Builder*, base::TimeDelta)> Function;
+  typedef base::Callback<void(typename T::Builder*)> TimeIndependentFunction;
+
+  // Helper function to convert from time independent function to a time
+  // dependent function.
+  static void CallTimeIndependentFunction(
+      const TimeIndependentFunction& time_independent_function,
+      typename T::Builder* builder, base::TimeDelta time) {
+    UNREFERENCED_PARAMETER(time);
+    time_independent_function.Run(builder);
+  }
 };
 
 // The AnimationListBase is used so that we can acquire a non-template handle
@@ -77,6 +88,7 @@
 class AnimationListBase : public base::RefCountedThreadSafe<AnimationListBase> {
  public:
   virtual base::TimeDelta GetExpiry() const = 0;
+  virtual base::TimeDelta GetDependsOnTimeExpiry() const = 0;
 
  protected:
   virtual ~AnimationListBase() {}
@@ -100,18 +112,36 @@
   struct Builder {
     DECLARE_AS_MOVABLE(Builder);
 
-    Builder() : expiry(base::TimeDelta::Max()) {}
+    Builder() : expiry(base::TimeDelta::Max()),
+                depends_on_time_expiry(base::TimeDelta::Max()) {}
     explicit Builder(Moved moved) { animations.swap(moved->animations); }
     explicit Builder(const typename Animation<T>::Function& single_animation,
                      base::TimeDelta expiry)
-        : expiry(expiry) {
+        : expiry(expiry), depends_on_time_expiry(expiry) {
       animations.push_back(single_animation);
     }
+    explicit Builder(
+        const typename Animation<T>::TimeIndependentFunction& single_animation,
+        base::TimeDelta expiry)
+        : expiry(expiry),
+          // Since this animation is time independent, we mark its
+          // time-dependent expiration to be already expired.
+          depends_on_time_expiry(-base::TimeDelta::Max()) {
+      // Transform from a time independent function to a time dependent
+      // function, since we store all functions as time dependent functions.
+      animations.push_back(base::Bind(Animation<T>::CallTimeIndependentFunction,
+                                      single_animation));
+    }
 
     InternalList animations;
     // When do the animations expire?  base::TimeDelta::Max() implies that they
     // never expire.
     base::TimeDelta expiry;
+
+    // Similar to |expiry|, but only set for animations that depend on the
+    // time parameter passed into their animation callback functions.
+    // Optimizations can be performed for animations that don't depend on this.
+    base::TimeDelta depends_on_time_expiry;
   };
 
   explicit AnimationList(typename Builder::Moved builder) : data_(builder) {}
@@ -122,10 +152,17 @@
       const typename Animation<T>::Function& single_animation,
       base::TimeDelta expiry)
       : data_(single_animation, expiry) {}
+  explicit AnimationList(
+      const typename Animation<T>::TimeIndependentFunction& single_animation,
+      base::TimeDelta expiry)
+      : data_(single_animation, expiry) {}
 
   const Builder& data() const { return data_; }
 
   base::TimeDelta GetExpiry() const OVERRIDE { return data_.expiry; }
+  base::TimeDelta GetDependsOnTimeExpiry() const OVERRIDE {
+    return data_.depends_on_time_expiry;
+  }
 
  private:
   ~AnimationList() OVERRIDE {}
diff --git a/src/cobalt/render_tree/blur_filter.h b/src/cobalt/render_tree/blur_filter.h
index 5f6fbe7..cbe95a0 100644
--- a/src/cobalt/render_tree/blur_filter.h
+++ b/src/cobalt/render_tree/blur_filter.h
@@ -29,6 +29,10 @@
   // |blur_sigma| must be non-negative.
   explicit BlurFilter(float blur_sigma) { set_blur_sigma(blur_sigma); }
 
+  bool operator==(const BlurFilter& other) const {
+    return blur_sigma_ == other.blur_sigma_;
+  }
+
   void set_blur_sigma(float blur_sigma) {
     DCHECK_LE(0.0f, blur_sigma);
     blur_sigma_ = blur_sigma;
diff --git a/src/cobalt/render_tree/border.h b/src/cobalt/render_tree/border.h
index 761c99f..9b72706 100644
--- a/src/cobalt/render_tree/border.h
+++ b/src/cobalt/render_tree/border.h
@@ -53,6 +53,11 @@
   Border(const BorderSide& left, const BorderSide& right, const BorderSide& top,
          const BorderSide& bottom);
 
+  bool operator==(const Border& other) const {
+    return left == other.left && right == other.right && top == other.top &&
+           bottom == other.bottom;
+  }
+
   BorderSide left;
   BorderSide right;
   BorderSide top;
diff --git a/src/cobalt/render_tree/brush.h b/src/cobalt/render_tree/brush.h
index fba8063..988e470 100644
--- a/src/cobalt/render_tree/brush.h
+++ b/src/cobalt/render_tree/brush.h
@@ -45,6 +45,10 @@
  public:
   explicit SolidColorBrush(const ColorRGBA& color) : color_(color) {}
 
+  bool operator==(const SolidColorBrush& other) const {
+    return color_ == other.color_;
+  }
+
   // A type-safe branching.
   void Accept(BrushVisitor* visitor) const OVERRIDE;
 
@@ -68,6 +72,10 @@
   ColorStop(float position, const ColorRGBA& color)
       : position(position), color(color) {}
 
+  bool operator==(const ColorStop& other) const {
+    return position == other.position && color == other.color;
+  }
+
   float position;
   ColorRGBA color;
 };
@@ -102,12 +110,20 @@
                       const ColorRGBA& source_color,
                       const ColorRGBA& dest_color);
 
+  bool operator==(const LinearGradientBrush& other) const {
+    return data_ == other.data_;
+  }
+
   struct Data {
     Data();
     Data(const math::PointF& source, const math::PointF& dest,
          const ColorStopList& color_stops);
     Data(const math::PointF& source, const math::PointF& dest);
 
+    bool operator==(const Data& other) const {
+      return source_ == other.source_ && dest_ == other.dest_ &&
+             color_stops_ == other.color_stops_;
+    }
     math::PointF source_;
     math::PointF dest_;
 
@@ -167,6 +183,11 @@
                       float radius_y, const ColorRGBA& source_color,
                       const ColorRGBA& dest_color);
 
+  bool operator==(const RadialGradientBrush& other) const {
+    return center_ == other.center_ && radius_x_ == other.radius_x_ &&
+           radius_y_ == other.radius_y_ && color_stops_ == other.color_stops_;
+  }
+
   // A type-safe branching.
   void Accept(BrushVisitor* visitor) const OVERRIDE;
 
@@ -192,6 +213,36 @@
 
 scoped_ptr<Brush> CloneBrush(const Brush* brush);
 
+class EqualsBrushVisitor : public BrushVisitor {
+ public:
+  explicit EqualsBrushVisitor(Brush* brush_a)
+      : brush_a_(brush_a), result_(false) {}
+
+  bool result() const { return result_; }
+
+  void Visit(const SolidColorBrush* solid_color_brush) OVERRIDE {
+    result_ =
+        brush_a_->GetTypeId() == base::GetTypeId<SolidColorBrush>() &&
+        *static_cast<const SolidColorBrush*>(brush_a_) == *solid_color_brush;
+  }
+
+  void Visit(const LinearGradientBrush* linear_gradient_brush) OVERRIDE {
+    result_ = brush_a_->GetTypeId() == base::GetTypeId<LinearGradientBrush>() &&
+              *static_cast<const LinearGradientBrush*>(brush_a_) ==
+                  *linear_gradient_brush;
+  }
+
+  void Visit(const RadialGradientBrush* radial_gradient_brush) OVERRIDE {
+    result_ = brush_a_->GetTypeId() == base::GetTypeId<RadialGradientBrush>() &&
+              *static_cast<const RadialGradientBrush*>(brush_a_) ==
+                  *radial_gradient_brush;
+  }
+
+ private:
+  Brush* brush_a_;
+  bool result_;
+};
+
 }  // namespace render_tree
 }  // namespace cobalt
 
diff --git a/src/cobalt/render_tree/composition_node.h b/src/cobalt/render_tree/composition_node.h
index 2614822..01d1296 100644
--- a/src/cobalt/render_tree/composition_node.h
+++ b/src/cobalt/render_tree/composition_node.h
@@ -72,6 +72,10 @@
       children_.swap(moved->children_);
     }
 
+    bool operator==(const Builder& other) const {
+      return offset_ == other.offset_ && children_ == other.children_;
+    }
+
     // Add a child node to the list of children we are building.  When a
     // CompositionNode is constructed from this CompositionNode::Builder, it
     // will have as children all nodes who were passed into the builder via this
@@ -94,6 +98,11 @@
     const math::Vector2dF& offset() const { return offset_; }
     void set_offset(const math::Vector2dF& offset) { offset_ = offset; }
 
+    void Reset() {
+      offset_ = math::Vector2dF();
+      ClearChildren();
+    }
+
    private:
     // A translation offset to be applied to each member of this composition
     // node.
diff --git a/src/cobalt/render_tree/filter_node.h b/src/cobalt/render_tree/filter_node.h
index 5ba8ac7..820f31e 100644
--- a/src/cobalt/render_tree/filter_node.h
+++ b/src/cobalt/render_tree/filter_node.h
@@ -51,6 +51,13 @@
 
     math::RectF GetBounds() const;
 
+    bool operator==(const Builder& other) const {
+      return source == other.source && opacity_filter == other.opacity_filter &&
+             viewport_filter == other.viewport_filter &&
+             blur_filter == other.blur_filter &&
+             map_to_mesh_filter == other.map_to_mesh_filter;
+    }
+
     // The source tree, which will be used as the input to the filters specified
     // in this FilterNode.
     scoped_refptr<render_tree::Node> source;
diff --git a/src/cobalt/render_tree/image_node.h b/src/cobalt/render_tree/image_node.h
index ccd4773..7b5f048 100644
--- a/src/cobalt/render_tree/image_node.h
+++ b/src/cobalt/render_tree/image_node.h
@@ -36,6 +36,12 @@
             const math::RectF& destination_rect,
             const math::Matrix3F& local_transform);
 
+    bool operator==(const Builder& other) const {
+      return source == other.source &&
+             destination_rect == other.destination_rect &&
+             local_transform == other.local_transform;
+    }
+
     // A source of pixels. May be smaller or larger than layed out image.
     // The class does not own the image, it merely refers it from a resource
     // pool.
diff --git a/src/cobalt/render_tree/map_to_mesh_filter.h b/src/cobalt/render_tree/map_to_mesh_filter.h
index 2c126e6..bbe4587 100644
--- a/src/cobalt/render_tree/map_to_mesh_filter.h
+++ b/src/cobalt/render_tree/map_to_mesh_filter.h
@@ -47,6 +47,12 @@
  public:
   struct Builder {
     Builder() {}
+    bool operator==(const Builder& other) const {
+      return resolution_matched_meshes_ == other.resolution_matched_meshes_ &&
+             left_eye_default_mesh_ == other.left_eye_default_mesh_ &&
+             right_eye_default_mesh_ == other.right_eye_default_mesh_;
+    }
+
     void SetDefaultMeshes(
         const scoped_refptr<render_tree::Mesh>& left_eye_mesh,
         const scoped_refptr<render_tree::Mesh>& right_eye_mesh) {
@@ -106,6 +112,10 @@
     }
   }
 
+  bool operator==(const MapToMeshFilter& other) const {
+    return stereo_mode_ == other.stereo_mode_ && data_ == other.data_;
+  }
+
   StereoMode stereo_mode() const { return stereo_mode_; }
 
   // The omission of the |resolution| parameter will yield the default
diff --git a/src/cobalt/render_tree/matrix_transform_3d_node.h b/src/cobalt/render_tree/matrix_transform_3d_node.h
index bbb517a..3732b68 100644
--- a/src/cobalt/render_tree/matrix_transform_3d_node.h
+++ b/src/cobalt/render_tree/matrix_transform_3d_node.h
@@ -37,6 +37,10 @@
     Builder(const scoped_refptr<Node>& source, const glm::mat4& transform)
         : source(source), transform(transform) {}
 
+    bool operator==(const Builder& other) const {
+      return source == other.source && transform == other.transform;
+    }
+
     // The subtree that will be rendered with |transform| applied to it.
     scoped_refptr<Node> source;
 
diff --git a/src/cobalt/render_tree/matrix_transform_node.h b/src/cobalt/render_tree/matrix_transform_node.h
index 3baafcc..93d9685 100644
--- a/src/cobalt/render_tree/matrix_transform_node.h
+++ b/src/cobalt/render_tree/matrix_transform_node.h
@@ -34,6 +34,10 @@
     Builder(const scoped_refptr<Node>& source, const math::Matrix3F& transform)
         : source(source), transform(transform) {}
 
+    bool operator==(const Builder& other) const {
+      return source == other.source && transform == other.transform;
+    }
+
     // The subtree that will be rendered with |transform| applied to it.
     scoped_refptr<Node> source;
 
diff --git a/src/cobalt/render_tree/opacity_filter.h b/src/cobalt/render_tree/opacity_filter.h
index 93e8718..113c909 100644
--- a/src/cobalt/render_tree/opacity_filter.h
+++ b/src/cobalt/render_tree/opacity_filter.h
@@ -27,6 +27,10 @@
  public:
   explicit OpacityFilter(float opacity) { set_opacity(opacity); }
 
+  bool operator==(const OpacityFilter& other) const {
+    return opacity_ == other.opacity_;
+  }
+
   void set_opacity(float opacity) {
     DCHECK_LE(0.0f, opacity);
     DCHECK_GE(1.0f, opacity);
diff --git a/src/cobalt/render_tree/punch_through_video_node.h b/src/cobalt/render_tree/punch_through_video_node.h
index 3683d6a..f96da9a 100644
--- a/src/cobalt/render_tree/punch_through_video_node.h
+++ b/src/cobalt/render_tree/punch_through_video_node.h
@@ -45,6 +45,10 @@
     Builder(const math::RectF& rect, const SetBoundsCB& set_bounds_cb)
         : rect(rect), set_bounds_cb(set_bounds_cb) {}
 
+    bool operator==(const Builder& other) const {
+      return rect == other.rect && set_bounds_cb.Equals(other.set_bounds_cb);
+    }
+
     // The destination rectangle (size includes border).
     math::RectF rect;
     const SetBoundsCB set_bounds_cb;
diff --git a/src/cobalt/render_tree/rect_node.cc b/src/cobalt/render_tree/rect_node.cc
index 7b6f656..33a7ad3 100644
--- a/src/cobalt/render_tree/rect_node.cc
+++ b/src/cobalt/render_tree/rect_node.cc
@@ -84,6 +84,23 @@
       border(moved->border.Pass()),
       rounded_corners(moved->rounded_corners.Pass()) {}
 
+bool RectNode::Builder::operator==(const Builder& other) const {
+  bool brush_equals = !background_brush && !other.background_brush;
+  if (background_brush && other.background_brush) {
+    EqualsBrushVisitor brush_equals_visitor(background_brush.get());
+    other.background_brush->Accept(&brush_equals_visitor);
+    brush_equals = brush_equals_visitor.result();
+  }
+  bool border_equals = (!border && !other.border) ||
+                       (border && other.border && *border == *other.border);
+  bool rounded_corners_equals = (!rounded_corners && !other.rounded_corners) ||
+                                (rounded_corners && other.rounded_corners &&
+                                 *rounded_corners == *other.rounded_corners);
+
+  return rect == other.rect && brush_equals && border_equals &&
+         rounded_corners_equals;
+}
+
 void RectNode::Accept(NodeVisitor* visitor) { visitor->Visit(this); }
 
 math::RectF RectNode::GetBounds() const { return data_.rect; }
diff --git a/src/cobalt/render_tree/rect_node.h b/src/cobalt/render_tree/rect_node.h
index d896aef..13ab23a 100644
--- a/src/cobalt/render_tree/rect_node.h
+++ b/src/cobalt/render_tree/rect_node.h
@@ -52,6 +52,8 @@
     explicit Builder(const Builder& other);
     explicit Builder(Moved moved);
 
+    bool operator==(const Builder& other) const;
+
     // The destination rectangle (size includes border).
     math::RectF rect;
 
diff --git a/src/cobalt/render_tree/rect_shadow_node.h b/src/cobalt/render_tree/rect_shadow_node.h
index 22344ad..de752f5 100644
--- a/src/cobalt/render_tree/rect_shadow_node.h
+++ b/src/cobalt/render_tree/rect_shadow_node.h
@@ -38,6 +38,11 @@
             float spread)
         : rect(rect), shadow(shadow), inset(inset), spread(spread) {}
 
+    bool operator==(const Builder& other) const {
+      return rect == other.rect && rounded_corners == other.rounded_corners &&
+             shadow == other.shadow && inset == other.inset &&
+             spread == other.spread;
+    }
     // The destination rectangle.
     math::RectF rect;
 
diff --git a/src/cobalt/render_tree/rounded_corners.h b/src/cobalt/render_tree/rounded_corners.h
index cb630e8..19d1f4c 100644
--- a/src/cobalt/render_tree/rounded_corners.h
+++ b/src/cobalt/render_tree/rounded_corners.h
@@ -80,6 +80,12 @@
         bottom_right(bottom_right),
         bottom_left(bottom_left) {}
 
+  bool operator==(const RoundedCorners& other) const {
+    return top_left == other.top_left && top_right == other.top_right &&
+           bottom_right == other.bottom_right &&
+           bottom_left == other.bottom_left;
+  }
+
   RoundedCorners Inset(float left, float top, float right, float bottom) const {
     return RoundedCorners(
         top_left.Inset(left, top), top_right.Inset(right, top),
diff --git a/src/cobalt/render_tree/shadow.h b/src/cobalt/render_tree/shadow.h
index caefb8a..057c30a 100644
--- a/src/cobalt/render_tree/shadow.h
+++ b/src/cobalt/render_tree/shadow.h
@@ -32,6 +32,11 @@
          const ColorRGBA& color)
       : offset(offset), blur_sigma(blur_sigma), color(color) {}
 
+  bool operator==(const Shadow& other) const {
+    return offset == other.offset && blur_sigma == other.blur_sigma &&
+           color == other.color;
+  }
+
   // Since the blur parameters represent standard deviations, most of the
   // blur is contained within 3 of them, so report that as the extent of the
   // blur.
diff --git a/src/cobalt/render_tree/text_node.h b/src/cobalt/render_tree/text_node.h
index 2dde330..3d35b43 100644
--- a/src/cobalt/render_tree/text_node.h
+++ b/src/cobalt/render_tree/text_node.h
@@ -46,6 +46,11 @@
             const scoped_refptr<GlyphBuffer>& glyph_buffer,
             const ColorRGBA& color);
 
+    bool operator==(const Builder& other) const {
+      return offset == other.offset && glyph_buffer == other.glyph_buffer &&
+             color == other.color && shadows == other.shadows;
+    }
+
     math::Vector2dF offset;
 
     // All of the glyph data needed to render the text.
diff --git a/src/cobalt/render_tree/viewport_filter.h b/src/cobalt/render_tree/viewport_filter.h
index 92f3d58..e4a0afa 100644
--- a/src/cobalt/render_tree/viewport_filter.h
+++ b/src/cobalt/render_tree/viewport_filter.h
@@ -33,6 +33,11 @@
                           const RoundedCorners& rounded_corners)
       : viewport_(viewport), rounded_corners_(rounded_corners) {}
 
+  bool operator==(const ViewportFilter& other) const {
+    return viewport_ == other.viewport_ &&
+           rounded_corners_ == other.rounded_corners_;
+  }
+
   const math::RectF& viewport() const { return viewport_; }
 
   bool has_rounded_corners() const { return !!rounded_corners_; }
diff --git a/src/cobalt/renderer/copy_font_data.gypi b/src/cobalt/renderer/copy_font_data.gypi
index b62574e..0102e42 100644
--- a/src/cobalt/renderer/copy_font_data.gypi
+++ b/src/cobalt/renderer/copy_font_data.gypi
@@ -21,22 +21,9 @@
     'source_font_files_dir': '<(static_contents_source_dir)/fonts/font_files',
 
     'conditions': [
-      [ 'cobalt_font_package == "expanded"', {
-        'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
-
-        'package_named_sans_serif': 4,
-        'package_named_serif': 3,
-        'package_named_fcc_fonts': 2,
-        'package_fallback_lang_non_cjk': 2,
-        'package_fallback_lang_cjk': 2,
-        'package_fallback_lang_cjk_low_quality': 0,
-        'package_fallback_lang_jp': 0,
-        'package_fallback_emoji': 1,
-        'package_fallback_symbols': 1,
-      }],
-
       # 'unlimited' is deprecated but is mapped to 'standard'
-      [ 'cobalt_font_package == "standard" or cobalt_font_package == "unlimited"', {
+      # 'expanded' also currently maps to 'standard' but this may change in the future
+      [ 'cobalt_font_package == "standard" or cobalt_font_package == "unlimited" or cobalt_font_package == "expanded"', {
         'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
 
         'package_named_sans_serif': 4,
@@ -45,13 +32,12 @@
         'package_fallback_lang_non_cjk': 2,
         'package_fallback_lang_cjk': 1,
         'package_fallback_lang_cjk_low_quality': 0,
-        'package_fallback_lang_jp': 0,
         'package_fallback_emoji': 1,
         'package_fallback_symbols': 1,
       }],
 
-      # '10megabytes' is deprecated but is mapped to 'limited_with_jp'
-      [ 'cobalt_font_package == "limited_with_jp" or cobalt_font_package == "10megabytes"', {
+      # '10megabytes' and 'limited_with_jp' are deprecated but map to 'limited'
+      [ 'cobalt_font_package == "limited" or cobalt_font_package == "limited_with_jp" or cobalt_font_package == "10megabytes"', {
         'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
 
         'package_named_sans_serif': 2,
@@ -60,21 +46,6 @@
         'package_fallback_lang_non_cjk': 1,
         'package_fallback_lang_cjk': 0,
         'package_fallback_lang_cjk_low_quality': 1,
-        'package_fallback_lang_jp': 1,
-        'package_fallback_emoji': 1,
-        'package_fallback_symbols': 1,
-      }],
-
-      [ 'cobalt_font_package == "limited"', {
-        'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
-
-        'package_named_sans_serif': 2,
-        'package_named_serif': 0,
-        'package_named_fcc_fonts': 0,
-        'package_fallback_lang_non_cjk': 1,
-        'package_fallback_lang_cjk': 0,
-        'package_fallback_lang_cjk_low_quality': 1,
-        'package_fallback_lang_jp': 0,
         'package_fallback_emoji': 1,
         'package_fallback_symbols': 1,
       }],
@@ -88,7 +59,6 @@
         'package_fallback_lang_non_cjk': 0,
         'package_fallback_lang_cjk': 0,
         'package_fallback_lang_cjk_low_quality': 0,
-        'package_fallback_lang_jp': 0,
         'package_fallback_emoji': 0,
         'package_fallback_symbols': 0,
       }],
@@ -108,7 +78,6 @@
         'package_fallback_lang_non_cjk': 0,
         'package_fallback_lang_cjk': 0,
         'package_fallback_lang_cjk_low_quality': 0,
-        'package_fallback_lang_jp': 0,
         'package_fallback_emoji': 1,
         'package_fallback_symbols': 0,
       }],
@@ -131,9 +100,6 @@
       [ 'cobalt_font_package_override_fallback_lang_cjk_low_quality >= 0', {
         'package_fallback_lang_cjk_low_quality': '<(cobalt_font_package_override_fallback_lang_cjk_low_quality)',
       }],
-      [ 'cobalt_font_package_override_fallback_lang_jp >= 0', {
-        'package_fallback_lang_jp': '<(cobalt_font_package_override_fallback_lang_jp)',
-      }],
       [ 'cobalt_font_package_override_fallback_emoji >= 0', {
         'package_fallback_emoji': '<(cobalt_font_package_override_fallback_emoji)',
       }],
@@ -309,21 +275,11 @@
             '<(source_font_files_dir)/NotoSansCJK-Regular.ttc',
           ],
         }],
-        [ 'package_fallback_lang_cjk >= 2', {
-          'files+': [
-            '<(source_font_files_dir)/NotoSansCJK-Bold.ttc',
-          ],
-        }],
         [ 'package_fallback_lang_cjk_low_quality >= 1', {
           'files+': [
             '<(source_font_files_dir)/DroidSansFallback.ttf',
           ],
         }],
-        [ 'package_fallback_lang_jp >= 1', {
-          'files+': [
-            '<(source_font_files_dir)/NotoSansJP-Regular.otf',
-          ],
-        }],
         [ 'package_fallback_emoji >= 1', {
           'files+': [
             '<(source_font_files_dir)/NotoEmoji-Regular.ttf',
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index 534f38c..187e684 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -81,13 +81,22 @@
       submission_disposal_thread_("Rasterizer Submission Disposal"),
       submit_even_if_render_tree_is_unchanged_(
           submit_even_if_render_tree_is_unchanged),
-      last_render_animations_active_(false),
+      last_did_rasterize_(false),
+      last_animations_expired_(true),
+      last_stat_tracked_animations_expired_(true),
       rasterize_periodic_timer_("Renderer.Rasterize.Duration",
                                 kRasterizePeriodicTimerEntriesPerUpdate,
                                 false /*enable_entry_list_c_val*/),
       rasterize_animations_timer_("Renderer.Rasterize.Animations",
                                   kRasterizeAnimationsTimerMaxEntries,
                                   true /*enable_entry_list_c_val*/),
+      rasterize_periodic_interval_timer_("Renderer.Rasterize.DurationInterval",
+                                         kRasterizeAnimationsTimerMaxEntries,
+                                         true /*enable_entry_list_c_val*/),
+      rasterize_animations_interval_timer_(
+          "Renderer.Rasterize.AnimationsInterval",
+          kRasterizeAnimationsTimerMaxEntries,
+          true /*enable_entry_list_c_val*/),
       new_render_tree_rasterize_count_(
           "Count.Renderer.Rasterize.NewRenderTree", 0,
           "Total number of new render trees rasterized."),
@@ -152,6 +161,7 @@
   // must be destroyed before we shutdown the rasterizer thread since it may
   // contain references to render tree nodes and resources.
   last_render_tree_ = NULL;
+  last_animated_render_tree_ = NULL;
 
   // Submit a shutdown task to the rasterizer thread so that it can shutdown
   // anything that must be shutdown from that thread.
@@ -252,12 +262,13 @@
   DCHECK(rasterizer_thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("cobalt::renderer", "Pipeline::RasterizeCurrentTree()");
 
-  base::TimeTicks now = base::TimeTicks::Now();
-  Submission submission = submission_queue_->GetCurrentSubmission(now);
+  base::TimeTicks start_rasterize_time = base::TimeTicks::Now();
+  Submission submission =
+      submission_queue_->GetCurrentSubmission(start_rasterize_time);
 
   bool is_new_render_tree = submission.render_tree != last_render_tree_;
   bool has_render_tree_changed =
-      last_render_animations_active_ || is_new_render_tree;
+      !last_animations_expired_ || is_new_render_tree;
 
   // If our render tree hasn't changed from the one that was previously
   // rendered and it's okay on this system to not flip the display buffer
@@ -271,63 +282,99 @@
   render_tree::animations::AnimateNode* animate_node =
       base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
           submission.render_tree.get());
-  bool are_animations_active = animate_node->expiry() > submission.time_offset;
 
-  // If animations are going from being inactive to active, then set the c_val
-  // prior to starting the animation so that it's in the correct state while the
-  // tree is being rendered.
-  if (!last_render_animations_active_ && are_animations_active) {
-    has_active_animations_c_val_ = true;
-  }
+  // Rasterize the last submitted render tree.
+  bool did_rasterize =
+      RasterizeSubmissionToRenderTarget(submission, render_target_);
+
+  bool animations_expired = animate_node->expiry() <= submission.time_offset;
+  bool stat_tracked_animations_expired =
+      animate_node->depends_on_time_expiry() <= submission.time_offset;
+
+  UpdateRasterizeStats(did_rasterize, stat_tracked_animations_expired,
+                       is_new_render_tree, start_rasterize_time,
+                       base::TimeTicks::Now());
+
+  last_did_rasterize_ = did_rasterize;
+  last_animations_expired_ = animations_expired;
+  last_stat_tracked_animations_expired_ = stat_tracked_animations_expired;
+}
+
+void Pipeline::UpdateRasterizeStats(bool did_rasterize,
+                                    bool are_stat_tracked_animations_expired,
+                                    bool is_new_render_tree,
+                                    base::TimeTicks start_time,
+                                    base::TimeTicks end_time) {
+  bool last_animations_active =
+      !last_stat_tracked_animations_expired_ && last_did_rasterize_;
+  bool animations_active =
+      !are_stat_tracked_animations_expired && did_rasterize;
 
   // The rasterization is only timed with the periodic timer when the render
   // tree has changed. This ensures that the frames being timed are consistent
   // between platforms that submit unchanged trees and those that don't.
-  bool should_run_periodic_timer = has_render_tree_changed;
-
-  // The rasterization is only timed with the animations timer when there are
-  // animations to track. This applies when animations were active during either
-  // the last rasterization or the current one. The reason for including the
-  // last one is that if animations have just expired, then this rasterization
-  // produces the final state of the animated tree.
-  bool should_run_animations_timer =
-      last_render_animations_active_ || are_animations_active;
-
-  if (should_run_periodic_timer) {
-    rasterize_periodic_timer_.Start(now);
-  }
-  if (should_run_animations_timer) {
-    rasterize_animations_timer_.Start(now);
+  if (did_rasterize) {
+    rasterize_periodic_timer_.Start(start_time);
+    rasterize_periodic_timer_.Stop(end_time);
   }
 
-  // Rasterize the last submitted render tree.
-  RasterizeSubmissionToRenderTarget(submission, render_target_);
+  if (last_animations_active || animations_active) {
+    // The rasterization is only timed with the animations timer when there are
+    // animations to track. This applies when animations were active during
+    // either the last rasterization or the current one. The reason for
+    // including the last one is that if animations have just expired, then this
+    // rasterization produces the final state of the animated tree.
+    if (did_rasterize) {
+      rasterize_animations_timer_.Start(start_time);
+      rasterize_animations_timer_.Stop(end_time);
+    }
 
-  if (should_run_periodic_timer) {
-    rasterize_periodic_timer_.Stop();
-  }
-  if (should_run_animations_timer) {
-    rasterize_animations_timer_.Stop();
+    // If animations are going from being inactive to active, then set the c_val
+    // prior to starting the animation so that it's in the correct state while
+    // the tree is being rendered.
+    // Also, start the interval timer now. While the first entry only captures a
+    // partial interval, it's recorded to include the duration of the first
+    // submission. All subsequent entries will record a full interval.
+    if (!last_animations_active && animations_active) {
+      has_active_animations_c_val_ = true;
+      rasterize_periodic_interval_timer_.Start(start_time);
+      rasterize_animations_interval_timer_.Start(start_time);
+    }
+
+    if (!did_rasterize) {
+      // If we didn't actually rasterize anything, don't count this sample.
+      rasterize_periodic_interval_timer_.Cancel();
+      rasterize_animations_interval_timer_.Cancel();
+    } else {
+      rasterize_periodic_interval_timer_.Stop(end_time);
+      rasterize_animations_interval_timer_.Stop(end_time);
+    }
+
+    // If animations are active, then they are guaranteed at least one more
+    // interval. Start the timer to record its duration.
+    if (animations_active) {
+      rasterize_periodic_interval_timer_.Start(end_time);
+      rasterize_animations_interval_timer_.Start(end_time);
+    }
+
+    // Check for if the animations are starting or ending.
+    if (!last_animations_active && animations_active) {
+      animations_start_time_ = end_time.ToInternalValue();
+    } else if (last_animations_active && !animations_active) {
+      animations_end_time_ = end_time.ToInternalValue();
+      has_active_animations_c_val_ = false;
+      rasterize_animations_interval_timer_.Flush();
+      rasterize_animations_timer_.Flush();
+    }
   }
 
   if (is_new_render_tree) {
     ++new_render_tree_rasterize_count_;
-    new_render_tree_rasterize_time_ = base::TimeTicks::Now().ToInternalValue();
+    new_render_tree_rasterize_time_ = end_time.ToInternalValue();
   }
-
-  // Check for if the animations are starting or ending.
-  if (!last_render_animations_active_ && are_animations_active) {
-    animations_start_time_ = base::TimeTicks::Now().ToInternalValue();
-  } else if (last_render_animations_active_ && !are_animations_active) {
-    animations_end_time_ = base::TimeTicks::Now().ToInternalValue();
-    has_active_animations_c_val_ = false;
-    rasterize_animations_timer_.Flush();
-  }
-
-  last_render_animations_active_ = are_animations_active;
 }
 
-void Pipeline::RasterizeSubmissionToRenderTarget(
+bool Pipeline::RasterizeSubmissionToRenderTarget(
     const Submission& submission,
     const scoped_refptr<backend::RenderTarget>& render_target) {
   TRACE_EVENT0("cobalt::renderer",
@@ -338,14 +385,17 @@
   // |previous_animated_area_|.
   if (submission.render_tree != last_render_tree_) {
     last_render_tree_ = submission.render_tree;
+    last_animated_render_tree_ = NULL;
     previous_animated_area_ = base::nullopt;
     last_render_time_ = base::nullopt;
   }
 
   // Animate the render tree using the submitted animations.
   render_tree::animations::AnimateNode* animate_node =
-      base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
-          submission.render_tree.get());
+      last_animated_render_tree_
+          ? last_animated_render_tree_.get()
+          : base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
+                submission.render_tree.get());
 
   // Some animations require a GL graphics context to be current.  Specifically,
   // a call to SbPlayerGetCurrentFrame() may be made to get the current video
@@ -355,6 +405,12 @@
   render_tree::animations::AnimateNode::AnimateResults results =
       animate_node->Apply(submission.time_offset);
 
+  if (results.animated == last_animated_render_tree_ &&
+      !submit_even_if_render_tree_is_unchanged_) {
+    return false;
+  }
+  last_animated_render_tree_ = results.animated;
+
   // Calculate a bounding box around the active animations.  Union it with the
   // bounding box around active animations from the previous frame, and we get
   // a scissor rectangle marking the dirty regions of the screen.
@@ -370,13 +426,16 @@
   // Rasterize the animated render tree.
   rasterizer::Rasterizer::Options rasterizer_options;
   rasterizer_options.dirty = redraw_area;
-  rasterizer_->Submit(results.animated, render_target, rasterizer_options);
+  rasterizer_->Submit(
+      results.animated->source(), render_target, rasterizer_options);
 
   if (!submission.on_rasterized_callback.is_null()) {
     submission.on_rasterized_callback.Run();
   }
 
   last_render_time_ = submission.time_offset;
+
+  return true;
 }
 
 void Pipeline::InitializeRasterizerThread(
@@ -468,7 +527,8 @@
   render_tree::animations::AnimateNode::AnimateResults results =
       animate_node->Apply(submission.time_offset);
 
-  std::string tree_dump = render_tree::DumpRenderTreeToString(results.animated);
+  std::string tree_dump =
+      render_tree::DumpRenderTreeToString(results.animated->source());
   if (message.empty() || message == "undefined") {
     // If no filename was specified, send output to the console.
     LOG(INFO) << tree_dump.c_str();
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index 9df0d8a..a9abff7 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -109,10 +109,18 @@
 
   // Rasterize the animated |render_tree_submission| to |render_target|,
   // applying the time_offset in the submission to the animations.
-  void RasterizeSubmissionToRenderTarget(
+  // Returns true only if a rasterization actually took place.
+  bool RasterizeSubmissionToRenderTarget(
       const Submission& render_tree_submission,
       const scoped_refptr<backend::RenderTarget>& render_target);
 
+  // Updates the rasterizer timer stats according to the |start_time| and
+  // |end_time| of the most recent rasterize call.
+  void UpdateRasterizeStats(bool did_rasterize,
+                            bool are_stat_tracked_animations_expired,
+                            bool is_new_render_tree, base::TimeTicks start_time,
+                            base::TimeTicks end_time);
+
   // This method is executed on the rasterizer thread and is responsible for
   // constructing the rasterizer.
   void InitializeRasterizerThread(
@@ -179,6 +187,10 @@
 
   // Keeps track of the last rendered animated render tree.
   scoped_refptr<render_tree::Node> last_render_tree_;
+
+  scoped_refptr<render_tree::animations::AnimateNode>
+      last_animated_render_tree_;
+
   // Keeps track of the area of the screen that animations previously existed
   // within, so that we can know which regions of the screens would be dirty
   // next frame.
@@ -187,8 +199,14 @@
   base::optional<base::TimeDelta> last_render_time_;
   // Keep track of whether the last rendered tree had active animations. This
   // allows us to skip rasterizing that render tree if we see it again and it
-  // did not have active animations.
-  bool last_render_animations_active_;
+  // did have expired animations.
+  bool last_animations_expired_;
+  // Keep track of whether the last rendered tree had animations that we're
+  // tracking stats on.
+  bool last_stat_tracked_animations_expired_;
+
+  // Did a rasterization take place in the last frame?
+  bool last_did_rasterize_;
 
   // Timer tracking the amount of time spent in
   // |RasterizeSubmissionToRenderTarget| when the render tree has changed.
@@ -199,6 +217,17 @@
   // tracking is flushed when the animations expire.
   base::CValCollectionTimerStats<base::CValDebug> rasterize_animations_timer_;
 
+  // Accumulates render tree rasterization interval times but does not flush
+  // them until the maximum number of samples is gathered.
+  base::CValCollectionTimerStats<base::CValPublic>
+  rasterize_periodic_interval_timer_;
+
+  // Timer tracking the amount of time spent in
+  // |RasterizeSubmissionToRenderTarget| while animations are active. The
+  // tracking is flushed when the animations expire.
+  base::CValCollectionTimerStats<base::CValPublic>
+      rasterize_animations_interval_timer_;
+
   // The total number of new render trees that have been rasterized.
   base::CVal<int> new_render_tree_rasterize_count_;
   // The last time that a newly encountered render tree was first rasterized.
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
index 4ad07b9..ae68696 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
@@ -54,6 +54,10 @@
   return stream_->duplicate();
 }
 
+size_t SkTypeface_CobaltStream::GetStreamLength() const {
+  return stream_->getLength();
+}
+
 SkTypeface_CobaltStreamProvider::SkTypeface_CobaltStreamProvider(
     SkFileMemoryChunkStreamProvider* stream_provider, int face_index,
     Style style, bool is_fixed_pitch, const SkString& family_name,
@@ -83,3 +87,11 @@
   *face_index = face_index_;
   return stream_provider_->OpenStream();
 }
+
+size_t SkTypeface_CobaltStreamProvider::GetStreamLength() const {
+  DLOG(WARNING)
+      << "Requesting stream length of SkTypeface_CobaltStreamProvider. "
+         "This requires a file load and should be used sparingly.";
+  SkAutoTUnref<SkFileMemoryChunkStream> stream(stream_provider_->OpenStream());
+  return stream->getLength();
+}
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
index 2ac66ab..5e30052 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
@@ -28,10 +28,12 @@
   SkTypeface_Cobalt(int face_index, Style style, bool is_fixed_pitch,
                     const SkString& family_name);
 
+  virtual size_t GetStreamLength() const = 0;
+
   bool synthesizes_bold() const { return synthesizes_bold_; }
 
  protected:
-  virtual void onGetFamilyName(SkString* family_name) const SK_OVERRIDE;
+  void onGetFamilyName(SkString* family_name) const SK_OVERRIDE;
 
   int face_index_;
   SkString family_name_;
@@ -46,10 +48,12 @@
   SkTypeface_CobaltStream(SkStreamAsset* stream, int face_index, Style style,
                           bool is_fixed_pitch, const SkString& family_name);
 
-  virtual void onGetFontDescriptor(SkFontDescriptor* descriptor,
-                                   bool* serialize) const SK_OVERRIDE;
+  void onGetFontDescriptor(SkFontDescriptor* descriptor,
+                           bool* serialize) const SK_OVERRIDE;
 
-  virtual SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+  SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+
+  size_t GetStreamLength() const SK_OVERRIDE;
 
  private:
   typedef SkTypeface_Cobalt INHERITED;
@@ -64,10 +68,12 @@
       Style style, bool is_fixed_pitch, const SkString& family_name,
       bool disable_synthetic_bolding);
 
-  virtual void onGetFontDescriptor(SkFontDescriptor* descriptor,
-                                   bool* serialize) const SK_OVERRIDE;
+  void onGetFontDescriptor(SkFontDescriptor* descriptor,
+                           bool* serialize) const SK_OVERRIDE;
 
-  virtual SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+  SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+
+  size_t GetStreamLength() const SK_OVERRIDE;
 
  private:
   typedef SkTypeface_Cobalt INHERITED;
diff --git a/src/cobalt/renderer/rasterizer/skia/typeface.cc b/src/cobalt/renderer/rasterizer/skia/typeface.cc
index e4396fe..6e0866d 100644
--- a/src/cobalt/renderer/rasterizer/skia/typeface.cc
+++ b/src/cobalt/renderer/rasterizer/skia/typeface.cc
@@ -18,12 +18,6 @@
 
 #include "third_party/skia/include/core/SkPaint.h"
 
-namespace {
-
-const uint32 kEstimatedBytesPerGlyph = 256;
-
-}  // namespace
-
 namespace cobalt {
 namespace renderer {
 namespace rasterizer {
@@ -43,7 +37,7 @@
 }
 
 uint32 SkiaTypeface::GetEstimatedSizeInBytes() const {
-  return typeface_->countGlyphs() * kEstimatedBytesPerGlyph;
+  return static_cast<uint32>(typeface_->GetStreamLength());
 }
 
 scoped_refptr<render_tree::Font> SkiaTypeface::CreateFontWithSize(
diff --git a/src/cobalt/renderer/submission_queue.cc b/src/cobalt/renderer/submission_queue.cc
index 20a1e39..b7b861e 100644
--- a/src/cobalt/renderer/submission_queue.cc
+++ b/src/cobalt/renderer/submission_queue.cc
@@ -89,8 +89,11 @@
         base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
             submission_queue_.front().render_tree.get());
 
-    if (animate_node->expiry() <= submission_time(now) &&
-        animate_node->expiry() <=
+    // Check the expiration of only animations that depend on the time
+    // parameter, since they are the only ones that will be affected by snapping
+    // time.
+    if (animate_node->depends_on_time_expiry() <= submission_time(now) &&
+        animate_node->depends_on_time_expiry() <=
             latest_to_submission_time + render_time(now)) {
       to_submission_time_in_ms_.SnapToTarget();
     }
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
index 8951eaf..0f32130 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -108,7 +108,11 @@
   packet.data = const_cast<uint8_t*>(input_buffer.data());
   packet.size = input_buffer.size();
 
+#if LIBAVUTIL_VERSION_MAJOR > 52
+  av_frame_unref(av_frame_);
+#else   // LIBAVUTIL_VERSION_MAJOR > 52
   avcodec_get_frame_defaults(av_frame_);
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
   int frame_decoded = 0;
   int result =
       avcodec_decode_audio4(codec_context_, av_frame_, &frame_decoded, &packet);
@@ -219,7 +223,11 @@
     return;
   }
 
+#if LIBAVUTIL_VERSION_MAJOR > 52
+  av_frame_ = av_frame_alloc();
+#else   // LIBAVUTIL_VERSION_MAJOR > 52
   av_frame_ = avcodec_alloc_frame();
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
   if (av_frame_ == NULL) {
     SB_LOG(ERROR) << "Unable to allocate audio frame";
     TeardownCodec();
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_common.h b/src/starboard/shared/ffmpeg/ffmpeg_common.h
index 4203e91..9c63c1d 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_common.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_common.h
@@ -27,6 +27,16 @@
 
 #include "starboard/shared/internal_only.h"
 
+#if !defined(LIBAVUTIL_VERSION_MAJOR)
+#error "LIBAVUTIL_VERSION_MAJOR not defined"
+#endif  // !defined(LIBAVUTIL_VERSION_MAJOR)
+
+#if LIBAVUTIL_VERSION_MAJOR > 52
+#define PIX_FMT_NONE AV_PIX_FMT_NONE
+#define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P
+#define PIX_FMT_YUVJ420P AV_PIX_FMT_YUVJ420P
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
+
 namespace starboard {
 namespace shared {
 namespace ffmpeg {
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 2d4e659..48b5774 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -37,6 +37,58 @@
   return width * height * 3 / 2;
 }
 
+#if LIBAVUTIL_VERSION_MAJOR > 52
+
+void ReleaseBuffer(void* opaque, uint8_t* data) {
+  SbMemorySet(data, 0, sizeof(data));
+  SbMemoryDeallocate(data);
+}
+
+int AllocateBuffer(AVCodecContext* codec_context, AVFrame* frame, int flags) {
+  if (codec_context->pix_fmt != PIX_FMT_YUV420P &&
+      codec_context->pix_fmt != PIX_FMT_YUVJ420P) {
+    SB_DLOG(WARNING) << "Unsupported pix_fmt " << codec_context->pix_fmt;
+    return AVERROR(EINVAL);
+  }
+
+  int ret =
+      av_image_check_size(codec_context->width, codec_context->height, 0, NULL);
+  if (ret < 0) {
+    return ret;
+  }
+
+  // Align to kAlignment * 2 as we will divide y_stride by 2 for u and v planes
+  size_t y_stride = AlignUp(codec_context->width, kAlignment * 2);
+  size_t uv_stride = y_stride / 2;
+  size_t aligned_height = AlignUp(codec_context->height, kAlignment * 2);
+
+  uint8_t* frame_buffer = reinterpret_cast<uint8_t*>(SbMemoryAllocateAligned(
+      kAlignment, GetYV12SizeInBytes(y_stride, aligned_height)));
+
+  frame->data[0] = frame_buffer;
+  frame->linesize[0] = y_stride;
+
+  frame->data[1] = frame_buffer + y_stride * aligned_height;
+  frame->linesize[1] = uv_stride;
+
+  frame->data[2] = frame->data[1] + uv_stride * aligned_height / 2;
+  frame->linesize[2] = uv_stride;
+
+  frame->opaque = frame;
+  frame->width = codec_context->width;
+  frame->height = codec_context->height;
+  frame->format = codec_context->pix_fmt;
+
+  frame->reordered_opaque = codec_context->reordered_opaque;
+
+  frame->buf[0] = av_buffer_create(frame_buffer,
+                                   GetYV12SizeInBytes(y_stride, aligned_height),
+                                   &ReleaseBuffer, frame->opaque, 0);
+  return 0;
+}
+
+#else  // LIBAVUTIL_VERSION_MAJOR > 52
+
 int AllocateBuffer(AVCodecContext* codec_context, AVFrame* frame) {
   if (codec_context->pix_fmt != PIX_FMT_YUV420P &&
       codec_context->pix_fmt != PIX_FMT_YUVJ420P) {
@@ -91,6 +143,7 @@
   SbMemorySet(frame->data, 0, sizeof(frame->data));
 }
 
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
 }  // namespace
 
 VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec)
@@ -205,7 +258,11 @@
 bool VideoDecoder::DecodePacket(AVPacket* packet) {
   SB_DCHECK(packet != NULL);
 
+#if LIBAVUTIL_VERSION_MAJOR > 52
+  av_frame_unref(av_frame_);
+#else   // LIBAVUTIL_VERSION_MAJOR > 52
   avcodec_get_frame_defaults(av_frame_);
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
   int frame_decoded = 0;
   int result =
       avcodec_decode_video2(codec_context_, av_frame_, &frame_decoded, packet);
@@ -251,8 +308,12 @@
   codec_context_->thread_count = 2;
   codec_context_->opaque = this;
   codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
+#if LIBAVUTIL_VERSION_MAJOR > 52
+  codec_context_->get_buffer2 = AllocateBuffer;
+#else   // LIBAVUTIL_VERSION_MAJOR > 52
   codec_context_->get_buffer = AllocateBuffer;
   codec_context_->release_buffer = ReleaseBuffer;
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
 
   codec_context_->extradata = NULL;
   codec_context_->extradata_size = 0;
@@ -272,7 +333,11 @@
     return;
   }
 
+#if LIBAVUTIL_VERSION_MAJOR > 52
+  av_frame_ = av_frame_alloc();
+#else   // LIBAVUTIL_VERSION_MAJOR > 52
   av_frame_ = avcodec_alloc_frame();
+#endif  // LIBAVUTIL_VERSION_MAJOR > 52
   if (av_frame_ == NULL) {
     SB_LOG(ERROR) << "Unable to allocate audio frame";
     TeardownCodec();
diff --git a/src/starboard/tools/raspi/run_test.py b/src/starboard/tools/raspi/run_test.py
index 5119c3a..326b654 100755
--- a/src/starboard/tools/raspi/run_test.py
+++ b/src/starboard/tools/raspi/run_test.py
@@ -21,9 +21,11 @@
 import functools
 import logging
 import os
+import re
 import signal
 import sys
 import textwrap
+import time
 
 import pexpect
 
@@ -32,6 +34,22 @@
 _RASPI_USERNAME = 'pi'
 _RASPI_PASSWORD = 'raspberry'
 
+# Timeouts are in seconds
+_PEXPECT_DEFAULT_TIMEOUT = 600
+_PEXPECT_EXPECT_TIMEOUT = 60
+
+
+def _CleanupProcess(process):
+  """Closes current pexpect process.
+
+  Args:
+    process: Current pexpect process.
+  """
+  if process is not None and process.isalive():
+    # Send ctrl-c to the raspi.
+    process.sendline(chr(3))
+    process.close()
+
 
 # pylint: disable=unused-argument
 def _SigIntOrSigTermHandler(process, signum, frame):
@@ -44,14 +62,11 @@
     frame: Current stack frame.  Passed in when the signal handler is called by
       python runtime.
   """
-  if process.isalive():
-    # Send ctrl-c to the raspi.
-    process.sendline(chr(3))
-    process.close()
+  _CleanupProcess(process)
   sys.exit(signum)
 
 
-def RunTest(test_path, raspi_ip, flags):
+def _RunTest(test_path, raspi_ip, flags):
   """Run a test on a Raspi machine and print relevant stdout.
 
   Args:
@@ -61,70 +76,103 @@
 
   Returns:
     Exit status of running the test.
-
-  Raises:
-    ValueError: Raised if test_path is invalid.
   """
-  if not os.path.isfile(test_path):
-    raise ValueError('test_path ({}) must be a file.'.format(test_path))
+  return_value = 1
 
-  sys.stdout.write('Process launched, ID={}\n'.format(os.getpid()))
-  sys.stdout.flush()
+  try:
+    process = None
 
-  test_dir_path, test_file = os.path.split(test_path)
-  test_base_dir = os.path.basename(os.path.normpath(test_dir_path))
+    if not os.path.isfile(test_path):
+      raise ValueError('test_path ({}) must be a file.'.format(test_path))
 
-  raspi_user_hostname = _RASPI_USERNAME + '@' + raspi_ip
-  raspi_test_path = os.path.join(test_base_dir, test_file)
+    # This is used to strip ansi color codes from output.
+    sanitize_line_re = re.compile(r'\x1b[^m]*m')
 
-  # rsync the test files to the raspi
-  options = '-avzh --exclude obj*'
-  source = test_dir_path
-  destination = raspi_user_hostname + ':~/'
-  command = 'rsync ' + options + ' ' + source + ' ' + destination
-  process = pexpect.spawn(command, timeout=120)
+    sys.stdout.write('Process launched, ID={}\n'.format(os.getpid()))
+    sys.stdout.flush()
 
-  signal.signal(signal.SIGINT,
-                functools.partial(_SigIntOrSigTermHandler, process))
-  signal.signal(signal.SIGTERM,
-                functools.partial(_SigIntOrSigTermHandler, process))
+    test_dir_path, test_file = os.path.split(test_path)
+    test_base_dir = os.path.basename(os.path.normpath(test_dir_path))
 
-  process.expect(r'\S+ password:')
-  process.sendline(_RASPI_PASSWORD)
+    raspi_user_hostname = _RASPI_USERNAME + '@' + raspi_ip
+    raspi_test_path = os.path.join(test_base_dir, test_file)
 
-  while True:
-    line = process.readline()
-    if line:
+    # rsync the test files to the raspi
+    options = '-avzh --exclude obj*'
+    source = test_dir_path
+    destination = raspi_user_hostname + ':~/'
+    rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination
+    process = pexpect.spawn(rsync_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+
+    signal.signal(signal.SIGINT,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+    signal.signal(signal.SIGTERM,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+
+    process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
+    process.sendline(_RASPI_PASSWORD)
+
+    while True:
+      line = sanitize_line_re.sub('', process.readline())
+      if line:
+        sys.stdout.write(line)
+        sys.stdout.flush()
+      else:
+        break
+
+    # ssh into the raspi and run the test
+    ssh_command = 'ssh ' + raspi_user_hostname
+    process = pexpect.spawn(ssh_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+
+    signal.signal(signal.SIGINT,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+    signal.signal(signal.SIGTERM,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+
+    process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
+    process.sendline(_RASPI_PASSWORD)
+
+    # Escape command line metacharacters in the flags
+    meta_chars = '()[]{}%!^"<>&|'
+    meta_re = re.compile('(' + '|'.join(
+        re.escape(char) for char in list(meta_chars)) + ')')
+    escaped_flags = re.subn(meta_re, r'\\\1', flags)[0]
+
+    test_command = raspi_test_path + ' ' + escaped_flags
+    test_time_tag = 'TEST-{time}'.format(time=time.time())
+    test_success_tag = 'succeeded'
+    test_failure_tag = 'failed'
+    test_success_output = ' && echo ' + test_time_tag + ' ' + test_success_tag
+    test_failure_output = ' || echo ' + test_time_tag + ' ' + test_failure_tag
+    process.sendline(test_command + test_success_output + test_failure_output)
+
+    while True:
+      line = sanitize_line_re.sub('', process.readline())
+      if not line:
+        break
       sys.stdout.write(line)
       sys.stdout.flush()
-    else:
-      break
+      if line.startswith(test_time_tag):
+        if line.find(test_success_tag) != -1:
+          return_value = 0
+        break
 
-  # ssh into the raspi and run the test
-  command = 'ssh ' + raspi_user_hostname
-  process = pexpect.spawn(command, timeout=120)
+  except ValueError:
+    logging.exception('Test path invalid.')
+  except pexpect.EOF:
+    logging.exception('pexpect encountered EOF while reading line.')
+  except pexpect.TIMEOUT:
+    logging.exception('pexpect timed out while reading line.')
+  # pylint: disable=W0703
+  except Exception:
+    logging.exception('Error occured while running test.')
+  finally:
+    _CleanupProcess(process)
 
-  signal.signal(signal.SIGINT,
-                functools.partial(_SigIntOrSigTermHandler, process))
-  signal.signal(signal.SIGTERM,
-                functools.partial(_SigIntOrSigTermHandler, process))
-
-  process.expect(r'\S+ password:')
-  process.sendline(_RASPI_PASSWORD)
-  process.sendline(raspi_test_path + ' ' + flags)
-
-  while True:
-    line = process.readline()
-    if line:
-      sys.stdout.write(line)
-      sys.stdout.flush()
-    else:
-      break
-
-  return 0
+  return return_value
 
 
-def AddTargetFlag(parser):
+def _AddTargetFlag(parser):
   """Add target to argument parser."""
   parser.add_argument(
       '-t',
@@ -138,7 +186,7 @@
       nargs='?')
 
 
-def AddFlagsFlag(parser, default=None):
+def _AddFlagsFlag(parser, default=None):
   """Add flags to argument parser.
 
   Args:
@@ -173,21 +221,13 @@
       formatter_class=argparse.ArgumentDefaultsHelpFormatter,
       description=textwrap.dedent(__doc__))
 
-  AddFlagsFlag(parser)
-  AddTargetFlag(parser)
+  _AddFlagsFlag(parser)
+  _AddTargetFlag(parser)
   parser.add_argument('test_path', help='Path of test to be run.', type=str)
 
   args = parser.parse_args()
 
-  try:
-    return_value = RunTest(
-        args.test_path, raspi_ip=args.target, flags=args.flags)
-  # pylint: disable=W0703
-  except Exception:
-    logging.exception('Error occured while running binary.')
-    return_value = 1
-
-  return return_value
+  return _RunTest(args.test_path, raspi_ip=args.target, flags=args.flags)
 
 
 if __name__ == '__main__':
diff --git a/src/third_party/libwebp/dec/idec.c b/src/third_party/libwebp/dec/idec.c
index 5bf79d3..105a3dd 100644
--- a/src/third_party/libwebp/dec/idec.c
+++ b/src/third_party/libwebp/dec/idec.c
@@ -358,30 +358,33 @@
 }
 
 // Partition #0
-static int CopyParts0Data(WebPIDecoder* const idec) {
+static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) {
   VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
   VP8BitReader* const br = &dec->br_;
-  const size_t psize = br->buf_end_ - br->buf_;
+  const size_t part_size = br->buf_end_ - br->buf_;
   MemBuffer* const mem = &idec->mem_;
   assert(!idec->is_lossless_);
   assert(mem->part0_buf_ == NULL);
-  assert(psize > 0);
-  assert(psize <= mem->part0_size_);  // Format limit: no need for runtime check
+  // the following is a format limitation, no need for runtime check:
+  assert(part_size <= mem->part0_size_);
+  if (part_size == 0) {   // can't have zero-size partition #0
+    return VP8_STATUS_BITSTREAM_ERROR;
+  }
   if (mem->mode_ == MEM_MODE_APPEND) {
     // We copy and grab ownership of the partition #0 data.
-    uint8_t* const part0_buf = (uint8_t*)malloc(psize);
+    uint8_t* const part0_buf = (uint8_t*)malloc(part_size);
     if (part0_buf == NULL) {
-      return 0;
+      return VP8_STATUS_OUT_OF_MEMORY;
     }
-    memcpy(part0_buf, br->buf_, psize);
+    memcpy(part0_buf, br->buf_, part_size);
     mem->part0_buf_ = part0_buf;
     br->buf_ = part0_buf;
-    br->buf_end_ = part0_buf + psize;
+    br->buf_end_ = part0_buf + part_size;
   } else {
     // Else: just keep pointers to the partition #0's data in dec_->br_.
   }
-  mem->start_ += psize;
-  return 1;
+  mem->start_ += part_size;
+  return VP8_STATUS_OK;
 }
 
 static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) {
@@ -412,8 +415,9 @@
     return IDecError(idec, dec->status_);
   }
 
-  if (!CopyParts0Data(idec)) {
-    return IDecError(idec, VP8_STATUS_OUT_OF_MEMORY);
+  dec->status_ = CopyParts0Data(idec);
+  if (dec->status_ != VP8_STATUS_OK) {
+    return IDecError(idec, dec->status_);
   }
 
   // Finish setting up the decoding parameters. Will call io->setup().