Import Cobalt 17.179149

Change-Id: I7ac21d1c9985c7fb1bc6ec0c04b37eb7c8a7683f
diff --git a/src/base/base64.cc b/src/base/base64.cc
index 1907978..b496af7 100644
--- a/src/base/base64.cc
+++ b/src/base/base64.cc
@@ -25,13 +25,18 @@
   return true;
 }
 
-bool Base64Decode(const StringPiece& input, std::string* output) {
-  std::string temp;
+template <class Container>
+bool Base64DecodeInternal(const StringPiece& input, Container* output) {
+  Container temp;
   temp.resize(modp_b64_decode_len(input.size()));
 
-  // does not null terminate result since result is binary data!
   int input_size = static_cast<int>(input.size());
-  int output_size = modp_b64_decode(&(temp[0]), input.data(), input_size);
+  // When using this template for a new type, make sure its content is unsigned
+  // char or char.
+  static_assert(sizeof(typename Container::value_type) == 1,
+                "Input type should be char or equivalent.");
+  int output_size = modp_b64_decode(reinterpret_cast<char*>(&(temp[0])),
+                                    input.data(), input_size);
   if (output_size < 0)
     return false;
 
@@ -40,4 +45,12 @@
   return true;
 }
 
+bool Base64Decode(const StringPiece& input, std::string* output) {
+  return Base64DecodeInternal(input, output);
+}
+
+bool Base64Decode(const StringPiece& input, std::vector<uint8_t>* output) {
+  return Base64DecodeInternal(input, output);
+}
+
 }  // namespace base
diff --git a/src/base/base64.h b/src/base/base64.h
index 983d5e2..ea6f40f 100644
--- a/src/base/base64.h
+++ b/src/base/base64.h
@@ -6,6 +6,7 @@
 #define BASE_BASE64_H__
 
 #include <string>
+#include <vector>
 
 #include "base/base_export.h"
 #include "base/string_piece.h"
@@ -16,10 +17,15 @@
 // otherwise.  The output string is only modified if successful.
 BASE_EXPORT bool Base64Encode(const StringPiece& input, std::string* output);
 
-// Decodes the base64 input string.  Returns true if successful and false
+// Decodes the base64 input string. Returns true if successful and false
 // otherwise.  The output string is only modified if successful.
 BASE_EXPORT bool Base64Decode(const StringPiece& input, std::string* output);
 
+// Decodes the base64 input string. Returns true if successful and false
+// otherwise.  The output vector is only modified if successful.
+BASE_EXPORT bool Base64Decode(const StringPiece& input,
+                              std::vector<uint8_t>* output);
+
 }  // namespace base
 
 #endif  // BASE_BASE64_H__
diff --git a/src/base/file_path.cc b/src/base/file_path.cc
index b5c7900..123132d 100644
--- a/src/base/file_path.cc
+++ b/src/base/file_path.cc
@@ -514,7 +514,8 @@
 }
 
 FilePath FilePath::AppendASCII(const base::StringPiece& component) const {
-  DCHECK(IsStringASCII(component));
+  DCHECK(IsStringASCII(component))
+      << "invalid component " << component << " being appended to " << value();
 #if defined(OS_WIN)
   return Append(ASCIIToUTF16(component.as_string()));
 #elif defined(OS_POSIX) || defined(OS_STARBOARD)
diff --git a/src/base/file_util_starboard.cc b/src/base/file_util_starboard.cc
index d024542..34aeca3 100644
--- a/src/base/file_util_starboard.cc
+++ b/src/base/file_util_starboard.cc
@@ -347,14 +347,33 @@
     last_path = path;
   }
 
+  // Root path is now at index 0.
+  std::reverse(subpaths.begin(), subpaths.end());
+
+  int existing_directory_index = -1;
+
+  // Some platforms disallow access to some parent folders in the hierarchy.
+  for (size_t i = 0; i < subpaths.size(); ++i) {
+    const auto& path = subpaths[i];
+    if (DirectoryExists(path)) {
+      existing_directory_index = static_cast<int>(i);
+    }
+  }
+
+  // No sub-directories existed, including the root.
+  if (existing_directory_index < 0) {
+    return false;
+  }
+
   // Iterate through the parents and create the missing ones.
-  for (std::vector<FilePath>::reverse_iterator i = subpaths.rbegin();
-       i != subpaths.rend(); ++i) {
-    if (DirectoryExists(*i)) {
+  for (size_t i = static_cast<size_t>(existing_directory_index);
+       i < subpaths.size(); ++i) {
+    const auto& path = subpaths[i];
+    if (DirectoryExists(path)) {
       continue;
     }
 
-    if (!SbDirectoryCreate(i->value().c_str())) {
+    if (!SbDirectoryCreate(path.value().c_str())) {
       return false;
     }
   }
diff --git a/src/base/memory/aligned_memory.h b/src/base/memory/aligned_memory.h
index 8ea96b7..fa3b2f2 100644
--- a/src/base/memory/aligned_memory.h
+++ b/src/base/memory/aligned_memory.h
@@ -38,10 +38,10 @@
 #include "base/basictypes.h"
 #include "base/compiler_specific.h"
 
-#if defined(COMPILER_MSVC)
-#include <malloc.h>
-#elif defined(OS_STARBOARD)
+#if defined(OS_STARBOARD)
 #include "starboard/memory.h"
+#elif defined(COMPILER_MSVC)
+#include <malloc.h>
 #else
 #include <stdlib.h>
 #endif
@@ -96,10 +96,10 @@
 BASE_EXPORT void* AlignedAlloc(size_t size, size_t alignment);
 
 inline void AlignedFree(void* ptr) {
-#if defined(COMPILER_MSVC)
-  _aligned_free(ptr);
-#elif defined(OS_STARBOARD)
+#if defined(OS_STARBOARD)
   SbMemoryDeallocateAligned(ptr);
+#elif defined(COMPILER_MSVC)
+  _aligned_free(ptr);
 #else
   free(ptr);
 #endif
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index f300b74..59b3927 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -2,7 +2,26 @@
 
 This document records all notable changes made to Cobalt since the last release.
 
+## Version 17
+ - **Improvements and Bug Fixes**
+   - Fix pointer/mouse events not being dispatched to JavaScript in the same
+     order that they are generated by Starboard, relative to other input events
+     like key presses.
+
+ - **Storage format changed from sqlite3 to protobuf**
+
+   Cobalt's internal representation for persistent storage of cookies and local
+   storage entries changed from sqlite3 to protobuf. The header of the
+   SbStorageRecord blob was updated from SAV0 to SAV1 and all the data will be
+   migrated to the new format the next time Cobalt is launched.
+   The schema is available at cobalt/storage/store/storage.proto.
+
 ## Version 16
+ - **Rebase libwebp to version 1.0.0**
+
+   Update the version of libwebp used by Cobalt from 0.3.1 to 1.0.0.  The new
+   version brings with it performance improvements and bug fixes.
+
  - **Move ``javascript_engine`` and ``cobalt_enable_jit`` build variables**
 
    Move gyp variables ``javascript_engine`` and ``cobalt_enable_jit``, which
diff --git a/src/cobalt/accessibility/accessibility.gyp b/src/cobalt/accessibility/accessibility.gyp
index 08db41c..50de511 100644
--- a/src/cobalt/accessibility/accessibility.gyp
+++ b/src/cobalt/accessibility/accessibility.gyp
@@ -33,7 +33,6 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
       ],
     },
   ]
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc
index b0ad972..ed08da8 100644
--- a/src/cobalt/audio/async_audio_decoder.cc
+++ b/src/cobalt/audio/async_audio_decoder.cc
@@ -34,12 +34,9 @@
 
   if (reader) {
     decode_finish_callback.Run(reader->sample_rate(),
-                               reader->number_of_frames(),
-                               reader->number_of_channels(),
-                               reader->sample_data(), reader->sample_type());
+                               reader->ResetAndReturnAudioBus().Pass());
   } else {
-    decode_finish_callback.Run(0.f, 0, 0, scoped_array<uint8>(),
-                               kSampleTypeFloat32);
+    decode_finish_callback.Run(0.f, scoped_ptr<ShellAudioBus>());
   }
 }
 
diff --git a/src/cobalt/audio/async_audio_decoder.h b/src/cobalt/audio/async_audio_decoder.h
index 1e8a025..f3536ee 100644
--- a/src/cobalt/audio/async_audio_decoder.h
+++ b/src/cobalt/audio/async_audio_decoder.h
@@ -15,21 +15,20 @@
 #ifndef COBALT_AUDIO_ASYNC_AUDIO_DECODER_H_
 #define COBALT_AUDIO_ASYNC_AUDIO_DECODER_H_
 
+#include "base/basictypes.h"
 #include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/threading/thread.h"
-#include "cobalt/audio/audio_buffer.h"
 #include "cobalt/audio/audio_helpers.h"
-#include "cobalt/dom/array_buffer.h"
 
 namespace cobalt {
 namespace audio {
 
 class AsyncAudioDecoder {
  public:
-  typedef base::Callback<void(float sample_rate, int32 number_of_frames,
-                              int32 number_of_channels,
-                              scoped_array<uint8> channels_data,
-                              SampleType sample_type)> DecodeFinishCallback;
+  typedef base::Callback<void(float sample_rate,
+                              scoped_ptr<ShellAudioBus> audio_bus)>
+      DecodeFinishCallback;
 
   AsyncAudioDecoder();
 
diff --git a/src/cobalt/audio/audio.gyp b/src/cobalt/audio/audio.gyp
index 60bdcf6..d9eb61a 100644
--- a/src/cobalt/audio/audio.gyp
+++ b/src/cobalt/audio/audio.gyp
@@ -48,8 +48,6 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
-        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
       ],
       'export_dependent_settings': [
         # Additionally, ensure that the include directories for generated
@@ -58,35 +56,5 @@
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
       ]
     },
-
-    {
-      'target_name': 'audio_test',
-      'type': '<(gtest_target_type)',
-      'sources': [
-        'audio_node_input_output_test.cc',
-      ],
-      'dependencies': [
-        '<(DEPTH)/cobalt/audio/audio.gyp:audio',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
-        '<(DEPTH)/testing/gmock.gyp:gmock',
-        '<(DEPTH)/testing/gtest.gyp:gtest',
-
-        # TODO: Remove the dependency below, it works around the fact that
-        #       ScriptValueFactory has non-virtual method CreatePromise().
-        '<(DEPTH)/cobalt/script/engine.gyp:engine',
-      ],
-    },
-
-    {
-      'target_name': 'audio_test_deploy',
-      'type': 'none',
-      'dependencies': [
-        'audio_test',
-      ],
-      'variables': {
-        'executable_name': 'audio_test',
-      },
-      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
-    },
   ],
 }
diff --git a/src/cobalt/audio/audio_buffer.cc b/src/cobalt/audio/audio_buffer.cc
index 6e9e804..045c9b4 100644
--- a/src/cobalt/audio/audio_buffer.cc
+++ b/src/cobalt/audio/audio_buffer.cc
@@ -20,80 +20,11 @@
 namespace cobalt {
 namespace audio {
 
-AudioBuffer::AudioBuffer(script::EnvironmentSettings* settings,
-                         float sample_rate, int32 number_of_frames,
-                         int32 number_of_channels,
-                         scoped_array<uint8> channels_data,
-                         SampleType sample_type)
-    : sample_rate_(sample_rate),
-      length_(number_of_frames),
-      sample_type_(sample_type) {
+AudioBuffer::AudioBuffer(float sample_rate, scoped_ptr<ShellAudioBus> audio_bus)
+    : sample_rate_(sample_rate), audio_bus_(audio_bus.Pass()) {
   DCHECK_GT(sample_rate_, 0);
-  DCHECK_GT(length_, 0);
-  DCHECK_GT(number_of_channels, 0);
-
-  // Create an ArrayBuffer stores sample data from all channels.
-  const uint32 length =
-      number_of_frames * number_of_channels *
-      (sample_type == kSampleTypeFloat32 ? sizeof(float) : sizeof(int16));
-  scoped_refptr<dom::ArrayBuffer> array_buffer(
-      new dom::ArrayBuffer(settings, channels_data.Pass(), length));
-
-  // Each channel should have |number_of_frames * size_of_sample_type| bytes.
-  // We create |number_of_channels| of {Float32,Int16}Array as views into the
-  // above ArrayBuffer.  This does not need any extra allocation.
-  if (sample_type == kSampleTypeFloat32) {
-    channels_data_.resize(static_cast<size_t>(number_of_channels));
-    uint32 start_offset_in_bytes = 0;
-    for (int32 i = 0; i < number_of_channels; ++i) {
-      channels_data_[static_cast<size_t>(i)] =
-          new dom::Float32Array(settings, array_buffer, start_offset_in_bytes,
-                                static_cast<uint32>(number_of_frames), NULL);
-      start_offset_in_bytes += number_of_frames * sizeof(float);
-    }
-  } else if (sample_type == kSampleTypeInt16) {
-    channels_int16_data_.resize(static_cast<size_t>(number_of_channels));
-    uint32 start_offset_in_bytes = 0;
-    for (int32 i = 0; i < number_of_channels; ++i) {
-      channels_int16_data_[static_cast<size_t>(i)] =
-          new dom::Int16Array(settings, array_buffer, start_offset_in_bytes,
-                              static_cast<uint32>(number_of_frames), NULL);
-      start_offset_in_bytes += number_of_frames * sizeof(int16);
-    }
-  } else {
-    NOTREACHED();
-  }
-}
-
-scoped_refptr<dom::Float32Array> AudioBuffer::GetChannelData(
-    uint32 channel_index, script::ExceptionState* exception_state) const {
-  DCHECK_EQ(sample_type_, kSampleTypeFloat32);
-  // The index value MUST be less than number_of_channels() or an INDEX_SIZE_ERR
-  // exception MUST be thrown.
-  if (channel_index >= channels_data_.size()) {
-    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
-    return NULL;
-  }
-
-  return channels_data_[channel_index];
-}
-
-scoped_refptr<dom::Int16Array> AudioBuffer::GetChannelDataInt16(
-    uint32 channel_index, script::ExceptionState* exception_state) const {
-  DCHECK_EQ(sample_type_, kSampleTypeInt16);
-  // The index value MUST be less than number_of_channels() or an INDEX_SIZE_ERR
-  // exception MUST be thrown.
-  if (channel_index >= channels_int16_data_.size()) {
-    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
-    return NULL;
-  }
-
-  return channels_int16_data_[channel_index];
-}
-
-void AudioBuffer::TraceMembers(script::Tracer* tracer) {
-  tracer->TraceItems(channels_data_);
-  tracer->TraceItems(channels_int16_data_);
+  DCHECK_GT(length(), 0);
+  DCHECK_GT(number_of_channels(), 0);
 }
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_buffer.h b/src/cobalt/audio/audio_buffer.h
index e9ebea9..f69d262 100644
--- a/src/cobalt/audio/audio_buffer.h
+++ b/src/cobalt/audio/audio_buffer.h
@@ -39,57 +39,31 @@
 //   https://www.w3.org/TR/webaudio/#AudioBuffer
 class AudioBuffer : public script::Wrappable {
  public:
-  // The audio data passed in |channels_data| stores multi-channel audio in
-  // planar.  So for stereo audio, the first channel will be stored in the first
-  // half of |channels_data| and the second channel will be stored in the second
-  // half.
-  AudioBuffer(script::EnvironmentSettings* settings, float sample_rate,
-              int32 number_of_frames, int32 number_of_channels,
-              scoped_array<uint8> channels_data, SampleType sample_type);
+  AudioBuffer(float sample_rate, scoped_ptr<ShellAudioBus> audio_bus);
 
   // Web API: AudioBuffer
   //
   // The sample-rate for the PCM audio data in samples per second.
   float sample_rate() const { return sample_rate_; }
-
   // Length of the PCM audio data in sample-frames.
-  int32 length() const { return length_; }
-
+  int32 length() const { return static_cast<int32>(audio_bus_->frames()); }
   // Duration of the PCM audio data in seconds.
   double duration() const { return length() / sample_rate(); }
-
   // The number of discrete audio channels.
   int32 number_of_channels() const {
-    if (sample_type_ == kSampleTypeFloat32) {
-      return static_cast<int32>(channels_data_.size());
-    } else if (sample_type_ == kSampleTypeInt16) {
-      return static_cast<int32>(channels_int16_data_.size());
-    } else {
-      NOTREACHED();
-      return 0;
-    }
+    return static_cast<int32>(audio_bus_->channels());
   }
 
-  // Represents the PCM audio data for the specific channel.
-  scoped_refptr<dom::Float32Array> GetChannelData(
-      uint32 channel_index, script::ExceptionState* exception_state) const;
-
-  scoped_refptr<dom::Int16Array> GetChannelDataInt16(
-      uint32 channel_index, script::ExceptionState* exception_state) const;
+  // Custom, not in any spec
+  //
+  ShellAudioBus* audio_bus() { return audio_bus_.get(); }
 
   DEFINE_WRAPPABLE_TYPE(AudioBuffer);
-  void TraceMembers(script::Tracer* tracer) override;
 
  private:
-  typedef std::vector<scoped_refptr<dom::Float32Array> > Float32ArrayVector;
-  typedef std::vector<scoped_refptr<dom::Int16Array> > Int16ArrayVector;
+  const float sample_rate_;
 
-  float sample_rate_;
-  int32 length_;
-  SampleType sample_type_;
-
-  Float32ArrayVector channels_data_;
-  Int16ArrayVector channels_int16_data_;
+  scoped_ptr<ShellAudioBus> audio_bus_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioBuffer);
 };
diff --git a/src/cobalt/audio/audio_buffer.idl b/src/cobalt/audio/audio_buffer.idl
index 53f9e09..77a151c 100644
--- a/src/cobalt/audio/audio_buffer.idl
+++ b/src/cobalt/audio/audio_buffer.idl
@@ -21,6 +21,4 @@
   // in seconds
   readonly attribute double duration;
   readonly attribute long numberOfChannels;
-
-  [RaisesException] Float32Array getChannelData(unsigned long channel);
 };
diff --git a/src/cobalt/audio/audio_buffer_source_node.cc b/src/cobalt/audio/audio_buffer_source_node.cc
index adbfc95..f6aa6e9 100644
--- a/src/cobalt/audio/audio_buffer_source_node.cc
+++ b/src/cobalt/audio/audio_buffer_source_node.cc
@@ -92,63 +92,54 @@
                              exception_state);
     return;
   }
-  state_ = kStoped;
+  state_ = kStopped;
 }
 
 scoped_ptr<ShellAudioBus> AudioBufferSourceNode::PassAudioBusFromSource(
-    int32 number_of_frames, SampleType sample_type) {
+    int32 number_of_frames, SampleType sample_type, bool* finished) {
+  DCHECK_GT(number_of_frames, 0);
+  DCHECK(finished);
+
   // This is called by Audio thread.
   audio_lock()->AssertLocked();
 
-  if (state_ != kStarted || !buffer_ || buffer_->length() == read_index_) {
+  *finished = false;
+
+  if (state_ == kNone || !buffer_) {
     return scoped_ptr<ShellAudioBus>();
   }
 
-  DCHECK_GT(number_of_frames, 0);
+  if (state_ == kStopped || buffer_->length() == read_index_) {
+    *finished = true;
+    return scoped_ptr<ShellAudioBus>();
+  }
+
+  DCHECK_EQ(state_, kStarted);
+
+  auto audio_bus = buffer_->audio_bus();
+  DCHECK_EQ(sample_type, audio_bus->sample_type());
+
   int32 frames_to_end = buffer_->length() - read_index_;
   number_of_frames = std::min(number_of_frames, frames_to_end);
 
-  size_t channels = static_cast<size_t>(buffer_->number_of_channels());
+  scoped_ptr<ShellAudioBus> result;
 
-  if (sample_type == kSampleTypeFloat32) {
-    std::vector<scoped_refptr<dom::Float32Array>> audio_buffer_storages(
-        channels);
-    std::vector<float*> audio_buffers(channels, NULL);
-    for (size_t i = 0; i < channels; ++i) {
-      scoped_refptr<dom::Float32Array> buffer_data =
-          buffer_->GetChannelData(static_cast<uint32>(i), NULL);
-      audio_buffer_storages[i] = buffer_data->Subarray(
-          NULL, read_index_, read_index_ + number_of_frames);
-      audio_buffers[i] = audio_buffer_storages[i]->data();
-    }
+  if (sample_type == kSampleTypeInt16) {
+    result.reset(new media::ShellAudioBus(
+        audio_bus->channels(), number_of_frames,
+        reinterpret_cast<int16*>(audio_bus->interleaved_data()) +
+            read_index_ * audio_bus->channels()));
+  } else {
+    DCHECK_EQ(sample_type, kSampleTypeFloat32);
 
-    read_index_ += number_of_frames;
-
-    scoped_ptr<ShellAudioBus> audio_bus(new ShellAudioBus(
-        static_cast<size_t>(number_of_frames), audio_buffers));
-
-    return audio_bus.Pass();
-  } else if (sample_type == kSampleTypeInt16) {
-    std::vector<scoped_refptr<dom::Int16Array>> audio_buffer_storages(channels);
-    std::vector<int16*> audio_buffers(channels, NULL);
-    for (size_t i = 0; i < channels; ++i) {
-      scoped_refptr<dom::Int16Array> buffer_data =
-          buffer_->GetChannelDataInt16(static_cast<uint32>(i), NULL);
-      audio_buffer_storages[i] = buffer_data->Subarray(
-          NULL, read_index_, read_index_ + number_of_frames);
-      audio_buffers[i] = audio_buffer_storages[i]->data();
-    }
-
-    read_index_ += number_of_frames;
-
-    scoped_ptr<ShellAudioBus> audio_bus(new ShellAudioBus(
-        static_cast<size_t>(number_of_frames), audio_buffers));
-
-    return audio_bus.Pass();
+    result.reset(new media::ShellAudioBus(
+        audio_bus->channels(), number_of_frames,
+        reinterpret_cast<float*>(audio_bus->interleaved_data()) +
+            read_index_ * audio_bus->channels()));
   }
 
-  NOTREACHED();
-  return scoped_ptr<ShellAudioBus>();
+  read_index_ += number_of_frames;
+  return result.Pass();
 }
 
 void AudioBufferSourceNode::TraceMembers(script::Tracer* tracer) {
diff --git a/src/cobalt/audio/audio_buffer_source_node.h b/src/cobalt/audio/audio_buffer_source_node.h
index b017abb..c3bbe85 100644
--- a/src/cobalt/audio/audio_buffer_source_node.h
+++ b/src/cobalt/audio/audio_buffer_source_node.h
@@ -77,8 +77,9 @@
     SetAttributeEventListener(base::Tokens::ended(), event_listener);
   }
 
-  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames, SampleType sample_type) override;
+  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames,
+                                                   SampleType sample_type,
+                                                   bool* finished) override;
 
   DEFINE_WRAPPABLE_TYPE(AudioBufferSourceNode);
   void TraceMembers(script::Tracer* tracer) override;
@@ -90,7 +91,7 @@
   enum State {
     kNone,
     kStarted,
-    kStoped,
+    kStopped,
   };
 
   scoped_refptr<AudioBuffer> buffer_;
diff --git a/src/cobalt/audio/audio_context.cc b/src/cobalt/audio/audio_context.cc
index 1399b4d..dccd821 100644
--- a/src/cobalt/audio/audio_context.cc
+++ b/src/cobalt/audio/audio_context.cc
@@ -102,16 +102,12 @@
 // Success callback and error callback should be scheduled to run on the main
 // thread's event loop.
 void AudioContext::DecodeFinish(int callback_id, float sample_rate,
-                                int32 number_of_frames,
-                                int32 number_of_channels,
-                                scoped_array<uint8> channels_data,
-                                SampleType sample_type) {
+                                scoped_ptr<ShellAudioBus> audio_bus) {
   if (!main_message_loop_->BelongsToCurrentThread()) {
     main_message_loop_->PostTask(
         FROM_HERE,
         base::Bind(&AudioContext::DecodeFinish, weak_this_, callback_id,
-                   sample_rate, number_of_frames, number_of_channels,
-                   base::Passed(&channels_data), sample_type));
+                   sample_rate, base::Passed(&audio_bus)));
     return;
   }
 
@@ -122,10 +118,9 @@
   scoped_ptr<DecodeCallbackInfo> info(info_iterator->second);
   pending_decode_callbacks_.erase(info_iterator);
 
-  if (channels_data) {
+  if (audio_bus) {
     const scoped_refptr<AudioBuffer>& audio_buffer =
-        new AudioBuffer(info->env_settings, sample_rate, number_of_frames,
-                        number_of_channels, channels_data.Pass(), sample_type);
+        new AudioBuffer(sample_rate, audio_bus.Pass());
     info->success_callback.value().Run(audio_buffer);
   } else if (info->error_callback) {
     info->error_callback.value().value().Run();
diff --git a/src/cobalt/audio/audio_context.h b/src/cobalt/audio/audio_context.h
index ac07c20..ab34490 100644
--- a/src/cobalt/audio/audio_context.h
+++ b/src/cobalt/audio/audio_context.h
@@ -19,6 +19,7 @@
 
 #include "base/callback.h"
 #include "base/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
@@ -26,6 +27,7 @@
 #include "cobalt/audio/audio_buffer.h"
 #include "cobalt/audio/audio_buffer_source_node.h"
 #include "cobalt/audio/audio_destination_node.h"
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/dom/array_buffer.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/event_target.h"
@@ -164,9 +166,8 @@
   std::string GetDebugName() override { return "AudioContext"; }
 
   void DecodeAudioDataInternal(scoped_ptr<DecodeCallbackInfo> info);
-  void DecodeFinish(int callback_id, float sample_rate, int32 number_of_frames,
-                    int32 number_of_channels, scoped_array<uint8> channels_data,
-                    SampleType sample_type);
+  void DecodeFinish(int callback_id, float sample_rate,
+                    scoped_ptr<ShellAudioBus> audio_bus);
 
   base::WeakPtrFactory<AudioContext> weak_ptr_factory_;
   // We construct a WeakPtr upon AudioContext's construction in order to
diff --git a/src/cobalt/audio/audio_destination_node.cc b/src/cobalt/audio/audio_destination_node.cc
index 78d414f..a9308c4 100644
--- a/src/cobalt/audio/audio_destination_node.cc
+++ b/src/cobalt/audio/audio_destination_node.cc
@@ -33,7 +33,9 @@
 // numberOfInputs  : 1
 // numberOfOutputs : 0
 AudioDestinationNode::AudioDestinationNode(AudioContext* context)
-    : AudioNode(context), max_channel_count_(kMaxChannelCount) {
+    : AudioNode(context),
+      message_loop_(MessageLoop::current()),
+      max_channel_count_(kMaxChannelCount) {
   AudioLock::AutoLock lock(audio_lock());
 
   AddInput(new AudioNodeInput(this));
@@ -57,16 +59,31 @@
     audio_device_.reset(
         new AudioDevice(static_cast<int>(channel_count(NULL)), this));
   }
+  audio_device_to_delete_ = NULL;
 }
 
-void AudioDestinationNode::FillAudioBus(ShellAudioBus* audio_bus,
+void AudioDestinationNode::FillAudioBus(bool all_consumed,
+                                        ShellAudioBus* audio_bus,
                                         bool* silence) {
-  // This is called by Audio thread.
+  // This is called on Audio thread.
   AudioLock::AutoLock lock(audio_lock());
 
   // Destination node only has one input.
   DCHECK_EQ(number_of_inputs(), 1u);
-  Input(0)->FillAudioBus(audio_bus, silence);
+  bool all_finished = true;
+  Input(0)->FillAudioBus(audio_bus, silence, &all_finished);
+  if (all_consumed && all_finished) {
+    audio_device_to_delete_ = audio_device_.get();
+    message_loop_->PostTask(
+        FROM_HERE, base::Bind(&AudioDestinationNode::DestroyAudioDevice,
+                              base::Unretained(this)));
+  }
+}
+
+void AudioDestinationNode::DestroyAudioDevice() {
+  if (audio_device_ == audio_device_to_delete_) {
+    audio_device_.reset();
+  }
 }
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_destination_node.h b/src/cobalt/audio/audio_destination_node.h
index eafebef..bc36afc 100644
--- a/src/cobalt/audio/audio_destination_node.h
+++ b/src/cobalt/audio/audio_destination_node.h
@@ -17,6 +17,7 @@
 
 #include <vector>
 
+#include "base/message_loop.h"
 #include "cobalt/audio/audio_device.h"
 #include "cobalt/audio/audio_helpers.h"
 #include "cobalt/audio/audio_node.h"
@@ -55,14 +56,16 @@
 
   // From AudioNode.
   void OnInputNodeConnected() override;
-  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 /*number_of_frames*/,
-                                                   SampleType) override {
+  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
+      int32 /*number_of_frames*/, SampleType /*sample_type*/,
+      bool* /*finished*/) override {
     NOTREACHED();
     return scoped_ptr<ShellAudioBus>();
   }
 
   // From AudioDevice::RenderCallback.
-  void FillAudioBus(ShellAudioBus* audio_bus, bool* silence) override;
+  void FillAudioBus(bool all_consumed, ShellAudioBus* audio_bus,
+                    bool* silence) override;
 
   DEFINE_WRAPPABLE_TYPE(AudioDestinationNode);
 
@@ -70,9 +73,13 @@
   ~AudioDestinationNode() override;
 
  private:
+  void DestroyAudioDevice();
+
+  MessageLoop* message_loop_;
   uint32 max_channel_count_;
 
   scoped_ptr<AudioDevice> audio_device_;
+  AudioDevice* audio_device_to_delete_ = NULL;
 
   DISALLOW_COPY_AND_ASSIGN(AudioDestinationNode);
 };
diff --git a/src/cobalt/audio/audio_device.cc b/src/cobalt/audio/audio_device.cc
index 8404a2e..5be7e64 100644
--- a/src/cobalt/audio/audio_device.cc
+++ b/src/cobalt/audio/audio_device.cc
@@ -51,6 +51,14 @@
 
 #if defined(SB_USE_SB_AUDIO_SINK)
 
+namespace {
+// Write |kTailSizeInFrames| frames of silence at the end of playback to ensure
+// the audible frames being played on platforms with strict underflow control.
+// Increasing this value will also increase the silence between two sounds.  So
+// it shouldn't be set to a very too large.
+const int kTailSizeInFrames = kRenderBufferSizeFrames * 8;
+}  // namespace
+
 class AudioDevice::Impl {
  public:
   Impl(int number_of_channels, RenderCallback* callback);
@@ -87,12 +95,14 @@
   scoped_array<uint8> output_frame_buffer_;
 
   void* frame_buffers_[1];
-  int64 frames_rendered_;  // Frames retrieved from |render_callback_|.
-  int64 frames_consumed_;  // Accumulated frames consumed reported by the sink.
+  int64 frames_rendered_ = 0;  // Frames retrieved from |render_callback_|.
+  int64 frames_consumed_ = 0;  // Accumulated frames consumed by the sink.
+  int64 silence_written_ = 0;  // Silence frames written after all nodes are
+                               // finished.
 
-  bool was_silence_last_update_;
+  bool was_silence_last_update_ = false;
 
-  SbAudioSink audio_sink_;
+  SbAudioSink audio_sink_ = kSbAudioSinkInvalid;
 
   DISALLOW_COPY_AND_ASSIGN(Impl);
 };
@@ -107,11 +117,7 @@
                        GetPreferredOutputSampleType(), ShellAudioBus::kPlanar),
       output_frame_buffer_(
           new uint8[kFramesPerChannel * number_of_channels_ *
-                    GetStarboardSampleTypeSize(output_sample_type_)]),
-      frames_rendered_(0),
-      frames_consumed_(0),
-      was_silence_last_update_(false),
-      audio_sink_(kSbAudioSinkInvalid) {
+                    GetStarboardSampleTypeSize(output_sample_type_)]) {
   DCHECK(number_of_channels_ == 1 || number_of_channels_ == 2)
       << "Invalid number of channels: " << number_of_channels_;
   DCHECK(render_callback_);
@@ -189,11 +195,21 @@
     }
 
     bool silence = true;
+    bool all_consumed =
+        silence_written_ != 0 && *frames_in_buffer <= silence_written_;
 
-    // Fill our temporary buffer with planar PCM float samples.
-    render_callback_->FillAudioBus(&input_audio_bus_, &silence);
+    render_callback_->FillAudioBus(all_consumed, &input_audio_bus_, &silence);
 
-    if (!silence) {
+    bool fill_output = true;
+    if (silence) {
+      fill_output = kTailSizeInFrames > silence_written_;
+      silence_written_ += kRenderBufferSizeFrames;
+    } else {
+      // Reset |silence_written_| if a new sound is played after some silence
+      // frames were injected.
+      silence_written_ = 0;
+    }
+    if (fill_output) {
       FillOutputAudioBus();
 
       frames_rendered_ += kRenderBufferSizeFrames;
@@ -357,8 +373,9 @@
 
   if ((kFramesPerChannel - *total_frames) >= kRenderBufferSizeFrames) {
     // Fill our temporary buffer with PCM float samples.
+    bool all_consumed = false;  // Keep the sink alive on legacy platforms.
     bool silence = true;
-    render_callback_->FillAudioBus(&audio_bus_, &silence);
+    render_callback_->FillAudioBus(all_consumed, &audio_bus_, &silence);
 
     if (!silence) {
       FillOutputAudioBus();
diff --git a/src/cobalt/audio/audio_device.h b/src/cobalt/audio/audio_device.h
index dd5f898..7e227ab 100644
--- a/src/cobalt/audio/audio_device.h
+++ b/src/cobalt/audio/audio_device.h
@@ -38,11 +38,16 @@
     typedef ::media::ShellAudioBus ShellAudioBus;
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
 
+    // |all_consumed| will be set to true if all audio frames has been consumed.
+    // This gives the AudioDestinationNode a chance to decide if the AudioDevice
+    // should be killed.
+    // |audio_buffer| contains the audio frames to be mixed with input audio if
+    // there is any.
     // |silence| will be set to true before calling if |audio_buffer| contains
-    // only silence samples, it will be set to |false| otherwise.  On return
-    // FillAudioBus() will set |silence| to |false| if it has modified
-    // |audio_buffer|.
-    virtual void FillAudioBus(ShellAudioBus* audio_buffer, bool* silence) = 0;
+    // only silence samples, it will be set to |false| otherwise.  It will be
+    // set to false on return if |audio_buffer| has been modified.
+    virtual void FillAudioBus(bool all_consumed, ShellAudioBus* audio_buffer,
+                              bool* silence) = 0;
 
    protected:
     ~RenderCallback() {}
diff --git a/src/cobalt/audio/audio_file_reader.h b/src/cobalt/audio/audio_file_reader.h
index b7c95ca..b7721da 100644
--- a/src/cobalt/audio/audio_file_reader.h
+++ b/src/cobalt/audio/audio_file_reader.h
@@ -15,8 +15,7 @@
 #ifndef COBALT_AUDIO_AUDIO_FILE_READER_H_
 #define COBALT_AUDIO_AUDIO_FILE_READER_H_
 
-#include "base/memory/scoped_ptr.h"  // For scoped_array
-
+#include "base/memory/scoped_ptr.h"
 #include "cobalt/audio/audio_helpers.h"
 
 namespace cobalt {
@@ -29,14 +28,12 @@
   static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size,
                                                SampleType sample_type);
 
-  // Returns the sample data stored as float sample in planar form.  Note that
-  // this function transfers the ownership of the data to the caller so it can
-  // only be called once.
-  virtual scoped_array<uint8> sample_data() = 0;
   virtual float sample_rate() const = 0;
   virtual int32 number_of_frames() const = 0;
   virtual int32 number_of_channels() const = 0;
   virtual SampleType sample_type() const = 0;
+
+  virtual scoped_ptr<ShellAudioBus> ResetAndReturnAudioBus() = 0;
 };
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_file_reader_wav.cc b/src/cobalt/audio/audio_file_reader_wav.cc
index 9be4c11..3cbd47c 100644
--- a/src/cobalt/audio/audio_file_reader_wav.cc
+++ b/src/cobalt/audio/audio_file_reader_wav.cc
@@ -21,6 +21,7 @@
 #else  // defined(COBALT_MEDIA_SOURCE_2016)
 #include "media/base/endian_util.h"
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
+#include "starboard/memory.h"
 
 namespace cobalt {
 namespace audio {
@@ -105,7 +106,7 @@
 
 void AudioFileReaderWAV::ParseChunks(const uint8* data, size_t size) {
   uint32 offset = kWAVRIFFChunkHeaderSize;
-  bool is_sample_in_float = false;
+  bool is_src_sample_in_float = false;
   // If the WAV file is PCM format, it has two sub-chunks: first one is "fmt"
   // and the second one is "data".
   // TODO: support the cases that the WAV file is non-PCM format and the
@@ -124,12 +125,12 @@
     }
 
     if (chunk_id == kWAVChunkID_fmt && i == 0) {
-      if (!ParseWAV_fmt(data, offset, chunk_size, &is_sample_in_float)) {
+      if (!ParseWAV_fmt(data, offset, chunk_size, &is_src_sample_in_float)) {
         DLOG(WARNING) << "Parse fmt chunk failed.";
         break;
       }
     } else if (chunk_id == kWAVChunkID_data && i == 1) {
-      if (!ParseWAV_data(data, offset, chunk_size, is_sample_in_float)) {
+      if (!ParseWAV_data(data, offset, chunk_size, is_src_sample_in_float)) {
         DLOG(WARNING) << "Parse data chunk failed.";
         break;
       }
@@ -143,7 +144,10 @@
 }
 
 bool AudioFileReaderWAV::ParseWAV_fmt(const uint8* data, size_t offset,
-                                      size_t size, bool* is_sample_in_float) {
+                                      size_t size,
+                                      bool* is_src_sample_in_float) {
+  DCHECK(is_src_sample_in_float);
+
   // Check size for complete header.
   if (size < kWAVfmtChunkHeaderSize) {
     return false;
@@ -157,7 +161,7 @@
     return false;
   }
 
-  *is_sample_in_float = format_code == kWAVFormatCodeFloat;
+  *is_src_sample_in_float = format_code == kWAVFormatCodeFloat;
 
   // Load channel count.
   number_of_channels_ = load_uint16_little_endian(data + offset + 2);
@@ -176,8 +180,8 @@
 
   // Check sample size, we only support 32 bit floats or 16 bit PCM.
   uint16 bits_per_sample = load_uint16_little_endian(data + offset + 14);
-  if ((*is_sample_in_float && bits_per_sample != 32) ||
-      (!*is_sample_in_float && bits_per_sample != 16)) {
+  if ((*is_src_sample_in_float && bits_per_sample != 32) ||
+      (!*is_src_sample_in_float && bits_per_sample != 16)) {
     DLOG(ERROR) << "Bad bits per sample on WAV. "
                 << "Bits per sample: " << bits_per_sample;
     return false;
@@ -187,57 +191,92 @@
 }
 
 bool AudioFileReaderWAV::ParseWAV_data(const uint8* data, size_t offset,
-                                       size_t size, bool is_sample_in_float) {
-  const uint8* data_samples = data + offset;
-
+                                       size_t size,
+                                       bool is_src_sample_in_float) {
   // Set number of frames based on size of data chunk.
-  const int32 bytes_per_src_sample =
-      static_cast<int32>(is_sample_in_float ? sizeof(float) : sizeof(int16));
+  const int32 bytes_per_src_sample = static_cast<int32>(
+      is_src_sample_in_float ? sizeof(float) : sizeof(int16));
   number_of_frames_ =
       static_cast<int32>(size / (bytes_per_src_sample * number_of_channels_));
-  const int32 bytes_per_dest_sample =
-      static_cast<int32>(GetSampleTypeSize(sample_type_));
-  const bool is_dest_float = sample_type_ == kSampleTypeFloat32;
 
   // We store audio samples in the current platform's preferred format.
-  sample_data_.reset(new uint8[static_cast<size_t>(
-      number_of_frames_ * number_of_channels_ * bytes_per_dest_sample)]);
+  audio_bus_.reset(new ShellAudioBus(number_of_channels_, number_of_frames_,
+                                     sample_type_,
+                                     ShellAudioBus::kInterleaved));
 
-  // Here we handle all 4 possible conversion cases.  Also note that the
-  // source data is stored interleaved, and that need to convert it to planar.
-  uint8* dest_sample = sample_data_.get();
-  for (int32 i = 0; i < number_of_channels_; ++i) {
-    const uint8* src_samples = data_samples + i * bytes_per_src_sample;
+// Both the source data and the destination data are stored in interleaved.
+#if SB_IS(LITTLE_ENDIAN)
+  if ((!is_src_sample_in_float && sample_type_ == kSampleTypeInt16) ||
+      (is_src_sample_in_float && sample_type_ == kSampleTypeFloat32)) {
+    SbMemoryCopy(audio_bus_->interleaved_data(), data + offset, size);
+  } else if (!is_src_sample_in_float && sample_type_ == kSampleTypeFloat32) {
+    // Convert from int16 to float32
+    const int16* src_samples = reinterpret_cast<const int16*>(data + offset);
+    float* dest_samples =
+        reinterpret_cast<float*>(audio_bus_->interleaved_data());
 
-    for (int32 j = 0; j < number_of_frames_; ++j) {
-      if (is_dest_float) {
-        float sample;
-        if (is_sample_in_float) {
-          uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
-          sample = bit_cast<float>(sample_as_uint32);
-        } else {
-          uint16 sample_pcm_unsigned = load_uint16_little_endian(src_samples);
-          int16 sample_pcm = bit_cast<int16>(sample_pcm_unsigned);
-          sample = ConvertSample<int16, float>(sample_pcm);
-        }
-        reinterpret_cast<float*>(dest_sample)[i * number_of_frames_ + j] =
-            sample;
-        src_samples += bytes_per_src_sample * number_of_channels_;
-      } else {
-        int16 sample;
-        if (is_sample_in_float) {
-          uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
-          float value = bit_cast<float>(sample_as_uint32);
-          sample = ConvertSample<float, int16>(value);
-        } else {
-          sample = bit_cast<int16>(load_uint16_little_endian(src_samples));
-        }
-        reinterpret_cast<int16*>(dest_sample)[i * number_of_frames_ + j] =
-            sample;
-        src_samples += bytes_per_src_sample * number_of_channels_;
-      }
+    for (int32 i = 0; i < number_of_frames_ * number_of_channels_; ++i) {
+      *dest_samples = ConvertSample<int16, float>(*src_samples);
+      ++src_samples;
+      ++dest_samples;
+    }
+  } else {
+    // Convert from float32 to int16
+    const float* src_samples = reinterpret_cast<const float*>(data + offset);
+    int16* dest_samples =
+        reinterpret_cast<int16*>(audio_bus_->interleaved_data());
+
+    for (int32 i = 0; i < number_of_frames_ * number_of_channels_; ++i) {
+      *dest_samples = ConvertSample<float, int16>(*src_samples);
+      ++src_samples;
+      ++dest_samples;
     }
   }
+#else   // SB_IS(LITTLE_ENDIAN)
+  if (!is_src_sample_in_float && sample_type_ == kSampleTypeInt16) {
+    const uint8_t* src_samples = data + offset;
+    int16* dest_samples =
+        reinterpret_cast<int16*>(audio_bus_->interleaved_data());
+    for (int32 i = 0; i < number_of_frames_ * number_of_channels_; ++i) {
+      *dest_samples = load_uint16_little_endian(src_samples);
+      src_samples += bytes_per_src_sample;
+      ++dest_samples;
+    }
+  } else if (is_src_sample_in_float && sample_type_ == kSampleTypeFloat32) {
+    const uint8_t* src_samples = data + offset;
+    float* dest_samples =
+        reinterpret_cast<float*>(audio_bus_->interleaved_data());
+    for (int32 i = 0; i < number_of_frames_ * number_of_channels_; ++i) {
+      uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
+      *dest_samples = bit_cast<float>(sample_as_uint32);
+      src_samples += bytes_per_src_sample;
+      ++dest_samples;
+    }
+  } else if (!is_src_sample_in_float && sample_type_ == kSampleTypeFloat32) {
+    // Convert from int16 to float32
+    const uint8_t* src_samples = data + offset;
+    float* dest_samples =
+        reinterpret_cast<float*>(audio_bus_->interleaved_data());
+    for (int32 i = 0; i < number_of_frames_ * number_of_channels_; ++i) {
+      int16 sample_as_int16 = load_int16_little_endian(src_samples);
+      *dest_samples = ConvertSample<int16, float>(sample_as_int16);
+      src_samples += bytes_per_src_sample;
+      ++dest_samples;
+    }
+  } else {
+    // Convert from float32 to int16
+    const uint8_t* src_samples = data + offset;
+    int16* dest_samples =
+        reinterpret_cast<int16*>(audio_bus_->interleaved_data());
+    for (int32 i = 0; i < number_of_frames_ * number_of_channels_; ++i) {
+      uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
+      *dest_samples =
+          ConvertSample<float, int16>(bit_cast<float>(sample_as_uint32));
+      src_samples += bytes_per_src_sample;
+      ++dest_samples;
+    }
+  }
+#endif  // SB_IS(LITTLE_ENDIAN)
 
   return true;
 }
diff --git a/src/cobalt/audio/audio_file_reader_wav.h b/src/cobalt/audio/audio_file_reader_wav.h
index 5ae0e51..cf3cdb4 100644
--- a/src/cobalt/audio/audio_file_reader_wav.h
+++ b/src/cobalt/audio/audio_file_reader_wav.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_AUDIO_AUDIO_FILE_READER_WAV_H_
 #define COBALT_AUDIO_AUDIO_FILE_READER_WAV_H_
 
+#include "base/memory/scoped_ptr.h"
 #include "cobalt/audio/audio_file_reader.h"
 #include "cobalt/audio/audio_helpers.h"
 
@@ -28,25 +29,28 @@
   static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size,
                                                SampleType sample_type);
 
-  scoped_array<uint8> sample_data() override { return sample_data_.Pass(); }
   float sample_rate() const override { return sample_rate_; }
   int32 number_of_frames() const override { return number_of_frames_; }
   int32 number_of_channels() const override { return number_of_channels_; }
   SampleType sample_type() const override { return sample_type_; }
 
+  scoped_ptr<ShellAudioBus> ResetAndReturnAudioBus() override {
+    return audio_bus_.Pass();
+  }
+
  private:
   AudioFileReaderWAV(const uint8* data, size_t size, SampleType sample_type);
 
   bool ParseRIFFHeader(const uint8* data, size_t size);
   void ParseChunks(const uint8* data, size_t size);
   bool ParseWAV_fmt(const uint8* data, size_t offset, size_t size,
-                    bool* is_sample_in_float);
+                    bool* is_src_sample_in_float);
   bool ParseWAV_data(const uint8* data, size_t offset, size_t size,
-                     bool is_sample_in_float);
+                     bool is_src_sample_in_float);
 
-  bool is_valid() { return sample_data_ != NULL; }
+  bool is_valid() { return audio_bus_ != NULL; }
 
-  scoped_array<uint8> sample_data_;
+  scoped_ptr<ShellAudioBus> audio_bus_;
   float sample_rate_;
   int32 number_of_frames_;
   int32 number_of_channels_;
diff --git a/src/cobalt/audio/audio_helpers.h b/src/cobalt/audio/audio_helpers.h
index 2d36bec..24cea54 100644
--- a/src/cobalt/audio/audio_helpers.h
+++ b/src/cobalt/audio/audio_helpers.h
@@ -30,10 +30,12 @@
 namespace audio {
 
 #if defined(COBALT_MEDIA_SOURCE_2016)
+typedef media::ShellAudioBus ShellAudioBus;
 typedef media::ShellAudioBus::SampleType SampleType;
 const SampleType kSampleTypeInt16 = media::ShellAudioBus::kInt16;
 const SampleType kSampleTypeFloat32 = media::ShellAudioBus::kFloat32;
 #else   // defined(COBALT_MEDIA_SOURCE_2016)
+typedef ::media::ShellAudioBus ShellAudioBus;
 typedef ::media::ShellAudioBus::SampleType SampleType;
 const SampleType kSampleTypeInt16 = ::media::ShellAudioBus::kInt16;
 const SampleType kSampleTypeFloat32 = ::media::ShellAudioBus::kFloat32;
diff --git a/src/cobalt/audio/audio_node.h b/src/cobalt/audio/audio_node.h
index 630013e..a8c2dd8 100644
--- a/src/cobalt/audio/audio_node.h
+++ b/src/cobalt/audio/audio_node.h
@@ -112,9 +112,8 @@
   // Called when a new input node has been connected.
   virtual void OnInputNodeConnected() {}
 
-  // TODO: Support wrapping ShellAudioBus into another ShellAudioBus.
   virtual scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames, SampleType sample_type) = 0;
+      int32 number_of_frames, SampleType sample_type, bool* finished) = 0;
 
   AudioLock* audio_lock() const { return audio_lock_.get(); }
 
diff --git a/src/cobalt/audio/audio_node_input.cc b/src/cobalt/audio/audio_node_input.cc
index 529986d..cac480a 100644
--- a/src/cobalt/audio/audio_node_input.cc
+++ b/src/cobalt/audio/audio_node_input.cc
@@ -227,10 +227,14 @@
 }
 
 void AudioNodeInput::FillAudioBus(ShellAudioBus* output_audio_bus,
-                                  bool* silence) {
+                                  bool* silence, bool* all_finished) {
+  DCHECK(silence);
+  DCHECK(all_finished);
+
   // This is called by Audio thread.
   owner_node_->audio_lock()->AssertLocked();
 
+  *all_finished = true;
   // TODO: Consider computing computedNumberOfChannels and do up-mix or
   // down-mix base on computedNumberOfChannels. The current implementation
   // is based on the fact that the channelCountMode is max.
@@ -244,9 +248,11 @@
   // from one or more AudioNode outputs. Fan-in is supported.
   for (std::set<AudioNodeOutput*>::iterator iter = outputs_.begin();
        iter != outputs_.end(); ++iter) {
+    bool finished = false;
     scoped_ptr<ShellAudioBus> audio_bus = (*iter)->PassAudioBusFromSource(
         static_cast<int32>(output_audio_bus->frames()),
-        output_audio_bus->sample_type());
+        output_audio_bus->sample_type(), &finished);
+    *all_finished &= finished;
 
     if (audio_bus) {
       if (*silence && audio_bus->channels() == output_audio_bus->channels()) {
diff --git a/src/cobalt/audio/audio_node_input.h b/src/cobalt/audio/audio_node_input.h
index a186e60..6ed0af5 100644
--- a/src/cobalt/audio/audio_node_input.h
+++ b/src/cobalt/audio/audio_node_input.h
@@ -58,7 +58,8 @@
   // For each input, an AudioNode performs a mixing of all connections to that
   // input. FillAudioBus() performs that action. In the case of multiple
   // connections, it sums the result into |audio_bus|.
-  void FillAudioBus(ShellAudioBus* audio_bus, bool* silence);
+  void FillAudioBus(ShellAudioBus* audio_bus, bool* silence,
+                    bool* all_finished);
 
  private:
   AudioNode* const owner_node_;
diff --git a/src/cobalt/audio/audio_node_input_output_test.cc b/src/cobalt/audio/audio_node_input_output_test.cc
index 612edaf..6d394eb 100644
--- a/src/cobalt/audio/audio_node_input_output_test.cc
+++ b/src/cobalt/audio/audio_node_input_output_test.cc
@@ -17,6 +17,8 @@
 #include "cobalt/audio/audio_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+// TODO: Consolidate ShellAudioBus creation code
+
 namespace cobalt {
 namespace audio {
 
@@ -46,32 +48,32 @@
 
   // From AudioNode.
   scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32, /*number_of_frames*/
-                                                   SampleType) override {
+                                                   SampleType, bool*) override {
     NOTREACHED();
     return scoped_ptr<ShellAudioBus>();
   }
 
   // From AudioDevice::RenderCallback.
-  void FillAudioBus(ShellAudioBus* audio_bus, bool* silence) override {
+  void FillAudioBus(bool all_consumed, ShellAudioBus* audio_bus,
+                    bool* silence) override {
+    UNREFERENCED_PARAMETER(all_consumed);
+
     AudioLock::AutoLock lock(audio_lock());
 
+    bool all_finished;
     // Destination node only has one input.
-    Input(0)->FillAudioBus(audio_bus, silence);
+    Input(0)->FillAudioBus(audio_bus, silence, &all_finished);
   }
 };
 
 void FillAudioBusFromOneSource(
-    size_t num_of_src_channel, size_t num_of_frames,
-    scoped_array<uint8> src_data,
+    scoped_ptr<ShellAudioBus> src_data,
     const AudioNodeChannelInterpretation& interpretation,
     ShellAudioBus* audio_bus, bool* silence) {
   scoped_refptr<AudioContext> audio_context(new AudioContext());
   scoped_refptr<AudioBufferSourceNode> source(
       audio_context->CreateBufferSource());
-  scoped_refptr<AudioBuffer> buffer(
-      new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames),
-                      static_cast<int32>(num_of_src_channel), src_data.Pass(),
-                      kSampleTypeFloat32));
+  scoped_refptr<AudioBuffer> buffer(new AudioBuffer(44100, src_data.Pass()));
   source->set_buffer(buffer);
 
   scoped_refptr<AudioDestinationNodeMock> destination(
@@ -80,7 +82,7 @@
   source->Connect(destination, 0, 0, NULL);
   source->Start(0, 0, NULL);
 
-  destination->FillAudioBus(audio_bus, silence);
+  destination->FillAudioBus(true, audio_bus, silence);
 }
 
 class AudioNodeInputOutputTest : public ::testing::Test {
@@ -89,35 +91,34 @@
 };
 
 TEST_F(AudioNodeInputOutputTest, StereoToStereoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 2;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 2;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[50];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 20.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          20.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[200]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 200 * sizeof(uint8));
-
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 20);
         } else {
@@ -131,35 +132,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, StereoToStereoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 2;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 2;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[50];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 20.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          20.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[200]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 200 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 20);
         } else {
@@ -173,33 +174,32 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, MonoToStereoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 1;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 1;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[25];
-  for (size_t i = 0; i < num_of_frames; ++i) {
+  for (size_t i = 0; i < kNumOfFrames; ++i) {
     src_data_in_float[i] = 50.0f;
   }
 
-  scoped_array<uint8> src_data(new uint8[100]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 100 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 50);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -209,33 +209,32 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, MonoToStereoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 1;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 1;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[25];
-  for (size_t i = 0; i < num_of_frames; ++i) {
+  for (size_t i = 0; i < kNumOfFrames; ++i) {
     src_data_in_float[i] = 50.0f;
   }
 
-  scoped_array<uint8> src_data(new uint8[100]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 100 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames && c == 0) {
+      if (i < kNumOfFrames && c == 0) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 50);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -245,35 +244,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToStereoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 4;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 4;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[100];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[400]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 400 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 20);
         } else {
@@ -287,35 +286,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToStereoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 4;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 4;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[100];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[400]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 400 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 10);
         } else {
@@ -329,35 +328,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToStereoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 6;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 10;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 6;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 10;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[60];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[240]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 240 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         if (c == 0) {
           EXPECT_FLOAT_EQ(audio_bus->GetFloat32Sample(c, i), 66.568f);
         } else {
@@ -371,35 +370,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToStereoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 6;
-  size_t num_of_dest_channel = 2;
-  size_t num_of_frames = 10;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 6;
+  const size_t kNumOfDestChannels = 2;
+  const size_t kNumOfFrames = 10;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[60];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[240]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 240 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 10);
         } else {
@@ -413,35 +412,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, StereoToMonoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 2;
-  size_t num_of_dest_channel = 1;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 2;
+  const size_t kNumOfDestChannels = 1;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[50];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 20.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          20.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[200]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 200 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 30);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -451,35 +450,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, StereoToMonoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 2;
-  size_t num_of_dest_channel = 1;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 2;
+  const size_t kNumOfDestChannels = 1;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[50];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 20.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          20.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[200]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 200 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 20);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -489,35 +488,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToMonoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 4;
-  size_t num_of_dest_channel = 1;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 4;
+  const size_t kNumOfDestChannels = 1;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[100];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[400]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 400 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 25);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -527,35 +526,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToMonoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 4;
-  size_t num_of_dest_channel = 1;
-  size_t num_of_frames = 25;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 4;
+  const size_t kNumOfDestChannels = 1;
+  const size_t kNumOfFrames = 25;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[100];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[400]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 400 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 10);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -565,35 +564,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToMonoSpeakersLayoutTest) {
-  size_t num_of_src_channel = 6;
-  size_t num_of_dest_channel = 1;
-  size_t num_of_frames = 10;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 6;
+  const size_t kNumOfDestChannels = 1;
+  const size_t kNumOfFrames = 10;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
   float src_data_in_float[60];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[240]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 240 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_FLOAT_EQ(audio_bus->GetFloat32Sample(c, i), 106.213f);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -603,35 +602,35 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToMonoDiscreteLayoutTest) {
-  size_t num_of_src_channel = 6;
-  size_t num_of_dest_channel = 1;
-  size_t num_of_frames = 10;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 6;
+  const size_t kNumOfDestChannels = 1;
+  const size_t kNumOfFrames = 10;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
   float src_data_in_float[60];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames; ++i) {
-      src_data_in_float[c * num_of_frames + i] = 10.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames; ++frame) {
+      src_data_in_float[frame * kNumOfSrcChannels + channel] =
+          10.0f * (channel + 1);
     }
   }
 
-  scoped_array<uint8> src_data(new uint8[240]);
-  uint8* src_buffer = src_data.get();
-  memcpy(src_buffer, src_data_in_float, 240 * sizeof(uint8));
+  scoped_ptr<ShellAudioBus> src_data(
+      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  FillAudioBusFromOneSource(num_of_src_channel, num_of_frames, src_data.Pass(),
-                            interpretation, audio_bus.get(), &silence);
+  FillAudioBusFromOneSource(src_data.Pass(), kInterpretation, audio_bus.get(),
+                            &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames) {
+      if (i < kNumOfFrames) {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 10);
       } else {
         EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 0);
@@ -643,72 +642,70 @@
 TEST_F(AudioNodeInputOutputTest, MultipleInputNodesLayoutTest) {
   scoped_refptr<AudioContext> audio_context(new AudioContext());
 
-  size_t num_of_src_channel = 2;
-  size_t num_of_dest_channel = 2;
-  AudioNodeChannelInterpretation interpretation =
+  const size_t kNumOfSrcChannels = 2;
+  const size_t kNumOfDestChannels = 2;
+  const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
-  size_t num_of_frames_1 = 25;
+  const size_t kNumOfFrames_1 = 25;
   float src_data_in_float_1[50];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames_1; ++i) {
-      src_data_in_float_1[c * num_of_frames_1 + i] = 20.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames_1; ++frame) {
+      src_data_in_float_1[frame * kNumOfSrcChannels + channel] =
+          20.0f * (channel + 1);
     }
   }
-  scoped_array<uint8> src_data_1(new uint8[200]);
-  uint8* src_buffer_1 = src_data_1.get();
-  memcpy(src_buffer_1, src_data_in_float_1, 200 * sizeof(uint8));
+
+  scoped_ptr<ShellAudioBus> src_data_1(new ShellAudioBus(
+      kNumOfSrcChannels, kNumOfFrames_1, src_data_in_float_1));
   scoped_refptr<AudioBufferSourceNode> source_1(
       audio_context->CreateBufferSource());
   scoped_refptr<AudioBuffer> buffer_1(
-      new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames_1),
-                      static_cast<int32>(num_of_src_channel), src_data_1.Pass(),
-                      kSampleTypeFloat32));
+      new AudioBuffer(44100, src_data_1.Pass()));
   source_1->set_buffer(buffer_1);
 
-  size_t num_of_frames_2 = 50;
+  const size_t kNumOfFrames_2 = 50;
   float src_data_in_float_2[100];
-  for (size_t c = 0; c < num_of_src_channel; ++c) {
-    for (size_t i = 0; i < num_of_frames_2; ++i) {
-      src_data_in_float_2[c * num_of_frames_2 + i] = 40.0f * (c + 1);
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfFrames_2; ++frame) {
+      src_data_in_float_2[frame * kNumOfSrcChannels + channel] =
+          40.0f * (channel + 1);
     }
   }
-  scoped_array<uint8> src_data_2(new uint8[400]);
-  uint8* src_buffer_2 = src_data_2.get();
-  memcpy(src_buffer_2, src_data_in_float_2, 400 * sizeof(uint8));
+
+  scoped_ptr<ShellAudioBus> src_data_2(new ShellAudioBus(
+      kNumOfSrcChannels, kNumOfFrames_2, src_data_in_float_2));
   scoped_refptr<AudioBufferSourceNode> source_2(
       audio_context->CreateBufferSource());
   scoped_refptr<AudioBuffer> buffer_2(
-      new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames_2),
-                      static_cast<int32>(num_of_src_channel), src_data_2.Pass(),
-                      kSampleTypeFloat32));
+      new AudioBuffer(44100, src_data_2.Pass()));
   source_2->set_buffer(buffer_2);
 
   scoped_refptr<AudioDestinationNodeMock> destination(
       new AudioDestinationNodeMock(audio_context.get()));
-  destination->set_channel_interpretation(interpretation);
+  destination->set_channel_interpretation(kInterpretation);
   source_1->Connect(destination, 0, 0, NULL);
   source_2->Connect(destination, 0, 0, NULL);
   source_1->Start(0, 0, NULL);
   source_2->Start(0, 0, NULL);
 
   scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(num_of_dest_channel, kRenderBufferSizeFrames,
+      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
                         ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
   audio_bus->ZeroAllFrames();
   bool silence = true;
-  destination->FillAudioBus(audio_bus.get(), &silence);
+  destination->FillAudioBus(true, audio_bus.get(), &silence);
   EXPECT_FALSE(silence);
 
-  for (size_t c = 0; c < num_of_dest_channel; ++c) {
+  for (size_t c = 0; c < kNumOfDestChannels; ++c) {
     for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) {
-      if (i < num_of_frames_1) {
+      if (i < kNumOfFrames_1) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 60);
         } else {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 120);
         }
-      } else if (i < num_of_frames_2) {
+      } else if (i < kNumOfFrames_2) {
         if (c == 0) {
           EXPECT_EQ(audio_bus->GetFloat32Sample(c, i), 40);
         } else {
diff --git a/src/cobalt/audio/audio_node_output.cc b/src/cobalt/audio/audio_node_output.cc
index a761084..91dbfe7 100644
--- a/src/cobalt/audio/audio_node_output.cc
+++ b/src/cobalt/audio/audio_node_output.cc
@@ -60,12 +60,13 @@
 }
 
 scoped_ptr<ShellAudioBus> AudioNodeOutput::PassAudioBusFromSource(
-    int32 number_of_frames, SampleType sample_type) {
+    int32 number_of_frames, SampleType sample_type, bool* finished) {
   // This is called by Audio thread.
   owner_node_->audio_lock()->AssertLocked();
 
   // Pull audio buffer from its owner node.
-  return owner_node_->PassAudioBusFromSource(number_of_frames, sample_type)
+  return owner_node_
+      ->PassAudioBusFromSource(number_of_frames, sample_type, finished)
       .Pass();
 }
 
diff --git a/src/cobalt/audio/audio_node_output.h b/src/cobalt/audio/audio_node_output.h
index 03d285b..728465d 100644
--- a/src/cobalt/audio/audio_node_output.h
+++ b/src/cobalt/audio/audio_node_output.h
@@ -52,7 +52,8 @@
   void DisconnectAll();
 
   scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames,
-                                                   SampleType sample_type);
+                                                   SampleType sample_type,
+                                                   bool* finished);
 
  private:
   AudioNode* const owner_node_;
diff --git a/src/cobalt/audio/audio_test.gyp b/src/cobalt/audio/audio_test.gyp
new file mode 100644
index 0000000..8f15ec9
--- /dev/null
+++ b/src/cobalt/audio/audio_test.gyp
@@ -0,0 +1,67 @@
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
+  'targets': [
+    # This target can choose the correct media dependency.
+    {
+      'target_name': 'media',
+      'type': 'static_library',
+      'conditions': [
+        ['cobalt_media_source_2016==1', {
+          'dependencies': [
+            '<(DEPTH)/cobalt/media/media2.gyp:media2',
+          ],
+        }, {
+          'dependencies': [
+            '<(DEPTH)/cobalt/media/media.gyp:media',
+          ],
+        }],
+      ],
+    },
+    {
+      'target_name': 'audio_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'audio_node_input_output_test.cc',
+      ],
+      'dependencies': [
+        'media',
+        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+
+        # TODO: Remove the dependency below, it works around the fact that
+        #       ScriptValueFactory has non-virtual method CreatePromise().
+        '<(DEPTH)/cobalt/script/engine.gyp:engine',
+      ],
+    },
+
+    {
+      'target_name': 'audio_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'audio_test',
+      ],
+      'variables': {
+        'executable_name': 'audio_test',
+      },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h
index 758a2d2..32f9c55 100644
--- a/src/cobalt/base/tokens.h
+++ b/src/cobalt/base/tokens.h
@@ -53,6 +53,7 @@
     MacroOpWithNameOnly(childList)                                   \
     MacroOpWithNameOnly(click)                                       \
     MacroOpWithNameOnly(close)                                       \
+    MacroOpWithNameOnly(dataavailable)                               \
     MacroOpWithNameOnly(deviceorientation)                           \
     MacroOpWithNameOnly(durationchange)                              \
     MacroOpWithNameOnly(emptied)                                     \
diff --git a/src/cobalt/bindings/bindings.gypi b/src/cobalt/bindings/bindings.gypi
index a54745c..0ee07e5 100644
--- a/src/cobalt/bindings/bindings.gypi
+++ b/src/cobalt/bindings/bindings.gypi
@@ -400,7 +400,8 @@
       ],
       'sources': [
         '<@(source_idl_files)',
-        '<@(generated_header_idl_files)'
+        '<@(generated_header_idl_files)',
+        'shared/idl_conditional_macros.h',
       ],
       'actions': [{
         'action_name': 'generate_type_conversion_header',
diff --git a/src/cobalt/bindings/code_generator_cobalt.py b/src/cobalt/bindings/code_generator_cobalt.py
index a16a417..61f0641 100644
--- a/src/cobalt/bindings/code_generator_cobalt.py
+++ b/src/cobalt/bindings/code_generator_cobalt.py
@@ -566,6 +566,7 @@
     if interface.parent:
       context['parent_interface'] = self.path_builder.BindingsClass(
           interface.parent)
+      context['parent_interface_name'] = interface.parent
     context['is_exception_interface'] = interface.is_exception
     context['forward_declarations'] = sorted(
         referenced_class_contexts, key=lambda x: x['fully_qualified_name'])
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.cc
index 07111cf..6786eb6 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.cc
@@ -19,6 +19,11 @@
 // This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
 // Auto-generated from template: bindings/mozjs45/templates/interface.cc.template
 
+
+// This must be included above the check for ENABLE_CONDITIONAL_INTERFACE, since
+// ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(ENABLE_CONDITIONAL_INTERFACE)
 
 #include "cobalt/bindings/testing/mozjs_conditional_interface.h"
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.h b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.h
index d827418..9a95d0c 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.h
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_conditional_interface.h
@@ -20,6 +20,11 @@
 #ifndef MozjsConditionalInterface_h
 #define MozjsConditionalInterface_h
 
+
+// This must be included above the check for ENABLE_CONDITIONAL_INTERFACE, since
+// ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(ENABLE_CONDITIONAL_INTERFACE)
 
 #include "base/hash_tables.h"
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc
index f7fe523..c6b64dd 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc
@@ -793,6 +793,11 @@
 
   JS::RootedObject parent_prototype(
       context, MozjsNamedIndexedGetterInterface::GetPrototype(context, global_object));
+  static_assert(
+      std::is_base_of<NamedIndexedGetterInterface, DerivedGetterSetterInterface>::value,
+      "Expected DerivedGetterSetterInterface to have C++ parent class "
+      "NamedIndexedGetterInterface, because that is its WebIDL parent.");
+
   DCHECK(parent_prototype);
 
   interface_data->prototype = JS_NewObjectWithGivenProto(
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc
index 1705331..f90a539 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc
@@ -332,6 +332,11 @@
 
   JS::RootedObject parent_prototype(
       context, MozjsBaseInterface::GetPrototype(context, global_object));
+  static_assert(
+      std::is_base_of<BaseInterface, DerivedInterface>::value,
+      "Expected DerivedInterface to have C++ parent class "
+      "BaseInterface, because that is its WebIDL parent.");
+
   DCHECK(parent_prototype);
 
   interface_data->prototype = JS_NewObjectWithGivenProto(
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc
index ec676ae..f3f9986 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc
@@ -19,6 +19,11 @@
 // This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
 // Auto-generated from template: bindings/mozjs45/templates/interface.cc.template
 
+
+// This must be included above the check for NO_ENABLE_CONDITIONAL_INTERFACE, since
+// NO_ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(NO_ENABLE_CONDITIONAL_INTERFACE)
 
 #include "cobalt/bindings/testing/mozjs_disabled_interface.h"
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.h b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.h
index b07b77d..c491a56 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.h
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.h
@@ -20,6 +20,11 @@
 #ifndef MozjsDisabledInterface_h
 #define MozjsDisabledInterface_h
 
+
+// This must be included above the check for NO_ENABLE_CONDITIONAL_INTERFACE, since
+// NO_ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(NO_ENABLE_CONDITIONAL_INTERFACE)
 
 #include "base/hash_tables.h"
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc
index bf69b82..4507ed8 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc
@@ -931,6 +931,11 @@
 
   JS::RootedObject parent_prototype(
       context, MozjsGlobalInterfaceParent::GetPrototype(context, global_object));
+  static_assert(
+      std::is_base_of<GlobalInterfaceParent, Window>::value,
+      "Expected Window to have C++ parent class "
+      "GlobalInterfaceParent, because that is its WebIDL parent.");
+
   DCHECK(parent_prototype);
 
   interface_data->prototype = JS_NewObjectWithGivenProto(
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.cc
index bcdaf92..eea832e 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.cc
@@ -19,6 +19,11 @@
 // This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
 // Auto-generated from template: bindings/v8c/templates/interface.cc.template
 
+
+// This must be included above the check for ENABLE_CONDITIONAL_INTERFACE, since
+// ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(ENABLE_CONDITIONAL_INTERFACE)
 
 #include "cobalt/bindings/testing/v8c_conditional_interface.h"
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.h b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.h
index 212639b..3be275f 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.h
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_conditional_interface.h
@@ -21,6 +21,11 @@
 #ifndef V8cConditionalInterface_h
 #define V8cConditionalInterface_h
 
+
+// This must be included above the check for ENABLE_CONDITIONAL_INTERFACE, since
+// ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(ENABLE_CONDITIONAL_INTERFACE)
 
 #include "base/hash_tables.h"
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc
index 9b0252e..972fd8e 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc
@@ -627,6 +627,10 @@
     // inherited interface.
     v8::Local<v8::FunctionTemplate> parent_template = V8cNamedIndexedGetterInterface::GetTemplate(isolate);
     function_template->Inherit(parent_template);
+    static_assert(
+        std::is_base_of<NamedIndexedGetterInterface, DerivedGetterSetterInterface>::value,
+        "Expected DerivedGetterSetterInterface to have C++ parent class "
+        "NamedIndexedGetterInterface, because that is its WebIDL parent.");
   }
 
   // https://heycam.github.io/webidl/#es-constants
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_interface.cc
index e7a1eca..bc4610d 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_interface.cc
@@ -239,6 +239,10 @@
     // inherited interface.
     v8::Local<v8::FunctionTemplate> parent_template = V8cBaseInterface::GetTemplate(isolate);
     function_template->Inherit(parent_template);
+    static_assert(
+        std::is_base_of<BaseInterface, DerivedInterface>::value,
+        "Expected DerivedInterface to have C++ parent class "
+        "BaseInterface, because that is its WebIDL parent.");
   }
 
   // https://heycam.github.io/webidl/#es-constants
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.cc
index 16f758d..763c975 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.cc
@@ -19,6 +19,11 @@
 // This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
 // Auto-generated from template: bindings/v8c/templates/interface.cc.template
 
+
+// This must be included above the check for NO_ENABLE_CONDITIONAL_INTERFACE, since
+// NO_ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(NO_ENABLE_CONDITIONAL_INTERFACE)
 
 #include "cobalt/bindings/testing/v8c_disabled_interface.h"
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.h b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.h
index e5734ce..99f6750 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.h
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_disabled_interface.h
@@ -21,6 +21,11 @@
 #ifndef V8cDisabledInterface_h
 #define V8cDisabledInterface_h
 
+
+// This must be included above the check for NO_ENABLE_CONDITIONAL_INTERFACE, since
+// NO_ENABLE_CONDITIONAL_INTERFACE may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined(NO_ENABLE_CONDITIONAL_INTERFACE)
 
 #include "base/hash_tables.h"
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc
index 07237ba..9865651 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc
@@ -710,6 +710,10 @@
     // inherited interface.
     v8::Local<v8::FunctionTemplate> parent_template = V8cGlobalInterfaceParent::GetTemplate(isolate);
     function_template->Inherit(parent_template);
+    static_assert(
+        std::is_base_of<GlobalInterfaceParent, Window>::value,
+        "Expected Window to have C++ parent class "
+        "GlobalInterfaceParent, because that is its WebIDL parent.");
   }
 
   // https://heycam.github.io/webidl/#es-constants
diff --git a/src/cobalt/bindings/mozjs45/templates/interface.cc.template b/src/cobalt/bindings/mozjs45/templates/interface.cc.template
index 6ec949a..1b2fd72 100644
--- a/src/cobalt/bindings/mozjs45/templates/interface.cc.template
+++ b/src/cobalt/bindings/mozjs45/templates/interface.cc.template
@@ -731,6 +731,11 @@
 {% if parent_interface %}
   JS::RootedObject parent_prototype(
       context, {{parent_interface}}::GetPrototype(context, global_object));
+  static_assert(
+      std::is_base_of<{{parent_interface_name}}, {{interface_name}}>::value,
+      "Expected {{interface_name}} to have C++ parent class "
+      "{{parent_interface_name}}, because that is its WebIDL parent.");
+
 {% elif is_exception_interface %}
   // Get Error prototype.
   JS::RootedObject parent_prototype(context);
diff --git a/src/cobalt/bindings/shared/idl_conditional_macros.h b/src/cobalt/bindings/shared/idl_conditional_macros.h
new file mode 100644
index 0000000..e1dddce
--- /dev/null
+++ b/src/cobalt/bindings/shared/idl_conditional_macros.h
@@ -0,0 +1,32 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_BINDINGS_SHARED_IDL_CONDITIONAL_MACROS_H_
+#define COBALT_BINDINGS_SHARED_IDL_CONDITIONAL_MACROS_H_
+
+#include "starboard/configuration.h"
+
+// Define preprocessor macros that cannot be determined at GYP time for use in
+// IDL files with Conditionals (e.g. conditional interface definitions, or
+// conditional attributes). This is necessary to make macros for IDL
+// Conditionals that are dependent on Starboard feature macros that get defined
+// in header files.
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+// This is used to conditionally define the On Screen Keyboard interface and
+// attribute.
+#define COBALT_ENABLE_ON_SCREEN_KEYBOARD
+#endif  // SB_HAS(ON_SCREEN_KEYBOARD)
+
+#endif  // COBALT_BINDINGS_SHARED_IDL_CONDITIONAL_MACROS_H_
diff --git a/src/cobalt/bindings/templates/interface-base.cc.template b/src/cobalt/bindings/templates/interface-base.cc.template
index a0e297e..f965c49 100644
--- a/src/cobalt/bindings/templates/interface-base.cc.template
+++ b/src/cobalt/bindings/templates/interface-base.cc.template
@@ -33,6 +33,11 @@
 // Auto-generated from template: {{template_path}}
 
 {% if conditional %}
+
+// This must be included above the check for {{conditional}}, since
+// {{conditional}} may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined({{conditional}})
 
 {% endif %}
diff --git a/src/cobalt/bindings/templates/interface-base.h.template b/src/cobalt/bindings/templates/interface-base.h.template
index 5b8dd3a..192eb91 100644
--- a/src/cobalt/bindings/templates/interface-base.h.template
+++ b/src/cobalt/bindings/templates/interface-base.h.template
@@ -36,6 +36,11 @@
 #define {{binding_class}}_h
 
 {% if conditional %}
+
+// This must be included above the check for {{conditional}}, since
+// {{conditional}} may be defined within.
+#include "cobalt/bindings/shared/idl_conditional_macros.h"
+
 #if defined({{conditional}})
 
 {% endif %}
diff --git a/src/cobalt/bindings/testing/array_buffers_test.cc b/src/cobalt/bindings/testing/array_buffers_test.cc
index 1dd1e16..16c6999 100644
--- a/src/cobalt/bindings/testing/array_buffers_test.cc
+++ b/src/cobalt/bindings/testing/array_buffers_test.cc
@@ -15,6 +15,7 @@
 #include "cobalt/bindings/testing/arbitrary_interface.h"
 #include "cobalt/bindings/testing/bindings_test_base.h"
 #include "cobalt/script/array_buffer.h"
+#include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/data_view.h"
 #include "cobalt/script/typed_arrays.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -84,6 +85,11 @@
   // going wrong here.
 }
 
+TEST_F(ArrayBufferTest, ArrayBufferViewTest) {
+  // TODO: Add a test for ArrayBufferView.  Bindings support is required in
+  // order to write any meaningful test here.
+}
+
 TEST_F(ArrayBufferTest, DataViewTest) {
   auto array_buffer = script::ArrayBuffer::New(global_environment_, 1024);
   auto data_view =
diff --git a/src/cobalt/bindings/testing/date_bindings_test.cc b/src/cobalt/bindings/testing/date_bindings_test.cc
index 56c3c37..536f85e 100644
--- a/src/cobalt/bindings/testing/date_bindings_test.cc
+++ b/src/cobalt/bindings/testing/date_bindings_test.cc
@@ -70,6 +70,15 @@
   EXPECT_STREQ("Invalid Date", result.c_str());
 }
 
+TEST_F(DateBindingsTest, PosixEpoch) {
+  std::string result;
+
+  EvaluateScript("Date.now();", &result);
+  auto js_now_ms = std::stoll(result);
+  auto posix_now_ms = SbTimeToPosix(SbTimeGetNow()) / kSbTimeMillisecond;
+  EXPECT_LT(std::abs(posix_now_ms - js_now_ms), 1000);
+}
+
 }  // namespace
 }  // namespace testing
 }  // namespace bindings
diff --git a/src/cobalt/bindings/v8c/templates/interface.cc.template b/src/cobalt/bindings/v8c/templates/interface.cc.template
index 5914f60..2d94f1d 100644
--- a/src/cobalt/bindings/v8c/templates/interface.cc.template
+++ b/src/cobalt/bindings/v8c/templates/interface.cc.template
@@ -556,6 +556,10 @@
     // inherited interface.
     v8::Local<v8::FunctionTemplate> parent_template = {{parent_interface}}::GetTemplate(isolate);
     function_template->Inherit(parent_template);
+    static_assert(
+        std::is_base_of<{{parent_interface_name}}, {{interface_name}}>::value,
+        "Expected {{interface_name}} to have C++ parent class "
+        "{{parent_interface_name}}, because that is its WebIDL parent.");
   }
 {% elif is_exception_interface %}
   {
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index 41d9f03..e15fce0 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_tests.py
@@ -5,9 +5,6 @@
 import argparse
 import importlib
 import logging
-import os
-import socket
-import subprocess
 import sys
 import unittest
 
@@ -20,6 +17,7 @@
 # These tests can only be run on platforms whose app launcher can send suspend/
 # resume signals.
 _TESTS_NEEDING_SYSTEM_SIGNAL = [
+    'cancel_sync_loads_when_suspended',
     'preload_font',
     'timer_hit_in_preload',
     'timer_hit_after_preload',
@@ -33,8 +31,6 @@
     'allow_eval',
     'disable_eval_with_csp',
 ]
-# Port number of the HTTP server serving test data.
-_DEFAULT_TEST_DATA_SERVER_PORT = 8000
 # Location of test files.
 _TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
 # Platform dependent device parameters.
@@ -49,17 +45,6 @@
   sys.argv = sys.argv[:1]
 
 
-def GetDefaultBlackBoxTestDataAddress():
-  """Gets the ip address with port for the server hosting test data."""
-  # We are careful to choose this method that allows external device to connect
-  # to the host running the web server hosting test data.
-  address_pack_list = socket.getaddrinfo(socket.gethostname(),
-                                         _DEFAULT_TEST_DATA_SERVER_PORT)
-  first_address_pack = address_pack_list[0]
-  ip_address, port = first_address_pack[4]
-  return 'http://{}:{}/'.format(ip_address, port)
-
-
 class BlackBoxTestCase(unittest.TestCase):
 
   def __init__(self, *args, **kwargs):
@@ -73,9 +58,6 @@
   def tearDownClass(cls):
     print('Done ' + cls.__name__)
 
-  def GetURL(self, file_name):
-    return GetDefaultBlackBoxTestDataAddress() + file_name
-
   def CreateCobaltRunner(self, url, target_params=None):
     new_runner = black_box_cobalt_runner.BlackBoxCobaltRunner(
         device_params=_device_params, url=url, target_params=target_params)
@@ -113,9 +95,6 @@
     self.test_name = test_name
 
   def Run(self):
-
-    if not self._StartTestdataServer():
-      return 1
     logging.basicConfig(level=logging.DEBUG)
     GetDeviceParams()
     if self.test_name:
@@ -125,36 +104,8 @@
       suite = LoadTests(_device_params.platform, _device_params.config)
     return_code = not unittest.TextTestRunner(
         verbosity=0, stream=sys.stdout).run(suite).wasSuccessful()
-    self._KillTestdataServer()
     return return_code
 
-  def _StartTestdataServer(self):
-    """Start a local server to serve test data."""
-    # Some tests like preload_font requires server feature support.
-    # Using HTTP URL instead of file URL also saves the trouble to
-    # deploy test data to device.
-    self.default_test_data_server_process = subprocess.Popen(
-        [
-            'python', '-m', 'SimpleHTTPServer',
-            '{}'.format(_DEFAULT_TEST_DATA_SERVER_PORT)
-        ],
-        cwd=os.path.join(
-            os.path.dirname(os.path.realpath(__file__)), 'testdata'),
-        stdout=subprocess.PIPE,
-        stderr=subprocess.STDOUT)
-    if self.default_test_data_server_process.returncode is not None:
-      # If the return code is not None now, server is not running normally.
-      print('can not start default test data server.')
-      return False
-    else:
-      print('Starting HTTP server on port: {}'.format(
-          _DEFAULT_TEST_DATA_SERVER_PORT))
-      return True
-
-  def _KillTestdataServer(self):
-    """Exit black_box_test_runner with test result."""
-    self.default_test_data_server_process.kill()
-
 
 def main():
   parser = argparse.ArgumentParser()
diff --git a/src/cobalt/black_box_tests/testdata/cancel_sync_loads_when_suspended.html b/src/cobalt/black_box_tests/testdata/cancel_sync_loads_when_suspended.html
new file mode 100644
index 0000000..960bc50
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/cancel_sync_loads_when_suspended.html
@@ -0,0 +1,8 @@
+<HTML>
+  <HEAD></HEAD>
+  <BODY>
+    <script src='black_box_js_test_utils.js'></script>
+    <script src='cancel_sync_loads_when_suspended.js'></script>
+    <script>onEndTest()</script>
+  </BODY>
+</HTML>
diff --git a/src/cobalt/black_box_tests/testdata/retry_async_script_loads_after_suspend.html b/src/cobalt/black_box_tests/testdata/retry_async_script_loads_after_suspend.html
new file mode 100644
index 0000000..faf79c1
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/retry_async_script_loads_after_suspend.html
@@ -0,0 +1,20 @@
+<HTML>
+  <HEAD></HEAD>
+  <BODY>
+    <script src='black_box_js_test_utils.js'></script>
+    <script>
+      var s = document.createElement('script');
+      var onLoadCalled = false;
+      var scriptExecuted = false;
+      // notReached and onEndTest are defined in black_box_js_test_utils.js
+      s.onerror = notReached;
+      s.onload = () => {
+        assertTrue(scriptExecuted);
+        assertFalse(onLoadCalled);
+        onEndTest();
+      }
+      s.src='script_executed.js'
+      document.head.appendChild(s);
+    </script>
+  </BODY>
+</HTML>
diff --git a/src/cobalt/black_box_tests/testdata/script_executed.js b/src/cobalt/black_box_tests/testdata/script_executed.js
new file mode 100644
index 0000000..9c07464
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/script_executed.js
@@ -0,0 +1,16 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+assertFalse(scriptExecuted);
+scriptExecuted = true;
diff --git a/src/cobalt/black_box_tests/tests/allow_eval.py b/src/cobalt/black_box_tests/tests/allow_eval.py
index ca6b47d..22fa60f 100644
--- a/src/cobalt/black_box_tests/tests/allow_eval.py
+++ b/src/cobalt/black_box_tests/tests/allow_eval.py
@@ -7,13 +7,15 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 
 class AllowEvalTest(black_box_tests.BlackBoxTestCase):
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='allow_eval.html')
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/allow_eval.html')
 
-    with self.CreateCobaltRunner(url=url) as runner:
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(url=url) as runner:
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py b/src/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py
new file mode 100644
index 0000000..b8f0b7c
--- /dev/null
+++ b/src/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py
@@ -0,0 +1,133 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests cancelation of synchronous loading of scripts on Suspend."""
+
+# This test script works by splitting the work over 3 threads, so that they
+# can each make progress even if they come across blocking operations.
+# The three threads are:
+#   1. Main thread, runs BlackBoxTestCase, sends suspend/resume signals, etc.
+#   2. HTTP Server, responsible for slowly responding to a fetch of a javascript
+#      file.
+#   3. Webdriver thread, instructs Cobalt to navigate to a URL
+#
+# Steps in ~ chronological order:
+#   1. Create a TCP socket and listen on all interfaces.
+#   2. Start Cobalt, and point it to the socket created in Step 1.
+#   3. Wait HTTP request for html resource.
+#   4. Respond to a HTTP request.
+#   5. Wait for a request for javascript resource.
+#   6. Suspend Cobalt process.
+#   7. Cobalt disconnects from the socket.
+#   8. Resume Cobalt process, which enables the JS test to pass.
+#   9. Check to see if JSTestsSucceeded().
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import _env  # pylint: disable=unused-import,g-bad-import-order
+
+import os
+import SimpleHTTPServer
+import threading
+import traceback
+import urlparse
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+_CANCEL_SYNC_LOADS_WHEN_SUSPENDED_HTML = 'cancel_sync_loads_when_suspended.html'
+_CANCEL_SYNC_LOADS_WHEN_SUSPENDED_JS = 'cancel_sync_loads_when_suspended.js'
+_MAX_ALLOTTED_TIME_SECONDS = 60
+
+_received_script_resource_request = threading.Event()
+_test_finished = threading.Event()
+
+# The base path of the requested assets is the parent directory.
+_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
+
+
+class JavascriptRequestDetector(MakeRequestHandlerClass(_SERVER_ROOT_PATH)):
+  """Proxies everything to SimpleHTTPRequestHandler, except some paths."""
+
+  def do_GET(self):  # pylint: disable=invalid-name
+    """Handles HTTP GET requests for resources."""
+
+    parsed_path = urlparse.urlparse(self.path)
+    if parsed_path.path == '/testdata/' + _CANCEL_SYNC_LOADS_WHEN_SUSPENDED_JS:
+      _received_script_resource_request.set()
+      # It is important not to send any response back, so we block.
+      print('Waiting on test to finish.')
+      _test_finished.wait()
+      print('Test is finished.')
+      return
+
+    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+
+class CancelSyncLoadsWhenSuspended(black_box_tests.BlackBoxTestCase):
+  """Tests cancelation of synchronous loading of scripts on Suspend."""
+
+  def _LoadPage(self, webdriver, url):
+    """Instructs webdriver to navigate to url."""
+    try:
+      # Note: The following is a blocking request, and returns only when the
+      # page has fully loaded.  In this test, the page will not fully load
+      # so, this does not return until Cobalt exits.
+      webdriver.get(url)
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+
+  def test_simple(self):
+
+    # Step 2. Start Cobalt, and point it to the socket created in Step 1.
+    try:
+      with ThreadedWebServer(
+          JavascriptRequestDetector) as server, self.CreateCobaltRunner(
+              url='about:blank') as runner:
+        target_url = server.GetURL(file_name='../testdata/' +
+                                   _CANCEL_SYNC_LOADS_WHEN_SUSPENDED_HTML)
+        cobalt_launcher_thread = threading.Thread(
+            target=CancelSyncLoadsWhenSuspended._LoadPage,
+            args=(self, runner.webdriver, target_url))
+        cobalt_launcher_thread.start()
+
+        # Step 3. Wait HTTP request for html resource.
+        print('Waiting for script resource request')
+        request_received = _received_script_resource_request.wait(
+            _MAX_ALLOTTED_TIME_SECONDS)
+        print('Request received: {}'.format(request_received))
+        # Step 5. Wait for a request for javascript resource.
+        self.assertTrue(request_received)
+
+        # Step 6. Suspend Cobalt process.
+        print('Suspending Cobalt.')
+        runner.SendSuspend()
+        # Step 7. Cobalt disconnects from the socket.
+        # Step 8. Resume Cobalt process, which enables the JS test to pass.
+        print('Resuming Cobalt.')
+        runner.SendResume()
+
+        # Step 9. Check to see if JSTestsSucceeded().
+        # Note that this call will check the DOM multiple times for a period of
+        # time (current default is 30 seconds).
+        self.assertTrue(runner.JSTestsSucceeded())
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+      # Consider an exception being thrown as a test failure.
+      self.assertTrue(False)
+    finally:
+      print('Cleaning up.')
+      _test_finished.set()
diff --git a/src/cobalt/black_box_tests/tests/disable_eval_with_csp.py b/src/cobalt/black_box_tests/tests/disable_eval_with_csp.py
index 936c57b..062dc11 100644
--- a/src/cobalt/black_box_tests/tests/disable_eval_with_csp.py
+++ b/src/cobalt/black_box_tests/tests/disable_eval_with_csp.py
@@ -7,13 +7,15 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 
 class DisableEvalWithCSPTest(black_box_tests.BlackBoxTestCase):
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='disable_eval_with_csp.html')
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/disable_eval_with_csp.html')
 
-    with self.CreateCobaltRunner(url=url) as runner:
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(url=url) as runner:
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/persistent_cookie.py b/src/cobalt/black_box_tests/tests/persistent_cookie.py
index 4bbefc5..a7dba95 100644
--- a/src/cobalt/black_box_tests/tests/persistent_cookie.py
+++ b/src/cobalt/black_box_tests/tests/persistent_cookie.py
@@ -7,6 +7,7 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 from cobalt.tools.automated_testing import webdriver_utils
 
 keys = webdriver_utils.import_selenium_module('webdriver.common.keys')
@@ -20,27 +21,28 @@
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='persistent_cookie.html')
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/persistent_cookie.html')
 
-    # The webpage listens for NUMPAD1, NUMPAD2 and NUMPAD3 at opening.
-    with self.CreateCobaltRunner(url=url) as runner:
-      # Press NUMPAD1 to verify basic cookie functionality and set
-      # a persistent cookie.
-      runner.WaitForJSTestsSetup()
-      runner.SendKeys(keys.Keys.NUMPAD1)
-      self.assertTrue(runner.JSTestsSucceeded())
+      # The webpage listens for NUMPAD1, NUMPAD2 and NUMPAD3 at opening.
+      with self.CreateCobaltRunner(url=url) as runner:
+        # Press NUMPAD1 to verify basic cookie functionality and set
+        # a persistent cookie.
+        runner.WaitForJSTestsSetup()
+        runner.SendKeys(keys.Keys.NUMPAD1)
+        self.assertTrue(runner.JSTestsSucceeded())
 
-    with self.CreateCobaltRunner(url=url) as runner:
-      runner.WaitForJSTestsSetup()
-      # Press NUMPAD2 to indicate this is the second time we opened
-      # the webpage and verify a persistent cookie is on device. Then
-      # clear this persistent cookie.
-      runner.SendKeys(keys.Keys.NUMPAD2)
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        # Press NUMPAD2 to indicate this is the second time we opened
+        # the webpage and verify a persistent cookie is on device. Then
+        # clear this persistent cookie.
+        runner.SendKeys(keys.Keys.NUMPAD2)
+        self.assertTrue(runner.JSTestsSucceeded())
 
-    with self.CreateCobaltRunner(url=url) as runner:
-      runner.WaitForJSTestsSetup()
-      # Press NUMPAD3 to verify the persistent cookie we cleared is
-      # not on the device for this URL any more.
-      runner.SendKeys(keys.Keys.NUMPAD3)
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        # Press NUMPAD3 to verify the persistent cookie we cleared is
+        # not on the device for this URL any more.
+        runner.SendKeys(keys.Keys.NUMPAD3)
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/preload_font.py b/src/cobalt/black_box_tests/tests/preload_font.py
index b50e779..e38ea25 100644
--- a/src/cobalt/black_box_tests/tests/preload_font.py
+++ b/src/cobalt/black_box_tests/tests/preload_font.py
@@ -9,6 +9,7 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 _MAX_RESUME_WAIT_SECONDS = 30
 
@@ -17,17 +18,18 @@
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='preload_font.html')
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/preload_font.html')
 
-    with self.CreateCobaltRunner(
-        url=url, target_params=['--preload']) as runner:
-      runner.WaitForJSTestsSetup()
-      runner.SendResume()
-      start_time = time.time()
-      while runner.IsInPreload():
-        if time.time() - start_time > _MAX_RESUME_WAIT_SECONDS:
-          raise Exception('Cobalt can not exit preload mode after receiving'
-                          'resume signal')
-        time.sleep(.1)
-      # At this point, Cobalt is in started mode.
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(
+          url=url, target_params=['--preload']) as runner:
+        runner.WaitForJSTestsSetup()
+        runner.SendResume()
+        start_time = time.time()
+        while runner.IsInPreload():
+          if time.time() - start_time > _MAX_RESUME_WAIT_SECONDS:
+            raise Exception('Cobalt can not exit preload mode after receiving'
+                            'resume signal')
+          time.sleep(.1)
+        # At this point, Cobalt is in started mode.
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/preload_visibility.py b/src/cobalt/black_box_tests/tests/preload_visibility.py
index cd23614..5a81881 100644
--- a/src/cobalt/black_box_tests/tests/preload_visibility.py
+++ b/src/cobalt/black_box_tests/tests/preload_visibility.py
@@ -7,17 +7,19 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 
 class PreloadVisibilityTest(black_box_tests.BlackBoxTestCase):
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='preload_visibility.html')
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/preload_visibility.html')
 
-    with self.CreateCobaltRunner(
-        url=url, target_params=['--preload']) as runner:
-      runner.WaitForJSTestsSetup()
-      self.assertTrue(runner.IsInPreload())
-      runner.SendResume()
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(
+          url=url, target_params=['--preload']) as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.IsInPreload())
+        runner.SendResume()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py b/src/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py
new file mode 100644
index 0000000..214d680
--- /dev/null
+++ b/src/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py
@@ -0,0 +1,145 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests retry of asynchronous loading of scripts on Suspend/Resume."""
+
+# This test script works by splitting the work over 3 threads, so that they
+# can each make progress even if they come across blocking operations.
+# The three threads are:
+#   1. Main thread, runs BlackBoxTestCase, sends suspend/resume signals, etc.
+#   2. HTTP Server, responsible for slowly responding to a fetch of a javascript
+#      file.
+#   3. Webdriver thread, instructs Cobalt to navigate to a URL
+#
+# Steps in ~ chronological order:
+#   1. Create a TCP socket and listen on all interfaces.
+#   2. Start Cobalt, and point it to the socket created in Step 1.
+#   3. Wait HTTP request for html resource.
+#   4. Respond to a HTTP request.
+#   5. Wait for a request for javascript resource.
+#   6. Suspend Cobalt process.
+#   7. Cobalt disconnects from the socket.
+#   8. Resume Cobalt process.
+#   9. Retry javascript resource, which allows the test to pass.
+#   9. Check to see if JSTestsSucceeded().
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import _env  # pylint: disable=unused-import,g-bad-import-order
+
+import logging
+import os
+import SimpleHTTPServer
+import threading
+import traceback
+import urlparse
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+_HTML_FILE_TO_REQUEST = 'retry_async_script_loads_after_suspend.html'
+_JS_FILE_TO_REQUEST = 'script_executed.js'
+_MAX_ALLOTTED_TIME_SECONDS = 60
+
+_received_script_resource_request = threading.Event()
+_test_finished = threading.Event()
+
+# The base path of the requested assets is the parent directory.
+_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
+
+
+class JavascriptRequestDetector(MakeRequestHandlerClass(_SERVER_ROOT_PATH)):
+  """Proxies everything to SimpleHTTPRequestHandler, except some paths."""
+  _counter_lock = threading.Lock()
+  _request_counter = 0
+
+  def do_GET(self):  # pylint: disable=invalid-name
+    """Handles HTTP GET requests for resources."""
+
+    parsed_path = urlparse.urlparse(self.path)
+
+    if parsed_path.path == '/testdata/' + _JS_FILE_TO_REQUEST:
+      _received_script_resource_request.set()
+      current_count = None
+      with self._counter_lock:
+        JavascriptRequestDetector._request_counter += 1
+        current_count = JavascriptRequestDetector._request_counter
+
+      if current_count < 2:
+        # It is important not to send any response back, so we block.
+        logging.info('Waiting on test to finish.')
+        _test_finished.wait()
+        # Returns a 404, to make sure the page isn't loaded by the first
+        # request.
+        return
+
+    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+
+class RetryAsyncScriptLoadsAfterSuspend(black_box_tests.BlackBoxTestCase):
+  """Tests cancelation of synchronous loading of scripts on Suspend."""
+
+  def _LoadPage(self, webdriver, url):
+    """Instructs webdriver to navigate to url."""
+    try:
+      # Note: The following is a blocking request, and returns only when the
+      # page has fully loaded.  In this test, the page will not fully load
+      # so, this does not return until Cobalt exits.
+      webdriver.get(url)
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+
+  def test_simple(self):
+
+    # Step 2. Start Cobalt, and point it to the socket created in Step 1.
+    try:
+      with ThreadedWebServer(
+          JavascriptRequestDetector) as server, self.CreateCobaltRunner(
+              url='about:blank') as runner:
+        target_url = server.GetURL(file_name='../testdata/' +
+                                   _HTML_FILE_TO_REQUEST)
+        cobalt_launcher_thread = threading.Thread(
+            target=RetryAsyncScriptLoadsAfterSuspend._LoadPage,
+            args=(self, runner.webdriver, target_url))
+        cobalt_launcher_thread.start()
+
+        # Step 3. Wait HTTP request for html resource.
+        logging.info('Waiting for script resource request')
+        request_received = _received_script_resource_request.wait(
+            _MAX_ALLOTTED_TIME_SECONDS)
+        logging.info('Request received: {}'.format(request_received))
+        # Step 5. Wait for a request for javascript resource.
+        self.assertTrue(request_received)
+
+        # Step 6. Suspend Cobalt process.
+        logging.info('Suspending Cobalt.')
+        runner.SendSuspend()
+        # Step 7. Cobalt disconnects from the socket.
+        # Step 8. Resume Cobalt process, which enables the JS test to pass.
+        logging.info('Resuming Cobalt.')
+        runner.SendResume()
+
+        # Step 9. Check to see if JSTestsSucceeded().
+        # Note that this call will check the DOM multiple times for a period of
+        # time (current default is 30 seconds).
+        self.assertTrue(runner.JSTestsSucceeded())
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+      # Consider an exception being thrown as a test failure.
+      self.assertTrue(False)
+    finally:
+      logging.info('Cleaning up.')
+      _test_finished.set()
diff --git a/src/cobalt/black_box_tests/tests/suspend_visibility.py b/src/cobalt/black_box_tests/tests/suspend_visibility.py
index 66b8c0c..e7dbe4d 100644
--- a/src/cobalt/black_box_tests/tests/suspend_visibility.py
+++ b/src/cobalt/black_box_tests/tests/suspend_visibility.py
@@ -7,16 +7,18 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 
 class SuspendVisibilityTest(black_box_tests.BlackBoxTestCase):
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='suspend_visibility.html')
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/suspend_visibility.html')
 
-    with self.CreateCobaltRunner(url=url) as runner:
-      runner.WaitForJSTestsSetup()
-      runner.SendSuspend()
-      runner.SendResume()
-      self.assertTrue(runner.JSTestsSucceeded())
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        runner.SendSuspend()
+        runner.SendResume()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/timer_hit_after_preload.py b/src/cobalt/black_box_tests/tests/timer_hit_after_preload.py
index d0d803e..c889b48 100644
--- a/src/cobalt/black_box_tests/tests/timer_hit_after_preload.py
+++ b/src/cobalt/black_box_tests/tests/timer_hit_after_preload.py
@@ -7,18 +7,19 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 
 class TimerAfterPreloadTest(black_box_tests.BlackBoxTestCase):
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='timer_hit_after_preload.html')
-
-    with self.CreateCobaltRunner(
-        url=url, target_params=['--preload']) as runner:
-      self.assertTrue(runner.IsInPreload())
-      # setInterval will hit once during the .5 seconds.
-      runner.PollUntilFound('#script_executed')
-      runner.SendResume()
-      self.assertTrue(runner.JSTestsSucceeded())
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/timer_hit_after_preload.html')
+      with self.CreateCobaltRunner(
+          url=url, target_params=['--preload']) as runner:
+        self.assertTrue(runner.IsInPreload())
+        # setInterval will hit once during the .5 seconds.
+        runner.PollUntilFound('#script_executed')
+        runner.SendResume()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/timer_hit_in_preload.py b/src/cobalt/black_box_tests/tests/timer_hit_in_preload.py
index 320f0a5..ea9c48a 100644
--- a/src/cobalt/black_box_tests/tests/timer_hit_in_preload.py
+++ b/src/cobalt/black_box_tests/tests/timer_hit_in_preload.py
@@ -7,15 +7,16 @@
 import _env  # pylint: disable=unused-import
 
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
 
 class TimerInPreloadTest(black_box_tests.BlackBoxTestCase):
 
   def test_simple(self):
 
-    url = self.GetURL(file_name='timer_hit_in_preload.html')
-
-    with self.CreateCobaltRunner(
-        url=url, target_params=['--preload']) as runner:
-      self.assertTrue(runner.JSTestsSucceeded())
-      self.assertTrue(runner.IsInPreload())
+    with ThreadedWebServer() as server:
+      url = server.GetURL(file_name='testdata/timer_hit_in_preload.html')
+      with self.CreateCobaltRunner(
+          url=url, target_params=['--preload']) as runner:
+        self.assertTrue(runner.JSTestsSucceeded())
+        self.assertTrue(runner.IsInPreload())
diff --git a/src/cobalt/black_box_tests/threaded_web_server.py b/src/cobalt/black_box_tests/threaded_web_server.py
new file mode 100644
index 0000000..72bf5cc
--- /dev/null
+++ b/src/cobalt/black_box_tests/threaded_web_server.py
@@ -0,0 +1,107 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a threaded web server used for serving testdata."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import SimpleHTTPServer
+import socket
+import SocketServer
+import threading
+
+
+class _ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+  pass
+
+
+def MakeRequestHandlerClass(base_path):
+  """RequestHandler that serves files that reside relative to base_path.
+
+  Args:
+    base_path: A path considered to be the root directory.
+
+  Returns:
+    A RequestHandler class.
+  """
+
+  class TestDataHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    """Handles HTTP requests, but changes the base directory for assets."""
+
+    _current_working_directory = os.getcwd()
+    _base_path = base_path
+
+    def translate_path(self, path):
+      """Translate the request path to the file in the testdata directory."""
+
+      potential_path = SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(
+          self, path)
+      potential_path = potential_path.replace(self._current_working_directory,
+                                              self._base_path)
+      return potential_path
+
+  return TestDataHTTPRequestHandler
+
+
+class ThreadedWebServer(object):
+  """A HTTP WebServer that serves requests in a separate thread."""
+
+  def __init__(self,
+               handler=MakeRequestHandlerClass(os.path.dirname(__file__))):
+    _ThreadedTCPServer.allow_reuse_address = True
+    # Get the socket address for the ANY interface.  Doing it this way
+    # has it so that it will work for IPv4, and IPv6 only networks.
+    # Note that putting '::' as the hostname does not work at this time
+    # (see https://bugs.python.org/issue20215).  Instead, the following code
+    # was inspired by https://docs.python.org/2/library/socket.html.
+    for result in socket.getaddrinfo(None, 0, socket.AF_UNSPEC,
+                                     socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
+      # This is (0.0.0.0, 0) or equivalent in IPv6 (could be more than 2
+      # elements).
+      socket_address = result[4]
+      break
+
+    self._server = _ThreadedTCPServer(socket_address, handler)
+    self._server_thread = None
+
+    self._bound_port = self._server.server_address[1]
+    address_pack_list = socket.getaddrinfo(socket.gethostname(),
+                                           self._bound_port)
+    first_address_pack = address_pack_list[0]
+    self._bound_ip, _ = first_address_pack[4]
+    self._bound_host, _ = first_address_pack[4]
+
+  def GetURL(self, file_name):
+    """Given a |file_name|, return a HTTP URI that can be fetched.
+
+    Args:
+      file_name: a string containing a file_name.
+
+    Returns:
+      A string containing a HTTP URI.
+    """
+    return 'http://{}:{}/{}'.format(self._bound_host, self._bound_port,
+                                    file_name)
+
+  def __enter__(self):
+    self._server_thread = threading.Thread(target=self._server.serve_forever)
+    self._server_thread.start()
+    return self
+
+  def __exit__(self, exc_type, exc_value, traceback):
+    self._server.shutdown()
+    self._server.server_close()
+    self._server_thread.join()
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index ccce8cb..f2352c2 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -370,8 +370,10 @@
                           &options->scratch_surface_cache_size_in_bytes);
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   auto command_line = CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(browser::switches::kDisableRasterizerCaching)) {
-    options->disable_rasterizer_caching = true;
+  if (command_line->HasSwitch(browser::switches::kDisableRasterizerCaching) ||
+      command_line->HasSwitch(
+          browser::switches::kForceDeterministicRendering)) {
+    options->force_deterministic_rendering = true;
   }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 }
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index c4c63e2..75b1859 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -129,6 +129,7 @@
         '../dom/navigator.idl',
         '../dom/node.idl',
         '../dom/node_list.idl',
+        '../dom/on_error_event_listener.idl',
         '../dom/on_screen_keyboard.idl',
         '../dom/performance.idl',
         '../dom/performance_timing.idl',
@@ -185,8 +186,12 @@
 
         '../media_capture/media_device_info.idl',
         '../media_capture/media_devices.idl',
+        '../media_capture/media_recorder.idl',
         '../media_session/media_metadata.idl',
         '../media_session/media_session.idl',
+        '../media_session/media_session_action_details.idl',
+        '../media_stream/media_stream.idl',
+        '../media_stream/media_stream_track.idl',
 
         '../speech/speech_recognition.idl',
         '../speech/speech_recognition_alternative.idl',
@@ -255,10 +260,14 @@
         '../dom/track_default_type.idl',
         '../dom/wheel_event_init.idl',
         '../media_capture/media_device_kind.idl',
+        '../media_capture/media_recorder_options.idl',
+        '../media_capture/recording_state.idl',
         '../media_session/media_image.idl',
         '../media_session/media_metadata_init.idl',
         '../media_session/media_session_action.idl',
         '../media_session/media_session_playback_state.idl',
+        '../media_stream/media_stream_constraints.idl',
+        '../media_stream/media_track_settings.idl',
         '../page_visibility/visibility_state.idl',
         '../speech/speech_recognition_error_code.idl',
         '../speech/speech_synthesis_error_code.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 7911067..1eb7f99 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -608,33 +608,52 @@
     const FilePath& path,
     loader::image::EncodedStaticImage::ImageFormat image_format,
     const base::Closure& done_callback) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToFile()");
   DCHECK(screen_shot_writer_);
-  DCHECK(main_web_module_layer_);
-  base::optional<renderer::Submission> last_submission =
-      main_web_module_layer_->GetCurrentSubmission();
-  if (!last_submission) {
-    LOG(WARNING) << "Unable to find last submission.";
+
+  scoped_refptr<render_tree::Node> render_tree = GetLastSubmissionAnimated();
+  if (!render_tree) {
+    LOG(WARNING) << "Unable to get animated render tree";
     return;
   }
-  DCHECK(last_submission->render_tree);
+
   screen_shot_writer_->RequestScreenshotToFile(
-      image_format, path, last_submission->render_tree, done_callback);
+      image_format, path, render_tree, done_callback);
 }
 
 void BrowserModule::RequestScreenshotToBuffer(
     loader::image::EncodedStaticImage::ImageFormat image_format,
     const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToBuffer()");
   DCHECK(screen_shot_writer_);
+
+  scoped_refptr<render_tree::Node> render_tree = GetLastSubmissionAnimated();
+  if (!render_tree) {
+    LOG(WARNING) << "Unable to get animated render tree";
+    return;
+  }
+
+  screen_shot_writer_->RequestScreenshotToMemory(
+      image_format, render_tree, screenshot_ready);
+}
+
+scoped_refptr<render_tree::Node> BrowserModule::GetLastSubmissionAnimated() {
   DCHECK(main_web_module_layer_);
   base::optional<renderer::Submission> last_submission =
       main_web_module_layer_->GetCurrentSubmission();
   if (!last_submission) {
     LOG(WARNING) << "Unable to find last submission.";
-    return;
+    return nullptr;
   }
   DCHECK(last_submission->render_tree);
-  screen_shot_writer_->RequestScreenshotToMemory(
-      image_format, last_submission->render_tree, screenshot_ready);
+
+  render_tree::animations::AnimateNode* animate_node =
+      base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
+          last_submission->render_tree.get());
+  render_tree::animations::AnimateNode::AnimateResults results =
+      animate_node->Apply(last_submission->time_offset);
+
+  return results.animated->source();
 }
 
 void BrowserModule::ProcessRenderTreeSubmissionQueue() {
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 90bc287..22e3bed 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -392,6 +392,10 @@
   // Get the SbWindow via |system_window_| or potentially NULL.
   SbWindow GetSbWindow();
 
+  // This returns the render tree of the most recent submission, with animations
+  // applied according to the current time.
+  scoped_refptr<render_tree::Node> GetLastSubmissionAnimated();
+
   // TODO:
   //     WeakPtr usage here can be avoided if BrowserModule has a thread to
   //     own where it can ensure that its tasks are all resolved when it is
diff --git a/src/cobalt/browser/screen_shot_writer.cc b/src/cobalt/browser/screen_shot_writer.cc
index fa01378..2ce4557 100644
--- a/src/cobalt/browser/screen_shot_writer.cc
+++ b/src/cobalt/browser/screen_shot_writer.cc
@@ -16,6 +16,7 @@
 
 #include "base/basictypes.h"
 #include "base/bind.h"
+#include "base/debug/trace_event.h"
 #include "base/file_util.h"
 #include "cobalt/loader/image/image_encoder.h"
 #include "cobalt/render_tree/resource_provider_stub.h"
@@ -73,6 +74,7 @@
         void(const scoped_refptr<loader::image::EncodedStaticImage>&)>&
         done_encoding_callback,
     scoped_array<uint8> pixel_data, const math::Size& image_dimensions) {
+  TRACE_EVENT0("cobalt::browser", "ScreenshotWriter::EncodeData()");
   scoped_refptr<loader::image::EncodedStaticImage> image_data =
       loader::image::CompressRGBAImage(desired_format, pixel_data.get(),
                                        image_dimensions);
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index f26b317..fe94555 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -35,10 +35,21 @@
 const char kDisableImageAnimationsHelp[] =
     "Enables/disables animations on animated images (e.g. animated WebP).";
 
+const char kForceDeterministicRendering[] = "force_deterministic_rendering";
+const char kForceDeterministicRenderingHelp[] =
+    "Forces the renderer to avoid doing anything that may result in "
+    "1-pixel-off non-deterministic rendering output.  For example, a renderer "
+    "may implement an optimization where text glyphs are rendered once and "
+    "cached and re-used in situations where the cached glyph is approximately "
+    "very similar, even if it is not exactly the same.  Setting this flag "
+    "avoids that kind of behavior, allowing strict screen-diff tests to pass.";
+
 const char kDisableRasterizerCaching[] = "disable_rasterizer_caching";
 const char kDisableRasterizerCachingHelp[] =
     "Disables caching of rasterized render tree nodes; caching improves "
-    "performance but may result in sub-pixel differences.";
+    "performance but may result in sub-pixel differences.  Note that this "
+    "is deprecated, the '--force_deterministic_rendering' flag should be "
+    "used instead which does the same thing.";
 
 const char kDisableSignIn[] = "disable_sign_in";
 const char kDisableSignInHelp[] =
@@ -325,6 +336,7 @@
     {kAudioDecoderStub, kAudioDecoderStubHelp},
         {kDebugConsoleMode, kDebugConsoleModeHelp},
         {kDisableImageAnimations, kDisableImageAnimationsHelp},
+        {kForceDeterministicRendering, kForceDeterministicRenderingHelp},
         {kDisableRasterizerCaching, kDisableRasterizerCachingHelp},
         {kDisableSignIn, kDisableSignInHelp},
         {kDisableSplashScreenOnReloads, kDisableSplashScreenOnReloadsHelp},
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 56a7a00..a203feb 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -28,6 +28,7 @@
 extern const char kDebugConsoleModeHelp[];
 extern const char kDisableImageAnimations[];
 extern const char kDisableImageAnimationsHelp[];
+extern const char kForceDeterministicRendering[];
 extern const char kDisableRasterizerCaching[];
 extern const char kDisableSignIn[];
 extern const char kDisableSignInHelp[];
diff --git a/src/cobalt/browser/user_agent_string.cc b/src/cobalt/browser/user_agent_string.cc
index eaf091d..23720c3 100644
--- a/src/cobalt/browser/user_agent_string.cc
+++ b/src/cobalt/browser/user_agent_string.cc
@@ -21,6 +21,7 @@
 #if defined(COBALT_ENABLE_LIB)
 #include "cobalt/browser/lib/exported/user_agent.h"
 #endif
+#include "cobalt/renderer/get_default_rasterizer_for_platform.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/version.h"
 #include "cobalt_build_id.h"  // NOLINT(build/include)
@@ -126,16 +127,6 @@
 
 namespace {
 
-#if SB_API_VERSION == SB_EXPERIMENTAL_API_VERSION
-const char kStarboardStabilitySuffix[] = "-Experimental";
-#elif defined(SB_RELEASE_CANDIDATE_API_VERSION) &&        \
-    SB_API_VERSION >= SB_RELEASE_CANDIDATE_API_VERSION && \
-    SB_API_VERSION < SB_EXPERIMENTAL_API_VERSION
-const char kStarboardStabilitySuffix[] = "-ReleaseCandidate";
-#else
-const char kStarboardStabilitySuffix[] = "";
-#endif
-
 struct SanitizeReplacements {
   const char* replace_chars;
   const char* replace_with;
@@ -204,7 +195,7 @@
   UserAgentPlatformInfo platform_info;
 
   platform_info.starboard_version = base::StringPrintf(
-      "Starboard/%d%s", SB_API_VERSION, kStarboardStabilitySuffix);
+      "Starboard/%d", SB_API_VERSION);
 
   const size_t kSystemPropertyMaxLength = 1024;
   char value[kSystemPropertyMaxLength];
@@ -228,6 +219,9 @@
   platform_info.javascript_engine_version =
       script::GetJavaScriptEngineNameAndVersion();
 
+  platform_info.rasterizer_type =
+      renderer::GetDefaultRasterizerForPlatform().rasterizer_name;
+
   platform_info.cobalt_version = COBALT_VERSION;
   platform_info.cobalt_build_version_number = COBALT_BUILD_VERSION_NUMBER;
 
@@ -338,19 +332,25 @@
                       platform_info.cobalt_build_version_number.c_str(),
                       platform_info.build_configuration.c_str());
 
-  //   JavaScript Engine Name/Version
+  // JavaScript Engine Name/Version
   if (!platform_info.javascript_engine_version.empty()) {
     base::StringAppendF(&user_agent, " %s",
                         platform_info.javascript_engine_version.c_str());
   }
 
-  //   Starboard/APIVersion,
+  // Rasterizer Type
+  if (!platform_info.rasterizer_type.empty()) {
+    base::StringAppendF(&user_agent, " %s",
+                        platform_info.rasterizer_type.c_str());
+  }
+
+  // Starboard/APIVersion,
   if (!platform_info.starboard_version.empty()) {
     base::StringAppendF(&user_agent, " %s",
                         platform_info.starboard_version.c_str());
   }
 
-  //   Device/FirmwareVersion (Brand, Model, ConnectionType)
+  // Device/FirmwareVersion (Brand, Model, ConnectionType)
   base::StringAppendF(
       &user_agent, ", %s_%s_%s_%s/%s (%s, %s, %s)",
       Sanitize(platform_info.network_operator.value_or("")).c_str(),
diff --git a/src/cobalt/browser/user_agent_string.h b/src/cobalt/browser/user_agent_string.h
index 900c8a9..27a720b 100644
--- a/src/cobalt/browser/user_agent_string.h
+++ b/src/cobalt/browser/user_agent_string.h
@@ -37,6 +37,7 @@
   std::string aux_field;
   base::optional<SbSystemConnectionType> connection_type;
   std::string javascript_engine_version;
+  std::string rasterizer_type;
 
   std::string cobalt_version;
   std::string cobalt_build_version_number;
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index d004068..cd52dc8 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -43,6 +43,7 @@
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/event.h"
 #include "cobalt/dom/global_stats.h"
+#include "cobalt/dom/html_script_element.h"
 #include "cobalt/dom/input_event.h"
 #include "cobalt/dom/input_event_init.h"
 #include "cobalt/dom/keyboard_event.h"
@@ -247,6 +248,8 @@
   void LogScriptError(const base::SourceLocation& source_location,
                       const std::string& error_message);
 
+  void CancelSynchronousLoads();
+
  private:
   class DocumentLoadedObserver;
 
@@ -441,6 +444,13 @@
 
   scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>
       system_caption_settings_;
+
+  // This event is used to interrupt the loader when JavaScript is loaded
+  // synchronously.  It is manually reset so that events like Suspend can be
+  // correctly execute, even if there are multiple synchronous loads in queue
+  // before the suspend (or other) event handlers.
+  base::WaitableEvent synchronous_loader_interrupt_ = {
+      true /* manually reset */, false /* initially signaled */};
 };
 
 class WebModule::Impl::DocumentLoadedObserver : public dom::DocumentObserver {
@@ -578,8 +588,6 @@
   global_environment_ = javascript_engine_->CreateGlobalEnvironment();
   DCHECK(global_environment_);
 
-  mutation_observer_task_manager_.RegisterAsTracingRoot(global_environment_);
-
   execution_state_ =
       script::ExecutionState::CreateExecutionState(global_environment_);
   DCHECK(execution_state_);
@@ -598,11 +606,16 @@
   dom::Window::CacheCallback splash_screen_cache_callback =
       CacheUrlContentCallback(data.options.splash_screen_cache);
 
+  // These members will reference other |Traceable|s, however are not
+  // accessible from |Window|, so we must explicitly add them as roots.
+  global_environment_->AddRoot(&mutation_observer_task_manager_);
+  global_environment_->AddRoot(media_source_registry_.get());
+
   window_ = new dom::Window(
       data.window_dimensions.width(), data.window_dimensions.height(),
       data.video_pixel_ratio, data.initial_application_state, css_parser_.get(),
-      dom_parser_.get(), fetcher_factory_.get(), &resource_provider_,
-      animated_image_tracker_.get(), image_cache_.get(),
+      dom_parser_.get(), fetcher_factory_.get(), loader_factory_.get(),
+      &resource_provider_, animated_image_tracker_.get(), image_cache_.get(),
       reduced_image_cache_capacity_manager_.get(), remote_typeface_cache_.get(),
       mesh_cache_.get(), local_storage_database_.get(),
       data.can_play_type_handler, data.web_media_player_factory,
@@ -627,7 +640,7 @@
       base::Bind(&WebModule::Impl::OnStartDispatchEvent,
                  base::Unretained(this)),
       base::Bind(&WebModule::Impl::OnStopDispatchEvent, base::Unretained(this)),
-      data.options.provide_screenshot_function,
+      data.options.provide_screenshot_function, &synchronous_loader_interrupt_,
       data.options.csp_insecure_allowed_token, data.dom_max_element_depth,
       data.options.video_playback_rate_multiplier,
 #if defined(ENABLE_TEST_RUNNER)
@@ -750,6 +763,8 @@
 
 void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
                                        const scoped_refptr<dom::Event>& event) {
+  TRACE_EVENT1("cobalt::browser", "WebModule::Impl::InjectInputEvent()",
+               "event", event->type().c_str());
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(is_running_);
   DCHECK(window_);
@@ -757,7 +772,17 @@
   if (element) {
     element->DispatchEvent(event);
   } else {
-    window_->InjectEvent(event);
+    if (dom::PointerState::CanQueueEvent(event)) {
+      // As an optimization we batch together pointer/mouse events for as long
+      // as we can get away with it (e.g. until a non-pointer event is received
+      // or whenever the next layout occurs).
+      window_->document()->pointer_state()->QueuePointerEvent(event);
+    } else {
+      // In order to maintain the correct input event ordering, we first
+      // dispatch any queued pending pointer events.
+      HandlePointerEvents();
+      window_->InjectEvent(event);
+    }
   }
 }
 
@@ -912,6 +937,10 @@
   }
 }
 
+void WebModule::Impl::CancelSynchronousLoads() {
+  synchronous_loader_interrupt_.Signal();
+}
+
 #if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
 void WebModule::Impl::OnPartialLayoutConsoleCommandReceived(
     const std::string& message) {
@@ -1074,6 +1103,7 @@
 
 void WebModule::Impl::Unpause() {
   TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Unpause()");
+  synchronous_loader_interrupt_.Reset();
   SetApplicationState(base::kApplicationStateStarted);
 }
 
@@ -1132,6 +1162,7 @@
 
 void WebModule::Impl::Resume(render_tree::ResourceProvider* resource_provider) {
   TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Resume()");
+  synchronous_loader_interrupt_.Reset();
   SetResourceProvider(resource_provider);
   SetApplicationState(base::kApplicationStatePaused);
 }
@@ -1142,6 +1173,7 @@
   if (!is_running_) {
     return;
   }
+  synchronous_loader_interrupt_.Reset();
 
   layout_manager_->Purge();
 
@@ -1599,6 +1631,8 @@
   // Must only be called by a thread external from the WebModule thread.
   DCHECK_NE(MessageLoop::current(), message_loop());
 
+  impl_->CancelSynchronousLoads();
+
   // We must block here so that the call doesn't return until the web
   // application has had a chance to process the whole event.
   message_loop()->PostBlockingTask(
@@ -1619,6 +1653,8 @@
   // Must only be called by a thread external from the WebModule thread.
   DCHECK_NE(MessageLoop::current(), message_loop());
 
+  impl_->CancelSynchronousLoads();
+
   // We must block here so that we don't queue the finish until after
   // SuspendLoaders has run to completion, and therefore has already queued any
   // precipitate tasks.
@@ -1647,6 +1683,8 @@
   // Must only be called by a thread external from the WebModule thread.
   DCHECK_NE(MessageLoop::current(), message_loop());
 
+  impl_->CancelSynchronousLoads();
+
   // We block here so that we block the Low Memory event handler until we have
   // reduced our memory consumption.
   message_loop()->PostBlockingTask(FROM_HERE,
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 82230f8..a5fd30c 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -408,6 +408,8 @@
 
   void ClearAllIntervalsAndTimeouts();
 
+  void CancelSynchronousLoads();
+
 #if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
   void OnPartialLayoutConsoleCommandReceived(const std::string& message);
 #endif  // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index 3a22d93..cf79008 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -33,6 +33,7 @@
         '<(DEPTH)/base/base.gyp:base_unittests',
         '<(DEPTH)/cobalt/accessibility/accessibility_test.gyp:*',
         '<(DEPTH)/cobalt/audio/audio.gyp:*',
+        '<(DEPTH)/cobalt/audio/audio_test.gyp:*',
         '<(DEPTH)/cobalt/base/base.gyp:*',
         '<(DEPTH)/cobalt/bindings/testing/testing.gyp:*',
         '<(DEPTH)/cobalt/browser/browser.gyp:*',
@@ -44,7 +45,9 @@
         '<(DEPTH)/cobalt/debug/debug.gyp:*',
         '<(DEPTH)/cobalt/dom/dom.gyp:*',
         '<(DEPTH)/cobalt/dom/dom_test.gyp:*',
+        '<(DEPTH)/cobalt/dom/testing/dom_testing.gyp:*',
         '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:*',
+        '<(DEPTH)/cobalt/dom_parser/dom_parser_test.gyp:*',
         '<(DEPTH)/cobalt/h5vcc/h5vcc.gyp:*',
         '<(DEPTH)/cobalt/input/input.gyp:*',
         '<(DEPTH)/cobalt/layout/layout.gyp:*',
@@ -54,8 +57,11 @@
         '<(DEPTH)/cobalt/math/math.gyp:*',
         '<(DEPTH)/cobalt/media/sandbox/sandbox.gyp:*',
         '<(DEPTH)/cobalt/media_capture/media_capture.gyp:*',
+        '<(DEPTH)/cobalt/media_capture/media_capture_test.gyp:*',
         '<(DEPTH)/cobalt/media_session/media_session.gyp:*',
         '<(DEPTH)/cobalt/media_session/media_session_test.gyp:*',
+        '<(DEPTH)/cobalt/media_stream/media_stream.gyp:*',
+        '<(DEPTH)/cobalt/media_stream/media_stream_test.gyp:*',
         '<(DEPTH)/cobalt/network/network.gyp:*',
         '<(DEPTH)/cobalt/overlay_info/overlay_info.gyp:*',
         '<(DEPTH)/cobalt/page_visibility/page_visibility.gyp:*',
@@ -63,11 +69,14 @@
         '<(DEPTH)/cobalt/renderer/renderer.gyp:*',
         '<(DEPTH)/cobalt/renderer/sandbox/sandbox.gyp:*',
         '<(DEPTH)/cobalt/samples/simple_example/simple_example.gyp:*',
+        '<(DEPTH)/cobalt/script/engine.gyp:engine_shell',
         '<(DEPTH)/cobalt/script/script.gyp:*',
-        '<(DEPTH)/cobalt/script/engine.gyp:all_engines',
         '<(DEPTH)/cobalt/speech/sandbox/sandbox.gyp:*',
         '<(DEPTH)/cobalt/speech/speech.gyp:*',
         '<(DEPTH)/cobalt/storage/storage.gyp:*',
+        '<(DEPTH)/cobalt/storage/store/store.gyp:*',
+        '<(DEPTH)/cobalt/storage/store_upgrade/upgrade.gyp:*',
+        '<(DEPTH)/cobalt/storage/store_upgrade/upgrade_tool.gyp:*',
         '<(DEPTH)/cobalt/trace_event/trace_event.gyp:*',
         '<(DEPTH)/cobalt/web_animations/web_animations.gyp:*',
         '<(DEPTH)/cobalt/webdriver/webdriver.gyp:*',
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 5ea67dc..501b9b9 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-162639
\ No newline at end of file
+179149
\ No newline at end of file
diff --git a/src/cobalt/build/cobalt_configuration.gypi b/src/cobalt/build/cobalt_configuration.gypi
index f75cede..3934f82 100644
--- a/src/cobalt/build/cobalt_configuration.gypi
+++ b/src/cobalt/build/cobalt_configuration.gypi
@@ -366,7 +366,7 @@
     'cobalt_gc_zeal%': 0,
 
     # Use media source extension implementation that is conformed to the
-    # Candidate Recommandation of July 5th 2016.
+    # Candidate Recommendation of July 5th 2016.
     'cobalt_media_source_2016%': '<(cobalt_media_source_2016)',
 
     # Note that the following media buffer related variables are only used when
diff --git a/src/cobalt/build/cobalt_configuration.py b/src/cobalt/build/cobalt_configuration.py
index c6458b5..90fb8d6 100644
--- a/src/cobalt/build/cobalt_configuration.py
+++ b/src/cobalt/build/cobalt_configuration.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 """Base cobalt configuration for GYP."""
 
+import logging
 import os
 
 import _env  # pylint: disable=unused-import
@@ -21,7 +22,6 @@
 import cobalt.tools.webdriver_benchmark_config as wb_config
 from starboard.build import application_configuration
 
-
 # The canonical Cobalt application name.
 APPLICATION_NAME = 'cobalt'
 
@@ -52,6 +52,7 @@
         # Whether to enable VR.
         'enable_vr': int(os.environ.get('USE_VR', 0)),
     }
+    logging.info('Build Number: {}'.format(variables['cobalt_version']))
     return variables
 
   def GetPostIncludes(self):
@@ -79,6 +80,8 @@
         'loader_test',
         'math_test',
         'media_session_test',
+        'media_stream_test',
+        'memory_store_test',
         'nb_test',
         'net_unittests',
         'network_test',
@@ -88,6 +91,7 @@
         'renderer_test',
         'sql_unittests',
         'storage_test',
+        'storage_upgrade_test',
         'trace_event_test',
         'web_animations_test',
         'web_platform_tests',
diff --git a/src/cobalt/build/config/base.gni b/src/cobalt/build/config/base.gni
index cfcd816..fa84134 100644
--- a/src/cobalt/build/config/base.gni
+++ b/src/cobalt/build/config/base.gni
@@ -418,7 +418,7 @@
 }
 
 # Use media source extension implementation that is conformed to the
-# Candidate Recommandation of July 5th 2016.
+# Candidate Recommendation of July 5th 2016.
 if (!defined(cobalt_use_media_source_2016)) {
   cobalt_use_media_source_2016 = true
 }
diff --git a/src/cobalt/build/gyp_utils.py b/src/cobalt/build/gyp_utils.py
index b04f13c..f60badb 100644
--- a/src/cobalt/build/gyp_utils.py
+++ b/src/cobalt/build/gyp_utils.py
@@ -71,7 +71,6 @@
     with open(BUILD_ID_PATH, 'r') as build_id_file:
       build_number = int(build_id_file.read().replace('\n', ''))
       logging.info('Retrieving build number from %s', BUILD_ID_PATH)
-      logging.info('Build Number: %d', build_number)
       return build_number
 
   revinfo = GetRevinfo()
@@ -99,7 +98,6 @@
     data = data[len(_XSSI_PREFIX):]
   results = json.loads(data)
   build_number = results.get('build_number', 0)
-  logging.info('Build Number: %d', build_number)
   return build_number
 
 
diff --git a/src/cobalt/content/ssl/certs/7992b8bb.0 b/src/cobalt/content/ssl/certs/7992b8bb.0
deleted file mode 100644
index 870bc4a..0000000
--- a/src/cobalt/content/ssl/certs/7992b8bb.0
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UEBhMCVFIxDzAN
-BgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp
-bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1Qg
-RWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAw
-ODA3MDFaFw0yMzA0MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0w
-SwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnE
-n2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRp
-ZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEApCUZ4WWe60ghUEoI5RHwWrom/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537
-jVJp45wnEFPzpALFp/kRGml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1m
-ep5Fimh34khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z5UNP
-9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0hO8EuPbJbKoCPrZV
-4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QIDAQABo0IwQDAdBgNVHQ4EFgQUVpkH
-HtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
-hvcNAQELBQADggEBAJ5FdnsXSDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPo
-BP5yCccLqh0lVX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq
-URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nfpeYVhDfwwvJl
-lpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CFYv4HAqGEVka+lgqaE9chTLd8
-B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW+qtB4Uu2NQvAmxU=
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/9007ae68.0 b/src/cobalt/content/ssl/certs/9007ae68.0
deleted file mode 100644
index cdc2805..0000000
--- a/src/cobalt/content/ssl/certs/9007ae68.0
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw
-EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
-ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx
-EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
-c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy
-3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8
-u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2
-m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk
-CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa
-YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6
-vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL
-LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX
-ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is
-XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV
-HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ
-04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
-xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B
-LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM
-CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb
-VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85
-YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS
-ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix
-lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N
-UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ
-a7+h89n07eLw4+1knj0vllJPgFOL
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/9d520b32.0 b/src/cobalt/content/ssl/certs/9d520b32.0
deleted file mode 100644
index 494787d..0000000
--- a/src/cobalt/content/ssl/certs/9d520b32.0
+++ /dev/null
@@ -1,18 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
-U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh
-dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE
-BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl
-Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
-AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO
-/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX
-WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z
-ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4
-bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK
-9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
-SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm
-iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG
-Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW
-mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW
-T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/cb59f961.0 b/src/cobalt/content/ssl/certs/cb59f961.0
deleted file mode 100644
index b97542a..0000000
--- a/src/cobalt/content/ssl/certs/cb59f961.0
+++ /dev/null
@@ -1,24 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
-QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
-ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
-NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
-YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
-MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
-ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
-1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
-by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
-6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
-8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
-BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
-aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
-Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
-aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
-ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
-bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
-PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
-gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
-PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
-IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
-t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/f90208f7.0 b/src/cobalt/content/ssl/certs/f90208f7.0
deleted file mode 100644
index 60d878e..0000000
--- a/src/cobalt/content/ssl/certs/f90208f7.0
+++ /dev/null
@@ -1,24 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
-QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
-ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
-NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
-cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
-MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
-AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
-xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
-NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
-DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
-d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
-EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
-cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
-AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
-bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
-VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
-aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
-fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
-L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
-UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
-ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
-erfutGWaIZDgqtCYvDi1czyL+Nw=
------END CERTIFICATE-----
diff --git a/src/cobalt/cssom/css_computed_style_data.h b/src/cobalt/cssom/css_computed_style_data.h
index 88d90a6..1d0dc90 100644
--- a/src/cobalt/cssom/css_computed_style_data.h
+++ b/src/cobalt/cssom/css_computed_style_data.h
@@ -33,7 +33,8 @@
 
 // CSSComputedStyleData which has PropertyValue type properties only used
 // internally and it is not exposed to JavaScript.
-class CSSComputedStyleData : public base::RefCounted<CSSComputedStyleData> {
+class CSSComputedStyleData
+    : public base::RefCountedThreadSafe<CSSComputedStyleData> {
  public:
   // This class provides the ability to determine whether the properties of two
   // CSSComputedStyleData objects match for a given set of property keys.
diff --git a/src/cobalt/cssom/selector_tree.cc b/src/cobalt/cssom/selector_tree.cc
index 011032f..1a32266 100644
--- a/src/cobalt/cssom/selector_tree.cc
+++ b/src/cobalt/cssom/selector_tree.cc
@@ -183,7 +183,7 @@
 SelectorTree::Node* SelectorTree::GetOrCreateNodeForComplexSelector(
     ComplexSelector* complex_selector) {
   CompoundSelector* selector = complex_selector->first_selector();
-  Node* node = GetOrCreateNodeForCompoundSelector(selector, &root_,
+  Node* node = GetOrCreateNodeForCompoundSelector(selector, &root_node_,
                                                   kDescendantCombinator);
 
   while (selector->right_combinator()) {
diff --git a/src/cobalt/cssom/selector_tree.h b/src/cobalt/cssom/selector_tree.h
index 10f1545..98f33f6 100644
--- a/src/cobalt/cssom/selector_tree.h
+++ b/src/cobalt/cssom/selector_tree.h
@@ -43,84 +43,12 @@
 // https://docs.google.com/document/d/1LTbSenGsGR94JTGg6DfZDXYB3MBBCp4C8dRC4rckt_8/
 class SelectorTree {
  public:
-  typedef std::vector<base::WeakPtr<CSSStyleRule> > Rules;
-
   class Node;
 
-  // This class can be used to store Nodes.  It stores the Nodes in its
-  // internal buffer whose size can be configured via template parameter.
-  // After the internal buffer is used up the extra Nodes will be stored inside
-  // the contained std::vector.
-  // TODO: Move this off to its own file if this can also be used by
-  // other code.
-  template <size_t InternalCacheSize>
-  class NodeSet {
-   public:
-    // Minimum interface for iterator.
-    class const_iterator {
-     public:
-      const_iterator(const NodeSet* set, size_t index)
-          : set_(set), index_(index) {}
-      void operator++() { ++index_; }
-      const Node* operator*() const { return set_->GetNode(index_); }
-      bool operator!=(const const_iterator& that) const {
-        return set_ != that.set_ || index_ != that.index_;
-      }
+  typedef std::vector<base::WeakPtr<CSSStyleRule>> Rules;
 
-     private:
-      const NodeSet* set_;
-      size_t index_;
-    };
-
-    NodeSet() : size_(0) {}
-    void insert(const Node* node, bool check_for_duplicate = false) {
-      // If |check_for_duplicate| is true, then check if the node is already
-      // contained. In nearly all cases, this check is unnecessary because it is
-      // already known that the node is not a duplicate. As a result, the caller
-      // must explicitly request the check when needed.
-      if (check_for_duplicate) {
-        for (size_t i = 0; i < size_; ++i) {
-          if (GetNode(i) == node) {
-            return;
-          }
-        }
-      }
-
-      if (size_ < InternalCacheSize) {
-        nodes_[size_] = node;
-      } else {
-        nodes_vector_.push_back(node);
-      }
-      ++size_;
-    }
-    template <class ConstIterator>
-    void insert(ConstIterator begin, ConstIterator end,
-                bool check_for_duplicate = false) {
-      while (begin != end) {
-        insert(*begin, check_for_duplicate);
-        ++begin;
-      }
-    }
-
-    const_iterator begin() const { return const_iterator(this, 0); }
-    const_iterator end() const { return const_iterator(this, size_); }
-    size_t size() const { return size_; }
-    void clear() {
-      size_ = 0;
-      nodes_vector_.clear();
-    }
-    const Node* GetNode(size_t index) const {
-      if (index < InternalCacheSize) {
-        return nodes_[index];
-      }
-      return nodes_vector_[index - InternalCacheSize];
-    }
-
-   private:
-    size_t size_;
-    const Node* nodes_[InternalCacheSize];
-    std::vector<const Node*> nodes_vector_;
-  };
+  typedef std::vector<const Node*> Nodes;
+  typedef std::vector<std::pair<const Node*, const Node*>> NodePairs;
 
   struct CompoundNodeLessThan {
     bool operator()(const CompoundSelector* lhs,
@@ -255,10 +183,16 @@
     Rules rules_;
   };
 
-  SelectorTree() : has_sibling_combinators_(false) { root_set_.insert(&root_); }
+  SelectorTree() : has_sibling_combinators_(false) {
+    root_nodes_.push_back(&root_node_);
+  }
 
-  const Node* root() const { return &root_; }
-  const NodeSet<1>& root_set() const { return root_set_; }
+  const Node* root_node() const { return &root_node_; }
+  const Nodes& root_nodes() const { return root_nodes_; }
+
+  Nodes* scratchpad_nodes_1() { return &scratchpad_nodes_1_; }
+  Nodes* scratchpad_nodes_2() { return &scratchpad_nodes_2_; }
+  NodePairs* scratchpad_node_pairs() { return &scratchpad_node_pairs_; }
 
   bool has_sibling_combinators() const { return has_sibling_combinators_; }
 
@@ -284,8 +218,15 @@
                                            Node* parent_node,
                                            CombinatorType combinator);
 
-  Node root_;
-  NodeSet<1> root_set_;
+  Node root_node_;
+  Nodes root_nodes_;
+
+  // These member variables are available for temporary operations, so that
+  // Node vectors associated with the SelectorTree don't have to be repeatedly
+  // re-allocated. This significantly speeds up rule matching.
+  Nodes scratchpad_nodes_1_;
+  Nodes scratchpad_nodes_2_;
+  NodePairs scratchpad_node_pairs_;
 
   // This variable maps from a parent Node and a combinator type to child Nodes
   // under the particular parent Node corresponding to the particular combinator
diff --git a/src/cobalt/cssom/selector_tree_test.cc b/src/cobalt/cssom/selector_tree_test.cc
index 304f5c9..22560dd 100644
--- a/src/cobalt/cssom/selector_tree_test.cc
+++ b/src/cobalt/cssom/selector_tree_test.cc
@@ -26,15 +26,17 @@
 TEST(SelectorTreeTest, RootShouldHaveNoChildrenAfterInitialization) {
   SelectorTree selector_tree;
   EXPECT_TRUE(
-      selector_tree.children(selector_tree.root(), kChildCombinator).empty());
-  EXPECT_TRUE(
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kChildCombinator)
           .empty());
   EXPECT_TRUE(
-      selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .empty());
   EXPECT_TRUE(
-      selector_tree.children(selector_tree.root(), kFollowingSiblingCombinator)
+      selector_tree.children(selector_tree.root_node(), kNextSiblingCombinator)
+          .empty());
+  EXPECT_TRUE(
+      selector_tree
+          .children(selector_tree.root_node(), kFollowingSiblingCombinator)
           .empty());
 }
 
@@ -56,20 +58,22 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_TRUE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(1,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree.children(selector_tree.root(),
-                                      kFollowingSiblingCombinator)
+  ASSERT_EQ(1, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
 
   const SelectorTree::Node* node_1 =
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .begin()
           ->second;
   ASSERT_EQ(1, node_1->rules().size());
@@ -102,17 +106,19 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_TRUE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(1,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree.children(selector_tree.root(),
-                                      kFollowingSiblingCombinator)
+  ASSERT_EQ(1, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
 }
 
 TEST(SelectorTreeTest, AppendRuleSimpleShouldTakeTwoIdenticalRules) {
@@ -138,19 +144,21 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_TRUE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(1,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree.children(selector_tree.root(),
-                                      kFollowingSiblingCombinator)
+  ASSERT_EQ(1, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
   const SelectorTree::Node* node_1 =
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .begin()
           ->second;
   ASSERT_EQ(2, node_1->rules().size());
@@ -184,19 +192,21 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_TRUE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(1,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree.children(selector_tree.root(),
-                                      kFollowingSiblingCombinator)
+  ASSERT_EQ(1, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
   const SelectorTree::Node* node_1 =
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .begin()
           ->second;
   ASSERT_EQ(1, node_1->rules().size());
@@ -246,19 +256,21 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_FALSE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(2,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree
-                   .children(selector_tree.root(), kFollowingSiblingCombinator)
+  ASSERT_EQ(2, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
   auto node_iter =
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .begin();
   const SelectorTree::Node* node_1 = node_iter->second;
   ASSERT_EQ(1, node_1->rules().size());
@@ -297,19 +309,21 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_TRUE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(2,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree
-                   .children(selector_tree.root(), kFollowingSiblingCombinator)
+  ASSERT_EQ(2, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
   auto node_iter =
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .begin();
   const SelectorTree::Node* node_1 = node_iter->second;
   ASSERT_EQ(1, node_1->rules().size());
@@ -347,19 +361,21 @@
   base::VersionCompatibility::GetInstance()->SetMinimumVersion(1);
   EXPECT_TRUE(selector_tree.ValidateVersionCompatibility());
 
-  ASSERT_EQ(
-      0, selector_tree.children(selector_tree.root(), kChildCombinator).size());
-  ASSERT_EQ(1,
-            selector_tree.children(selector_tree.root(), kDescendantCombinator)
-                .size());
   ASSERT_EQ(0,
-            selector_tree.children(selector_tree.root(), kNextSiblingCombinator)
+            selector_tree.children(selector_tree.root_node(), kChildCombinator)
                 .size());
-  ASSERT_EQ(0, selector_tree
-                   .children(selector_tree.root(), kFollowingSiblingCombinator)
+  ASSERT_EQ(1, selector_tree
+                   .children(selector_tree.root_node(), kDescendantCombinator)
                    .size());
+  ASSERT_EQ(0, selector_tree
+                   .children(selector_tree.root_node(), kNextSiblingCombinator)
+                   .size());
+  ASSERT_EQ(
+      0, selector_tree
+             .children(selector_tree.root_node(), kFollowingSiblingCombinator)
+             .size());
   const SelectorTree::Node* node_1 =
-      selector_tree.children(selector_tree.root(), kDescendantCombinator)
+      selector_tree.children(selector_tree.root_node(), kDescendantCombinator)
           .begin()
           ->second;
   ASSERT_EQ(2, node_1->rules().size());
diff --git a/src/cobalt/debug/debug_web_server.cc b/src/cobalt/debug/debug_web_server.cc
index b87a5fe..6749690 100644
--- a/src/cobalt/debug/debug_web_server.cc
+++ b/src/cobalt/debug/debug_web_server.cc
@@ -169,6 +169,11 @@
 
   // Construct the local disk path corresponding to the request path.
   FilePath file_path(content_root_dir_);
+  if (!IsStringASCII(url_path)) {
+    LOG(WARNING) << "Got HTTP request with non-ASCII URL path.";
+    server_->Send404(connection_id);
+    return;
+  }
   file_path = file_path.AppendASCII(url_path);
 
   // If the disk path is a directory, look for an index file.
diff --git a/src/cobalt/doc/performance_tuning.md b/src/cobalt/doc/performance_tuning.md
index a142028..a3aa232 100644
--- a/src/cobalt/doc/performance_tuning.md
+++ b/src/cobalt/doc/performance_tuning.md
@@ -12,7 +12,9 @@
 
 Many of the tweaks involve adding a new gyp variable to your platform's
 `gyp_configuration.gypi` file.  The default values for these variables are
-defined in [`base.gypi`](../build/config/base.gypi).
+defined in either
+[`base_configuration.gypi`](../../starboard/build/base_configuration.gypi) or
+[`cobalt_configuration.gypi`](../build/cobalt_configuration.gypi).
 
 ### Use a Release Build
 
@@ -182,7 +184,8 @@
 
 ### Try enabling rendering only to regions that change
 
-If you set the [`base.gypi`](../build/config/base.gypi) variable,
+If you set the
+[`cobalt_configuration.gypi`](../build/cobalt_configuration.gypi) variable,
 `render_dirty_region_only` to `1`, then Cobalt will invoke logic to detect which
 part of the frame has been affected by animations and can be configured to only
 render to that region.  However, this feature requires support from the driver
@@ -232,11 +235,17 @@
 flags will carry over from external shell environment settings; they
 must be set explicitly in `gyp_configuration.gypi`.
 
+**Tags:** *framerate, startup, browse-to-watch, input latency*
+
+
 #### Link Time Optimization (LTO)
 If your toolchain supports it, it is recommended that you enable the LTO
 optimization, as it has been reported to yield significant performance
 improvements in many high profile projects.
 
+**Tags:** *framerate, startup, browse-to-watch, input latency*
+
+
 #### The GCC '-mplt' flag for MIPS architectures
 The '-mplt' flag has been found to improve all around performance by
 ~20% on MIPS architecture platforms.  If your platform has a MIPS
@@ -305,6 +314,19 @@
 **Tags:** *configuration_public.h, cpu memory.*
 
 
+### Adjust media buffer size settings
+
+Many of the parameters around media buffer allocation can be adjusted in your
+gyp_configuration.gypi file.  The variables in question are the family of
+`cobalt_media_*` variables, whose default values are specified in
+[`cobalt_configuration.gypi`](../build/cobalt_configuration.gypi).  In
+particular, if your maximum video output resolution is less than 1080, then you
+may lower the budgets for many of the categories according to your maximum
+resolution.
+
+**Tags:** *cpu memory*
+
+
 ### Avoid using a the YouTube web app FPS counter (i.e. "?fps=1")
 
 The YouTube web app is able to display a Frames Per Second (FPS) counter in the
diff --git a/src/cobalt/dom/base64.cc b/src/cobalt/dom/base64.cc
new file mode 100644
index 0000000..c47318c
--- /dev/null
+++ b/src/cobalt/dom/base64.cc
@@ -0,0 +1,150 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/base64.h"
+
+#include "base/base64.h"
+
+namespace cobalt {
+namespace dom {
+
+namespace {
+// https://infra.spec.whatwg.org/#ascii-whitespace
+const char kAsciiWhitespace[] = {0x09, 0x0a, 0x0c, 0x0d, 0x20};
+
+bool IsAtobAllowedChar(char c) {
+  // atob() allows '+', '/', and ASCII alphanumeric characters.
+  // https://infra.spec.whatwg.org/#forgiving-base64-decode
+  if (c == '+') {
+    return true;
+  }
+  if (c < '/') {
+    return false;
+  }
+  if (c <= '9') {
+    return true;
+  }
+  if (c < 'A') {
+    return false;
+  }
+  if (c <= 'Z') {
+    return true;
+  }
+  if (c < 'a') {
+    return false;
+  }
+  if (c <= 'z') {
+    return true;
+  }
+  return false;
+}
+
+bool IsNotAsciiWhitespace(char c) {
+  for (char sp_char : kAsciiWhitespace) {
+    if (c == sp_char) {
+      return false;
+    }
+  }
+  return true;
+}
+
+base::optional<std::string> GetAtobAllowedStr(const std::string& input_str) {
+  // Our base64 decoding method does not check for forbidden characters. We use
+  // the following method to reject string containing disallowed character or
+  // format in atob().
+  // Step 1-4 of https://infra.spec.whatwg.org/#forgiving-base64-decode
+  std::string output_str;
+  // Step 1: Remove all ASCII whitespace from data.
+  std::copy_if(input_str.begin(), input_str.end(),
+               std::back_inserter(output_str), IsNotAsciiWhitespace);
+  // Step 2: If data's length divides by 4 leaving no remainder, then:
+  //        1. if data ends with one or two U+003D (=) code points, then
+  //           remove them from data.
+  if (!output_str.empty() && output_str.length() % 4 == 0 &&
+      output_str.back() == '=') {
+    output_str.pop_back();
+    if (output_str.back() == '=') {
+      output_str.pop_back();
+    }
+  }
+  // Step 3: If data's length divides by 4 leaving a remainder of 1, return
+  // failure.
+  if (output_str.length() % 4 == 1) {
+    return base::nullopt;
+  }
+  // Step 4: If data contains a code point that is not allowed, return failure.
+  for (char current_char : output_str) {
+    if (!IsAtobAllowedChar(current_char)) {
+      return base::nullopt;
+    }
+  }
+  // Customized step: add padding to string less than 4 character.
+  for (size_t i = output_str.length() % 4; i > 1 && i < 4; i++) {
+    output_str.push_back('=');
+  }
+  return output_str;
+}
+
+base::optional<std::string> Utf8ToLatin1(const std::string& input) {
+  std::string output;
+  unsigned char current_char_remainder = 0x00;
+  for (unsigned char c : input) {
+    if (c <= 0x7f && !current_char_remainder) {
+      output.push_back(c);
+    } else if (c <= 0xbf && current_char_remainder) {
+      // This is the only byte or second byte of one character.
+      output.push_back(current_char_remainder | (c & 0x3f));
+      current_char_remainder = 0x00;
+    } else if (c == 0xc2 && !current_char_remainder) {
+      current_char_remainder = c & 0x80;
+    } else if (c == 0xc3 && !current_char_remainder) {
+      current_char_remainder = 0xc0;
+    } else {
+      return base::nullopt;
+    }
+  }
+  return output;
+}
+}  // namespace
+
+base::optional<std::string> ForgivingBase64Encode(
+    const std::string& string_to_encode) {
+  // https://infra.spec.whatwg.org/#forgiving-base64-encode
+  auto maybe_string_to_encode_in_latin1 = Utf8ToLatin1(string_to_encode);
+  std::string output;
+  if (!maybe_string_to_encode_in_latin1 ||
+      !base::Base64Encode(*maybe_string_to_encode_in_latin1, &output)) {
+    return base::nullopt;
+  }
+  return output;
+}
+
+base::optional<std::vector<uint8_t>> ForgivingBase64Decode(
+    const std::string& encoded_string) {
+  // https://infra.spec.whatwg.org/#forgiving-base64-decode
+  // Step 1-4:
+  auto maybe_encoded_string_no_whitespace = GetAtobAllowedStr(encoded_string);
+  // Step 5-10:
+  std::vector<uint8_t> output;
+  // If input string format is not allowed or base64 encoding failed, return
+  // nullopt to signal failure.
+  if (!maybe_encoded_string_no_whitespace ||
+      !base::Base64Decode(*maybe_encoded_string_no_whitespace, &output)) {
+    return base::nullopt;
+  }
+  return output;
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/base64.h b/src/cobalt/dom/base64.h
new file mode 100644
index 0000000..7e46d34
--- /dev/null
+++ b/src/cobalt/dom/base64.h
@@ -0,0 +1,37 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_BASE64_H_
+#define COBALT_DOM_BASE64_H_
+
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+
+namespace cobalt {
+namespace dom {
+
+// https://infra.spec.whatwg.org/#forgiving-base64-encode
+base::optional<std::string> ForgivingBase64Encode(
+    const std::string& string_to_encode);
+
+// https://infra.spec.whatwg.org/#forgiving-base64-decode
+base::optional<std::vector<uint8_t>> ForgivingBase64Decode(
+    const std::string& encoded_string);
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_BASE64_H_
diff --git a/src/cobalt/dom/blob.cc b/src/cobalt/dom/blob.cc
index cbcb039..e448cf8 100644
--- a/src/cobalt/dom/blob.cc
+++ b/src/cobalt/dom/blob.cc
@@ -72,19 +72,22 @@
 // so the media type can be exposed and used, as described in:
 //    https://www.w3.org/TR/FileAPI/#constructorBlob
 Blob::Blob(script::EnvironmentSettings* settings,
-           script::Sequence<BlobPart> blobParts, const BlobPropertyBag& options)
+           const script::Sequence<BlobPart>& blob_parts,
+           const BlobPropertyBag& options)
     : buffer_(new ArrayBuffer(settings, 0)), type_(options.type()) {
   size_t byte_length = 0;
-  for (script::Sequence<BlobPart>::size_type i = 0; i < blobParts.size(); i++) {
-    byte_length += DataLength(blobParts.at(i));
+  for (script::Sequence<BlobPart>::size_type i = 0; i < blob_parts.size();
+       ++i) {
+    byte_length += DataLength(blob_parts.at(i));
   }
   buffer_ = new ArrayBuffer(settings, static_cast<uint32>(byte_length));
 
   uint8* destination = buffer_->data();
   size_t offset = 0;
-  for (script::Sequence<BlobPart>::size_type i = 0; i < blobParts.size(); i++) {
-    const uint8* source = DataStart(blobParts.at(i));
-    uint64 count = DataLength(blobParts.at(i));
+  for (script::Sequence<BlobPart>::size_type i = 0; i < blob_parts.size();
+       ++i) {
+    const uint8* source = DataStart(blob_parts.at(i));
+    uint64 count = DataLength(blob_parts.at(i));
 
     std::copy(source, source + count, destination + offset);
     offset += count;
diff --git a/src/cobalt/dom/blob.h b/src/cobalt/dom/blob.h
index c38e0a3..577f898 100644
--- a/src/cobalt/dom/blob.h
+++ b/src/cobalt/dom/blob.h
@@ -52,7 +52,7 @@
        const scoped_refptr<ArrayBuffer>& buffer = NULL);
 
   Blob(script::EnvironmentSettings* settings,
-       script::Sequence<BlobPart> blob_parts,
+       const script::Sequence<BlobPart>& blob_parts,
        const BlobPropertyBag& options = EmptyBlobPropertyBag());
 
   const uint8* data() { return buffer_->data(); }
diff --git a/src/cobalt/dom/custom_event_test.cc b/src/cobalt/dom/custom_event_test.cc
index cbdc79d..cfc1569 100644
--- a/src/cobalt/dom/custom_event_test.cc
+++ b/src/cobalt/dom/custom_event_test.cc
@@ -19,6 +19,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/css_parser/parser.h"
 #include "cobalt/dom/custom_event_init.h"
 #include "cobalt/dom/local_storage_database.h"
@@ -26,6 +27,7 @@
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
@@ -56,26 +58,28 @@
         css_parser_(css_parser::Parser::Create()),
         dom_parser_(new dom_parser::Parser(mock_error_callback_)),
         fetcher_factory_(new loader::FetcherFactory(NULL)),
+        loader_factory_(new loader::LoaderFactory(
+            fetcher_factory_.get(), NULL, base::kThreadPriority_Default)),
         local_storage_database_(NULL),
-        url_("about:blank"),
-        window_(new Window(
-            1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
-            dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
-            NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL,
-            NULL, NULL, url_, "", "en-US", "en",
-            base::Callback<void(const GURL&)>(),
-            base::Bind(&MockErrorCallback::Run,
-                       base::Unretained(&mock_error_callback_)),
-            NULL, network_bridge::PostSender(), csp::kCSPRequired,
-            kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
-            base::Closure() /* ran_animation_frame_callbacks */,
-            dom::Window::CloseCallback() /* window_close */,
-            base::Closure() /* window_minimize */, NULL, NULL, NULL,
-            dom::Window::OnStartDispatchEventCallback(),
-            dom::Window::OnStopDispatchEventCallback(),
-            dom::ScreenshotManager::ProvideScreenshotFunctionCallback())) {
+        url_("about:blank") {
     engine_ = script::JavaScriptEngine::CreateEngine();
     global_environment_ = engine_->CreateGlobalEnvironment();
+    window_ = new Window(
+        1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
+        dom_parser_.get(), fetcher_factory_.get(), loader_factory_.get(), NULL,
+        NULL, NULL, NULL, NULL, NULL, &local_storage_database_, NULL, NULL,
+        NULL, NULL, global_environment_->script_value_factory(), NULL, NULL,
+        url_, "", "en-US", "en", base::Callback<void(const GURL&)>(),
+        base::Bind(&MockErrorCallback::Run,
+                   base::Unretained(&mock_error_callback_)),
+        NULL, network_bridge::PostSender(), csp::kCSPRequired,
+        kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
+        base::Closure() /* ran_animation_frame_callbacks */,
+        dom::Window::CloseCallback() /* window_close */,
+        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        dom::Window::OnStartDispatchEventCallback(),
+        dom::Window::OnStopDispatchEventCallback(),
+        dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
     global_environment_->CreateGlobalObject(window_,
                                             environment_settings_.get());
   }
@@ -92,9 +96,10 @@
   scoped_ptr<css_parser::Parser> css_parser_;
   scoped_ptr<dom_parser::Parser> dom_parser_;
   scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+  scoped_ptr<loader::LoaderFactory> loader_factory_;
   dom::LocalStorageDatabase local_storage_database_;
   GURL url_;
-  const scoped_refptr<Window> window_;
+  scoped_refptr<Window> window_;
 };
 
 bool CustomEventTest::EvaluateScript(const std::string& js_code,
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index 08c0146..805c8d8 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -92,6 +92,7 @@
       user_agent_style_sheet_(options.user_agent_style_sheet),
       initial_computed_style_declaration_(
           new cssom::CSSComputedStyleDeclaration()),
+      ready_state_(kDocumentReadyStateComplete),
       dom_max_element_depth_(options.dom_max_element_depth),
       render_postponed_(false) {
   DCHECK(html_element_context_);
@@ -499,12 +500,22 @@
 
 void Document::SetIndicatedElement(HTMLElement* indicated_element) {
   if (indicated_element != indicated_element_) {
-    is_selector_tree_dirty_ = true;
     if (indicated_element_) {
+      // Clear the rule matching state on this element and its ancestors, as
+      // their hover state may be changing. However, the tree's matching rules
+      // only need to be invalidated once, so only do it here if it won't occur
+      // below.
+      bool invalidate_tree_matching_rules = (indicated_element == NULL);
+      indicated_element_->ClearRuleMatchingStateOnElementAndAncestors(
+          invalidate_tree_matching_rules);
       indicated_element_->OnCSSMutation();
     }
     if (indicated_element) {
       indicated_element_ = base::AsWeakPtr(indicated_element);
+      // Clear the rule matching state on this element and its ancestors, as
+      // their hover state may be changing.
+      indicated_element_->ClearRuleMatchingStateOnElementAndAncestors(
+          true /*invalidate_tree_matching_rules*/);
       indicated_element_->OnCSSMutation();
     } else {
       indicated_element_.reset();
@@ -610,7 +621,7 @@
 
 void Document::OnDOMMutation() {
   // Something in the document's DOM has been modified, but we don't know what,
-  // so set the flag indicating that rule matching needs to be done.
+  // so set the flag indicating that computed styles need to be updated.
   is_computed_style_dirty_ = true;
 
   RecordMutation();
@@ -719,12 +730,15 @@
     scoped_refptr<HTMLElement> root = html();
     if (root) {
       DCHECK_EQ(this, root->parent_node());
-      // First update the computed style for root element.
+      // First, update the matching rules for all elements.
+      root->UpdateMatchingRulesRecursively();
+
+      // Then, update the computed style for the root element.
       root->UpdateComputedStyle(
           initial_computed_style_declaration_, initial_computed_style_data_,
           style_change_event_time, HTMLElement::kAncestorsAreDisplayed);
 
-      // Then update the computed styles for the other elements.
+      // Finally, update the computed styles for the other elements.
       root->UpdateComputedStyleRecursively(
           root->css_computed_style_declaration(), root->computed_style(),
           style_change_event_time, true, 0 /* current_element_depth */);
@@ -780,9 +794,13 @@
   for (std::vector<HTMLElement*>::reverse_iterator it = ancestors.rbegin();
        it != ancestors.rend(); ++it) {
     HTMLElement* current_element = *it;
-    bool is_valid = ancestors_were_valid &&
-                    current_element->matching_rules_valid() &&
-                    current_element->computed_style_valid();
+
+    // Ensure that the matching rules are up to date prior to updating the
+    // computed style.
+    current_element->UpdateMatchingRules();
+
+    bool is_valid =
+        ancestors_were_valid && current_element->AreComputedStylesValid();
     if (!is_valid) {
       DCHECK(initial_computed_style_declaration_);
       DCHECK(initial_computed_style_data_);
@@ -920,7 +938,7 @@
 
     scoped_refptr<HTMLHtmlElement> current_html = html();
     if (current_html) {
-      current_html->InvalidateMatchingRulesRecursively();
+      current_html->ClearRuleMatchingStateOnElementAndDescendants();
     }
 
     is_selector_tree_dirty_ = false;
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h
index ffca957..9a66a2b 100644
--- a/src/cobalt/dom/document.h
+++ b/src/cobalt/dom/document.h
@@ -26,6 +26,7 @@
 #include "base/observer_list.h"
 #include "base/optional.h"
 #include "base/string_piece.h"
+#include "base/synchronization/waitable_event.h"
 #include "cobalt/base/clock.h"
 #include "cobalt/cssom/css_computed_style_declaration.h"
 #include "cobalt/cssom/css_keyframes_rule.h"
@@ -261,6 +262,14 @@
 
   cssom::SelectorTree* selector_tree() { return selector_tree_.get(); }
 
+  cssom::RulesWithCascadePrecedence* scratchpad_html_element_matching_rules() {
+    return &scratchpad_html_element_matching_rules_;
+  }
+  cssom::RulesWithCascadePrecedence* scratchpad_pseudo_element_matching_rules(
+      PseudoElementType element_type) {
+    return &(scratchpad_pseudo_element_matching_rules_[element_type]);
+  }
+
   // Returns a mapping from keyframes name to CSSKeyframesRule.  This can be
   // used to quickly lookup the @keyframes rule given a string identifier.
   const cssom::CSSKeyframesRule::NameMap& keyframes_map() const {
@@ -497,6 +506,11 @@
   // recreate the selector tree than to attempt to manage updating all of its
   // internal state.
   bool should_recreate_selector_tree_;
+  // Matching rules that are available for temporary operations, so that the
+  // vectors don't have to be repeatedly re-allocated during rule matching.
+  cssom::RulesWithCascadePrecedence scratchpad_html_element_matching_rules_;
+  cssom::RulesWithCascadePrecedence
+      scratchpad_pseudo_element_matching_rules_[kMaxPseudoElementType];
   // The document's latest sample from the global clock, used for updating
   // animations.
   const scoped_refptr<base::Clock> navigation_start_clock_;
diff --git a/src/cobalt/dom/document_test.cc b/src/cobalt/dom/document_test.cc
index e0b8462..5cfc674 100644
--- a/src/cobalt/dom/document_test.cc
+++ b/src/cobalt/dom/document_test.cc
@@ -68,10 +68,10 @@
 DocumentTest::DocumentTest()
     : css_parser_(css_parser::Parser::Create()),
       dom_stat_tracker_(new DomStatTracker("DocumentTest")),
-      html_element_context_(NULL, css_parser_.get(), NULL, NULL, NULL, NULL,
+      html_element_context_(NULL, NULL, css_parser_.get(), NULL, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                            dom_stat_tracker_.get(), "",
-                            base::kApplicationStateStarted) {
+                            NULL, dom_stat_tracker_.get(), "",
+                            base::kApplicationStateStarted, NULL) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
 }
 
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 793fa82..4f0154c 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -32,6 +32,8 @@
         'attr.h',
         'audio_track.h',
         'audio_track_list.h',
+        'base64.cc',
+        'base64.h',
         'benchmark_stat_names.cc',
         'benchmark_stat_names.h',
         'blob.cc',
@@ -100,7 +102,6 @@
         'event.cc',
         'event.h',
         'event_init.h',
-        'event_listener.cc',
         'event_listener.h',
         'event_queue.cc',
         'event_queue.h',
@@ -119,6 +120,8 @@
         'font_face_updater.h',
         'font_list.cc',
         'font_list.h',
+        'generic_event_handler_reference.cc',
+        'generic_event_handler_reference.h',
         'global_stats.cc',
         'global_stats.h',
         'history.cc',
@@ -215,6 +218,8 @@
         'node_list.h',
         'node_list_live.cc',
         'node_list_live.h',
+        'on_error_event_listener.cc',
+        'on_error_event_listener.h',
         'on_screen_keyboard.cc',
         'on_screen_keyboard.h',
         'on_screen_keyboard_bridge.h',
@@ -243,6 +248,8 @@
         'screenshot.h',
         'screenshot_manager.cc',
         'screenshot_manager.h',
+        'script_event_log.cc',
+        'script_event_log.h',
         'security_policy_violation_event.cc',
         'security_policy_violation_event.h',
         'serializer.cc',
@@ -302,6 +309,7 @@
         '<(DEPTH)/cobalt/network_bridge/network_bridge.gyp:network_bridge',
         '<(DEPTH)/cobalt/page_visibility/page_visibility.gyp:page_visibility',
         '<(DEPTH)/cobalt/script/script.gyp:script',
+        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
         '<(DEPTH)/cobalt/storage/storage.gyp:storage',
         '<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
         '<(DEPTH)/cobalt/web_animations/web_animations.gyp:web_animations',
@@ -373,25 +381,5 @@
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
       ],
     },
-
-    {
-      'target_name': 'dom_testing',
-      'type': 'static_library',
-      'sources': [
-        'testing/gtest_workarounds.h',
-        'testing/html_collection_testing.h',
-        'testing/mock_event_listener.h',
-        'testing/stub_css_parser.cc',
-        'testing/stub_css_parser.h',
-        'testing/stub_script_runner.cc',
-        'testing/stub_script_runner.h',
-        'testing/stub_window.h',
-      ],
-      'dependencies': [
-        # TODO: Remove the dependency below, it works around the fact that
-        #       ScriptValueFactory has non-virtual method CreatePromise().
-        '<(DEPTH)/cobalt/script/engine.gyp:engine',
-      ],
-    },
   ],
 }
diff --git a/src/cobalt/dom/dom_exception.cc b/src/cobalt/dom/dom_exception.cc
index a1ec7fb..5eb35b4 100644
--- a/src/cobalt/dom/dom_exception.cc
+++ b/src/cobalt/dom/dom_exception.cc
@@ -49,6 +49,8 @@
       return "ReadOnlyError";
     case DOMException::kInvalidPointerIdErr:
       return "InvalidPointerId";
+    case DOMException::kNotAllowedErr:
+      return "NotAllowedError";
   }
   NOTREACHED();
   return "";
diff --git a/src/cobalt/dom/dom_exception.h b/src/cobalt/dom/dom_exception.h
index 59f3919..faf85cb 100644
--- a/src/cobalt/dom/dom_exception.h
+++ b/src/cobalt/dom/dom_exception.h
@@ -46,7 +46,8 @@
     kQuotaExceededErr = 22,
     kHighestErrCodeValue = kQuotaExceededErr,
     kReadOnlyErr,
-    kInvalidPointerIdErr
+    kInvalidPointerIdErr,
+    kNotAllowedErr
   };
 
   explicit DOMException(ExceptionCode code);
diff --git a/src/cobalt/dom/dom_parser_test.cc b/src/cobalt/dom/dom_parser_test.cc
index 7e18c40..9601bd6 100644
--- a/src/cobalt/dom/dom_parser_test.cc
+++ b/src/cobalt/dom/dom_parser_test.cc
@@ -14,6 +14,7 @@
 
 #include <string>
 
+#include "base/threading/platform_thread.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_parser.h"
 #include "cobalt/dom/html_element_context.h"
@@ -21,6 +22,7 @@
 #include "cobalt/dom/testing/stub_script_runner.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -32,6 +34,7 @@
   ~DOMParserTest() override {}
 
   loader::FetcherFactory fetcher_factory_;
+  loader::LoaderFactory loader_factory_;
   testing::StubCSSParser stub_css_parser_;
   scoped_ptr<dom_parser::Parser> dom_parser_parser_;
   testing::StubScriptRunner stub_script_runner_;
@@ -41,17 +44,21 @@
 
 DOMParserTest::DOMParserTest()
     : fetcher_factory_(NULL /* network_module */),
+      loader_factory_(&fetcher_factory_, NULL /* resource provider */,
+                      base::kThreadPriority_Default),
       dom_parser_parser_(new dom_parser::Parser()),
       html_element_context_(
-          &fetcher_factory_, &stub_css_parser_, dom_parser_parser_.get(),
-          NULL /* can_play_type_handler */, NULL /* web_media_player_factory */,
-          &stub_script_runner_, NULL /* script_value_factory */,
-          NULL /* media_source_registry */, NULL /* resource_provider */,
-          NULL /* animated_image_tracker */, NULL /* image_cache */,
+          &fetcher_factory_, &loader_factory_, &stub_css_parser_,
+          dom_parser_parser_.get(), NULL /* can_play_type_handler */,
+          NULL /* web_media_player_factory */, &stub_script_runner_,
+          NULL /* script_value_factory */, NULL /* media_source_registry */,
+          NULL /* resource_provider */, NULL /* animated_image_tracker */,
+          NULL /* image_cache */,
           NULL /* reduced_image_cache_capacity_manager */,
           NULL /* remote_typeface_cache */, NULL /* mesh_cache */,
           NULL /* dom_stat_tracker */, "" /* language */,
-          base::kApplicationStateStarted),
+          base::kApplicationStateStarted,
+          NULL /* synchronous_loader_interrupt */),
       dom_parser_(new DOMParser(&html_element_context_)) {}
 
 TEST_F(DOMParserTest, ParsesXML) {
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 3c0b534..5ab6948 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -75,7 +75,7 @@
         '<(DEPTH)/cobalt/browser/browser.gyp:browser',
         '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/dom/dom.gyp:dom_testing',
+        '<(DEPTH)/cobalt/dom/testing/dom_testing.gyp:dom_testing',
         '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
         '<(DEPTH)/cobalt/loader/loader.gyp:loader',
         '<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia',
diff --git a/src/cobalt/dom/element_test.cc b/src/cobalt/dom/element_test.cc
index 1932267..5c0be93 100644
--- a/src/cobalt/dom/element_test.cc
+++ b/src/cobalt/dom/element_test.cc
@@ -60,10 +60,10 @@
     : css_parser_(css_parser::Parser::Create()),
       dom_parser_(new dom_parser::Parser()),
       dom_stat_tracker_(new DomStatTracker("ElementTest")),
-      html_element_context_(NULL, css_parser_.get(), dom_parser_.get(), NULL,
+      html_element_context_(NULL, NULL, css_parser_.get(), dom_parser_.get(),
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                            NULL, NULL, dom_stat_tracker_.get(), "",
-                            base::kApplicationStateStarted) {
+                            NULL, NULL, NULL, dom_stat_tracker_.get(), "",
+                            base::kApplicationStateStarted, NULL) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
   document_ = new Document(&html_element_context_);
   xml_document_ = new XMLDocument(&html_element_context_);
diff --git a/src/cobalt/dom/eme/eme_helpers.h b/src/cobalt/dom/eme/eme_helpers.h
new file mode 100644
index 0000000..383e043
--- /dev/null
+++ b/src/cobalt/dom/eme/eme_helpers.h
@@ -0,0 +1,62 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_EME_EME_HELPERS_H_
+#define COBALT_DOM_EME_EME_HELPERS_H_
+
+#include <string>
+#include <vector>
+
+#include "cobalt/script/script_value_factory.h"
+#include "starboard/drm.h"
+
+namespace cobalt {
+namespace dom {
+namespace eme {
+
+template <typename PromiseValueReferenceType>
+void RejectPromise(PromiseValueReferenceType* promise_reference,
+                   SbDrmStatus status, const std::string& error_message) {
+  switch (status) {
+    case kSbDrmStatusSuccess:
+      NOTREACHED() << "'kSbDrmStatusSuccess' is not an error.";
+      break;
+    case kSbDrmStatusTypeError:
+      // TODO: Pass |error_message| once we support message on simple errors.
+      promise_reference->value().Reject(script::kTypeError);
+      break;
+    case kSbDrmStatusNotSupportedError:
+      promise_reference->value().Reject(
+          new DOMException(DOMException::kNotSupportedErr, error_message));
+      break;
+    case kSbDrmStatusInvalidStateError:
+      promise_reference->value().Reject(
+          new DOMException(DOMException::kInvalidStateErr, error_message));
+      break;
+    case kSbDrmStatusQuotaExceededError:
+      promise_reference->value().Reject(
+          new DOMException(DOMException::kQuotaExceededErr, error_message));
+      break;
+    case kSbDrmStatusUnknownError:
+      promise_reference->value().Reject(
+          new DOMException(DOMException::kNone, error_message));
+      break;
+  }
+}
+
+}  // namespace eme
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_EME_EME_HELPERS_H_
diff --git a/src/cobalt/dom/eme/media_key_message_type.idl b/src/cobalt/dom/eme/media_key_message_type.idl
index 1125463..0c1f0e0 100644
--- a/src/cobalt/dom/eme/media_key_message_type.idl
+++ b/src/cobalt/dom/eme/media_key_message_type.idl
@@ -16,8 +16,7 @@
 
 enum MediaKeyMessageType {
   "license-request",
-  // TODO: Implement other message types.
-  // "license-renewal",
-  // "license-release",
-  // "individualization-request"
+  "license-renewal",
+  "license-release",
+  "individualization-request"
 };
diff --git a/src/cobalt/dom/eme/media_key_session.cc b/src/cobalt/dom/eme/media_key_session.cc
index 8db9c20..ad995c3 100644
--- a/src/cobalt/dom/eme/media_key_session.cc
+++ b/src/cobalt/dom/eme/media_key_session.cc
@@ -19,6 +19,7 @@
 #include "cobalt/dom/array_buffer.h"
 #include "cobalt/dom/array_buffer_view.h"
 #include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/eme/eme_helpers.h"
 #include "cobalt/dom/eme/media_key_message_event.h"
 #include "cobalt/dom/eme/media_key_message_event_init.h"
 #include "cobalt/dom/eme/media_keys.h"
@@ -232,7 +233,8 @@
 // See
 // https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-generaterequest.
 void MediaKeySession::OnSessionUpdateRequestGenerated(
-    VoidPromiseValue::Reference* promise_reference, scoped_array<uint8> message,
+    VoidPromiseValue::Reference* promise_reference,
+    SbDrmSessionRequestType type, scoped_array<uint8> message,
     int message_size) {
   MediaKeyMessageEventInit media_key_message_event_init;
   // 10.9.4. If a license request for the requested license type can be
@@ -251,8 +253,24 @@
   //
   // TODO: Introduce message type parameter to |SbDrmSessionUpdateRequestFunc|
   //       and stop pretending that all messages are license requests.
-  media_key_message_event_init.set_message_type(
-      kMediaKeyMessageTypeLicenseRequest);
+  switch (type) {
+    case kSbDrmSessionRequestTypeLicenseRequest:
+      media_key_message_event_init.set_message_type(
+          kMediaKeyMessageTypeLicenseRequest);
+      break;
+    case kSbDrmSessionRequestTypeLicenseRenewal:
+      media_key_message_event_init.set_message_type(
+          kMediaKeyMessageTypeLicenseRenewal);
+      break;
+    case kSbDrmSessionRequestTypeLicenseRelease:
+      media_key_message_event_init.set_message_type(
+          kMediaKeyMessageTypeLicenseRelease);
+      break;
+    case kSbDrmSessionRequestTypeIndividualizationRequest:
+      media_key_message_event_init.set_message_type(
+          kMediaKeyMessageTypeIndividualizationRequest);
+      break;
+  }
 
   // 10.3. Let this object's callable value be true.
   callable_ = true;
@@ -279,12 +297,12 @@
 // See
 // https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-generaterequest.
 void MediaKeySession::OnSessionUpdateRequestDidNotGenerate(
-    VoidPromiseValue::Reference* promise_reference) {
+    VoidPromiseValue::Reference* promise_reference, SbDrmStatus status,
+    const std::string& error_message) {
   // 10.10.1. If any of the preceding steps failed, reject promise with a new
   //          DOMException whose name is the appropriate error name.
   //
-  // TODO: Introduce Starboard API that allows CDM to propagate error codes.
-  promise_reference->value().Reject(new DOMException(DOMException::kNone));
+  RejectPromise(promise_reference, status, error_message);
 }
 
 // See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update.
@@ -307,12 +325,12 @@
 
 // See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update.
 void MediaKeySession::OnSessionDidNotUpdate(
-    VoidPromiseValue::Reference* promise_reference) {
+    VoidPromiseValue::Reference* promise_reference, SbDrmStatus status,
+    const std::string& error_message) {
   // 8.1.3. If any of the preceding steps failed, reject promise with a new
   //        DOMException whose name is the appropriate error name.
   //
-  // TODO: Introduce Starboard API that allows CDM to propagate error codes.
-  promise_reference->value().Reject(new DOMException(DOMException::kNone));
+  RejectPromise(promise_reference, status, error_message);
 }
 
 // See https://www.w3.org/TR/encrypted-media/#update-key-statuses.
diff --git a/src/cobalt/dom/eme/media_key_session.h b/src/cobalt/dom/eme/media_key_session.h
index cdd39ab..4a026a3 100644
--- a/src/cobalt/dom/eme/media_key_session.h
+++ b/src/cobalt/dom/eme/media_key_session.h
@@ -70,11 +70,15 @@
 
   void OnSessionUpdateRequestGenerated(
       VoidPromiseValue::Reference* promise_reference,
-      scoped_array<uint8> message, int message_size);
+      SbDrmSessionRequestType type, scoped_array<uint8> message,
+      int message_size);
   void OnSessionUpdateRequestDidNotGenerate(
-      VoidPromiseValue::Reference* promise_reference);
+      VoidPromiseValue::Reference* promise_reference, SbDrmStatus status,
+      const std::string& error_message);
   void OnSessionUpdated(VoidPromiseValue::Reference* promise_reference);
-  void OnSessionDidNotUpdate(VoidPromiseValue::Reference* promise_reference);
+  void OnSessionDidNotUpdate(VoidPromiseValue::Reference* promise_reference,
+                             SbDrmStatus status,
+                             const std::string& error_message);
   void OnSessionUpdateKeyStatuses(
       const std::vector<std::string>& key_ids,
       const std::vector<SbDrmKeyStatus>& key_statuses);
diff --git a/src/cobalt/dom/eme/media_keys.cc b/src/cobalt/dom/eme/media_keys.cc
index 351ebfa..c1e6e2e 100644
--- a/src/cobalt/dom/eme/media_keys.cc
+++ b/src/cobalt/dom/eme/media_keys.cc
@@ -16,6 +16,7 @@
 
 #include "base/bind.h"
 #include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/eme/eme_helpers.h"
 #include "cobalt/dom/eme/media_key_session.h"
 
 namespace cobalt {
@@ -48,6 +49,47 @@
   return session;
 }
 
+MediaKeys::BoolPromiseHandle MediaKeys::SetServerCertificate(
+    const BufferSource& server_certificate) {
+  BoolPromiseHandle promise = script_value_factory_->CreateBasicPromise<bool>();
+
+  // 1. If the Key System implementation represented by this object's cdm
+  //    implementation value does not support server certificates, return a
+  //    promise resolved with false.
+  if (!drm_system_->IsServerCertificateUpdatable()) {
+    promise->Resolve(false);
+    return promise;
+  }
+
+  // 2. If serverCertificate is an empty array, return a promise rejected with a
+  //    newly created TypeError.
+  const uint8* server_certificate_buffer;
+  int server_certificate_buffer_size = 0;
+  GetBufferAndSize(server_certificate, &server_certificate_buffer,
+                   &server_certificate_buffer_size);
+
+  if (server_certificate_buffer_size == 0) {
+    promise->Reject(script::kTypeError);
+    return promise;
+  }
+
+  // 3. Let certificate be a copy of the contents of the serverCertificate
+  //    parameter.
+  // 4. Let promise be a new promise.
+  // 5. Run the following steps in parallel:
+  // 5.1 Let sanitized certificate be a validated and/or sanitized version of
+  //     certificate.
+  // 5.2 Use this object's cdm instance to process sanitized certificate.
+  drm_system_->UpdateServerCertificate(
+      server_certificate_buffer, server_certificate_buffer_size,
+      base::Bind(&MediaKeys::OnServerCertificateUpdated, base::AsWeakPtr(this),
+                 base::Owned(new BoolPromiseValue::Reference(this, promise))));
+
+  // 5.3 and 5.4 are pending processing in OnServerCertificateUpdated().
+  // 6. Return promise.
+  return promise;
+}
+
 void MediaKeys::OnSessionClosed(MediaKeySession* session) {
   // Erase-remove idiom, see
   // https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom.
@@ -56,6 +98,19 @@
       open_sessions_.end());
 }
 
+void MediaKeys::OnServerCertificateUpdated(
+    BoolPromiseValue::Reference* promise_reference, SbDrmStatus status,
+    const std::string& error_message) {
+  // 5.3 If the preceding step failed, resolve promise with a new DOMException
+  //     whose name is the appropriate error name.
+  if (status != kSbDrmStatusSuccess) {
+    RejectPromise(promise_reference, status, error_message);
+    return;
+  }
+  // 5.4 Resolve promise with true.
+  promise_reference->value().Resolve(true);
+}
+
 void MediaKeys::TraceMembers(script::Tracer* tracer) {
   tracer->TraceItems(open_sessions_);
 }
diff --git a/src/cobalt/dom/eme/media_keys.h b/src/cobalt/dom/eme/media_keys.h
index 759a9e2..ff1330f 100644
--- a/src/cobalt/dom/eme/media_keys.h
+++ b/src/cobalt/dom/eme/media_keys.h
@@ -26,6 +26,7 @@
 #include "cobalt/media/base/drm_system.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/wrappable.h"
+#include "starboard/drm.h"
 
 namespace cobalt {
 namespace dom {
@@ -38,6 +39,8 @@
                   public base::SupportsWeakPtr<MediaKeys> {
  public:
   // Custom, not in any spec.
+  typedef script::Handle<script::Promise<bool>> BoolPromiseHandle;
+  typedef script::ScriptValue<script::Promise<bool>> BoolPromiseValue;
 
   MediaKeys(const std::string& key_system,
             script::ScriptValueFactory* script_value_factory);
@@ -49,6 +52,8 @@
   scoped_refptr<MediaKeySession> CreateSession(
       MediaKeySessionType session_type,
       script::ExceptionState* exception_state);
+  BoolPromiseHandle SetServerCertificate(
+      const BufferSource& server_certificate);
 
   DEFINE_WRAPPABLE_TYPE(MediaKeys);
   void TraceMembers(script::Tracer* tracer) override;
@@ -57,6 +62,9 @@
   ~MediaKeys() override;
 
   void OnSessionClosed(MediaKeySession* session);
+  void OnServerCertificateUpdated(
+      BoolPromiseValue::Reference* promise_reference, SbDrmStatus status,
+      const std::string& error_message);
 
   script::ScriptValueFactory* script_value_factory_;
   scoped_refptr<media::DrmSystem> drm_system_;
diff --git a/src/cobalt/dom/eme/media_keys.idl b/src/cobalt/dom/eme/media_keys.idl
index fc1a35f..250f576 100644
--- a/src/cobalt/dom/eme/media_keys.idl
+++ b/src/cobalt/dom/eme/media_keys.idl
@@ -17,6 +17,5 @@
 interface MediaKeys {
   [RaisesException] MediaKeySession createSession(
       optional MediaKeySessionType sessionType = "temporary");
-  // TODO: Trivially implement server certificates.
-  // Promise<boolean> setServerCertificate(BufferSource serverCertificate);
+  Promise<boolean> setServerCertificate(BufferSource serverCertificate);
 };
diff --git a/src/cobalt/dom/error_event_test.cc b/src/cobalt/dom/error_event_test.cc
index 82ad15f..85d4e7a 100644
--- a/src/cobalt/dom/error_event_test.cc
+++ b/src/cobalt/dom/error_event_test.cc
@@ -19,6 +19,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/css_parser/parser.h"
 #include "cobalt/dom/error_event_init.h"
 #include "cobalt/dom/local_storage_database.h"
@@ -26,6 +27,7 @@
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
@@ -56,26 +58,30 @@
         css_parser_(css_parser::Parser::Create()),
         dom_parser_(new dom_parser::Parser(mock_error_callback_)),
         fetcher_factory_(new loader::FetcherFactory(NULL)),
+        loader_factory_(new loader::LoaderFactory(
+            fetcher_factory_.get(), NULL, base::kThreadPriority_Default)),
         local_storage_database_(NULL),
-        url_("about:blank"),
-        window_(new Window(
-            1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
-            dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
-            NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL,
-            NULL, NULL, url_, "", "en-US", "en",
-            base::Callback<void(const GURL&)>(),
-            base::Bind(&MockErrorCallback::Run,
-                       base::Unretained(&mock_error_callback_)),
-            NULL, network_bridge::PostSender(), csp::kCSPRequired,
-            kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
-            base::Closure() /* ran_animation_frame_callbacks */,
-            dom::Window::CloseCallback() /* window_close */,
-            base::Closure() /* window_minimize */, NULL, NULL, NULL,
-            dom::Window::OnStartDispatchEventCallback(),
-            dom::Window::OnStopDispatchEventCallback(),
-            dom::ScreenshotManager::ProvideScreenshotFunctionCallback())) {
+        url_("about:blank") {
     engine_ = script::JavaScriptEngine::CreateEngine();
     global_environment_ = engine_->CreateGlobalEnvironment();
+
+    window_ = new Window(
+        1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
+        dom_parser_.get(), fetcher_factory_.get(), loader_factory_.get(), NULL,
+        NULL, NULL, NULL, NULL, NULL, &local_storage_database_, NULL, NULL,
+        NULL, NULL, global_environment_->script_value_factory(), NULL, NULL,
+        url_, "", "en-US", "en", base::Callback<void(const GURL&)>(),
+        base::Bind(&MockErrorCallback::Run,
+                   base::Unretained(&mock_error_callback_)),
+        NULL, network_bridge::PostSender(), csp::kCSPRequired,
+        kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
+        base::Closure() /* ran_animation_frame_callbacks */,
+        dom::Window::CloseCallback() /* window_close */,
+        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        dom::Window::OnStartDispatchEventCallback(),
+        dom::Window::OnStopDispatchEventCallback(),
+        dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
+
     global_environment_->CreateGlobalObject(window_,
                                             environment_settings_.get());
   }
@@ -92,9 +98,10 @@
   scoped_ptr<css_parser::Parser> css_parser_;
   scoped_ptr<dom_parser::Parser> dom_parser_;
   scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+  scoped_ptr<loader::LoaderFactory> loader_factory_;
   dom::LocalStorageDatabase local_storage_database_;
   GURL url_;
-  const scoped_refptr<Window> window_;
+  scoped_refptr<Window> window_;
 };
 
 bool ErrorEventTest::EvaluateScript(const std::string& js_code,
diff --git a/src/cobalt/dom/event.cc b/src/cobalt/dom/event.cc
index da1a085..6f3d993 100644
--- a/src/cobalt/dom/event.cc
+++ b/src/cobalt/dom/event.cc
@@ -23,42 +23,48 @@
 
 Event::Event(UninitializedFlag /* uninitialized_flag */)
     : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+      time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   InitEventInternal(base::Token(), false, false);
 }
 
 Event::Event(const std::string& type)
     : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+      time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   InitEventInternal(base::Token(type), false, false);
 }
 
 Event::Event(const std::string& type, const EventInit& init_dict)
     : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+      time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   SB_DCHECK(init_dict.has_bubbles());
   SB_DCHECK(init_dict.has_cancelable());
+  if (init_dict.time_stamp() != 0) {
+    time_stamp_ = init_dict.time_stamp();
+  }
   InitEventInternal(base::Token(type), init_dict.bubbles(),
                     init_dict.cancelable());
 }
 
 Event::Event(base::Token type)
     : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+      time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   InitEventInternal(type, false, false);
 }
 
 Event::Event(base::Token type, Bubbles bubbles, Cancelable cancelable)
     : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+      time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   InitEventInternal(type, bubbles == kBubbles, cancelable == kCancelable);
 }
 
 Event::Event(base::Token type, const EventInit& init_dict)
     : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+      time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   SB_DCHECK(init_dict.has_bubbles());
   SB_DCHECK(init_dict.has_cancelable());
+  if (init_dict.time_stamp() != 0) {
+    time_stamp_ = init_dict.time_stamp();
+  }
   InitEventInternal(type, init_dict.bubbles(), init_dict.cancelable());
 }
 
@@ -105,5 +111,14 @@
   tracer->Trace(current_target_);
 }
 
+uint64 Event::GetEventTime(SbTimeMonotonic monotonic_time) {
+  // For now, continue using the old specification which specifies real time
+  // since 1970.
+  // https://www.w3.org/TR/dom/#dom-event-timestamp
+  SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
+  base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
+  return static_cast<uint64>(base_time.ToJsTime());
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/event.h b/src/cobalt/dom/event.h
index 4dba8b2..1359951 100644
--- a/src/cobalt/dom/event.h
+++ b/src/cobalt/dom/event.h
@@ -126,6 +126,13 @@
     return immediate_propagation_stopped_;
   }
 
+  // https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp
+  // An event's timeStamp should represent time since document creation, not
+  // real time. However, the old spec specifies time since 1970. To facilitate
+  // the transition to the new spec, use this function to calculate an event's
+  // time stamp.
+  static uint64 GetEventTime(SbTimeMonotonic monotonic_time);
+
   DEFINE_WRAPPABLE_TYPE(Event);
   void TraceMembers(script::Tracer* tracer) override;
 
diff --git a/src/cobalt/dom/event_init.idl b/src/cobalt/dom/event_init.idl
index f0974f5..28e9436 100644
--- a/src/cobalt/dom/event_init.idl
+++ b/src/cobalt/dom/event_init.idl
@@ -17,4 +17,7 @@
 dictionary EventInit {
   boolean bubbles = false;
   boolean cancelable = false;
+
+  // Cobalt customization - timeStamp is not part of the spec.
+  DOMTimeStamp timeStamp = 0;
 };
diff --git a/src/cobalt/dom/event_listener.cc b/src/cobalt/dom/event_listener.cc
deleted file mode 100644
index d53b8db..0000000
--- a/src/cobalt/dom/event_listener.cc
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/event_listener.h"
-
-#include "base/debug/trace_event.h"
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "cobalt/base/user_log.h"
-#include "cobalt/dom/event_target.h"
-
-namespace cobalt {
-namespace dom {
-namespace {
-
-// This class records the nested events and manages the user log information.
-class ScriptEventLog {
- public:
-  ScriptEventLog() : current_stack_depth_(0) {
-    base::UserLog::Register(base::UserLog::kEventStackLevelCountIndex,
-                            "EventLevelCnt", &current_stack_depth_,
-                            sizeof(current_stack_depth_));
-
-    memset(event_log_stack_, 0, sizeof(event_log_stack_));
-    const int kLogNameBufferSize = 16;
-    char log_name_buffer[kLogNameBufferSize];
-    for (int i = 0; i < base::UserLog::kEventStackMaxDepth; i++) {
-      snprintf(log_name_buffer, kLogNameBufferSize, "EventLevel%d", i);
-      base::UserLog::Register(static_cast<base::UserLog::Index>(
-                                  base::UserLog::kEventStackMinLevelIndex + i),
-                              log_name_buffer, event_log_stack_[i],
-                              kLogEntryMaxLength);
-    }
-  }
-
-  ~ScriptEventLog() {
-    base::UserLog::Deregister(base::UserLog::kEventStackLevelCountIndex);
-    for (int i = 0; i < base::UserLog::kEventStackMaxDepth; i++) {
-      base::UserLog::Deregister(static_cast<base::UserLog::Index>(
-          base::UserLog::kEventStackMinLevelIndex + i));
-    }
-  }
-
-  void PushEvent(Event* event) {
-    if (current_stack_depth_ < base::UserLog::kEventStackMaxDepth) {
-      snprintf(event_log_stack_[current_stack_depth_], kLogEntryMaxLength,
-               "%s@%s", event->type().c_str(),
-               event->current_target()->GetDebugName().c_str());
-    } else if (current_stack_depth_ == base::UserLog::kEventStackMaxDepth) {
-      DLOG(WARNING) << "Reached maximum depth of " << kLogEntryMaxLength
-                    << ". Subsequent events will not be logged.";
-    }
-    current_stack_depth_++;
-  }
-
-  void PopEvent() {
-    DCHECK(current_stack_depth_);
-    current_stack_depth_--;
-    if (current_stack_depth_ < base::UserLog::kEventStackMaxDepth) {
-      memset(event_log_stack_[current_stack_depth_], 0, kLogEntryMaxLength);
-    }
-  }
-
- private:
-  static const size_t kLogEntryMaxLength = 64;
-  int current_stack_depth_;
-  char event_log_stack_[base::UserLog::kEventStackMaxDepth][kLogEntryMaxLength];
-
-  DISALLOW_COPY_AND_ASSIGN(ScriptEventLog);
-};
-
-base::LazyInstance<ScriptEventLog> script_event_log = LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-void EventListener::HandleEvent(const scoped_refptr<Event>& event,
-                                Type listener_type) const {
-  TRACE_EVENT1("cobalt::dom", "EventListener::HandleEvent", "Event Name",
-               TRACE_STR_COPY(event->type().c_str()));
-  script_event_log.Get().PushEvent(event);
-
-  bool had_exception;
-  base::optional<bool> result =
-      HandleEvent(event->current_target(), event, &had_exception);
-
-  script_event_log.Get().PopEvent();
-
-  if (had_exception) {
-    return;
-  }
-  // EventHandlers (EventListeners set as attributes) may return false rather
-  // than call event.preventDefault() in the handler function.
-  if (listener_type == kAttribute && result && !result.value()) {
-    event->PreventDefault();
-  }
-}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/src/cobalt/dom/event_listener.h b/src/cobalt/dom/event_listener.h
index 7beab2b..1b6ab70 100644
--- a/src/cobalt/dom/event_listener.h
+++ b/src/cobalt/dom/event_listener.h
@@ -28,19 +28,6 @@
 //   https://www.w3.org/TR/2014/WD-dom-20140710/#eventtarget
 class EventListener {
  public:
-  // EventHandlers are implemented as EventListener?, so use this to
-  // differentiate between the two.
-  enum Type {
-    kAttribute,
-    kNotAttribute,
-  };
-
-  // Custom, not in any spec.
-  //
-  // Specify whether this is an attribute-type listener or not.
-  void HandleEvent(const scoped_refptr<Event>& event, Type listener_type) const;
-
- protected:
   // Web API: EventListener
   //
   // Cobalt's implementation of callback interfaces requires the 'callback this'
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index b76eeef..db84c25 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -33,10 +33,11 @@
     return;
   }
 
-  EventListenerScriptValue::Reference listener_reference(this, listener);
+  scoped_ptr<GenericEventHandlerReference> listener_reference(
+      new GenericEventHandlerReference(this, listener));
 
-  AddEventListenerInternal(base::Token(type), listener, use_capture,
-                           EventListener::kNotAttribute);
+  AddEventListenerInternal(base::Token(type), listener_reference.Pass(),
+                           use_capture, kNotAttribute);
 }
 
 void EventTarget::RemoveEventListener(const std::string& type,
@@ -49,9 +50,8 @@
 
   for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
        iter != event_listener_infos_.end(); ++iter) {
-    if ((*iter)->listener_type == EventListener::kNotAttribute &&
-        (*iter)->type == type.c_str() &&
-        (*iter)->listener.referenced_value().EqualTo(listener) &&
+    if ((*iter)->listener_type == kNotAttribute &&
+        (*iter)->type == type.c_str() && (*iter)->listener->EqualTo(listener) &&
         (*iter)->use_capture == use_capture) {
       event_listener_infos_.erase(iter);
       return;
@@ -124,44 +124,75 @@
 
 void EventTarget::SetAttributeEventListener(
     base::Token type, const EventListenerScriptValue& listener) {
-  // Remove existing attribute listener of the same type.
-  for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
-       iter != event_listener_infos_.end(); ++iter) {
-    if ((*iter)->listener_type == EventListener::kAttribute &&
-        (*iter)->type == type) {
-      event_listener_infos_.erase(iter);
-      break;
-    }
-  }
+  DCHECK(!unpack_onerror_events_ || type != base::Tokens::error());
 
-  if (listener.IsNull()) {
-    return;
-  }
-  AddEventListenerInternal(type, listener, false, EventListener::kAttribute);
+  scoped_ptr<GenericEventHandlerReference> listener_reference(
+      new GenericEventHandlerReference(this, listener));
+  SetAttributeEventListenerInternal(type, listener_reference.Pass());
 }
 
 const EventTarget::EventListenerScriptValue*
 EventTarget::GetAttributeEventListener(base::Token type) const {
-  for (EventListenerInfos::const_iterator iter = event_listener_infos_.begin();
-       iter != event_listener_infos_.end(); ++iter) {
-    if ((*iter)->listener_type == EventListener::kAttribute &&
-        (*iter)->type == type) {
-      return &(*iter)->listener.referenced_value();
-    }
-  }
-  return NULL;
+  DCHECK(!unpack_onerror_events_ || type != base::Tokens::error());
+
+  GenericEventHandlerReference* handler =
+      GetAttributeEventListenerInternal(type);
+  return handler ? handler->event_listener_value() : NULL;
+}
+
+void EventTarget::SetAttributeOnErrorEventListener(
+    base::Token type, const OnErrorEventListenerScriptValue& listener) {
+  DCHECK_EQ(base::Tokens::error(), type);
+
+  scoped_ptr<GenericEventHandlerReference> listener_reference(
+      new GenericEventHandlerReference(this, listener));
+  SetAttributeEventListenerInternal(type, listener_reference.Pass());
+}
+
+const EventTarget::OnErrorEventListenerScriptValue*
+EventTarget::GetAttributeOnErrorEventListener(base::Token type) const {
+  DCHECK_EQ(base::Tokens::error(), type);
+
+  GenericEventHandlerReference* handler =
+      GetAttributeEventListenerInternal(type);
+  return handler ? handler->on_error_event_listener_value() : NULL;
 }
 
 bool EventTarget::HasOneOrMoreAttributeEventListener() const {
   for (EventListenerInfos::const_iterator iter = event_listener_infos_.begin();
        iter != event_listener_infos_.end(); ++iter) {
-    if ((*iter)->listener_type == EventListener::kAttribute) {
+    if ((*iter)->listener_type == kAttribute) {
       return true;
     }
   }
   return false;
 }
 
+void EventTarget::SetAttributeEventListenerInternal(
+    base::Token type, scoped_ptr<GenericEventHandlerReference> event_handler) {
+  // Remove existing attribute listener of the same type.
+  for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
+       iter != event_listener_infos_.end(); ++iter) {
+    if ((*iter)->listener_type == kAttribute && (*iter)->type == type) {
+      event_listener_infos_.erase(iter);
+      break;
+    }
+  }
+
+  AddEventListenerInternal(type, event_handler.Pass(), false, kAttribute);
+}
+
+GenericEventHandlerReference* EventTarget::GetAttributeEventListenerInternal(
+    base::Token type) const {
+  for (EventListenerInfos::const_iterator iter = event_listener_infos_.begin();
+       iter != event_listener_infos_.end(); ++iter) {
+    if ((*iter)->listener_type == kAttribute && (*iter)->type == type) {
+      return (*iter)->listener.get();
+    }
+  }
+  return NULL;
+}
+
 void EventTarget::FireEventOnListeners(const scoped_refptr<Event>& event) {
   DCHECK(event->IsBeingDispatched());
   DCHECK(event->target());
@@ -174,7 +205,9 @@
        iter != event_listener_infos_.end(); ++iter) {
     if ((*iter)->type == event->type()) {
       event_listener_infos.push_back(new EventListenerInfo(
-          (*iter)->type, this, (*iter)->listener.referenced_value(),
+          (*iter)->type,
+          make_scoped_ptr(
+              new GenericEventHandlerReference(this, *(*iter)->listener)),
           (*iter)->use_capture, (*iter)->listener_type));
     }
   }
@@ -193,7 +226,9 @@
     if (event->event_phase() == Event::kBubblingPhase && (*iter)->use_capture) {
       continue;
     }
-    (*iter)->listener.value().HandleEvent(event, (*iter)->listener_type);
+
+    (*iter)->listener->HandleEvent(event, (*iter)->listener_type == kAttribute,
+                                   unpack_onerror_events_);
   }
 
   event->set_current_target(NULL);
@@ -206,26 +241,27 @@
 }
 
 void EventTarget::AddEventListenerInternal(
-    base::Token type, const EventListenerScriptValue& listener,
-    bool use_capture, EventListener::Type listener_type) {
+    base::Token type, scoped_ptr<GenericEventHandlerReference> listener,
+    bool use_capture, Type listener_type) {
   TRACK_MEMORY_SCOPE("DOM");
 
-  DCHECK(!listener.IsNull());
+  if (listener->IsNull()) {
+    return;
+  }
 
   for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
        iter != event_listener_infos_.end(); ++iter) {
-    if ((*iter)->type == type &&
-        (*iter)->listener.referenced_value().EqualTo(listener) &&
+    if ((*iter)->type == type && (*iter)->listener->EqualTo(*listener) &&
         (*iter)->use_capture == use_capture &&
         (*iter)->listener_type == listener_type) {
       // Attribute listeners should have already been removed.
-      DCHECK_EQ(listener_type, EventListener::kNotAttribute);
+      DCHECK_EQ(listener_type, kNotAttribute);
       return;
     }
   }
 
   event_listener_infos_.push_back(
-      new EventListenerInfo(type, this, listener, use_capture, listener_type));
+      new EventListenerInfo(type, listener.Pass(), use_capture, listener_type));
 }
 
 bool EventTarget::HasEventListener(base::Token type) {
@@ -241,11 +277,10 @@
 }
 
 EventTarget::EventListenerInfo::EventListenerInfo(
-    base::Token type, EventTarget* const event_target,
-    const EventListenerScriptValue& listener, bool use_capture,
-    EventListener::Type listener_type)
+    base::Token type, scoped_ptr<GenericEventHandlerReference> listener,
+    bool use_capture, Type listener_type)
     : type(type),
-      listener(event_target, listener),
+      listener(listener.Pass()),
       use_capture(use_capture),
       listener_type(listener_type) {
   GlobalStats::GetInstance()->AddEventListener();
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index a89dbe5..b9c5d9c 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -23,10 +23,13 @@
 #include "base/memory/scoped_vector.h"
 #include "base/memory/weak_ptr.h"
 #include "base/tracked_objects.h"
+#include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/event.h"
 #include "cobalt/dom/event_listener.h"
+#include "cobalt/dom/generic_event_handler_reference.h"
+#include "cobalt/dom/on_error_event_listener.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
@@ -42,7 +45,37 @@
 class EventTarget : public script::Wrappable,
                     public base::SupportsWeakPtr<EventTarget> {
  public:
+  // EventHandlers are implemented as EventListener?, so use this to
+  // differentiate between the two.
+  enum Type {
+    kAttribute,
+    kNotAttribute,
+  };
+
+  // Helper enum to decide whether or not onerror event parameters should be
+  // unpacked or not (e.g. in the special case of the |window| object).
+  // This special handling is described in:
+  //   https://html.spec.whatwg.org/#event-handler-attributes
+  // (search for "onerror")
+  enum UnpackOnErrorEventsBool {
+    kUnpackOnErrorEvents,
+    kDoNotUnpackOnErrorEvents,
+  };
+
+  // The parameter |unpack_onerror_events| can be set to true (e.g. for the
+  // |window| object) in order to indicate that the ErrorEvent should have
+  // its members unpacked before calling its event handler.  This is to
+  // accommodate for a special case in the window.onerror handling.  This
+  // special handling
+  explicit EventTarget(
+      UnpackOnErrorEventsBool onerror_event_parameter_handling =
+          kDoNotUnpackOnErrorEvents)
+      : unpack_onerror_events_(onerror_event_parameter_handling ==
+                               kUnpackOnErrorEvents) {}
+
   typedef script::ScriptValue<EventListener> EventListenerScriptValue;
+  typedef script::ScriptValue<OnErrorEventListener>
+      OnErrorEventListenerScriptValue;
 
   // Web API: EventTarget
   //
@@ -100,11 +133,11 @@
     SetAttributeEventListener(base::Tokens::click(), event_listener);
   }
 
-  const EventListenerScriptValue* onerror() {
-    return GetAttributeEventListener(base::Tokens::error());
+  const OnErrorEventListenerScriptValue* onerror() {
+    return GetAttributeOnErrorEventListener(base::Tokens::error());
   }
-  void set_onerror(const EventListenerScriptValue& event_listener) {
-    SetAttributeEventListener(base::Tokens::error(), event_listener);
+  void set_onerror(const OnErrorEventListenerScriptValue& event_listener) {
+    SetAttributeOnErrorEventListener(base::Tokens::error(), event_listener);
   }
 
   const EventListenerScriptValue* onfocus() {
@@ -393,6 +426,16 @@
   const EventListenerScriptValue* GetAttributeEventListener(
       base::Token type) const;
 
+  // Similar to SetAttributeEventListener(), but this function should only
+  // be called with type == base::Tokens::error().
+  void SetAttributeOnErrorEventListener(
+      base::Token type, const OnErrorEventListenerScriptValue& listener);
+
+  // Similar to GetAttributeEventListener(), but this function should only
+  // be called with type == base::Tokens::error().
+  const OnErrorEventListenerScriptValue* GetAttributeOnErrorEventListener(
+      base::Token type) const;
+
   // Return true if one or more event listeners are registered
   bool HasOneOrMoreAttributeEventListener() const;
 
@@ -409,24 +452,33 @@
 
  private:
   struct EventListenerInfo {
-    EventListenerInfo(base::Token type, EventTarget* const event_target,
-                      const EventListenerScriptValue& listener,
-                      bool use_capture, EventListener::Type listener_type);
+    EventListenerInfo(base::Token type,
+                      scoped_ptr<GenericEventHandlerReference> listener,
+                      bool use_capture, Type listener_type);
     ~EventListenerInfo();
 
     base::Token type;
-    script::ScriptValue<EventListener>::Reference listener;
+    scoped_ptr<GenericEventHandlerReference> listener;
     bool use_capture;
-    EventListener::Type listener_type;
+    Type listener_type;
   };
   typedef ScopedVector<EventListenerInfo> EventListenerInfos;
 
-  void AddEventListenerInternal(base::Token type,
-                                const EventListenerScriptValue& listener,
-                                bool use_capture,
-                                EventListener::Type listener_type);
+  void SetAttributeEventListenerInternal(
+      base::Token type, scoped_ptr<GenericEventHandlerReference> event_handler);
+  GenericEventHandlerReference* GetAttributeEventListenerInternal(
+      base::Token type) const;
+
+  void AddEventListenerInternal(
+      base::Token type, scoped_ptr<GenericEventHandlerReference> listener,
+      bool use_capture, Type listener_type);
 
   EventListenerInfos event_listener_infos_;
+
+  // Tracks whether this current event listener should unpack the onerror
+  // event object when calling its callback.  This is needed to implement
+  // the special case of window.onerror handling.
+  bool unpack_onerror_events_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/generic_event_handler_reference.cc b/src/cobalt/dom/generic_event_handler_reference.cc
new file mode 100644
index 0000000..ceeafec
--- /dev/null
+++ b/src/cobalt/dom/generic_event_handler_reference.cc
@@ -0,0 +1,125 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/generic_event_handler_reference.h"
+
+#include "base/debug/trace_event.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/dom/event_target.h"
+#include "cobalt/dom/script_event_log.h"
+
+namespace cobalt {
+namespace dom {
+
+GenericEventHandlerReference::GenericEventHandlerReference(
+    script::Wrappable* wrappable,
+    const EventListenerScriptValue& script_value) {
+  if (!script_value.IsNull()) {
+    event_listener_reference_.reset(
+        new EventListenerScriptValue::Reference(wrappable, script_value));
+  }
+}
+
+GenericEventHandlerReference::GenericEventHandlerReference(
+    script::Wrappable* wrappable,
+    const OnErrorEventListenerScriptValue& script_value) {
+  if (!script_value.IsNull()) {
+    on_error_event_listener_reference_.reset(
+        new OnErrorEventListenerScriptValue::Reference(wrappable,
+                                                       script_value));
+  }
+}
+
+GenericEventHandlerReference::GenericEventHandlerReference(
+    script::Wrappable* wrappable, const GenericEventHandlerReference& other) {
+  if (other.event_listener_reference_) {
+    event_listener_reference_.reset(new EventListenerScriptValue::Reference(
+        wrappable, other.event_listener_reference_->referenced_value()));
+  } else if (other.on_error_event_listener_reference_) {
+    on_error_event_listener_reference_.reset(
+        new OnErrorEventListenerScriptValue::Reference(
+            wrappable,
+            other.on_error_event_listener_reference_->referenced_value()));
+  }
+}
+
+void GenericEventHandlerReference::HandleEvent(
+    const scoped_refptr<Event>& event, bool is_attribute,
+    bool unpack_error_event) {
+  TRACE_EVENT1("cobalt::dom", "GenericEventHandlerReference::HandleEvent",
+               "Event Name", TRACE_STR_COPY(event->type().c_str()));
+  script_event_log.Get().PushEvent(event);
+
+  bool had_exception;
+  base::optional<bool> result;
+
+  // Forward the HandleEvent() call to the appropriate internal object.
+  if (event_listener_reference_) {
+    // Non-onerror event handlers cannot have their parameters unpacked.
+    result = event_listener_reference_->value().HandleEvent(
+        event->current_target(), event, &had_exception);
+  } else if (on_error_event_listener_reference_) {
+    result = on_error_event_listener_reference_->value().HandleEvent(
+        event->current_target(), event, &had_exception, unpack_error_event);
+  } else {
+    NOTREACHED();
+    had_exception = true;
+  }
+
+  script_event_log.Get().PopEvent();
+
+  if (had_exception) {
+    return;
+  }
+  // EventHandlers (EventListeners set as attributes) may return false rather
+  // than call event.preventDefault() in the handler function.
+  if (is_attribute && result && !result.value()) {
+    event->PreventDefault();
+  }
+}
+
+bool GenericEventHandlerReference::EqualTo(
+    const EventListenerScriptValue& other) {
+  return (IsNull() && other.IsNull()) ||
+         (event_listener_reference_ &&
+          event_listener_reference_->referenced_value().EqualTo(other));
+}
+
+bool GenericEventHandlerReference::EqualTo(
+    const GenericEventHandlerReference& other) {
+  if (IsNull() && other.IsNull()) {
+    return true;
+  }
+
+  if (event_listener_reference_ && other.event_listener_reference_) {
+    return event_listener_reference_->referenced_value().EqualTo(
+        other.event_listener_reference_->referenced_value());
+  }
+  if (on_error_event_listener_reference_ &&
+      other.on_error_event_listener_reference_) {
+    return on_error_event_listener_reference_->referenced_value().EqualTo(
+        other.on_error_event_listener_reference_->referenced_value());
+  }
+  return false;
+}
+
+bool GenericEventHandlerReference::IsNull() const {
+  return (!event_listener_reference_ ||
+          event_listener_reference_->referenced_value().IsNull()) &&
+         (!on_error_event_listener_reference_ ||
+          on_error_event_listener_reference_->referenced_value().IsNull());
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/generic_event_handler_reference.h b/src/cobalt/dom/generic_event_handler_reference.h
new file mode 100644
index 0000000..b5abfb7
--- /dev/null
+++ b/src/cobalt/dom/generic_event_handler_reference.h
@@ -0,0 +1,100 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_GENERIC_EVENT_HANDLER_REFERENCE_H_
+#define COBALT_DOM_GENERIC_EVENT_HANDLER_REFERENCE_H_
+
+#include "base/memory/ref_counted.h"
+#include "cobalt/dom/event_listener.h"
+#include "cobalt/dom/on_error_event_listener.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace dom {
+
+// Essentially acts as an abstract interface of the union for types
+// [script::ScriptValue<EventListener>,
+//  script::ScriptValue<OnErrorEventListener>].  In particular it primarily
+// allows code in event_target.cc to not need to concern itself with which
+// exact EventListener script value type it is dealing with.  The need for
+// this abstraction arises from the fact that the |window.onerror| event
+// handler requires special case handling:
+//   https://html.spec.whatwg.org/#onerroreventhandler )
+//
+// NOTE that this is *not* an ideal solution to the problem of generalizing
+// over multiple ScriptValue types.  The problem is that the ScriptValue
+// base class is both templated and abstract, making it difficult to cast its
+// internal type to get a new ScriptValue.  This problem could be solved by
+// refactoring ScriptValue such that there is a non-templated abstract type
+// (say, RawScriptValue) with a function like "void* GetValue()", but usually
+// not referenced directly by client code.  Instead there would be a separate
+// templated concrete wrapper type (say, ScriptValue) that wraps RawScriptValue
+// and manages the casting and type checking.  This would allow us to convert
+// between OnErrorEventListener and EventListener, if OnErrorEventListener was
+// derived from EventListener.
+class GenericEventHandlerReference {
+ public:
+  typedef script::ScriptValue<EventListener> EventListenerScriptValue;
+  typedef script::ScriptValue<OnErrorEventListener>
+      OnErrorEventListenerScriptValue;
+
+  GenericEventHandlerReference(script::Wrappable* wrappable,
+                               const EventListenerScriptValue& script_value);
+  GenericEventHandlerReference(
+      script::Wrappable* wrappable,
+      const OnErrorEventListenerScriptValue& script_value);
+  GenericEventHandlerReference(script::Wrappable* wrappable,
+                               const GenericEventHandlerReference& other);
+
+  // Forwards on to the internal event handler's HandleEvent() call, passing
+  // in the value of |unpack_error_event| if the internal type is a
+  // OnErrorEventListenerScriptValue type.
+  void HandleEvent(const scoped_refptr<Event>& event, bool is_attribute,
+                   bool unpack_error_event);
+
+  bool EqualTo(const EventListenerScriptValue& other);
+  bool EqualTo(const GenericEventHandlerReference& other);
+  bool IsNull() const;
+
+  // If the internal type is a EventListenerScriptValue, then its value will
+  // be returned, otherwise null is returned;
+  const EventListenerScriptValue* event_listener_value() {
+    return event_listener_reference_
+               ? &event_listener_reference_->referenced_value()
+               : nullptr;
+  }
+
+  // If the internal type is a OnErrorEventListenerScriptValue, then its value
+  // will be returned, otherwise null is returned;
+  const OnErrorEventListenerScriptValue* on_error_event_listener_value() {
+    return on_error_event_listener_reference_
+               ? &on_error_event_listener_reference_->referenced_value()
+               : nullptr;
+  }
+
+ private:
+  // At most only one of the below two fields may be non-null...  They are
+  // serving as a poor man's std::variant.
+  scoped_ptr<EventListenerScriptValue::Reference> event_listener_reference_;
+  scoped_ptr<OnErrorEventListenerScriptValue::Reference>
+      on_error_event_listener_reference_;
+
+  DISALLOW_COPY_AND_ASSIGN(GenericEventHandlerReference);
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_GENERIC_EVENT_HANDLER_REFERENCE_H_
diff --git a/src/cobalt/dom/global_event_handlers.idl b/src/cobalt/dom/global_event_handlers.idl
index df852fc..733dcc8 100644
--- a/src/cobalt/dom/global_event_handlers.idl
+++ b/src/cobalt/dom/global_event_handlers.idl
@@ -18,7 +18,7 @@
 interface GlobalEventHandlers {
   attribute EventHandler onblur;
   attribute EventHandler onclick;
-  attribute EventHandler onerror;
+  attribute OnErrorEventListener onerror;
   attribute EventHandler onfocus;
 
   attribute EventHandler onkeydown;
@@ -72,3 +72,4 @@
 // function. Define it as a nullable EventListener for now until we can do
 // some refactoring to accept both in the EventTarget implementation.
 typedef EventListener? EventHandler;
+typedef OnErrorEventListener? OnErrorEventHandler;
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index 6ddf6cf..74471b0 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -142,6 +142,27 @@
 
 }  // namespace
 
+void HTMLElement::RuleMatchingState::Clear() {
+  if (is_set) {
+    is_set = false;
+
+    parent_matching_nodes.clear();
+    parent_descendant_nodes.clear();
+
+    previous_sibling_matching_nodes.clear();
+    previous_sibling_following_sibling_nodes.clear();
+
+    matching_nodes_parent_nodes.clear();
+    matching_nodes.clear();
+
+    are_descendant_nodes_dirty = true;
+    descendant_nodes.clear();
+
+    are_following_sibling_nodes_dirty = true;
+    following_sibling_nodes.clear();
+  }
+}
+
 std::string HTMLElement::dir() const {
   // The dir attribute is limited to only known values. On getting, dir must
   // return the conforming value associated with the state the attribute is in,
@@ -593,30 +614,50 @@
   return NULL;
 }
 
+void HTMLElement::ClearRuleMatchingState() {
+  ClearRuleMatchingStateInternal(true /*invalidate_descendants*/);
+}
+
+void HTMLElement::ClearRuleMatchingStateInternal(bool invalidate_descendants) {
+  rule_matching_state_.Clear();
+  matching_rules_valid_ = false;
+
+  if (invalidate_descendants) {
+    InvalidateMatchingRulesRecursivelyInternal(true /*is_initial_element*/);
+  }
+}
+
+void HTMLElement::ClearRuleMatchingStateOnElementAndAncestors(
+    bool invalidate_matching_rules) {
+  Element* parent_element = this->parent_element();
+  HTMLElement* parent_html_element =
+      parent_element ? parent_element->AsHTMLElement() : NULL;
+  if (parent_html_element) {
+    parent_html_element->ClearRuleMatchingStateOnElementAndAncestors(
+        invalidate_matching_rules);
+  }
+  ClearRuleMatchingStateInternal(invalidate_matching_rules &&
+                                 parent_html_element == NULL);
+}
+
+void HTMLElement::ClearRuleMatchingStateOnElementAndDescendants() {
+  ClearRuleMatchingStateInternal(false /* invalidate_descendants*/);
+  for (Element* element = first_element_child(); element;
+       element = element->next_element_sibling()) {
+    HTMLElement* html_element = element->AsHTMLElement();
+    if (html_element) {
+      html_element->ClearRuleMatchingStateOnElementAndDescendants();
+    }
+  }
+}
+
 void HTMLElement::InvalidateMatchingRulesRecursively() {
-  InvalidateMatchingRulesRecursivelyInternal(true /*is_initial_invalidation*/);
+  InvalidateMatchingRulesRecursivelyInternal(true /*is_initial_element*/);
 }
 
 void HTMLElement::InvalidateMatchingRulesRecursivelyInternal(
-    bool is_initial_invalidation) {
-  if (matching_rules_valid_) {
-    // Move |matching_rules_| into |old_matching_rules_|. This is used for
-    // determining whether or not the matching rules actually changed when they
-    // are updated.
-    old_matching_rules_.swap(matching_rules_);
-
-    matching_rules_.clear();
-    rule_matching_state_.matching_nodes.clear();
-    rule_matching_state_.descendant_potential_nodes.clear();
-    rule_matching_state_.following_sibling_potential_nodes.clear();
-    for (int pseudo_element_type = 0;
-         pseudo_element_type < kMaxPseudoElementType; ++pseudo_element_type) {
-      if (pseudo_elements_[pseudo_element_type]) {
-        pseudo_elements_[pseudo_element_type]->ClearMatchingRules();
-      }
-    }
-    matching_rules_valid_ = false;
-  }
+    bool is_initial_element) {
+  matching_rules_valid_ = false;
 
   // Invalidate matching rules on all children.
   for (Element* element = first_element_child(); element;
@@ -624,26 +665,48 @@
     HTMLElement* html_element = element->AsHTMLElement();
     if (html_element) {
       html_element->InvalidateMatchingRulesRecursivelyInternal(
-          false /*is_initial_invalidation*/);
+          false /*is_initial_element*/);
     }
   }
 
   // Invalidate matching rules on all following siblings if this is the initial
-  // invalidation and sibling combinators are used; if this is not the initial
-  // invalidation, then these will already be handled by a previous call.
-  if (is_initial_invalidation &&
+  // element and sibling combinators are used; if this is not the initial
+  // element, then these will already be handled by a previous call.
+  if (is_initial_element &&
       node_document()->selector_tree()->has_sibling_combinators()) {
     for (Element* element = next_element_sibling(); element;
          element = element->next_element_sibling()) {
       HTMLElement* html_element = element->AsHTMLElement();
       if (html_element) {
         html_element->InvalidateMatchingRulesRecursivelyInternal(
-            false /*is_initial_invalidation*/);
+            false /*is_initial_element*/);
       }
     }
   }
 }
 
+void HTMLElement::UpdateMatchingRules() { UpdateElementMatchingRules(this); }
+
+void HTMLElement::UpdateMatchingRulesRecursively() {
+  UpdateMatchingRules();
+  for (Element* element = first_element_child(); element;
+       element = element->next_element_sibling()) {
+    HTMLElement* html_element = element->AsHTMLElement();
+    if (html_element) {
+      html_element->UpdateMatchingRulesRecursively();
+    }
+  }
+}
+
+void HTMLElement::OnMatchingRulesModified() {
+  dom_stat_tracker_->OnUpdateMatchingRules();
+  computed_style_valid_ = false;
+}
+
+void HTMLElement::OnPseudoElementMatchingRulesModified() {
+  pseudo_elements_computed_styles_valid_ = false;
+}
+
 void HTMLElement::UpdateComputedStyleRecursively(
     const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
         parent_computed_style_declaration,
@@ -655,9 +718,8 @@
     return;
   }
 
-  // Update computed style for this element.
-  bool is_valid =
-      ancestors_were_valid && matching_rules_valid_ && computed_style_valid_;
+  // Update computed styles for this element if any aren't valid.
+  bool is_valid = ancestors_were_valid && AreComputedStylesValid();
   if (!is_valid) {
     UpdateComputedStyle(parent_computed_style_declaration, root_computed_style,
                         style_change_event_time, kAncestorsAreDisplayed);
@@ -805,6 +867,7 @@
       style_(new cssom::CSSDeclaredStyleDeclaration(
           document->html_element_context()->css_parser())),
       computed_style_valid_(false),
+      pseudo_elements_computed_styles_valid_(false),
       descendant_computed_styles_valid_(false),
       ancestors_are_displayed_(kAncestorsAreDisplayed),
       css_computed_style_declaration_(new cssom::CSSComputedStyleDeclaration()),
@@ -858,25 +921,34 @@
     RunUnFocusingSteps();
     document->OnFocusChange();
   }
+
+  // Only clear the rule matching on this element. Descendants will be handled
+  // by OnRemovedFromDocument() being called on them from
+  // Node::OnRemovedFromDocument().
+  ClearRuleMatchingStateInternal(false /*invalidate_descendants*/);
 }
 
 void HTMLElement::OnMutation() { InvalidateMatchingRulesRecursively(); }
 
 void HTMLElement::OnSetAttribute(const std::string& name,
                                  const std::string& value) {
-  if (name == "class" || name == "id") {
-    InvalidateMatchingRulesRecursively();
-  } else if (name == "dir") {
+  if (name == "dir") {
     SetDirectionality(value);
   }
+
+  // Always clear the matching state when an attribute changes. Any attribute
+  // changing can potentially impact the matching rules.
+  ClearRuleMatchingState();
 }
 
 void HTMLElement::OnRemoveAttribute(const std::string& name) {
-  if (name == "class" || name == "id") {
-    InvalidateMatchingRulesRecursively();
-  } else if (name == "dir") {
+  if (name == "dir") {
     SetDirectionality("");
   }
+
+  // Always clear the matching state when an attribute changes. Any attribute
+  // changing can potentially impact the matching rules.
+  ClearRuleMatchingState();
 }
 
 // Algorithm for IsFocusable:
@@ -958,8 +1030,8 @@
                                Event::kNotCancelable, document->window(),
                                this));
 
-  // Custom, not in any sepc.
-  InvalidateMatchingRulesRecursively();
+  // Custom, not in any spec.
+  ClearRuleMatchingState();
 }
 
 // Algorithm for RunUnFocusingSteps:
@@ -993,8 +1065,8 @@
                                Event::kNotCancelable, document->window(),
                                this));
 
-  // Custom, not in any sepc.
-  InvalidateMatchingRulesRecursively();
+  // Custom, not in any spec.
+  ClearRuleMatchingState();
 }
 
 void HTMLElement::SetDirectionality(const std::string& value) {
@@ -1249,6 +1321,9 @@
   DCHECK(document) << "Element should be attached to document in order to "
                       "participate in layout.";
 
+  // Verify that the matching rules for this element are valid. They should have
+  // been updated prior to UpdateComputedStyle() being called.
+  DCHECK(matching_rules_valid_);
   // If there is no previous computed style, there should also be no layout
   // boxes.
   DCHECK(computed_style() || NULL == layout_boxes());
@@ -1259,18 +1334,6 @@
   // invalid or no computed style has been created yet.
   bool generate_computed_style = !computed_style_valid_ || !computed_style();
 
-  // Update matching rules if necessary.
-  if (!matching_rules_valid_) {
-    dom_stat_tracker_->OnUpdateMatchingRules();
-    UpdateMatchingRules(this);
-
-    // Check for whether the matching rules have changed. If they have, then a
-    // new computed style must be generated from them.
-    if (!generate_computed_style && old_matching_rules_ != matching_rules_) {
-      generate_computed_style = true;
-    }
-  }
-
   // If any declared properties inherited from the parent are no longer valid,
   // then a new computed style must be generated with the updated inherited
   // values.
@@ -1325,29 +1388,35 @@
   // Update the displayed status of our ancestors.
   ancestors_are_displayed_ = ancestors_are_displayed;
 
-  // NOTE: Currently, pseudo element's computed styles are always generated. If
-  // this becomes a performance bottleneck, change the logic so that it only
-  // occurs when needed.
-
-  // Promote the matching rules for all known pseudo elements.
-  for (int pseudo_element_type = 0; pseudo_element_type < kMaxPseudoElementType;
-       ++pseudo_element_type) {
-    if (pseudo_elements_[pseudo_element_type]) {
-      dom_stat_tracker_->OnGeneratePseudoElementComputedStyle();
-      DoComputedStyleUpdate(
-          pseudo_elements_[pseudo_element_type]->matching_rules(),
-          &property_key_to_base_url_map, NULL, css_computed_style_declaration(),
-          root_computed_style, document->viewport_size(),
-          pseudo_elements_[pseudo_element_type]->computed_style(),
-          style_change_event_time,
-          pseudo_elements_[pseudo_element_type]->css_transitions(),
-          pseudo_elements_[pseudo_element_type]->css_animations(),
-          document->keyframes_map(),
-          old_is_displayed ? kAncestorsAreDisplayed : kAncestorsAreNotDisplayed,
-          IsDisplayed() ? kAncestorsAreDisplayed : kAncestorsAreNotDisplayed,
-          kIsPseudoElement, &invalidation_flags,
-          pseudo_elements_[pseudo_element_type]
-              ->css_computed_style_declaration());
+  // Process pseudo elements. They must have their computed style generated if
+  // either their owning HTML element's style was just generated or their
+  // computed style is invalid (this occurs when their matching rules change).
+  for (int type = 0; type < kMaxPseudoElementType; ++type) {
+    PseudoElement* type_pseudo_element =
+        pseudo_element(PseudoElementType(type));
+    if (type_pseudo_element) {
+      if (generate_computed_style ||
+          type_pseudo_element->computed_style_invalid()) {
+        dom_stat_tracker_->OnGeneratePseudoElementComputedStyle();
+        DoComputedStyleUpdate(
+            type_pseudo_element->matching_rules(),
+            &property_key_to_base_url_map, NULL,
+            css_computed_style_declaration(), root_computed_style,
+            document->viewport_size(), type_pseudo_element->computed_style(),
+            style_change_event_time, type_pseudo_element->css_transitions(),
+            type_pseudo_element->css_animations(), document->keyframes_map(),
+            old_is_displayed ? kAncestorsAreDisplayed
+                             : kAncestorsAreNotDisplayed,
+            IsDisplayed() ? kAncestorsAreDisplayed : kAncestorsAreNotDisplayed,
+            kIsPseudoElement, &invalidation_flags,
+            type_pseudo_element->css_computed_style_declaration());
+        type_pseudo_element->clear_computed_style_invalid();
+      } else {
+        // Update the inherited data if a new style was not generated. The
+        // ancestor data with inherited properties may have changed.
+        type_pseudo_element->css_computed_style_declaration()
+            ->UpdateInheritedData();
+      }
     }
   }
 
@@ -1374,6 +1443,19 @@
   }
 
   computed_style_valid_ = true;
+  pseudo_elements_computed_styles_valid_ = true;
+}
+
+void HTMLElement::SetPseudoElement(PseudoElementType type,
+                                   scoped_ptr<PseudoElement> pseudo_element) {
+  DCHECK_EQ(this, pseudo_element->parent_element());
+  DCHECK(type < kMaxPseudoElementType);
+  pseudo_elements_[type] = pseudo_element.Pass();
+  pseudo_elements_computed_styles_valid_ = false;
+}
+
+bool HTMLElement::AreComputedStylesValid() const {
+  return computed_style_valid_ && pseudo_elements_computed_styles_valid_;
 }
 
 bool HTMLElement::IsDesignated() const {
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index 23e23df..b3481ea 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -90,14 +90,50 @@
 //   https://www.w3.org/TR/html5/dom.html#htmlelement
 class HTMLElement : public Element, public cssom::MutationObserver {
  public:
-  typedef cssom::SelectorTree::NodeSet<12> MatchingNodes;
-  typedef cssom::SelectorTree::NodeSet<40> DescendantPotentialNodes;
-  typedef cssom::SelectorTree::NodeSet<8> FollowingSiblingPotentialNodes;
+  typedef cssom::SelectorTree::Nodes SelectorTreeNodes;
 
+  // Cached state used with rule matching to minimize the amount of work that
+  // must occur during UpdateMatchingRules() when little has changed.
   struct RuleMatchingState {
-    MatchingNodes matching_nodes;
-    DescendantPotentialNodes descendant_potential_nodes;
-    FollowingSiblingPotentialNodes following_sibling_potential_nodes;
+    RuleMatchingState()
+        : is_set(false),
+          are_descendant_nodes_dirty(true),
+          are_following_sibling_nodes_dirty(true) {}
+
+    // Fully clears the state but does not deallocate the vectors. This allows
+    // the vectors to be reused without additional allocations the next time
+    // they are needed and speeds up rule matching.
+    void Clear();
+
+    // Whether or not the rule matching state is set. When it is not set, the
+    // next call to UpdateMatchingRules() always generates new matching rules
+    // from the rule matching state.
+    bool is_set;
+
+    // The cached node state of the parent and previous sibling. Caching these
+    // allows the element to know the exact nodes that have changed and
+    // resultantly need to be checked during calls to UpdateMatchingRules().
+    SelectorTreeNodes parent_matching_nodes;
+    SelectorTreeNodes parent_descendant_nodes;
+
+    SelectorTreeNodes previous_sibling_matching_nodes;
+    SelectorTreeNodes previous_sibling_following_sibling_nodes;
+
+    // The element's current matching nodes, along with their parent nodes.
+    // These are kept in sync. This allows matching nodes to be removed when
+    // their parent nodes are no longer available to the element.
+    SelectorTreeNodes matching_nodes_parent_nodes;
+    SelectorTreeNodes matching_nodes;
+
+    // The nodes that are to be used by the element's descendants and following
+    // siblings during their own rule matching. These are generated by combining
+    // the nodes from the element's parent and previous sibling with the
+    // element's own matching nodes that contain the required combinator.
+    bool are_descendant_nodes_dirty;
+    SelectorTreeNodes descendant_nodes;
+
+    bool are_following_sibling_nodes_dirty;
+    SelectorTreeNodes following_sibling_nodes;
   };
 
   enum AncestorsAreDisplayed {
@@ -184,15 +220,25 @@
 
   // Rule matching related methods.
   //
+  // Returns the rule matching state of this element.
+  RuleMatchingState* rule_matching_state() { return &rule_matching_state_; }
+
+  void ClearRuleMatchingState();
+  void ClearRuleMatchingStateOnElementAndAncestors(
+      bool invalidate_tree_matching_rules);
+  void ClearRuleMatchingStateOnElementAndDescendants();
+
   // Returns the cached matching rules of this element.
   cssom::RulesWithCascadePrecedence* matching_rules() {
     return &matching_rules_;
   }
-  // Returns the rule matching state of this element.
-  RuleMatchingState* rule_matching_state() { return &rule_matching_state_; }
-  // Invalidates the matching rules and rule matching state in this element,
-  // its descendants and its siblings.
+
   void InvalidateMatchingRulesRecursively();
+  void UpdateMatchingRules();
+  void UpdateMatchingRulesRecursively();
+
+  void OnMatchingRulesModified();
+  void OnPseudoElementMatchingRulesModified();
 
   // Computed style related methods.
   //
@@ -208,15 +254,6 @@
     return css_computed_style_declaration_->data();
   }
 
-  void MarkDisplayNoneOnNodeAndDescendants() override;
-  void PurgeCachedBackgroundImagesOfNodeAndDescendants() override;
-  void InvalidateComputedStylesOfNodeAndDescendants() override;
-  void InvalidateLayoutBoxesOfNodeAndAncestors() override;
-  void InvalidateLayoutBoxesOfNodeAndDescendants() override;
-  void InvalidateLayoutBoxSizes() override;
-  void InvalidateLayoutBoxCrossReferences() override;
-  void InvalidateLayoutBoxRenderTreeNodes() override;
-
   // Updates the cached computed style of this element and its descendants.
   void UpdateComputedStyleRecursively(
       const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
@@ -235,6 +272,15 @@
       const base::TimeDelta& style_change_event_time,
       AncestorsAreDisplayed ancestor_is_displayed);
 
+  void MarkDisplayNoneOnNodeAndDescendants() override;
+  void PurgeCachedBackgroundImagesOfNodeAndDescendants() override;
+  void InvalidateComputedStylesOfNodeAndDescendants() override;
+  void InvalidateLayoutBoxesOfNodeAndAncestors() override;
+  void InvalidateLayoutBoxesOfNodeAndDescendants() override;
+  void InvalidateLayoutBoxSizes() override;
+  void InvalidateLayoutBoxCrossReferences() override;
+  void InvalidateLayoutBoxRenderTreeNodes() override;
+
   // Layout box related methods.
   //
   // The LayoutContainerBox gives the HTML Element an interface to the container
@@ -251,17 +297,16 @@
     return pseudo_elements_[type].get();
   }
 
-  void set_pseudo_element(PseudoElementType type,
-                          scoped_ptr<PseudoElement> pseudo_element) {
-    DCHECK_EQ(this, pseudo_element->parent_element());
-    DCHECK(type < kMaxPseudoElementType);
-    pseudo_elements_[type] = pseudo_element.Pass();
-  }
+  void SetPseudoElement(PseudoElementType type,
+                        scoped_ptr<PseudoElement> pseudo_element);
 
-  bool computed_style_valid() const { return computed_style_valid_; }
+  // Returns true if the element's computed style and all of its pseudo
+  // element's computed styles are valid.
+  bool AreComputedStylesValid() const;
   bool descendant_computed_styles_valid() const {
     return descendant_computed_styles_valid_;
   }
+
   bool matching_rules_valid() const { return matching_rules_valid_; }
   void set_matching_rules_valid() { matching_rules_valid_ = true; }
 
@@ -318,10 +363,13 @@
   // directionality does not invalidate the computed style.
   void SetDirectionality(const std::string& value);
 
-  // Invalidates the matching rules and rule matching state in this element and
+  // Invalidate the matching rules and rule matching state in this element and
   // its descendants. In the case where this is the the initial invalidation,
   // it will also invalidate the rule matching state of its siblings.
-  void InvalidateMatchingRulesRecursivelyInternal(bool is_initial_invalidation);
+  void InvalidateMatchingRulesRecursivelyInternal(bool is_initial_element);
+  // Fully clear the rule matching state of this element and optionally
+  // invalidate all of its descendants matching rules.
+  void ClearRuleMatchingStateInternal(bool invalidate_descendants);
 
   // Clear the list of active background images, and notify the animated image
   // tracker to stop the animations.
@@ -364,6 +412,9 @@
   // Keeps track of whether the HTML element's current computed style is out
   // of date or not.
   bool computed_style_valid_;
+  // Keeps track of whether the HTML element's pseudo element's computed styles
+  // are out of date or not.
+  bool pseudo_elements_computed_styles_valid_;
   // Keeps track of whether the HTML element's descendants' computed styles are
   // out of date or not.
   bool descendant_computed_styles_valid_;
@@ -382,10 +433,9 @@
   cssom::AnimationSet css_animations_;
 
   // The following fields are used in rule matching.
-  cssom::RulesWithCascadePrecedence old_matching_rules_;
-  cssom::RulesWithCascadePrecedence matching_rules_;
   RuleMatchingState rule_matching_state_;
   bool matching_rules_valid_;
+  cssom::RulesWithCascadePrecedence matching_rules_;
 
   // This contains information about the boxes generated from the element.
   scoped_ptr<LayoutBoxes> layout_boxes_;
diff --git a/src/cobalt/dom/html_element_context.cc b/src/cobalt/dom/html_element_context.cc
index 60d0e15..089d5b1 100644
--- a/src/cobalt/dom/html_element_context.cc
+++ b/src/cobalt/dom/html_element_context.cc
@@ -21,6 +21,7 @@
 
 HTMLElementContext::HTMLElementContext()
     : fetcher_factory_(NULL),
+      loader_factory_(NULL),
       css_parser_(NULL),
       dom_parser_(NULL),
       can_play_type_handler_(NULL),
@@ -42,7 +43,8 @@
 }
 
 HTMLElementContext::HTMLElementContext(
-    loader::FetcherFactory* fetcher_factory, cssom::CSSParser* css_parser,
+    loader::FetcherFactory* fetcher_factory,
+    loader::LoaderFactory* loader_factory, cssom::CSSParser* css_parser,
     Parser* dom_parser, media::CanPlayTypeHandler* can_play_type_handler,
     media::WebMediaPlayerFactory* web_media_player_factory,
     script::ScriptRunner* script_runner,
@@ -57,8 +59,10 @@
     loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
     const std::string& font_language_script,
     base::ApplicationState initial_application_state,
+    base::WaitableEvent* synchronous_loader_interrupt,
     float video_playback_rate_multiplier)
     : fetcher_factory_(fetcher_factory),
+      loader_factory_(loader_factory),
       css_parser_(css_parser),
       dom_parser_(dom_parser),
       can_play_type_handler_(can_play_type_handler),
@@ -77,6 +81,7 @@
       font_language_script_(font_language_script),
       page_visibility_state_(initial_application_state),
       video_playback_rate_multiplier_(video_playback_rate_multiplier),
+      synchronous_loader_interrupt_(synchronous_loader_interrupt),
       sync_load_thread_("Synchronous Load"),
       html_element_factory_(new HTMLElementFactory()) {
   sync_load_thread_.Start();
diff --git a/src/cobalt/dom/html_element_context.h b/src/cobalt/dom/html_element_context.h
index cab1c71..bb3d58f 100644
--- a/src/cobalt/dom/html_element_context.h
+++ b/src/cobalt/dom/html_element_context.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/cssom/css_parser.h"
@@ -50,7 +51,8 @@
 
   HTMLElementContext();
   HTMLElementContext(
-      loader::FetcherFactory* fetcher_factory, cssom::CSSParser* css_parser,
+      loader::FetcherFactory* fetcher_factory,
+      loader::LoaderFactory* loader_factory, cssom::CSSParser* css_parser,
       Parser* dom_parser, media::CanPlayTypeHandler* can_play_type_handler,
       media::WebMediaPlayerFactory* web_media_player_factory,
       script::ScriptRunner* script_runner,
@@ -65,11 +67,14 @@
       loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
       const std::string& font_language_script,
       base::ApplicationState initial_application_state,
+      base::WaitableEvent* synchronous_loader_interrupt,
       float video_playback_rate_multiplier = 1.0);
   ~HTMLElementContext();
 
   loader::FetcherFactory* fetcher_factory() { return fetcher_factory_; }
 
+  loader::LoaderFactory* loader_factory() { return loader_factory_; }
+
   cssom::CSSParser* css_parser() { return css_parser_; }
 
   Parser* dom_parser() { return dom_parser_; }
@@ -99,6 +104,10 @@
     return resource_provider_;
   }
 
+  base::WaitableEvent* synchronous_loader_interrupt() {
+    return synchronous_loader_interrupt_;
+  }
+
   loader::image::AnimatedImageTracker* animated_image_tracker() const {
     return animated_image_tracker_;
   }
@@ -138,6 +147,7 @@
 
  private:
   loader::FetcherFactory* const fetcher_factory_;
+  loader::LoaderFactory* const loader_factory_;
   cssom::CSSParser* const css_parser_;
   Parser* const dom_parser_;
   media::CanPlayTypeHandler* can_play_type_handler_;
@@ -156,6 +166,7 @@
   const std::string font_language_script_;
   page_visibility::PageVisibilityState page_visibility_state_;
   const float video_playback_rate_multiplier_;
+  base::WaitableEvent* synchronous_loader_interrupt_ = nullptr;
 
   base::Thread sync_load_thread_;
   scoped_ptr<HTMLElementFactory> html_element_factory_;
diff --git a/src/cobalt/dom/html_element_factory_test.cc b/src/cobalt/dom/html_element_factory_test.cc
index 78c9687..377382a 100644
--- a/src/cobalt/dom/html_element_factory_test.cc
+++ b/src/cobalt/dom/html_element_factory_test.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/html_element_factory.h"
 
 #include "base/message_loop.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/html_anchor_element.h"
@@ -40,6 +41,7 @@
 #include "cobalt/dom/testing/stub_script_runner.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -49,11 +51,13 @@
  protected:
   HTMLElementFactoryTest()
       : fetcher_factory_(NULL /* network_module */),
+        loader_factory_(&fetcher_factory_, NULL /* resource loader */,
+                        base::kThreadPriority_Default),
         dom_parser_(new dom_parser::Parser()),
         dom_stat_tracker_(new DomStatTracker("HTMLElementFactoryTest")),
         html_element_context_(
-            &fetcher_factory_, &stub_css_parser_, dom_parser_.get(),
-            NULL /* can_play_type_handler */,
+            &fetcher_factory_, &loader_factory_, &stub_css_parser_,
+            dom_parser_.get(), NULL /* can_play_type_handler */,
             NULL /* web_media_player_factory */, &stub_script_runner_,
             NULL /* script_value_factory */, NULL /* media_source_registry */,
             NULL /* resource_provider */, NULL /* animated_image_tracker */,
@@ -61,11 +65,13 @@
             NULL /* reduced_image_cache_capacity_manager */,
             NULL /* remote_typeface_cache */, NULL /* mesh_cache */,
             dom_stat_tracker_.get(), "" /* language */,
-            base::kApplicationStateStarted),
+            base::kApplicationStateStarted,
+            NULL /* synchronous_loader_interrupt */),
         document_(new Document(&html_element_context_)) {}
   ~HTMLElementFactoryTest() override {}
 
   loader::FetcherFactory fetcher_factory_;
+  loader::LoaderFactory loader_factory_;
   scoped_ptr<Parser> dom_parser_;
   testing::StubCSSParser stub_css_parser_;
   testing::StubScriptRunner stub_script_runner_;
diff --git a/src/cobalt/dom/html_element_test.cc b/src/cobalt/dom/html_element_test.cc
index cc2362b..5f10c02 100644
--- a/src/cobalt/dom/html_element_test.cc
+++ b/src/cobalt/dom/html_element_test.cc
@@ -112,10 +112,10 @@
  protected:
   HTMLElementTest()
       : dom_stat_tracker_(new DomStatTracker("HTMLElementTest")),
-        html_element_context_(NULL, &css_parser_, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+        html_element_context_(NULL, NULL, &css_parser_, NULL, NULL, NULL, NULL,
+                              NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               dom_stat_tracker_.get(), "",
-                              base::kApplicationStateStarted),
+                              base::kApplicationStateStarted, NULL),
         document_(new Document(&html_element_context_)) {}
   ~HTMLElementTest() override {}
 
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc
index d8dbd04..d139c38 100644
--- a/src/cobalt/dom/html_link_element.cc
+++ b/src/cobalt/dom/html_link_element.cc
@@ -173,16 +173,20 @@
   }
 
   request_mode_ = GetRequestMode(GetAttribute("crossOrigin"));
-  loader_ = make_scoped_ptr(new loader::Loader(
-      base::Bind(
-          &loader::FetcherFactory::CreateSecureFetcher,
-          base::Unretained(document->html_element_context()->fetcher_factory()),
-          absolute_url_, csp_callback, request_mode_,
-          document->location() ? document->location()->GetOriginAsObject()
-                               : loader::Origin()),
-      scoped_ptr<loader::Decoder>(new loader::TextDecoder(
-          base::Bind(&HTMLLinkElement::OnLoadingDone, base::Unretained(this)))),
-      base::Bind(&HTMLLinkElement::OnLoadingError, base::Unretained(this))));
+
+  DCHECK(!loader_);
+  loader::Origin origin = document->location()
+                              ? document->location()->GetOriginAsObject()
+                              : loader::Origin();
+
+  loader_ = html_element_context()->loader_factory()
+                ->CreateLinkLoader(
+                    absolute_url_, origin, csp_callback, request_mode_,
+                    base::Bind(&HTMLLinkElement::OnLoadingDone,
+                               base::Unretained(this)),
+                    base::Bind(&HTMLLinkElement::OnLoadingError,
+                               base::Unretained(this)))
+                .Pass();
 }
 
 void HTMLLinkElement::OnLoadingDone(const loader::Origin& last_url_origin,
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 5aac100..b7b404f 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -1006,21 +1006,21 @@
 
     media_source_ =
         html_element_context()->media_source_registry()->Retrieve(url.spec());
-    // If media_source_ is NULL, the player will try to load it as a normal
-    // media resource url and throw a DOM exception when it fails.
-    if (media_source_) {
-#if defined(COBALT_MEDIA_SOURCE_2016)
-      media_source_->AttachToElement(this);
-#else   // defined(COBALT_MEDIA_SOURCE_2016)
-      media_source_->SetPlayer(player_.get());
-#endif  // defined(COBALT_MEDIA_SOURCE_2016)
-      media_source_url_ = url;
-    } else {
+    if (!media_source_) {
       NoneSupported("Media source is NULL.");
       return;
     }
+#if defined(COBALT_MEDIA_SOURCE_2016)
+    if (!media_source_->AttachToElement(this)) {
+      media_source_ = nullptr;
+      NoneSupported("Unable to attach media source.");
+      return;
+    }
+#else   // defined(COBALT_MEDIA_SOURCE_2016)
+    media_source_->SetPlayer(player_.get());
+#endif  // defined(COBALT_MEDIA_SOURCE_2016)
+    media_source_url_ = url;
   }
-
   // The resource fetch algorithm
   network_state_ = kNetworkLoading;
 
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index 50688c4..5336ed2 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -71,7 +71,9 @@
       inline_script_location_(GetSourceLocationName(), 1, 1),
       is_sync_load_successful_(false),
       prevent_garbage_collection_count_(0),
-      should_execute_(true) {
+      should_execute_(true),
+      synchronous_loader_interrupt_(
+          document->html_element_context()->synchronous_loader_interrupt()) {
   DCHECK(document->html_element_context()->script_runner());
 }
 
@@ -290,10 +292,16 @@
       // The element is the pending parsing-blocking script of the Document of
       // the parser that created the element. (There can only be one such script
       // per Document at a time.)
+
+      // This variable will be set to true in the completion callback for the
+      // loader below.  If that completion callback never fires, the variable
+      // will stay false.  This can happen if the loader was interrupted, or
+      // failed for another reason.
       is_sync_load_successful_ = false;
 
       loader::LoadSynchronously(
           html_element_context()->sync_load_thread()->message_loop(),
+          synchronous_loader_interrupt_,
           base::Bind(
               &loader::FetcherFactory::CreateSecureFetcher,
               base::Unretained(html_element_context()->fetcher_factory()), url_,
@@ -338,17 +346,19 @@
       // once the resource has been fetched (defined above) has been run.
       document_->IncreaseLoadingCounter();
 
-      loader_.reset(new loader::Loader(
-          base::Bind(
-              &loader::FetcherFactory::CreateSecureFetcher,
-              base::Unretained(html_element_context()->fetcher_factory()), url_,
-              csp_callback, request_mode_,
-              document_->location() ? document_->location()->GetOriginAsObject()
-                                    : loader::Origin()),
-          scoped_ptr<loader::Decoder>(new loader::TextDecoder(base::Bind(
-              &HTMLScriptElement::OnLoadingDone, base::Unretained(this)))),
-          base::Bind(&HTMLScriptElement::OnLoadingError,
-                     base::Unretained(this))));
+      loader::Origin origin = document_->location()
+                                  ? document_->location()->GetOriginAsObject()
+                                  : loader::Origin();
+
+      loader_ = html_element_context()
+                    ->loader_factory()
+                    ->CreateScriptLoader(
+                        url_, origin, csp_callback,
+                        base::Bind(&HTMLScriptElement::OnLoadingDone,
+                                   base::Unretained(this)),
+                        base::Bind(&HTMLScriptElement::OnLoadingError,
+                                   base::Unretained(this)))
+                    .Pass();
     } break;
     case 5: {
       // This is an asynchronous script. Prevent garbage collection until
@@ -365,17 +375,21 @@
       // The element must be added to the set of scripts that will execute as
       // soon as possible of the Document of the script element at the time the
       // prepare a script algorithm started.
-      loader_.reset(new loader::Loader(
-          base::Bind(
-              &loader::FetcherFactory::CreateSecureFetcher,
-              base::Unretained(html_element_context()->fetcher_factory()), url_,
-              csp_callback, request_mode_,
-              document_->location() ? document_->location()->GetOriginAsObject()
-                                    : loader::Origin()),
-          scoped_ptr<loader::Decoder>(new loader::TextDecoder(base::Bind(
-              &HTMLScriptElement::OnLoadingDone, base::Unretained(this)))),
-          base::Bind(&HTMLScriptElement::OnLoadingError,
-                     base::Unretained(this))));
+      DCHECK(!loader_);
+
+      loader::Origin origin = document_->location()
+                                  ? document_->location()->GetOriginAsObject()
+                                  : loader::Origin();
+
+      loader_ = html_element_context()
+                    ->loader_factory()
+                    ->CreateScriptLoader(
+                        url_, origin, csp_callback,
+                        base::Bind(&HTMLScriptElement::OnLoadingDone,
+                                   base::Unretained(this)),
+                        base::Bind(&HTMLScriptElement::OnLoadingError,
+                                   base::Unretained(this)))
+                    .Pass();
     } break;
     case 6: {
       // Otherwise.
diff --git a/src/cobalt/dom/html_script_element.h b/src/cobalt/dom/html_script_element.h
index d6c42ff..d507712 100644
--- a/src/cobalt/dom/html_script_element.h
+++ b/src/cobalt/dom/html_script_element.h
@@ -19,6 +19,7 @@
 
 #include "base/memory/scoped_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/dom/html_element.h"
@@ -149,6 +150,8 @@
   // javascript parser takes in to record if the error reqort should be muted
   // due to cross-origin fetched script.
   loader::Origin fetched_last_url_origin_;
+
+  base::WaitableEvent* synchronous_loader_interrupt_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/local_storage_database.cc b/src/cobalt/dom/local_storage_database.cc
index 02fc976..e573e81 100644
--- a/src/cobalt/dom/local_storage_database.cc
+++ b/src/cobalt/dom/local_storage_database.cc
@@ -18,120 +18,47 @@
 #include "cobalt/dom/storage_area.h"
 #include "cobalt/storage/storage_manager.h"
 #include "nb/memory_scope.h"
-#include "sql/statement.h"
 
 namespace cobalt {
 namespace dom {
 
 namespace {
 
-const int kOriginalLocalStorageSchemaVersion = 1;
-const int kLatestLocalStorageSchemaVersion = 1;
-
-void SqlInit(storage::SqlContext* sql_context) {
-  TRACK_MEMORY_SCOPE("Storage");
-  TRACE_EVENT0("cobalt::storage", "LocalStorage::SqlInit()");
-  sql::Connection* conn = sql_context->sql_connection();
-  int schema_version;
-  bool table_exists =
-      sql_context->GetSchemaVersion("LocalStorageTable", &schema_version);
-
-  if (table_exists) {
-    if (schema_version == storage::StorageManager::kSchemaTableIsNew) {
-      // This savegame predates the existence of the schema table.
-      // Since the local-storage table did not change between the initial
-      // release of the app and the introduction of the schema table, assume
-      // that this existing local-storage table is schema version 1.  This
-      // avoids a loss of data on upgrade.
-
-      DLOG(INFO) << "Updating LocalStorageTable schema version to "
-                 << kOriginalLocalStorageSchemaVersion;
-      sql_context->UpdateSchemaVersion("LocalStorageTable",
-                                       kOriginalLocalStorageSchemaVersion);
-    } else if (schema_version == storage::StorageManager::kSchemaVersionLost) {
-      // Since there has only been one schema so far, treat this the same as
-      // kSchemaTableIsNew.  When there are multiple schemas in the wild,
-      // we may want to drop the table instead.
-      sql_context->UpdateSchemaVersion("LocalStorageTable",
-                                       kOriginalLocalStorageSchemaVersion);
-    }
-  } else {
-    // The table does not exist, so create it in its latest form.
-    sql::Statement create_table(conn->GetUniqueStatement(
-        "CREATE TABLE LocalStorageTable ("
-        "  site_identifier TEXT, "
-        "  key TEXT, "
-        "  value TEXT NOT NULL ON CONFLICT FAIL, "
-        "  UNIQUE(site_identifier, key) ON CONFLICT REPLACE"
-        ")"));
-    bool ok = create_table.Run();
-    DCHECK(ok);
-    sql_context->UpdateSchemaVersion("LocalStorageTable",
-                                     kLatestLocalStorageSchemaVersion);
-  }
+void LocalStorageInit(const storage::MemoryStore& memory_store) {
+  LOG(INFO) << "local_storage Init";
+  UNREFERENCED_PARAMETER(memory_store);
 }
 
-void SqlReadValues(const std::string& id,
-                   const LocalStorageDatabase::ReadCompletionCallback& callback,
-                   storage::SqlContext* sql_context) {
+void LocalStorageReadValues(
+    const std::string& id,
+    const LocalStorageDatabase::ReadCompletionCallback& callback,
+    const storage::MemoryStore& memory_store) {
+  LOG(INFO) << "LocalStorageReadValues";
   TRACK_MEMORY_SCOPE("Storage");
-  scoped_ptr<StorageArea::StorageMap> values(new StorageArea::StorageMap);
-  sql::Connection* conn = sql_context->sql_connection();
-  sql::Statement get_values(conn->GetCachedStatement(
-      SQL_FROM_HERE,
-      "SELECT key, value FROM LocalStorageTable WHERE site_identifier = ?"));
-  get_values.BindString(0, id);
-  while (get_values.Step()) {
-    // TODO: In Steel, these were string16.
-    std::string key(get_values.ColumnString(0));
-    std::string value(get_values.ColumnString(1));
-    values->insert(std::make_pair(key, value));
-  }
+
+  scoped_ptr<storage::MemoryStore::LocalStorageMap> values(
+      new storage::MemoryStore::LocalStorageMap);
+  memory_store.ReadAllLocalStorage(id, values.get());
   callback.Run(values.Pass());
 }
 
-void SqlWrite(const std::string& id, const std::string& key,
-              const std::string& value, storage::SqlContext* sql_context) {
+void LocalStorageWrite(const std::string& id, const std::string& key,
+                       const std::string& value,
+                       storage::MemoryStore* memory_store) {
   TRACK_MEMORY_SCOPE("Storage");
-  sql::Connection* conn = sql_context->sql_connection();
-  sql::Statement write_statement(conn->GetCachedStatement(
-      SQL_FROM_HERE,
-      "INSERT INTO LocalStorageTable (site_identifier, key, value) "
-      "VALUES (?, ?, ?)"));
-  write_statement.BindString(0, id);
-  write_statement.BindString(1, key);
-  write_statement.BindString(2, value);
-  bool ok = write_statement.Run();
-  DCHECK(ok);
-  sql_context->FlushOnChange();
+  memory_store->WriteToLocalStorage(id, key, value);
 }
 
-void SqlDelete(const std::string& id, const std::string& key,
-               storage::SqlContext* sql_context) {
+void LocalStorageDelete(const std::string& id, const std::string& key,
+                        storage::MemoryStore* memory_store) {
   TRACK_MEMORY_SCOPE("Storage");
-  sql::Connection* conn = sql_context->sql_connection();
-  sql::Statement delete_statement(
-      conn->GetCachedStatement(SQL_FROM_HERE,
-                               "DELETE FROM LocalStorageTable "
-                               "WHERE site_identifier = ? AND key = ?"));
-  delete_statement.BindString(0, id);
-  delete_statement.BindString(1, key);
-  bool ok = delete_statement.Run();
-  DCHECK(ok);
-  sql_context->FlushOnChange();
+  memory_store->DeleteFromLocalStorage(id, key);
 }
 
-void SqlClear(const std::string& id, storage::SqlContext* sql_context) {
+void LocalStorageClear(const std::string& id,
+                       storage::MemoryStore* memory_store) {
   TRACK_MEMORY_SCOPE("Storage");
-  sql::Connection* conn = sql_context->sql_connection();
-  sql::Statement clear_statement(
-      conn->GetCachedStatement(SQL_FROM_HERE,
-                               "DELETE FROM LocalStorageTable "
-                               "WHERE site_identifier = ?"));
-  clear_statement.BindString(0, id);
-  bool ok = clear_statement.Run();
-  DCHECK(ok);
-  sql_context->FlushOnChange();
+  memory_store->ClearLocalStorage(id);
 }
 }  // namespace
 
@@ -142,7 +69,7 @@
 // a potential wait while the storage manager loads from disk.
 void LocalStorageDatabase::Init() {
   if (!initialized_) {
-    storage_->GetSqlContext(base::Bind(&SqlInit));
+    storage_->WithReadOnlyMemoryStore(base::Bind(&LocalStorageInit));
     initialized_ = true;
   }
 }
@@ -151,28 +78,32 @@
                                    const ReadCompletionCallback& callback) {
   TRACK_MEMORY_SCOPE("Storage");
   Init();
-  storage_->GetSqlContext(base::Bind(&SqlReadValues, id, callback));
+  storage_->WithReadOnlyMemoryStore(
+      base::Bind(&LocalStorageReadValues, id, callback));
 }
 
 void LocalStorageDatabase::Write(const std::string& id, const std::string& key,
                                  const std::string& value) {
   TRACK_MEMORY_SCOPE("Storage");
   Init();
-  storage_->GetSqlContext(base::Bind(&SqlWrite, id, key, value));
+  storage_->WithMemoryStore(base::Bind(&LocalStorageWrite, id, key, value));
 }
 
 void LocalStorageDatabase::Delete(const std::string& id,
                                   const std::string& key) {
+  TRACK_MEMORY_SCOPE("Storage");
   Init();
-  storage_->GetSqlContext(base::Bind(&SqlDelete, id, key));
+  storage_->WithMemoryStore(base::Bind(&LocalStorageDelete, id, key));
 }
 
 void LocalStorageDatabase::Clear(const std::string& id) {
+  TRACK_MEMORY_SCOPE("Storage");
   Init();
-  storage_->GetSqlContext(base::Bind(&SqlClear, id));
+  storage_->WithMemoryStore(base::Bind(&LocalStorageClear, id));
 }
 
 void LocalStorageDatabase::Flush(const base::Closure& callback) {
+  TRACK_MEMORY_SCOPE("Storage");
   storage_->FlushNow(callback);
 }
 
diff --git a/src/cobalt/dom/mutation_observer.cc b/src/cobalt/dom/mutation_observer.cc
index 228b9d4..8cc0ba2 100644
--- a/src/cobalt/dom/mutation_observer.cc
+++ b/src/cobalt/dom/mutation_observer.cc
@@ -157,7 +157,7 @@
 
 void MutationObserver::TraceMembers(script::Tracer* tracer) {
   tracer->TraceItems(observed_nodes_);
-  tracer->TraceSequence(record_queue_);
+  tracer->TraceItems(record_queue_);
 }
 
 void MutationObserver::TrackObservedNode(const scoped_refptr<dom::Node>& node) {
diff --git a/src/cobalt/dom/mutation_observer_task_manager.h b/src/cobalt/dom/mutation_observer_task_manager.h
index dbfeacc..8f815d1 100644
--- a/src/cobalt/dom/mutation_observer_task_manager.h
+++ b/src/cobalt/dom/mutation_observer_task_manager.h
@@ -19,7 +19,6 @@
 
 #include "base/hash_tables.h"
 #include "base/threading/thread_checker.h"
-#include "cobalt/script/global_environment.h"
 #include "cobalt/script/tracer.h"
 
 namespace cobalt {
@@ -38,12 +37,6 @@
  public:
   MutationObserverTaskManager() : task_posted_(false) {}
 
-  void RegisterAsTracingRoot(script::GlobalEnvironment* global_environment) {
-    // Note that we only add ourselves, and never remove ourselves, as we will
-    // actually outlive the web module.
-    global_environment->AddRoot(this);
-  }
-
   // These should be called in the constructor/destructor of the
   // MutationObserver.
   void OnMutationObserverCreated(MutationObserver* observer);
diff --git a/src/cobalt/dom/navigator.h b/src/cobalt/dom/navigator.h
index f195bdb..4bd92c7 100644
--- a/src/cobalt/dom/navigator.h
+++ b/src/cobalt/dom/navigator.h
@@ -80,6 +80,10 @@
   DEFINE_WRAPPABLE_TYPE(Navigator);
   void TraceMembers(script::Tracer* tracer) override;
 
+  void SetEnvironmentSettings(script::EnvironmentSettings* settings) {
+    media_devices_->SetEnvironmentSettings(settings);
+  }
+
  private:
   ~Navigator() override {}
 
diff --git a/src/cobalt/dom/node_list_live_test.cc b/src/cobalt/dom/node_list_live_test.cc
index 3c11bec..5e18279 100644
--- a/src/cobalt/dom/node_list_live_test.cc
+++ b/src/cobalt/dom/node_list_live_test.cc
@@ -28,9 +28,9 @@
   NodeListLiveTest()
       : dom_stat_tracker_("NodeListLiveTest"),
         html_element_context_(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL,
+                              NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               &dom_stat_tracker_, "",
-                              base::kApplicationStateStarted),
+                              base::kApplicationStateStarted, NULL),
         document_(new Document(&html_element_context_)) {}
 
   ~NodeListLiveTest() override {}
diff --git a/src/cobalt/dom/node_list_test.cc b/src/cobalt/dom/node_list_test.cc
index 48826be..fbdd198 100644
--- a/src/cobalt/dom/node_list_test.cc
+++ b/src/cobalt/dom/node_list_test.cc
@@ -28,9 +28,9 @@
   NodeListTest()
       : dom_stat_tracker_(new DomStatTracker("NodeListTest")),
         html_element_context_(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL,
+                              NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               dom_stat_tracker_.get(), "",
-                              base::kApplicationStateStarted),
+                              base::kApplicationStateStarted, NULL),
         document_(new Document(&html_element_context_)) {}
 
   ~NodeListTest() override {}
diff --git a/src/cobalt/dom/on_error_event_listener.cc b/src/cobalt/dom/on_error_event_listener.cc
new file mode 100644
index 0000000..6af5697
--- /dev/null
+++ b/src/cobalt/dom/on_error_event_listener.cc
@@ -0,0 +1,50 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/on_error_event_listener.h"
+
+#include <string>
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/error_event.h"
+#include "cobalt/dom/event_target.h"
+#include "cobalt/dom/script_event_log.h"
+
+namespace cobalt {
+namespace dom {
+
+base::optional<bool> OnErrorEventListener::HandleEvent(
+    const scoped_refptr<script::Wrappable>& callback_this,
+    const scoped_refptr<Event>& event, bool* had_exception,
+    bool unpack_error_events) const {
+  if (unpack_error_events) {
+    // Only ErrorEvents should be dispatched to OnErrorEventListeners.
+    ErrorEvent* error_event =
+        base::polymorphic_downcast<ErrorEvent*>(event.get());
+
+    // Unpack the error event into its components and pass those down.
+    return HandleEvent(callback_this, EventOrMessage(error_event->message()),
+                       error_event->filename(), error_event->lineno(),
+                       error_event->colno(), error_event->error(),
+                       had_exception);
+  } else {
+    return HandleEvent(callback_this, EventOrMessage(event), std::string(), 0,
+                       0, NULL, had_exception);
+  }
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/on_error_event_listener.h b/src/cobalt/dom/on_error_event_listener.h
new file mode 100644
index 0000000..411bdb7
--- /dev/null
+++ b/src/cobalt/dom/on_error_event_listener.h
@@ -0,0 +1,56 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_ON_ERROR_EVENT_LISTENER_H_
+#define COBALT_DOM_ON_ERROR_EVENT_LISTENER_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/dom/event_listener.h"
+#include "cobalt/script/union_type.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace dom {
+
+// This interface exists to support the special case OnErrorEventHandler:
+//   https://html.spec.whatwg.org/#onerroreventhandler
+class OnErrorEventListener {
+ public:
+  using EventOrMessage = script::UnionType2<scoped_refptr<Event>, std::string>;
+
+  // The public interface simply accepts the error event, which will be
+  // translated to the more complicated IDL handleEvent interface which is
+  // marked protected.  During that translation, the event parameters may
+  // be unpacked if the |unpack_error_events| flag is true.
+  base::optional<bool> HandleEvent(
+      const scoped_refptr<script::Wrappable>& callback_this,
+      const scoped_refptr<Event>& event, bool* had_exception,
+      bool unpack_error_events) const;
+
+ protected:
+  virtual base::optional<bool> HandleEvent(
+      const scoped_refptr<script::Wrappable>& callback_this,
+      EventOrMessage message, const std::string& filename, uint32 lineno,
+      uint32 colno, const script::ValueHandleHolder* error,
+      bool* had_exception) const = 0;
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_ON_ERROR_EVENT_LISTENER_H_
diff --git a/src/cobalt/dom/on_error_event_listener.idl b/src/cobalt/dom/on_error_event_listener.idl
new file mode 100644
index 0000000..d393f68
--- /dev/null
+++ b/src/cobalt/dom/on_error_event_listener.idl
@@ -0,0 +1,27 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This interface exists to support the special case OnErrorEventHandler:
+//   https://html.spec.whatwg.org/#onerroreventhandler
+
+callback interface OnErrorEventListener {
+  // TODO: Because the EventHandler type is typedef'd as a nullable
+  // EventListener, we need to allow the return of a boolean value. The value
+  // of this will only be checked if the EventListener was added as an
+  // attribute (not through addEventListener).
+  boolean? handleEvent(
+      (Event or DOMString) event, optional DOMString filename,
+      optional unsigned long lineno, optional unsigned long colno,
+      optional any error);
+};
diff --git a/src/cobalt/dom/on_screen_keyboard.idl b/src/cobalt/dom/on_screen_keyboard.idl
index 036b012..ed3761b 100644
--- a/src/cobalt/dom/on_screen_keyboard.idl
+++ b/src/cobalt/dom/on_screen_keyboard.idl
@@ -15,7 +15,9 @@
 // Custom API for hiding, showing, focusing, blurring, and receiving input
 // from an on screen keyboard.
 
-interface OnScreenKeyboard : EventTarget {
+[
+  Conditional=COBALT_ENABLE_ON_SCREEN_KEYBOARD
+] interface OnScreenKeyboard : EventTarget {
   Promise<void> show();
   Promise<void> hide();
   Promise<void> focus();
diff --git a/src/cobalt/dom/on_screen_keyboard_test.cc b/src/cobalt/dom/on_screen_keyboard_test.cc
index cbece44..565086f 100644
--- a/src/cobalt/dom/on_screen_keyboard_test.cc
+++ b/src/cobalt/dom/on_screen_keyboard_test.cc
@@ -17,6 +17,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/bindings/testing/utils.h"
 #include "cobalt/css_parser/parser.h"
 #include "cobalt/dom/local_storage_database.h"
@@ -24,6 +25,7 @@
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
@@ -177,6 +179,8 @@
         css_parser_(css_parser::Parser::Create()),
         dom_parser_(new dom_parser::Parser(mock_error_callback_)),
         fetcher_factory_(new loader::FetcherFactory(NULL)),
+        loader_factory_(new loader::LoaderFactory(
+            fetcher_factory_.get(), NULL, base::kThreadPriority_Default)),
         local_storage_database_(NULL),
         url_("about:blank"),
         engine_(script::JavaScriptEngine::CreateEngine()),
@@ -184,8 +188,9 @@
         on_screen_keyboard_bridge_(new OnScreenKeyboardMockBridge()),
         window_(new Window(
             1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
-            dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
-            NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL,
+            dom_parser_.get(), fetcher_factory_.get(), loader_factory_.get(),
+            NULL, NULL, NULL, NULL, NULL, NULL, &local_storage_database_, NULL,
+            NULL, NULL, NULL,
             global_environment_
                 ->script_value_factory() /* script_value_factory */,
             NULL, NULL, url_, "", "en-US", "en",
@@ -200,7 +205,8 @@
             on_screen_keyboard_bridge_.get(), NULL, NULL,
             dom::Window::OnStartDispatchEventCallback(),
             dom::Window::OnStopDispatchEventCallback(),
-            dom::ScreenshotManager::ProvideScreenshotFunctionCallback())) {
+            dom::ScreenshotManager::ProvideScreenshotFunctionCallback(),
+            NULL)) {
     global_environment_->CreateGlobalObject(window_,
                                             environment_settings_.get());
     on_screen_keyboard_bridge_->window_ = window_;
@@ -241,6 +247,7 @@
   scoped_ptr<css_parser::Parser> css_parser_;
   scoped_ptr<dom_parser::Parser> dom_parser_;
   scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+  scoped_ptr<loader::LoaderFactory> loader_factory_;
   dom::LocalStorageDatabase local_storage_database_;
   GURL url_;
 
@@ -266,39 +273,40 @@
 
 }  // namespace
 
+#if SB_HAS(ON_SCREEN_KEYBOARD)
 TEST_F(OnScreenKeyboardTest, ObjectExists) {
   std::string result;
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard;", &result));
 
   EXPECT_TRUE(bindings::testing::IsAcceptablePrototypeString("OnScreenKeyboard",
                                                              result));
 
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.show", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.show;", &result));
   EXPECT_PRED_FORMAT2(::testing::IsSubstring, "function show()",
                       result.c_str());
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.hide", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.hide;", &result));
   EXPECT_PRED_FORMAT2(::testing::IsSubstring, "function hide()",
                       result.c_str());
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.focus", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.focus;", &result));
   EXPECT_PRED_FORMAT2(::testing::IsSubstring, "function focus()",
                       result.c_str());
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.blur", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.blur;", &result));
   EXPECT_PRED_FORMAT2(::testing::IsSubstring, "function blur()",
                       result.c_str());
 
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.keepFocus", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.keepFocus;", &result));
   EXPECT_STREQ("false", result.c_str());
 
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.data", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.data;", &result));
   EXPECT_STREQ("", result.c_str());
 
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onshow", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onshow;", &result));
   EXPECT_STREQ("null", result.c_str());
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onhide", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onhide;", &result));
   EXPECT_STREQ("null", result.c_str());
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onblur", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onblur;", &result));
   EXPECT_STREQ("null", result.c_str());
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onfocus", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.onfocus;", &result));
   EXPECT_STREQ("null", result.c_str());
 }
 
@@ -438,7 +446,7 @@
       logString = 'show';
     };
     promise = window.onScreenKeyboard.show();
-    logString
+    logString;
   )";
   for (int i = 0; i < 3; ++i) {
     std::string result;
@@ -463,7 +471,7 @@
         logString2 = 'show2';
       });
     let promise = window.onScreenKeyboard.show();
-    logString1
+    logString1;
   )";
   EXPECT_TRUE(EvaluateScript(script, &result));
   EXPECT_EQ("show1", result);
@@ -484,7 +492,7 @@
       logString = 'hide';
     };
     promise = window.onScreenKeyboard.hide();
-    logString
+    logString;
   )";
   for (int i = 0; i < 3; ++i) {
     std::string result;
@@ -508,7 +516,7 @@
         logString2 = 'hide2';
       });
     let promise = window.onScreenKeyboard.hide();
-    logString1
+    logString1;
   )";
   EXPECT_TRUE(EvaluateScript(script, &result));
   EXPECT_EQ("hide1", result);
@@ -529,7 +537,7 @@
       logString = 'focus';
     };
     promise = window.onScreenKeyboard.focus();
-    logString
+    logString;
   )";
   for (int i = 0; i < 3; ++i) {
     std::string result;
@@ -553,7 +561,7 @@
         logString2 = 'focus2';
       });
     let promise = window.onScreenKeyboard.focus();
-    logString1
+    logString1;
   )";
   EXPECT_TRUE(EvaluateScript(script, &result));
   EXPECT_EQ("focus1", result);
@@ -574,7 +582,7 @@
       logString = 'blur';
     };
     promise = window.onScreenKeyboard.blur();
-    logString
+    logString;
   )";
   for (int i = 0; i < 3; ++i) {
     std::string result;
@@ -598,7 +606,7 @@
         logString2 = 'blur2';
       });
     let promise = window.onScreenKeyboard.blur();
-    logString1
+    logString1;
   )";
   EXPECT_TRUE(EvaluateScript(script, &result));
   EXPECT_EQ("blur1", result);
@@ -634,6 +642,36 @@
   )";
   EXPECT_TRUE(EvaluateScript(script, NULL));
 }
+#else   // SB_HAS(ON_SCREEN_KEYBOARD)
+TEST_F(OnScreenKeyboardTest, ObjectDoesntExist) {
+  std::string result;
+
+  EXPECT_TRUE(
+      EvaluateScript("window.hasOwnProperty('onScreenKeyboard');", &result));
+  EXPECT_EQ("false", result);
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard;", &result));
+  EXPECT_FALSE(bindings::testing::IsAcceptablePrototypeString(
+      "OnScreenKeyboard", result));
+  EXPECT_TRUE(EvaluateScript("typeof window.onScreenKeyboard === 'undefined';",
+                             &result));
+  EXPECT_EQ("true", result);
+
+  // We should be able to assign anything we like to window.onScreenKeyboard.
+  const char number_script[] = R"(
+    window.onScreenKeyboard = 42;
+    window.onScreenKeyboard;
+  )";
+  EXPECT_TRUE(EvaluateScript(number_script, &result));
+  EXPECT_EQ("42", result);
+
+  const char object_script[] = R"(
+    window.onScreenKeyboard = {foo: 'bar'};
+    window.onScreenKeyboard.hasOwnProperty('foo');
+  )";
+  EXPECT_TRUE(EvaluateScript(object_script, &result));
+  EXPECT_EQ("true", result);
+}
+#endif  // SB_HAS(ON_SCREEN_KEYBOARD)
 
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/pointer_state.cc b/src/cobalt/dom/pointer_state.cc
index 6f41a71..83ba908 100644
--- a/src/cobalt/dom/pointer_state.cc
+++ b/src/cobalt/dom/pointer_state.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 
+#include "base/debug/trace_event.h"
 #include "cobalt/dom/mouse_event.h"
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/wheel_event.h"
@@ -25,10 +26,11 @@
 namespace dom {
 
 void PointerState::QueuePointerEvent(const scoped_refptr<Event>& event) {
+  TRACE_EVENT1("cobalt::dom", "PointerState::QueuePointerEvent()", "event",
+               event->type().c_str());
+
   // Only accept this for event types that are MouseEvents or known derivatives.
-  SB_DCHECK(event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
-            event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
-            event->GetWrappableType() == base::GetTypeId<WheelEvent>());
+  SB_DCHECK(CanQueueEvent(event));
 
   // Queue the event to be handled on the next layout.
   pointer_events_.push(event);
@@ -259,5 +261,12 @@
   pointers_with_active_buttons_.clear();
 }
 
+// static
+bool PointerState::CanQueueEvent(const scoped_refptr<Event>& event) {
+  return event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
+         event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
+         event->GetWrappableType() == base::GetTypeId<WheelEvent>();
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/pointer_state.h b/src/cobalt/dom/pointer_state.h
index 0f73a29..d1c7d9c 100644
--- a/src/cobalt/dom/pointer_state.h
+++ b/src/cobalt/dom/pointer_state.h
@@ -77,6 +77,8 @@
   // shutdown.
   void ClearForShutdown();
 
+  static bool CanQueueEvent(const scoped_refptr<Event>& event);
+
  private:
   // Stores pointer events until they are handled after a layout.
   std::queue<scoped_refptr<Event> > pointer_events_;
diff --git a/src/cobalt/dom/pseudo_element.cc b/src/cobalt/dom/pseudo_element.cc
index cb54916..ffbbe58 100644
--- a/src/cobalt/dom/pseudo_element.cc
+++ b/src/cobalt/dom/pseudo_element.cc
@@ -22,8 +22,8 @@
 PseudoElement::PseudoElement(HTMLElement* parent_element)
     : parent_element_(parent_element),
       animations_(new web_animations::AnimationSet()),
-      css_computed_style_declaration_(
-          new cssom::CSSComputedStyleDeclaration()) {
+      css_computed_style_declaration_(new cssom::CSSComputedStyleDeclaration()),
+      computed_style_invalid_(true) {
   DCHECK(parent_element_);
   css_computed_style_declaration_->set_animations(animations_);
 
diff --git a/src/cobalt/dom/pseudo_element.h b/src/cobalt/dom/pseudo_element.h
index 9123f75..cebe07e 100644
--- a/src/cobalt/dom/pseudo_element.h
+++ b/src/cobalt/dom/pseudo_element.h
@@ -60,6 +60,11 @@
   cssom::RulesWithCascadePrecedence* matching_rules() {
     return &matching_rules_;
   }
+  void ClearMatchingRules() { matching_rules_.clear(); }
+
+  bool computed_style_invalid() const { return computed_style_invalid_; }
+  void set_computed_style_invalid() { computed_style_invalid_ = true; }
+  void clear_computed_style_invalid() { computed_style_invalid_ = false; }
 
   cssom::TransitionSet* css_transitions() { return &css_transitions_.value(); }
   cssom::AnimationSet* css_animations() { return &css_animations_.value(); }
@@ -69,7 +74,6 @@
   }
 
   HTMLElement* parent_element() { return parent_element_; }
-  void ClearMatchingRules() { matching_rules_.clear(); }
 
   void set_layout_boxes(scoped_ptr<LayoutBoxes> layout_boxes) {
     layout_boxes_ = layout_boxes.Pass();
@@ -91,6 +95,7 @@
   base::optional<cssom::AnimationSet> css_animations_;
 
   cssom::RulesWithCascadePrecedence matching_rules_;
+  bool computed_style_invalid_;
 
   // This contains information about the boxes generated from the element.
   scoped_ptr<LayoutBoxes> layout_boxes_;
diff --git a/src/cobalt/dom/rule_matching.cc b/src/cobalt/dom/rule_matching.cc
index f764718..41ca71e 100644
--- a/src/cobalt/dom/rule_matching.cc
+++ b/src/cobalt/dom/rule_matching.cc
@@ -64,7 +64,15 @@
 namespace {
 
 using cssom::SelectorTree;
-const size_t kRuleMatchingNodeSetSize = 60;
+
+struct NodeCombinatorType {
+  NodeCombinatorType(const SelectorTree::Node* node,
+                     cssom::CombinatorType combinator_type)
+      : node(node), combinator_type(combinator_type) {}
+
+  const SelectorTree::Node* node;
+  cssom::CombinatorType combinator_type;
+};
 
 //////////////////////////////////////////////////////////////////////////
 // Helper functions
@@ -345,226 +353,6 @@
   return selector_matcher.element();
 }
 
-void AddRulesOnNodeToElement(HTMLElement* element,
-                             const SelectorTree::Node* node) {
-  cssom::PseudoElement* pseudo_element =
-      node->compound_selector()->pseudo_element();
-
-  // Where to add matching rules.
-  cssom::RulesWithCascadePrecedence* target_matching_rules;
-
-  if (!pseudo_element) {
-    target_matching_rules = element->matching_rules();
-  } else {
-    PseudoElementType pseudo_element_type = kNotPseudoElementType;
-
-    if (pseudo_element->AsAfterPseudoElement()) {
-      pseudo_element_type = kAfterPseudoElementType;
-    } else if (pseudo_element->AsBeforePseudoElement()) {
-      pseudo_element_type = kBeforePseudoElementType;
-    } else {
-      NOTREACHED();
-    }
-
-    // Make sure the pseudo element exists under element.
-    if (!element->pseudo_element(pseudo_element_type)) {
-      element->set_pseudo_element(
-          pseudo_element_type,
-          make_scoped_ptr(new dom::PseudoElement(element)));
-    }
-
-    target_matching_rules =
-        element->pseudo_element(pseudo_element_type)->matching_rules();
-  }
-
-  element->rule_matching_state()->matching_nodes.insert(node);
-
-  for (SelectorTree::Rules::const_iterator rule_iterator =
-           node->rules().begin();
-       rule_iterator != node->rules().end(); ++rule_iterator) {
-    cssom::CSSStyleRule* rule = *rule_iterator;
-    if (!rule) {
-      continue;
-    }
-    DCHECK(rule->parent_style_sheet());
-    cssom::CascadePrecedence precedence(
-        pseudo_element ? cssom::kNormalOverride
-                       : rule->parent_style_sheet()->origin(),
-        node->cumulative_specificity(),
-        cssom::Appearance(rule->parent_style_sheet()->index(), rule->index()));
-    target_matching_rules->push_back(std::make_pair(rule, precedence));
-  }
-}
-
-void GatherCandidateNodesFromMap(
-    cssom::SimpleSelectorType simple_selector_type,
-    cssom::CombinatorType combinator_type,
-    const SelectorTree::SelectorTextToNodesMap* map, base::Token key,
-    SelectorTree::NodeSet<kRuleMatchingNodeSetSize>* candidate_nodes) {
-  SelectorTree::SelectorTextToNodesMap::const_iterator it = map->find(key);
-  if (it != map->end()) {
-    const SelectorTree::SimpleSelectorNodes& nodes = it->second;
-    for (SelectorTree::SimpleSelectorNodes::const_iterator nodes_iterator =
-             nodes.begin();
-         nodes_iterator != nodes.end(); ++nodes_iterator) {
-      if (nodes_iterator->simple_selector_type == simple_selector_type &&
-          nodes_iterator->combinator_type == combinator_type) {
-        candidate_nodes->insert(nodes_iterator->node);
-      }
-    }
-  }
-}
-
-void GatherCandidateNodesFromSet(
-    cssom::PseudoClassType pseudo_class_type,
-    cssom::CombinatorType combinator_type,
-    const SelectorTree::PseudoClassNodes& pseudo_class_nodes,
-    SelectorTree::NodeSet<kRuleMatchingNodeSetSize>* candidate_nodes) {
-  for (SelectorTree::PseudoClassNodes::const_iterator nodes_iterator =
-           pseudo_class_nodes.begin();
-       nodes_iterator != pseudo_class_nodes.end(); ++nodes_iterator) {
-    if (nodes_iterator->pseudo_class_type == pseudo_class_type &&
-        nodes_iterator->combinator_type == combinator_type) {
-      candidate_nodes->insert(nodes_iterator->node);
-    }
-  }
-}
-
-template <class NodeSet>
-void ForEachChildOnNodes(
-    const NodeSet& node_set, cssom::CombinatorType combinator_type,
-    HTMLElement* element,
-    const base::Callback<void(HTMLElement* element, const SelectorTree::Node*)>&
-        callback) {
-  // Gathering Phase: Generate candidate nodes from the node set.
-  SelectorTree::NodeSet<kRuleMatchingNodeSetSize> candidate_nodes;
-
-  const std::vector<base::Token>& element_class_list =
-      element->class_list()->GetTokens();
-
-  // Don't retrieve the element's attributes until they are needed. Retrieving
-  // them typically requires the creation of a NamedNodeMap.
-  scoped_refptr<NamedNodeMap> element_attributes;
-
-  // Iterate through all nodes in node_set.
-  for (typename NodeSet::const_iterator node_iterator = node_set.begin();
-       node_iterator != node_set.end(); ++node_iterator) {
-    const SelectorTree::Node* node = *node_iterator;
-
-    if (!node->HasCombinator(combinator_type)) {
-      continue;
-    }
-
-    // Gather candidate sets in node's children under the given combinator.
-
-    const SelectorTree::SelectorTextToNodesMap* selector_nodes_map =
-        node->selector_nodes_map();
-    if (selector_nodes_map) {
-      // Universal selector.
-      if (node->HasSimpleSelector(cssom::kUniversalSelector, combinator_type)) {
-        GatherCandidateNodesFromMap(cssom::kUniversalSelector, combinator_type,
-                                    selector_nodes_map, base::Token(),
-                                    &candidate_nodes);
-      }
-
-      // Type selector.
-      if (node->HasSimpleSelector(cssom::kTypeSelector, combinator_type)) {
-        GatherCandidateNodesFromMap(cssom::kTypeSelector, combinator_type,
-                                    selector_nodes_map, element->local_name(),
-                                    &candidate_nodes);
-      }
-
-      // Attribute selector.
-      if (node->HasSimpleSelector(cssom::kAttributeSelector, combinator_type)) {
-        // If the element's attributes have not been retrieved yet, then
-        // retrieve them now.
-        if (!element_attributes) {
-          element_attributes = element->attributes();
-        }
-
-        for (unsigned int index = 0; index < element_attributes->length();
-             ++index) {
-          GatherCandidateNodesFromMap(
-              cssom::kAttributeSelector, combinator_type, selector_nodes_map,
-              base::Token(element_attributes->Item(index)->name()),
-              &candidate_nodes);
-        }
-      }
-
-      // Class selector.
-      if (node->HasSimpleSelector(cssom::kClassSelector, combinator_type)) {
-        for (size_t index = 0; index < element_class_list.size(); ++index) {
-          GatherCandidateNodesFromMap(
-              cssom::kClassSelector, combinator_type, selector_nodes_map,
-              element_class_list[index], &candidate_nodes);
-        }
-      }
-
-      // Id selector.
-      if (node->HasSimpleSelector(cssom::kIdSelector, combinator_type)) {
-        GatherCandidateNodesFromMap(cssom::kIdSelector, combinator_type,
-                                    selector_nodes_map, element->id(),
-                                    &candidate_nodes);
-      }
-    }
-
-    if (node->HasAnyPseudoClass()) {
-      // Empty pseudo class.
-      if (node->HasPseudoClass(cssom::kEmptyPseudoClass, combinator_type) &&
-          element->IsEmpty()) {
-        GatherCandidateNodesFromSet(cssom::kEmptyPseudoClass, combinator_type,
-                                    node->pseudo_class_nodes(),
-                                    &candidate_nodes);
-      }
-
-      // Focus pseudo class.
-      if (node->HasPseudoClass(cssom::kFocusPseudoClass, combinator_type) &&
-          element->HasFocus()) {
-        GatherCandidateNodesFromSet(cssom::kFocusPseudoClass, combinator_type,
-                                    node->pseudo_class_nodes(),
-                                    &candidate_nodes);
-      }
-
-      // Hover pseudo class.
-      if (node->HasPseudoClass(cssom::kHoverPseudoClass, combinator_type) &&
-          element->IsDesignated()) {
-        GatherCandidateNodesFromSet(cssom::kHoverPseudoClass, combinator_type,
-                                    node->pseudo_class_nodes(),
-                                    &candidate_nodes);
-      }
-
-      // Not pseudo class.
-      if (node->HasPseudoClass(cssom::kNotPseudoClass, combinator_type)) {
-        GatherCandidateNodesFromSet(cssom::kNotPseudoClass, combinator_type,
-                                    node->pseudo_class_nodes(),
-                                    &candidate_nodes);
-      }
-    }
-  }
-
-  // Verifying Phase: Check all candidate nodes and run callback for matching
-  // nodes.
-  for (SelectorTree::NodeSet<kRuleMatchingNodeSetSize>::const_iterator
-           candidate_node_iterator = candidate_nodes.begin();
-       candidate_node_iterator != candidate_nodes.end();
-       ++candidate_node_iterator) {
-    const SelectorTree::Node* candidate_node = *candidate_node_iterator;
-
-    // Verify that this node is a match:
-    // 1. If the candidate node's compound selector doesn't require a visit to
-    //    verify the match, then the act of gathering the node as a candidate
-    //    proved the match.
-    // 2. Otherwise, the node requires additional verification checks, so call
-    //    MatchSelectorAndElement().
-    if (!candidate_node->compound_selector()
-             ->requires_rule_matching_verification_visit() ||
-        MatchSelectorAndElement(candidate_node->compound_selector(), element,
-                                false)) {
-      callback.Run(element, candidate_node);
-    }
-  }
-}
-
 bool MatchRuleAndElement(cssom::CSSStyleRule* rule, Element* element) {
   for (cssom::Selectors::const_iterator selector_iterator =
            rule->selectors().begin();
@@ -577,114 +365,562 @@
   return false;
 }
 
+void GatherCandidateNodesFromSelectorNodesMap(
+    cssom::SimpleSelectorType simple_selector_type,
+    cssom::CombinatorType combinator_type,
+    const SelectorTree::Node* parent_node,
+    const SelectorTree::SelectorTextToNodesMap* map, base::Token key,
+    SelectorTree::NodePairs* candidate_nodes) {
+  SelectorTree::SelectorTextToNodesMap::const_iterator it = map->find(key);
+  if (it != map->end()) {
+    const SelectorTree::SimpleSelectorNodes& nodes = it->second;
+    for (const auto& nodes_iterator : nodes) {
+      if (nodes_iterator.simple_selector_type == simple_selector_type &&
+          nodes_iterator.combinator_type == combinator_type) {
+        candidate_nodes->emplace_back(parent_node, nodes_iterator.node);
+      }
+    }
+  }
+}
+
+void GatherCandidateNodesFromPseudoClassNodes(
+    cssom::PseudoClassType pseudo_class_type,
+    cssom::CombinatorType combinator_type,
+    const SelectorTree::Node* parent_node,
+    const SelectorTree::PseudoClassNodes& pseudo_class_nodes,
+    SelectorTree::NodePairs* candidate_nodes) {
+  for (const auto& nodes_iterator : pseudo_class_nodes) {
+    if (nodes_iterator.pseudo_class_type == pseudo_class_type &&
+        nodes_iterator.combinator_type == combinator_type) {
+      candidate_nodes->emplace_back(parent_node, nodes_iterator.node);
+    }
+  }
+}
+
+bool GatherNodeModificationsAndUpdateTargetNodes(
+    const SelectorTree::Nodes& source_nodes, SelectorTree::Nodes* target_nodes,
+    SelectorTree::Nodes* added_nodes, SelectorTree::Nodes* removed_nodes) {
+  if (source_nodes == *target_nodes) {
+    return false;
+  }
+
+  added_nodes->clear();
+  removed_nodes->clear();
+
+  if (target_nodes->empty()) {
+    // If the previous state of the nodes (|target_nodes|) is empty, then all
+    // nodes in the new state (|source_nodes|) are being added.
+    *added_nodes = source_nodes;
+  } else if (source_nodes.empty()) {
+    // If the new state (|source_nodes|) is empty, then all nodes in the
+    // previous state (|target_nodes|) are being removed.
+    *removed_nodes = *target_nodes;
+  } else {
+    // If neither the previous state nor the new state or empty, then the lists
+    // must be manually searched to determine what is being added and removed.
+    // Remove all nodes in |source_nodes| from |target_nodes|. Any of the nodes
+    // that are not found in |target_nodes| are newly added nodes.
+    size_t start_index = 0;
+    for (const auto& check_node : source_nodes) {
+      bool node_found = false;
+      for (size_t index = start_index; index < target_nodes->size(); ++index) {
+        if ((*target_nodes)[index] == check_node) {
+          if (index == start_index) {
+            ++start_index;
+          }
+          node_found = true;
+          (*target_nodes)[index] = NULL;
+          break;
+        }
+      }
+      if (!node_found) {
+        added_nodes->push_back(check_node);
+      }
+    }
+    // Find nodes that remain in |target_nodes|. These did not exist in
+    // |source_nodes| and are newly removed nodes.
+    for (const auto& check_node : *target_nodes) {
+      if (check_node != NULL) {
+        removed_nodes->push_back(check_node);
+      }
+    }
+  }
+
+  *target_nodes = source_nodes;
+
+  // Return whether or not any modifications were found.
+  return !added_nodes->empty() || !removed_nodes->empty();
+}
+
+// Returns true if a matching node was added.
+bool GatherMatchingNodes(const SelectorTree::Nodes& nodes,
+                         cssom::CombinatorType combinator_type,
+                         HTMLElement* element,
+                         SelectorTree::NodePairs* scratchpad_node_pairs) {
+  bool matching_node_added = false;
+
+  // Gathering Phase: Generate candidate nodes from the nodes.
+  SelectorTree::NodePairs* candidate_node_pairs = scratchpad_node_pairs;
+  candidate_node_pairs->clear();
+
+  const std::vector<base::Token>& element_class_list =
+      element->class_list()->GetTokens();
+
+  // Don't retrieve the element's attributes until they are needed. Retrieving
+  // them typically requires the creation of a NamedNodeMap.
+  scoped_refptr<NamedNodeMap> element_attributes;
+
+  // Iterate through all nodes.
+  for (const auto& node : nodes) {
+    if (!node->HasCombinator(combinator_type)) {
+      continue;
+    }
+
+    // Gather candidate nodes in node's children under the given combinator.
+    const SelectorTree::SelectorTextToNodesMap* selector_nodes_map =
+        node->selector_nodes_map();
+    if (selector_nodes_map) {
+      // Universal selector.
+      if (node->HasSimpleSelector(cssom::kUniversalSelector, combinator_type)) {
+        GatherCandidateNodesFromSelectorNodesMap(
+            cssom::kUniversalSelector, combinator_type, node,
+            selector_nodes_map, base::Token(), candidate_node_pairs);
+      }
+
+      // Type selector.
+      if (node->HasSimpleSelector(cssom::kTypeSelector, combinator_type)) {
+        GatherCandidateNodesFromSelectorNodesMap(
+            cssom::kTypeSelector, combinator_type, node, selector_nodes_map,
+            element->local_name(), candidate_node_pairs);
+      }
+
+      // Attribute selector.
+      if (node->HasSimpleSelector(cssom::kAttributeSelector, combinator_type)) {
+        // If the element's attributes have not been retrieved yet, then
+        // retrieve them now.
+        if (!element_attributes) {
+          element_attributes = element->attributes();
+        }
+
+        for (unsigned int index = 0; index < element_attributes->length();
+             ++index) {
+          GatherCandidateNodesFromSelectorNodesMap(
+              cssom::kAttributeSelector, combinator_type, node,
+              selector_nodes_map,
+              base::Token(element_attributes->Item(index)->name()),
+              candidate_node_pairs);
+        }
+      }
+
+      // Class selector.
+      if (node->HasSimpleSelector(cssom::kClassSelector, combinator_type)) {
+        for (const auto& element_class : element_class_list) {
+          GatherCandidateNodesFromSelectorNodesMap(
+              cssom::kClassSelector, combinator_type, node, selector_nodes_map,
+              element_class, candidate_node_pairs);
+        }
+      }
+
+      // Id selector.
+      if (node->HasSimpleSelector(cssom::kIdSelector, combinator_type)) {
+        GatherCandidateNodesFromSelectorNodesMap(
+            cssom::kIdSelector, combinator_type, node, selector_nodes_map,
+            element->id(), candidate_node_pairs);
+      }
+    }
+
+    // Only check for specific pseudo classes when the node has as least one.
+    if (node->HasAnyPseudoClass()) {
+      // Empty pseudo class.
+      if (node->HasPseudoClass(cssom::kEmptyPseudoClass, combinator_type) &&
+          element->IsEmpty()) {
+        GatherCandidateNodesFromPseudoClassNodes(
+            cssom::kEmptyPseudoClass, combinator_type, node,
+            node->pseudo_class_nodes(), candidate_node_pairs);
+      }
+
+      // Focus pseudo class.
+      if (node->HasPseudoClass(cssom::kFocusPseudoClass, combinator_type) &&
+          element->HasFocus()) {
+        GatherCandidateNodesFromPseudoClassNodes(
+            cssom::kFocusPseudoClass, combinator_type, node,
+            node->pseudo_class_nodes(), candidate_node_pairs);
+      }
+
+      // Hover pseudo class.
+      if (node->HasPseudoClass(cssom::kHoverPseudoClass, combinator_type) &&
+          element->IsDesignated()) {
+        GatherCandidateNodesFromPseudoClassNodes(
+            cssom::kHoverPseudoClass, combinator_type, node,
+            node->pseudo_class_nodes(), candidate_node_pairs);
+      }
+
+      // Not pseudo class.
+      if (node->HasPseudoClass(cssom::kNotPseudoClass, combinator_type)) {
+        GatherCandidateNodesFromPseudoClassNodes(
+            cssom::kNotPseudoClass, combinator_type, node,
+            node->pseudo_class_nodes(), candidate_node_pairs);
+      }
+    }
+  }
+
+  // Verifying Phase: Check all candidate nodes and run callback for matching
+  // nodes.
+  for (const auto& candidate_node_pair : *candidate_node_pairs) {
+    const SelectorTree::Node* candidate_node = candidate_node_pair.second;
+
+    // Verify that this node is a match:
+    // 1. If the candidate node's compound selector doesn't require a visit to
+    //    verify the match, then the act of gathering the node as a candidate
+    //    proved the match.
+    // 2. Otherwise, the node requires additional verification checks, so call
+    //    MatchSelectorAndElement().
+    if (!candidate_node->compound_selector()
+             ->requires_rule_matching_verification_visit() ||
+        MatchSelectorAndElement(candidate_node->compound_selector(), element,
+                                false)) {
+      matching_node_added = true;
+      element->rule_matching_state()->matching_nodes_parent_nodes.push_back(
+          candidate_node_pair.first);
+      element->rule_matching_state()->matching_nodes.push_back(candidate_node);
+    }
+  }
+
+  return matching_node_added;
+}
+
+// Returns true if a matching node was removed.
+bool RemoveNodesFromMatchingNodes(
+    const SelectorTree::Nodes& parent_nodes_to_remove,
+    SelectorTree::Nodes* parent_nodes, SelectorTree::Nodes* matching_nodes) {
+  DCHECK_EQ(parent_nodes->size(), matching_nodes->size());
+  bool node_removed = false;
+
+  // Find any |parent_nodes| that are being removed. Remove these from both
+  // |parent_nodes| and |matching_nodes|, as these two containers are kept in
+  // sync.
+  for (const auto& parent_node_to_remove : parent_nodes_to_remove) {
+    for (size_t index = 0; index < parent_nodes->size(); ++index) {
+      if (parent_node_to_remove == (*parent_nodes)[index]) {
+        node_removed = true;
+        parent_nodes->erase(parent_nodes->begin() + index);
+        matching_nodes->erase(matching_nodes->begin() + index);
+        break;
+      }
+    }
+  }
+
+  return node_removed;
+}
+
+// Returns true if the matching nodes were modified.
+bool UpdateMatchingNodesFromNodeModifications(
+    const SelectorTree::Nodes& added_nodes,
+    const SelectorTree::Nodes& removed_nodes,
+    cssom::CombinatorType combinator_type, HTMLElement* element,
+    SelectorTree::NodePairs* scratchpad_node_pairs) {
+  // First gather the matching nodes found in |added_nodes|.
+  bool matching_nodes_modified = GatherMatchingNodes(
+      added_nodes, combinator_type, element, scratchpad_node_pairs);
+
+  // Now remove any matching nodes found in |removed_nodes|.
+  HTMLElement::RuleMatchingState* element_matching_state =
+      element->rule_matching_state();
+  matching_nodes_modified |= RemoveNodesFromMatchingNodes(
+      removed_nodes, &element_matching_state->matching_nodes_parent_nodes,
+      &element_matching_state->matching_nodes);
+
+  return matching_nodes_modified;
+}
+
+void UpdateRuleMatchingStateCombinatorNodes(
+    HTMLElement::RuleMatchingState* matching_state,
+    cssom::CombinatorType combinator_type) {
+  // Only descendant and following sibling combinators are supported.
+  DCHECK(combinator_type == cssom::kDescendantCombinator ||
+         combinator_type == cssom::kFollowingSiblingCombinator);
+  if (!matching_state) {
+    return;
+  }
+
+  DCHECK(matching_state->is_set);
+  bool& is_dirty = combinator_type == cssom::kDescendantCombinator
+                       ? matching_state->are_descendant_nodes_dirty
+                       : matching_state->are_following_sibling_nodes_dirty;
+  if (!is_dirty) {
+    return;
+  }
+
+  const SelectorTree::Nodes& base_combinator_nodes =
+      combinator_type == cssom::kDescendantCombinator
+          ? matching_state->parent_descendant_nodes
+          : matching_state->previous_sibling_following_sibling_nodes;
+  SelectorTree::Nodes& combinator_nodes =
+      combinator_type == cssom::kDescendantCombinator
+          ? matching_state->descendant_nodes
+          : matching_state->following_sibling_nodes;
+
+  // First copy the base combinator nodes and then add any matching nodes that
+  // have the required combinator and are not already included within the list.
+  combinator_nodes = base_combinator_nodes;
+  for (const auto& node : matching_state->matching_nodes) {
+    if (node->HasCombinator(combinator_type) &&
+        std::find(base_combinator_nodes.begin(), base_combinator_nodes.end(),
+                  node) == base_combinator_nodes.end()) {
+      combinator_nodes.push_back(node);
+    }
+  }
+
+  is_dirty = false;
+}
+
+// Returns true if the matching state was modified.
+bool UpdateElementRuleMatchingState(HTMLElement* element) {
+  SelectorTree* selector_tree = element->node_document()->selector_tree();
+  bool sibling_matching_active = selector_tree->has_sibling_combinators();
+
+  // Retrieve the parent and previous sibling of the current element.
+  HTMLElement* parent = element->parent_element()
+                            ? element->parent_element()->AsHTMLElement()
+                            : NULL;
+  HTMLElement* previous_sibling =
+      sibling_matching_active && element->previous_element_sibling()
+          ? element->previous_element_sibling()->AsHTMLElement()
+          : NULL;
+
+  // Retrieve the rule matching state of this element, its parent, and its
+  // previous sibling.
+  HTMLElement::RuleMatchingState* element_matching_state =
+      element->rule_matching_state();
+  HTMLElement::RuleMatchingState* parent_matching_state =
+      parent ? parent->rule_matching_state() : NULL;
+  HTMLElement::RuleMatchingState* previous_sibling_matching_state =
+      previous_sibling ? previous_sibling->rule_matching_state() : NULL;
+
+  // A parent must always have valid matching rules when its child is being
+  // updated.
+  DCHECK(!parent || parent->matching_rules_valid());
+
+  // Ensure that the previous sibling has updated matching rules. This is
+  // necessary because Document::UpdateComputedStyleOnElementAndAncestor() does
+  // not update previous siblings so it is possible for an element to have its
+  // computed style updated while previous siblings have invalid matching rules.
+  if (previous_sibling) {
+    UpdateElementMatchingRules(previous_sibling);
+    DCHECK(previous_sibling->matching_rules_valid());
+  }
+
+  // Ensure that the required combinator nodes of the parent and previous
+  // sibling are up to date. These are lazily updated, so they won't be updated
+  // until the first child/next sibling needs them.
+  UpdateRuleMatchingStateCombinatorNodes(parent_matching_state,
+                                         cssom::kDescendantCombinator);
+  UpdateRuleMatchingStateCombinatorNodes(previous_sibling_matching_state,
+                                         cssom::kFollowingSiblingCombinator);
+
+  bool matching_nodes_modified = !element_matching_state->is_set;
+
+  // Gather modifications between the element's previous state and current state
+  // for each combinator type, and use them to update the element's matching
+  // nodes.
+  SelectorTree::Nodes* added_nodes = selector_tree->scratchpad_nodes_1();
+  SelectorTree::Nodes* removed_nodes = selector_tree->scratchpad_nodes_2();
+
+  // Child combinator
+  if (parent_matching_state &&
+      GatherNodeModificationsAndUpdateTargetNodes(
+          parent_matching_state->matching_nodes,
+          &element_matching_state->parent_matching_nodes, added_nodes,
+          removed_nodes)) {
+    matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
+        *added_nodes, *removed_nodes, cssom::kChildCombinator, element,
+        selector_tree->scratchpad_node_pairs());
+  }
+
+  // Descendant combinator
+  const SelectorTree::Nodes& parent_descendant_nodes =
+      parent_matching_state ? parent_matching_state->descendant_nodes
+                            : selector_tree->root_nodes();
+  if (GatherNodeModificationsAndUpdateTargetNodes(
+          parent_descendant_nodes,
+          &element_matching_state->parent_descendant_nodes, added_nodes,
+          removed_nodes)) {
+    element_matching_state->are_descendant_nodes_dirty = true;
+    matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
+        *added_nodes, *removed_nodes, cssom::kDescendantCombinator, element,
+        selector_tree->scratchpad_node_pairs());
+  }
+
+  // Siblings only need to be checked if they're active.
+  if (sibling_matching_active) {
+    const SelectorTree::Nodes empty_nodes;
+
+    // Next sibling combinator
+    const SelectorTree::Nodes& previous_sibling_matching_nodes =
+        previous_sibling_matching_state
+            ? previous_sibling_matching_state->matching_nodes
+            : empty_nodes;
+    if (GatherNodeModificationsAndUpdateTargetNodes(
+            previous_sibling_matching_nodes,
+            &element_matching_state->previous_sibling_matching_nodes,
+            added_nodes, removed_nodes)) {
+      matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
+          *added_nodes, *removed_nodes, cssom::kNextSiblingCombinator, element,
+          selector_tree->scratchpad_node_pairs());
+    }
+
+    // Following sibling combinator
+    const SelectorTree::Nodes& previous_sibling_following_sibling_nodes =
+        previous_sibling_matching_state
+            ? previous_sibling_matching_state->following_sibling_nodes
+            : empty_nodes;
+    if (GatherNodeModificationsAndUpdateTargetNodes(
+            previous_sibling_following_sibling_nodes,
+            &element_matching_state->previous_sibling_following_sibling_nodes,
+            added_nodes, removed_nodes)) {
+      element_matching_state->are_following_sibling_nodes_dirty = true;
+      matching_nodes_modified |= UpdateMatchingNodesFromNodeModifications(
+          *added_nodes, *removed_nodes, cssom::kFollowingSiblingCombinator,
+          element, selector_tree->scratchpad_node_pairs());
+    }
+  }
+
+  element_matching_state->is_set = true;
+
+  // Matching node modifications may impact combinator nodes, so they must be
+  // dirtied. However, they are lazily updated the first time that they are
+  // needed by a child/following sibling, rather than immediately updated. This
+  // prevents unnecessary updates of leaf elements and last sibling elements.
+  if (matching_nodes_modified) {
+    element_matching_state->are_descendant_nodes_dirty = true;
+    element_matching_state->are_following_sibling_nodes_dirty = true;
+  }
+
+  return matching_nodes_modified;
+}
+
+void UpdateElementMatchingRulesFromRuleMatchingState(HTMLElement* element) {
+  Document* element_document = element->node_document();
+
+  // Store the element's and its pseudo element's old matching rules so that
+  // they can be compared to the new matching rules after they are generated.
+  cssom::RulesWithCascadePrecedence* element_old_matching_rules =
+      element_document->scratchpad_html_element_matching_rules();
+  *element_old_matching_rules = *element->matching_rules();
+  element->matching_rules()->clear();
+
+  for (int type = 0; type < kMaxPseudoElementType; ++type) {
+    PseudoElement* pseudo_element =
+        element->pseudo_element(PseudoElementType(type));
+    if (pseudo_element) {
+      cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
+          element_document->scratchpad_pseudo_element_matching_rules(
+              PseudoElementType(type));
+      *old_pseudo_element_matching_rules = *pseudo_element->matching_rules();
+      pseudo_element->ClearMatchingRules();
+    }
+  }
+
+  // Walk all matching nodes, generating matching rules for the element and
+  // its pseudo elements from them.
+  for (const auto& node : element->rule_matching_state()->matching_nodes) {
+    // Determine the matching rules that this specific node targets. If the
+    // node does not have a pseudo element, then this will be the element's
+    // matching rules; otherwise, it'll be the matching rules associated with
+    // the pseudo element's type.
+    cssom::RulesWithCascadePrecedence* target_matching_rules;
+
+    cssom::PseudoElement* pseudo_element =
+        node->compound_selector()->pseudo_element();
+    if (!pseudo_element) {
+      target_matching_rules = element->matching_rules();
+    } else {
+      PseudoElementType pseudo_element_type = kNotPseudoElementType;
+
+      if (pseudo_element->AsAfterPseudoElement()) {
+        pseudo_element_type = kAfterPseudoElementType;
+      } else if (pseudo_element->AsBeforePseudoElement()) {
+        pseudo_element_type = kBeforePseudoElementType;
+      } else {
+        NOTREACHED();
+      }
+
+      // Make sure the pseudo element exists under element. If not, create it
+      // and clear out the old matching rules since the pseudo element had no
+      // pre-existing matching rules.
+      if (!element->pseudo_element(pseudo_element_type)) {
+        element->SetPseudoElement(
+            pseudo_element_type,
+            make_scoped_ptr(new dom::PseudoElement(element)));
+
+        cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
+            element_document->scratchpad_pseudo_element_matching_rules(
+                PseudoElementType(pseudo_element_type));
+        old_pseudo_element_matching_rules->clear();
+      }
+
+      target_matching_rules =
+          element->pseudo_element(pseudo_element_type)->matching_rules();
+    }
+
+    // Add all of the node's rules to the target matching rules.
+    for (const auto& weak_rule : node->rules()) {
+      const auto rule = weak_rule.get();
+      if (!rule) {
+        continue;
+      }
+      DCHECK(rule->parent_style_sheet());
+      cssom::CascadePrecedence precedence(
+          pseudo_element ? cssom::kNormalOverride
+                         : rule->parent_style_sheet()->origin(),
+          node->cumulative_specificity(),
+          cssom::Appearance(rule->parent_style_sheet()->index(),
+                            rule->index()));
+      target_matching_rules->push_back(std::make_pair(rule, precedence));
+    }
+  }
+
+  // Check for if the newly generated matching rules are different for the
+  // element or its pseudo elements.
+  if (*element_old_matching_rules != *element->matching_rules()) {
+    element->OnMatchingRulesModified();
+  }
+
+  for (int type = 0; type < kMaxPseudoElementType; ++type) {
+    PseudoElement* pseudo_element =
+        element->pseudo_element(PseudoElementType(type));
+    if (pseudo_element) {
+      cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
+          element_document->scratchpad_pseudo_element_matching_rules(
+              PseudoElementType(type));
+      if (*old_pseudo_element_matching_rules !=
+          *pseudo_element->matching_rules()) {
+        pseudo_element->set_computed_style_invalid();
+        element->OnPseudoElementMatchingRulesModified();
+      }
+    }
+  }
+}
+
 }  // namespace
 
 //////////////////////////////////////////////////////////////////////////
 // Public APIs
 //////////////////////////////////////////////////////////////////////////
 
-void UpdateMatchingRules(HTMLElement* current_element) {
-  DCHECK(current_element);
-  DCHECK(current_element->node_document());
-  SelectorTree* selector_tree =
-      current_element->node_document()->selector_tree();
-
-  // Get parent and previous sibling of the current element.
-  HTMLElement* parent = current_element->parent_element()
-                            ? current_element->parent_element()->AsHTMLElement()
-                            : NULL;
-  HTMLElement* previous_sibling =
-      current_element->previous_element_sibling()
-          ? current_element->previous_element_sibling()->AsHTMLElement()
-          : NULL;
-
-  // Calculate current element's matching nodes.
-
-  if (parent) {
-    // Child combinator.
-    ForEachChildOnNodes(parent->rule_matching_state()->matching_nodes,
-                        cssom::kChildCombinator, current_element,
-                        base::Bind(&AddRulesOnNodeToElement));
-
-    // Descendant combinator.
-    ForEachChildOnNodes(
-        parent->rule_matching_state()->descendant_potential_nodes,
-        cssom::kDescendantCombinator, current_element,
-        base::Bind(&AddRulesOnNodeToElement));
-  } else {
-    // Descendant combinator from root.
-    ForEachChildOnNodes(selector_tree->root_set(), cssom::kDescendantCombinator,
-                        current_element, base::Bind(&AddRulesOnNodeToElement));
+void UpdateElementMatchingRules(HTMLElement* element) {
+  DCHECK(element);
+  DCHECK(element->node_document());
+  if (element->matching_rules_valid()) {
+    return;
   }
 
-  if (selector_tree->has_sibling_combinators()) {
-    if (previous_sibling) {
-      // Next sibling combinator.
-      ForEachChildOnNodes(
-          previous_sibling->rule_matching_state()->matching_nodes,
-          cssom::kNextSiblingCombinator, current_element,
-          base::Bind(&AddRulesOnNodeToElement));
-
-      // Following sibling combinator.
-      ForEachChildOnNodes(previous_sibling->rule_matching_state()
-                              ->following_sibling_potential_nodes,
-                          cssom::kFollowingSiblingCombinator, current_element,
-                          base::Bind(&AddRulesOnNodeToElement));
-    }
+  // Only update the matching rules if the rule matching state is modified.
+  if (UpdateElementRuleMatchingState(element)) {
+    UpdateElementMatchingRulesFromRuleMatchingState(element);
   }
 
-  // Calculate current element's descendant potential nodes.
-  if (parent) {
-    current_element->rule_matching_state()->descendant_potential_nodes.insert(
-        parent->rule_matching_state()->descendant_potential_nodes.begin(),
-        parent->rule_matching_state()->descendant_potential_nodes.end());
-  } else {
-    current_element->rule_matching_state()->descendant_potential_nodes.insert(
-        selector_tree->root());
-  }
-  // Walk all of the matching nodes, adding any that have a descendant
-  // combinator as a descendant potential node. Any missing that combinator can
-  // never match descendants.
-  for (dom::HTMLElement::MatchingNodes::const_iterator iter =
-           current_element->rule_matching_state()->matching_nodes.begin();
-       iter != current_element->rule_matching_state()->matching_nodes.end();
-       ++iter) {
-    if ((*iter)->HasCombinator(cssom::kDescendantCombinator)) {
-      // It is possible for the two lists to contain duplicate nodes, so only
-      // add the node if it isn't a duplicate.
-      current_element->rule_matching_state()->descendant_potential_nodes.insert(
-          *iter, true /*check_for_duplicate*/);
-    }
-  }
-
-  // Calculate current element's following sibling potential nodes.
-  if (selector_tree->has_sibling_combinators()) {
-    if (previous_sibling) {
-      current_element->rule_matching_state()
-          ->following_sibling_potential_nodes.insert(
-              previous_sibling->rule_matching_state()
-                  ->following_sibling_potential_nodes.begin(),
-              previous_sibling->rule_matching_state()
-                  ->following_sibling_potential_nodes.end());
-    }
-    // Walk all of the matching nodes, adding any that have a following sibling
-    // combinator as a following sibling potential node. Any missing that
-    // combinator can never match following siblings..
-    for (dom::HTMLElement::MatchingNodes::const_iterator iter =
-             current_element->rule_matching_state()->matching_nodes.begin();
-         iter != current_element->rule_matching_state()->matching_nodes.end();
-         ++iter) {
-      if ((*iter)->HasCombinator(cssom::kFollowingSiblingCombinator)) {
-        // It is possible for the two lists to contain duplicate nodes, so only
-        // add the node if it isn't a duplicate.
-        current_element->rule_matching_state()
-            ->following_sibling_potential_nodes.insert(
-                *iter, true /*check_for_duplicate*/);
-      }
-    }
-  }
-
-  current_element->set_matching_rules_valid();
+  element->set_matching_rules_valid();
 }
 
 scoped_refptr<Element> QuerySelector(Node* node, const std::string& selectors,
diff --git a/src/cobalt/dom/rule_matching.h b/src/cobalt/dom/rule_matching.h
index 31d2353..6f5fc31 100644
--- a/src/cobalt/dom/rule_matching.h
+++ b/src/cobalt/dom/rule_matching.h
@@ -37,7 +37,7 @@
 // Updates the matching rules on an element. The parent and previous sibling
 // need to have their matching rules updated before, since the current element's
 // rule matching will depend on their rule matching states.
-void UpdateMatchingRules(HTMLElement* current_element);
+void UpdateElementMatchingRules(HTMLElement* current_element);
 
 // Returns the first element in the subtree that matches the given selector.
 scoped_refptr<Element> QuerySelector(Node* node, const std::string& selectors,
diff --git a/src/cobalt/dom/rule_matching_test.cc b/src/cobalt/dom/rule_matching_test.cc
index e586475..07136d7 100644
--- a/src/cobalt/dom/rule_matching_test.cc
+++ b/src/cobalt/dom/rule_matching_test.cc
@@ -43,10 +43,10 @@
       : css_parser_(css_parser::Parser::Create()),
         dom_parser_(new dom_parser::Parser()),
         dom_stat_tracker_(new DomStatTracker("RuleMatchingTest")),
-        html_element_context_(NULL, css_parser_.get(), dom_parser_.get(), NULL,
+        html_element_context_(NULL, NULL, css_parser_.get(), dom_parser_.get(),
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, dom_stat_tracker_.get(), "",
-                              base::kApplicationStateStarted),
+                              NULL, NULL, NULL, dom_stat_tracker_.get(), "",
+                              base::kApplicationStateStarted, NULL),
         document_(new Document(&html_element_context_)),
         root_(document_->CreateElement("html")->AsHTMLElement()),
         head_(document_->CreateElement("head")->AsHTMLElement()),
@@ -83,7 +83,7 @@
   while (child) {
     if (child->AsElement()) {
       DCHECK(child->AsElement()->AsHTMLElement());
-      UpdateMatchingRules(child->AsElement()->AsHTMLElement());
+      UpdateElementMatchingRules(child->AsElement()->AsHTMLElement());
     }
     child = iterator.Next();
   }
@@ -591,7 +591,7 @@
             (*matching_rules)[0].first);
 }
 
-// "span + span" shouldm't match first span in <span/><span/>.
+// "span + span" shouldn't match first span in <span/><span/>.
 TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorNoMatch) {
   head_->set_inner_html("<style>span + span {}</style>");
   body_->set_inner_html("<span/><span/>");
@@ -868,5 +868,385 @@
             (*matching_rules)[0].first);
 }
 
+// div:hover should match after hover added to element.
+TEST_F(RuleMatchingTest, HoverPseudoClassSelectorAddHoverToElement) {
+  head_->set_inner_html("<style>div:hover {}</style>");
+  body_->set_inner_html("<div/>");
+
+  document_->SetIndicatedElement(body_->first_element_child()->AsHTMLElement());
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  document_->SetIndicatedElement(NULL);
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// div:hover should not match after hover removed from element.
+TEST_F(RuleMatchingTest, HoverPseudoClassSelectorRemoveHoverFromElement) {
+  head_->set_inner_html("<style>div:hover {}</style>");
+  body_->set_inner_html("<div/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  document_->SetIndicatedElement(body_->first_element_child()->AsHTMLElement());
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// div:hover should match after hover added to element's descendant.
+TEST_F(RuleMatchingTest, HoverPseudoClassSelectorAddHoverToDescendant) {
+  head_->set_inner_html("<style>div:hover {}</style>");
+  body_->set_inner_html("<div><span/></div>");
+
+  document_->SetIndicatedElement(
+      body_->first_element_child()->first_element_child()->AsHTMLElement());
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  document_->SetIndicatedElement(NULL);
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// div:hover should not match after hover removed from element's descendant.
+TEST_F(RuleMatchingTest, HoverPseudoClassSelectorRemoveHoverFromDescendant) {
+  head_->set_inner_html("<style>div:hover {}</style>");
+  body_->set_inner_html("<div><span/></div>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  document_->SetIndicatedElement(
+      body_->first_element_child()->first_element_child()->AsHTMLElement());
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// .my-class should match after class "my-class" set on element.
+TEST_F(RuleMatchingTest, ClassSelectorSetClassOnElement) {
+  head_->set_inner_html("<style>.my-class {}</style>");
+  body_->set_inner_html("<div class=\"other-class\"/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// .my-class should not match after class removed from element.
+TEST_F(RuleMatchingTest, ClassSelectorRemoveClassOnElement) {
+  head_->set_inner_html("<style>.my-class {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// Child should add matching rule from newly added matching node.
+TEST_F(RuleMatchingTest, ChildMatchingNodeAdded) {
+  head_->set_inner_html("<style>div.my-class > div {}</style>");
+  body_->set_inner_html("<div class=\"other-class\"><div/></div>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->first_element_child()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// Child should remove matching rule from newly removed matching node.
+TEST_F(RuleMatchingTest, ChildMatchingNodeRemoved) {
+  head_->set_inner_html("<style>div.my-class > div {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"><div/></div>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->first_element_child()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// Descendant should add matching rule from newly added matching node.
+TEST_F(RuleMatchingTest, DescendantMatchingNodeAdded) {
+  head_->set_inner_html("<style>div.my-class div {}</style>");
+  body_->set_inner_html("<div class=\"other-class\"><div/></div>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->first_element_child()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// Descendant should remove matching rule from newly removed matching node.
+TEST_F(RuleMatchingTest, DescendantMatchingNodeRemoved) {
+  head_->set_inner_html("<style>div.my-class div {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"><div/></div>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->first_element_child()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// Next sibling should add matching rule from newly added matching node.
+TEST_F(RuleMatchingTest, NextSiblingMatchingNodeAdded) {
+  head_->set_inner_html("<style>div.my-class + div {}</style>");
+  body_->set_inner_html("<div class=\"other-class\"/><div/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->next_element_sibling()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// Next sibling should remove matching rule from newly removed matching node.
+TEST_F(RuleMatchingTest, NextSiblingMatchingNodeRemoved) {
+  head_->set_inner_html("<style>div.my-class + div {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/><div/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->next_element_sibling()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// Following sibling should add matching rule from newly added matching node.
+TEST_F(RuleMatchingTest, FollowingSiblingMatchingNodeAdded) {
+  head_->set_inner_html("<style>div.my-class ~ div {}</style>");
+  body_->set_inner_html("<div class=\"other-class\"/><div/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->next_element_sibling()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(0, matching_rules->size());
+
+  body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// Following sibling should remove matching rule from newly removed matching
+// node.
+TEST_F(RuleMatchingTest, FollowingSiblingMatchingNodeRemoved) {
+  head_->set_inner_html("<style>div.my-class ~ div {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/><div/>");
+
+  UpdateAllMatchingRules();
+
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      body_->first_element_child()
+          ->next_element_sibling()
+          ->AsHTMLElement()
+          ->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
+
+  UpdateAllMatchingRules();
+
+  ASSERT_EQ(0, matching_rules->size());
+}
+
+// After pseudo element should add matching rule from newly added matching node.
+TEST_F(RuleMatchingTest, AfterPseudoElementMatchingNodeRemoved) {
+  head_->set_inner_html(
+      "<style>div.my-class:after {}</style>"
+      "<style>div:after {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/><div/>");
+
+  UpdateAllMatchingRules();
+
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      html_element->matching_rules();
+  EXPECT_EQ(0, matching_rules->size());
+  ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
+  matching_rules =
+      html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
+  ASSERT_EQ(2, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
+
+  UpdateAllMatchingRules();
+
+  matching_rules = html_element->matching_rules();
+  EXPECT_EQ(0, matching_rules->size());
+  ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
+  matching_rules =
+      html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(1)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
+// After pseudo element should remove matching rule from newly removed matching
+// node.
+TEST_F(RuleMatchingTest, AfterPseudoElementMatchingNodeAdded) {
+  head_->set_inner_html(
+      "<style>div.my-class:after {}</style>"
+      "<style>div:after {}</style>");
+  body_->set_inner_html("<div class=\"other-class\"/><div/>");
+
+  UpdateAllMatchingRules();
+
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
+  cssom::RulesWithCascadePrecedence* matching_rules =
+      html_element->matching_rules();
+  EXPECT_EQ(0, matching_rules->size());
+  ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
+  matching_rules =
+      html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
+  ASSERT_EQ(1, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(1)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+
+  body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
+
+  UpdateAllMatchingRules();
+
+  matching_rules = html_element->matching_rules();
+  EXPECT_EQ(0, matching_rules->size());
+  ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
+  matching_rules =
+      html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
+  ASSERT_EQ(2, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
+            (*matching_rules)[0].first);
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/script_event_log.cc b/src/cobalt/dom/script_event_log.cc
new file mode 100644
index 0000000..1ebdf60
--- /dev/null
+++ b/src/cobalt/dom/script_event_log.cc
@@ -0,0 +1,70 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/script_event_log.h"
+
+#include "cobalt/dom/event_target.h"
+
+namespace cobalt {
+namespace dom {
+
+ScriptEventLog::ScriptEventLog() : current_stack_depth_(0) {
+  base::UserLog::Register(base::UserLog::kEventStackLevelCountIndex,
+                          "EventLevelCnt", &current_stack_depth_,
+                          sizeof(current_stack_depth_));
+
+  memset(event_log_stack_, 0, sizeof(event_log_stack_));
+  const int kLogNameBufferSize = 16;
+  char log_name_buffer[kLogNameBufferSize];
+  for (int i = 0; i < base::UserLog::kEventStackMaxDepth; i++) {
+    snprintf(log_name_buffer, kLogNameBufferSize, "EventLevel%d", i);
+    base::UserLog::Register(static_cast<base::UserLog::Index>(
+                                base::UserLog::kEventStackMinLevelIndex + i),
+                            log_name_buffer, event_log_stack_[i],
+                            kLogEntryMaxLength);
+  }
+}
+
+ScriptEventLog::~ScriptEventLog() {
+  base::UserLog::Deregister(base::UserLog::kEventStackLevelCountIndex);
+  for (int i = 0; i < base::UserLog::kEventStackMaxDepth; i++) {
+    base::UserLog::Deregister(static_cast<base::UserLog::Index>(
+        base::UserLog::kEventStackMinLevelIndex + i));
+  }
+}
+
+void ScriptEventLog::PushEvent(Event* event) {
+  if (current_stack_depth_ < base::UserLog::kEventStackMaxDepth) {
+    snprintf(event_log_stack_[current_stack_depth_], kLogEntryMaxLength,
+             "%s@%s", event->type().c_str(),
+             event->current_target()->GetDebugName().c_str());
+  } else if (current_stack_depth_ == base::UserLog::kEventStackMaxDepth) {
+    DLOG(WARNING) << "Reached maximum depth of " << kLogEntryMaxLength
+                  << ". Subsequent events will not be logged.";
+  }
+  current_stack_depth_++;
+}
+
+void ScriptEventLog::PopEvent() {
+  DCHECK(current_stack_depth_);
+  current_stack_depth_--;
+  if (current_stack_depth_ < base::UserLog::kEventStackMaxDepth) {
+    memset(event_log_stack_[current_stack_depth_], 0, kLogEntryMaxLength);
+  }
+}
+
+base::LazyInstance<ScriptEventLog> script_event_log = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/script_event_log.h b/src/cobalt/dom/script_event_log.h
new file mode 100644
index 0000000..a419e8d
--- /dev/null
+++ b/src/cobalt/dom/script_event_log.h
@@ -0,0 +1,46 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DOM_SCRIPT_EVENT_LOG_H_
+#define COBALT_DOM_SCRIPT_EVENT_LOG_H_
+
+#include "base/lazy_instance.h"
+#include "cobalt/base/user_log.h"
+#include "cobalt/dom/event.h"
+
+namespace cobalt {
+namespace dom {
+
+// This class records the nested events and manages the user log information.
+class ScriptEventLog {
+ public:
+  ScriptEventLog();
+  ~ScriptEventLog();
+  void PushEvent(Event* event);
+  void PopEvent();
+
+ private:
+  static const size_t kLogEntryMaxLength = 64;
+  int current_stack_depth_;
+  char event_log_stack_[base::UserLog::kEventStackMaxDepth][kLogEntryMaxLength];
+
+  DISALLOW_COPY_AND_ASSIGN(ScriptEventLog);
+};
+
+extern base::LazyInstance<ScriptEventLog> script_event_log;
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_SCRIPT_EVENT_LOG_H_
diff --git a/src/cobalt/dom/serializer_test.cc b/src/cobalt/dom/serializer_test.cc
index bfe31c2..eb0683d 100644
--- a/src/cobalt/dom/serializer_test.cc
+++ b/src/cobalt/dom/serializer_test.cc
@@ -45,9 +45,9 @@
     : dom_parser_(new dom_parser::Parser()),
       dom_stat_tracker_(new DomStatTracker("SerializerTest")),
       html_element_context_(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                            NULL, NULL, NULL, NULL, NULL, NULL,
+                            NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                             dom_stat_tracker_.get(), "",
-                            base::kApplicationStateStarted),
+                            base::kApplicationStateStarted, NULL),
       document_(new Document(&html_element_context_)),
       root_(new Element(document_, base::Token("root"))),
       source_location_(base::SourceLocation("[object SerializerTest]", 1, 1)) {}
diff --git a/src/cobalt/dom/testing/dom_testing.gyp b/src/cobalt/dom/testing/dom_testing.gyp
new file mode 100644
index 0000000..3c72685
--- /dev/null
+++ b/src/cobalt/dom/testing/dom_testing.gyp
@@ -0,0 +1,42 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'dom_testing',
+      'type': 'static_library',
+      'sources': [
+        'gtest_workarounds.h',
+        'html_collection_testing.h',
+        'mock_event_listener.h',
+        'stub_css_parser.cc',
+        'stub_css_parser.h',
+        'stub_script_runner.cc',
+        'stub_script_runner.h',
+        'stub_window.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/browser/browser.gyp:browser',
+        '<(DEPTH)/cobalt/browser/browser_bindings.gyp:bindings',
+        '<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
+        '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
+        '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
+      ],
+    },
+  ],
+}
diff --git a/src/cobalt/dom/testing/stub_window.h b/src/cobalt/dom/testing/stub_window.h
index 298e799..6eb3913 100644
--- a/src/cobalt/dom/testing/stub_window.h
+++ b/src/cobalt/dom/testing/stub_window.h
@@ -20,11 +20,13 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/message_loop.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/css_parser/parser.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
@@ -38,20 +40,27 @@
 // stubbed out.
 class StubWindow {
  public:
-  StubWindow()
+  explicit StubWindow(
+      scoped_ptr<script::EnvironmentSettings> environment_settings =
+          scoped_ptr<script::EnvironmentSettings>(
+              new script::EnvironmentSettings))
       : message_loop_(MessageLoop::TYPE_DEFAULT),
         css_parser_(css_parser::Parser::Create()),
         dom_parser_(new dom_parser::Parser(base::Bind(&StubErrorCallback))),
         fetcher_factory_(new loader::FetcherFactory(NULL)),
+        loader_factory_(new loader::LoaderFactory(
+            fetcher_factory_.get(), NULL, base::kThreadPriority_Default)),
         local_storage_database_(NULL),
         url_("about:blank"),
-        dom_stat_tracker_(new dom::DomStatTracker("StubWindow")) {
+        dom_stat_tracker_(new dom::DomStatTracker("StubWindow")),
+        environment_settings_(environment_settings.Pass()) {
     engine_ = script::JavaScriptEngine::CreateEngine();
     global_environment_ = engine_->CreateGlobalEnvironment();
     window_ = new dom::Window(
         1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
-        dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL, NULL,
-        NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL, NULL,
+        dom_parser_.get(), fetcher_factory_.get(), loader_factory_.get(), NULL,
+        NULL, NULL, NULL, NULL, NULL, &local_storage_database_, NULL, NULL,
+        NULL, NULL, global_environment_->script_value_factory(), NULL,
         dom_stat_tracker_.get(), url_, "", "en-US", "en",
         base::Callback<void(const GURL&)>(), base::Bind(&StubErrorCallback),
         NULL, network_bridge::PostSender(), csp::kCSPRequired,
@@ -61,8 +70,9 @@
         base::Closure() /* window_minimize */, NULL, NULL, NULL,
         dom::Window::OnStartDispatchEventCallback(),
         dom::Window::OnStopDispatchEventCallback(),
-        dom::ScreenshotManager::ProvideScreenshotFunctionCallback());
-    global_environment_->CreateGlobalObject(window_, &environment_settings_);
+        dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
+    global_environment_->CreateGlobalObject(window_,
+                                            environment_settings_.get());
   }
 
   scoped_refptr<dom::Window> window() { return window_; }
@@ -70,6 +80,11 @@
     return global_environment_;
   }
   css_parser::Parser* css_parser() { return css_parser_.get(); }
+  script::EnvironmentSettings* environment_settings() {
+    return environment_settings_.get();
+  }
+
+  ~StubWindow() { window_->DestroyTimers(); }
 
  private:
   static void StubErrorCallback(const std::string& /*error*/) {}
@@ -78,10 +93,11 @@
   scoped_ptr<css_parser::Parser> css_parser_;
   scoped_ptr<dom_parser::Parser> dom_parser_;
   scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+  scoped_ptr<loader::LoaderFactory> loader_factory_;
   dom::LocalStorageDatabase local_storage_database_;
   GURL url_;
   scoped_ptr<dom::DomStatTracker> dom_stat_tracker_;
-  script::EnvironmentSettings environment_settings_;
+  scoped_ptr<script::EnvironmentSettings> environment_settings_;
   scoped_ptr<script::JavaScriptEngine> engine_;
   scoped_refptr<script::GlobalEnvironment> global_environment_;
   scoped_refptr<dom::Window> window_;
diff --git a/src/cobalt/dom/url_registry.h b/src/cobalt/dom/url_registry.h
index 4b6954a..aea50c9 100644
--- a/src/cobalt/dom/url_registry.h
+++ b/src/cobalt/dom/url_registry.h
@@ -19,6 +19,7 @@
 
 #include "base/hash_tables.h"
 #include "base/memory/ref_counted.h"
+#include "cobalt/script/tracer.h"
 
 namespace cobalt {
 namespace dom {
@@ -33,13 +34,17 @@
 // Note: It is unsafe to directly encode the pointer to the object in the
 // url as the url is assigned from JavaScript.
 template <typename ObjectType>
-class UrlRegistry {
+class UrlRegistry : public script::Traceable {
  public:
   void Register(const std::string& blob_url,
                 const scoped_refptr<ObjectType>& object);
   scoped_refptr<ObjectType> Retrieve(const std::string& blob_url);
   bool Unregister(const std::string& blob_url);
 
+  void TraceMembers(script::Tracer* tracer) override {
+    tracer->TraceValues(object_registry_);
+  }
+
  private:
   typedef base::hash_map<std::string, scoped_refptr<ObjectType> > UrlMap;
   UrlMap object_registry_;
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 0023eb0..0bfd760 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -15,13 +15,14 @@
 
 #include <algorithm>
 
-#include "base/base64.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/debug/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/cssom/css_computed_style_declaration.h"
 #include "cobalt/cssom/user_agent_style_sheet.h"
+#include "cobalt/dom/base64.h"
 #include "cobalt/dom/camera_3d.h"
 #include "cobalt/dom/console.h"
 #include "cobalt/dom/device_orientation_event.h"
@@ -91,6 +92,7 @@
     base::ApplicationState initial_application_state,
     cssom::CSSParser* css_parser, Parser* dom_parser,
     loader::FetcherFactory* fetcher_factory,
+    loader::LoaderFactory* loader_factory,
     render_tree::ResourceProvider** resource_provider,
     loader::image::AnimatedImageTracker* animated_image_tracker,
     loader::image::ImageCache* image_cache,
@@ -124,11 +126,15 @@
     const OnStopDispatchEventCallback& on_stop_dispatch_event_callback,
     const ScreenshotManager::ProvideScreenshotFunctionCallback&
         screenshot_function_callback,
+    base::WaitableEvent* synchronous_loader_interrupt,
     int csp_insecure_allowed_token, int dom_max_element_depth,
     float video_playback_rate_multiplier, ClockType clock_type,
     const CacheCallback& splash_screen_cache_callback,
     const scoped_refptr<captions::SystemCaptionSettings>& captions)
-    : width_(width),
+    // 'window' object EventTargets require special handling for onerror events,
+    // see EventTarget constructor for more details.
+    : EventTarget(kUnpackOnErrorEvents),
+      width_(width),
       height_(height),
       device_pixel_ratio_(device_pixel_ratio),
       is_resize_event_pending_(false),
@@ -137,12 +143,13 @@
       test_runner_(new TestRunner()),
 #endif  // ENABLE_TEST_RUNNER
       html_element_context_(new HTMLElementContext(
-          fetcher_factory, css_parser, dom_parser, can_play_type_handler,
-          web_media_player_factory, script_runner, script_value_factory,
-          media_source_registry, resource_provider, animated_image_tracker,
-          image_cache, reduced_image_cache_capacity_manager,
-          remote_typeface_cache, mesh_cache, dom_stat_tracker,
-          font_language_script, initial_application_state,
+          fetcher_factory, loader_factory, css_parser, dom_parser,
+          can_play_type_handler, web_media_player_factory, script_runner,
+          script_value_factory, media_source_registry, resource_provider,
+          animated_image_tracker, image_cache,
+          reduced_image_cache_capacity_manager, remote_typeface_cache,
+          mesh_cache, dom_stat_tracker, font_language_script,
+          initial_application_state, synchronous_loader_interrupt,
           video_playback_rate_multiplier)),
       performance_(new Performance(
 #if defined(ENABLE_TEST_RUNNER)
@@ -345,22 +352,24 @@
 
 std::string Window::Btoa(const std::string& string_to_encode,
                          script::ExceptionState* exception_state) {
-  std::string output;
-  if (!base::Base64Encode(string_to_encode, &output)) {
+  TRACE_EVENT0("cobalt::dom", "Window::Btoa()");
+  auto output = ForgivingBase64Encode(string_to_encode);
+  if (!output) {
     DOMException::Raise(DOMException::kInvalidCharacterErr, exception_state);
     return std::string();
   }
-  return output;
+  return *output;
 }
 
 std::vector<uint8_t> Window::Atob(const std::string& encoded_string,
                                   script::ExceptionState* exception_state) {
-  std::string output;
-  if (!base::Base64Decode(encoded_string, &output)) {
+  TRACE_EVENT0("cobalt::dom", "Window::Atob()");
+  auto output = ForgivingBase64Decode(encoded_string);
+  if (!output) {
     DOMException::Raise(DOMException::kInvalidCharacterErr, exception_state);
     return {};
   }
-  return {output.begin(), output.end()};
+  return *output;
 }
 
 int Window::SetTimeout(const WindowTimers::TimerCallbackArg& handler,
@@ -483,6 +492,9 @@
 }
 
 void Window::InjectEvent(const scoped_refptr<Event>& event) {
+  TRACE_EVENT1("cobalt::dom", "Window::InjectEvent()", "event",
+               event->type().c_str());
+
   // Forward the event on to the correct object in DOM.
   if (event->GetWrappableType() == base::GetTypeId<KeyboardEvent>()) {
     // Event.target:focused element processing the key event or if no element
@@ -500,10 +512,6 @@
     if (on_screen_keyboard_) {
       on_screen_keyboard_->DispatchEvent(event);
     }
-  } else if (event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
-             event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
-             event->GetWrappableType() == base::GetTypeId<WheelEvent>()) {
-    document_->pointer_state()->QueuePointerEvent(event);
   } else {
     SB_NOTREACHED();
   }
@@ -672,6 +680,7 @@
 
 void Window::SetEnvironmentSettings(script::EnvironmentSettings* settings) {
   screenshot_manager_.SetEnvironmentSettings(settings);
+  navigator_->SetEnvironmentSettings(settings);
 }
 
 void Window::CacheSplashScreen(const std::string& content) {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 80ef19b..4815246 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -23,6 +23,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/timer.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/cssom/css_parser.h"
@@ -54,6 +55,7 @@
 #include "cobalt/loader/image/image.h"
 #include "cobalt/loader/image/image_cache.h"
 #include "cobalt/loader/loader.h"
+#include "cobalt/loader/loader_factory.h"
 #include "cobalt/loader/mesh/mesh_cache.h"
 #include "cobalt/media/can_play_type_handler.h"
 #include "cobalt/media/web_media_player_factory.h"
@@ -127,6 +129,7 @@
       base::ApplicationState initial_application_state,
       cssom::CSSParser* css_parser, Parser* dom_parser,
       loader::FetcherFactory* fetcher_factory,
+      loader::LoaderFactory* loader_factory,
       render_tree::ResourceProvider** resource_provider,
       loader::image::AnimatedImageTracker* animated_image_tracker,
       loader::image::ImageCache* image_cache,
@@ -162,6 +165,7 @@
       const OnStopDispatchEventCallback& stop_tracking_dispatch_event_callback,
       const ScreenshotManager::ProvideScreenshotFunctionCallback&
           screenshot_function_callback,
+      base::WaitableEvent* synchronous_loader_interrupt,
       int csp_insecure_allowed_token = 0, int dom_max_element_depth = 0,
       float video_playback_rate_multiplier = 1.f,
       ClockType clock_type = kClockTypeSystemTime,
diff --git a/src/cobalt/dom/window_on_screen_keyboard.idl b/src/cobalt/dom/window_on_screen_keyboard.idl
index 3fdb0a1..b0cbbd7 100644
--- a/src/cobalt/dom/window_on_screen_keyboard.idl
+++ b/src/cobalt/dom/window_on_screen_keyboard.idl
@@ -16,5 +16,5 @@
 // screen keyboard.
 
 partial interface Window {
-  readonly attribute OnScreenKeyboard onScreenKeyboard;
+[ Conditional=COBALT_ENABLE_ON_SCREEN_KEYBOARD ] readonly attribute OnScreenKeyboard onScreenKeyboard;
 };
diff --git a/src/cobalt/dom/window_test.cc b/src/cobalt/dom/window_test.cc
index ea3f2fd..6401e7f 100644
--- a/src/cobalt/dom/window_test.cc
+++ b/src/cobalt/dom/window_test.cc
@@ -26,6 +26,8 @@
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/network_bridge/net_poster.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
 #include "googleurl/src/gurl.h"
 #include "starboard/window.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -46,23 +48,26 @@
         dom_parser_(new dom_parser::Parser(mock_error_callback_)),
         fetcher_factory_(new loader::FetcherFactory(NULL)),
         local_storage_database_(NULL),
-        url_("about:blank"),
-        window_(new Window(
-            1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
-            dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
-            NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL,
-            NULL, NULL, url_, "", "en-US", "en",
-            base::Callback<void(const GURL &)>(),
-            base::Bind(&MockErrorCallback::Run,
-                       base::Unretained(&mock_error_callback_)),
-            NULL, network_bridge::PostSender(), csp::kCSPRequired,
-            kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
-            base::Closure() /* ran_animation_frame_callbacks */,
-            dom::Window::CloseCallback() /* window_close */,
-            base::Closure() /* window_minimize */, NULL, NULL, NULL,
-            dom::Window::OnStartDispatchEventCallback(),
-            dom::Window::OnStopDispatchEventCallback(),
-            dom::ScreenshotManager::ProvideScreenshotFunctionCallback())) {}
+        url_("about:blank") {
+    engine_ = script::JavaScriptEngine::CreateEngine();
+    global_environment_ = engine_->CreateGlobalEnvironment();
+    window_ = new Window(
+        1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
+        dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL, NULL,
+        NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL,
+        global_environment_->script_value_factory(), NULL, NULL, url_, "",
+        "en-US", "en", base::Callback<void(const GURL &)>(),
+        base::Bind(&MockErrorCallback::Run,
+                   base::Unretained(&mock_error_callback_)),
+        NULL, network_bridge::PostSender(), csp::kCSPRequired,
+        kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
+        base::Closure() /* ran_animation_frame_callbacks */,
+        dom::Window::CloseCallback() /* window_close */,
+        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        dom::Window::OnStartDispatchEventCallback(),
+        dom::Window::OnStopDispatchEventCallback(),
+        dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
+  }
 
   ~WindowTest() override {}
 
@@ -72,6 +77,8 @@
   scoped_ptr<dom_parser::Parser> dom_parser_;
   scoped_ptr<loader::FetcherFactory> fetcher_factory_;
   dom::LocalStorageDatabase local_storage_database_;
+  scoped_ptr<script::JavaScriptEngine> engine_;
+  scoped_refptr<script::GlobalEnvironment> global_environment_;
   GURL url_;
   scoped_refptr<Window> window_;
 };
diff --git a/src/cobalt/dom_parser/dom_parser.gyp b/src/cobalt/dom_parser/dom_parser.gyp
index eaf84bf..9934f06 100644
--- a/src/cobalt/dom_parser/dom_parser.gyp
+++ b/src/cobalt/dom_parser/dom_parser.gyp
@@ -38,39 +38,8 @@
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/loader/loader.gyp:loader',
-        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
         '<(DEPTH)/third_party/libxml/libxml.gyp:libxml',
       ],
     },
-
-    {
-      'target_name': 'dom_parser_test',
-      'type': '<(gtest_target_type)',
-      'sources': [
-        'html_decoder_test.cc',
-        'xml_decoder_test.cc',
-      ],
-      'dependencies': [
-        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/dom/dom.gyp:dom_testing',
-        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
-        '<(DEPTH)/testing/gmock.gyp:gmock',
-        '<(DEPTH)/testing/gtest.gyp:gtest',
-        'dom_parser',
-      ],
-    },
-
-    {
-      'target_name': 'dom_parser_test_deploy',
-      'type': 'none',
-      'dependencies': [
-        'dom_parser_test',
-      ],
-      'variables': {
-        'executable_name': 'dom_parser_test',
-      },
-      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
-    },
   ],
 }
diff --git a/src/cobalt/dom_parser/dom_parser_test.gyp b/src/cobalt/dom_parser/dom_parser_test.gyp
new file mode 100644
index 0000000..6ed8f59
--- /dev/null
+++ b/src/cobalt/dom_parser/dom_parser_test.gyp
@@ -0,0 +1,49 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'dom_parser_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'html_decoder_test.cc',
+        'xml_decoder_test.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+        '<(DEPTH)/cobalt/dom/testing/dom_testing.gyp:dom_testing',
+        '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
+        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+    },
+
+    {
+      'target_name': 'dom_parser_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'dom_parser_test',
+      ],
+      'variables': {
+        'executable_name': 'dom_parser_test',
+      },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/cobalt/dom_parser/html_decoder_test.cc b/src/cobalt/dom_parser/html_decoder_test.cc
index dcfeba4..ef966fe 100644
--- a/src/cobalt/dom_parser/html_decoder_test.cc
+++ b/src/cobalt/dom_parser/html_decoder_test.cc
@@ -16,6 +16,7 @@
 
 #include "base/callback.h"
 #include "base/message_loop.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/dom/attr.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_stat_tracker.h"
@@ -28,6 +29,7 @@
 #include "cobalt/dom/text.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -47,6 +49,7 @@
   ~HTMLDecoderTest() override {}
 
   loader::FetcherFactory fetcher_factory_;
+  loader::LoaderFactory loader_factory_;
   scoped_ptr<Parser> dom_parser_;
   dom::testing::StubCSSParser stub_css_parser_;
   dom::testing::StubScriptRunner stub_script_runner_;
@@ -62,14 +65,17 @@
 
 HTMLDecoderTest::HTMLDecoderTest()
     : fetcher_factory_(NULL /* network_module */),
+      loader_factory_(&fetcher_factory_, NULL /* ResourceProvider */,
+                      base::kThreadPriority_Default),
       dom_parser_(new Parser()),
       dom_stat_tracker_(new dom::DomStatTracker("HTMLDecoderTest")),
       html_element_context_(
-          &fetcher_factory_, &stub_css_parser_, dom_parser_.get(),
-          NULL /* can_play_type_handler */, NULL /* web_media_player_factory */,
-          &stub_script_runner_, NULL /* script_value_factory */, NULL, NULL,
-          NULL, NULL, NULL, NULL, NULL, dom_stat_tracker_.get(), "",
-          base::kApplicationStateStarted),
+          &fetcher_factory_, &loader_factory_, &stub_css_parser_,
+          dom_parser_.get(), NULL /* can_play_type_handler */,
+          NULL /* web_media_player_factory */, &stub_script_runner_,
+          NULL /* script_value_factory */, NULL, NULL, NULL, NULL, NULL, NULL,
+          NULL, dom_stat_tracker_.get(), "", base::kApplicationStateStarted,
+          NULL),
       document_(new dom::Document(&html_element_context_)),
       root_(new dom::Element(document_, base::Token("element"))),
       source_location_(base::SourceLocation("[object HTMLDecoderTest]", 1, 1)) {
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index ddba793..2386d10 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -17,8 +17,10 @@
 #include <cmath>
 #include <string>
 
+#include "base/time.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/dom/event.h"
 #include "cobalt/dom/input_event.h"
 #include "cobalt/dom/input_event_init.h"
 #include "cobalt/dom/keyboard_event.h"
@@ -29,11 +31,23 @@
 #include "cobalt/dom/wheel_event_init.h"
 #include "cobalt/input/create_default_camera_3d.h"
 #include "cobalt/input/input_poller_impl.h"
+#include "cobalt/overlay_info/overlay_info_registry.h"
 #include "cobalt/system_window/input_event.h"
 
 namespace cobalt {
 namespace input {
 
+namespace {
+void UpdateEventInit(const system_window::InputEvent* input_event,
+                     EventInit* event) {
+  if (input_event->timestamp() != 0) {
+    // Convert SbTimeMonotonic to DOMTimeStamp.
+    event->set_time_stamp(cobalt::dom::Event::GetEventTime(
+        input_event->timestamp()));
+  }
+}
+}
+
 InputDeviceManagerDesktop::InputDeviceManagerDesktop(
     const KeyboardEventCallback& keyboard_event_callback,
     const PointerEventCallback& pointer_event_callback,
@@ -213,17 +227,23 @@
   dom::KeyboardEvent::KeyLocationCode location =
       dom::KeyboardEvent::KeyCodeToKeyLocation(key_code);
   dom::KeyboardEventInit keyboard_event;
+  UpdateEventInit(input_event, &keyboard_event);
   UpdateEventModifierInit(input_event, &keyboard_event);
   keyboard_event.set_location(location);
   keyboard_event.set_repeat(input_event->is_repeat());
   keyboard_event.set_char_code(key_code);
   keyboard_event.set_key_code(key_code);
   keypress_generator_filter_.HandleKeyboardEvent(type, keyboard_event);
+
+  int32_t key_code_in_int32 = static_cast<int32_t>(key_code);
+  overlay_info::OverlayInfoRegistry::Register(
+      "input_manager:keydown", &key_code_in_int32, sizeof(key_code_in_int32));
 }
 
 void InputDeviceManagerDesktop::HandlePointerEvent(
     base::Token type, const system_window::InputEvent* input_event) {
   dom::PointerEventInit pointer_event;
+  UpdateEventInit(input_event, &pointer_event);
   UpdateMouseEventInit(input_event, &pointer_event);
 
   switch (input_event->type()) {
@@ -262,6 +282,7 @@
     const system_window::InputEvent* input_event) {
   base::Token type = base::Tokens::wheel();
   dom::WheelEventInit wheel_event;
+  UpdateEventInit(input_event, &wheel_event);
   UpdateMouseEventInit(input_event, &wheel_event);
 
   wheel_event.set_delta_x(input_event->delta().x());
@@ -279,6 +300,7 @@
   base::Token type = base::Tokens::input();
 
   dom::InputEventInit input_event;
+  UpdateEventInit(event, &input_event);
   input_event.set_data(event->input_text());
   // We do not handle composition sessions currently, so isComposing should
   // always be false.
diff --git a/src/cobalt/layout/layout.gyp b/src/cobalt/layout/layout.gyp
index 1873f57..3ff7781 100644
--- a/src/cobalt/layout/layout.gyp
+++ b/src/cobalt/layout/layout.gyp
@@ -94,7 +94,6 @@
         '<(DEPTH)/cobalt/loader/loader.gyp:loader',
         '<(DEPTH)/cobalt/render_tree/render_tree.gyp:animations',
         '<(DEPTH)/cobalt/render_tree/render_tree.gyp:render_tree',
-        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
         '<(DEPTH)/third_party/icu/icu.gyp:icuuc',
       ],
       # Exporting dom so that layout_test gets the transitive include paths to
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc
index 502701e..fa1832c 100644
--- a/src/cobalt/layout/topmost_event_target.cc
+++ b/src/cobalt/layout/topmost_event_target.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/layout/topmost_event_target.h"
 
+#include "base/debug/trace_event.h"
 #include "base/optional.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
@@ -29,8 +30,6 @@
 #include "cobalt/dom/pointer_state.h"
 #include "cobalt/dom/ui_event.h"
 #include "cobalt/dom/wheel_event.h"
-#include "cobalt/layout/container_box.h"
-#include "cobalt/layout/used_style.h"
 #include "cobalt/math/vector2d.h"
 #include "cobalt/math/vector2d_f.h"
 
@@ -40,9 +39,15 @@
 scoped_refptr<dom::HTMLElement> TopmostEventTarget::FindTopmostEventTarget(
     const scoped_refptr<dom::Document>& document,
     const math::Vector2dF& coordinate) {
+  TRACE_EVENT0("cobalt::layout",
+               "TopmostEventTarget::FindTopmostEventTarget()");
   DCHECK(document);
   DCHECK(!box_);
   DCHECK(render_sequence_.empty());
+
+  // Make sure the document's layout box tree is up-to-date.
+  document->DoSynchronousLayout();
+
   html_element_ = document->html();
   ConsiderElement(html_element_, coordinate);
   box_ = NULL;
@@ -291,6 +296,9 @@
 
 void TopmostEventTarget::MaybeSendPointerEvents(
     const scoped_refptr<dom::Event>& event) {
+  TRACE_EVENT0("cobalt::layout",
+               "TopmostEventTarget::MaybeSendPointerEvents()");
+
   const dom::MouseEvent* const mouse_event =
       base::polymorphic_downcast<const dom::MouseEvent* const>(event.get());
   DCHECK(mouse_event);
diff --git a/src/cobalt/layout_tests/testdata/cobalt/image-onerror-expected.png b/src/cobalt/layout_tests/testdata/cobalt/image-onerror-expected.png
new file mode 100644
index 0000000..dec2811
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cobalt/image-onerror-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cobalt/image-onerror.html b/src/cobalt/layout_tests/testdata/cobalt/image-onerror.html
new file mode 100644
index 0000000..f7f10d7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cobalt/image-onerror.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>image-onerror</title>
+  <style>
+    #result {
+      width: 100px;
+      height:100px;
+      background-color:#0047AB;
+    }
+  </style>
+</head>
+<body>
+
+<div id="result"></div>
+
+<script>
+
+function expect(condition, message) {
+  if (!condition) {
+    document.querySelector('#result').style.display = 'none';
+    const fullMessage = `failed expectation at:
+${new Error().stack}
+${typeof message === 'undefined' ? '' : 'with message: ' + message}`;
+    console.error(fullMessage);
+  }
+}
+
+if (window.testRunner) {
+  window.testRunner.waitUntilDone();
+}
+
+const image = new Image();
+
+let imageOnErrorWasCalled = false;
+let imageErrorEventHandlerCalled = false;
+
+function endTestIfAllErrorHandlersCalled() {
+  if (imageOnErrorWasCalled && imageErrorEventHandlerCalled) {
+    // Success!
+    if (window.testRunner) {
+      window.testRunner.notifyDone();
+    }
+  }
+}
+
+image.onerror = (errorEvent) => {
+  expect(errorEvent.type === 'error', errorEvent.type);
+  imageOnErrorWasCalled = true;
+  endTestIfAllErrorHandlersCalled();
+};
+
+image.addEventListener('error', (errorEvent) => {
+  expect(errorEvent.type === 'error', errorEvent.type);
+  imageErrorEventHandlerCalled = true;
+  endTestIfAllErrorHandlersCalled();
+});
+
+image.src = 'bogusscheme://invalidurl';
+
+setTimeout(() => {
+  // If we hit this timeout we have failed because we expect the previous event
+  // handlers to have both fired before we get here.
+  expect(false);
+
+  // However, if we do get here, the subsequent expect statements can help
+  // identify which of the error handlers failed to be called.
+  expect(imageOnErrorWasCalled);
+  expect(imageErrorEventHandlerCalled);
+
+  if (window.testRunner) {
+    window.testRunner.notifyDone();
+  }
+}, 1000);
+
+</script>
+
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
index 5fa976c..d208efa 100644
--- a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
@@ -7,6 +7,7 @@
 fixed-width-divs-with-background-color
 font-weight
 image-from-blob
+image-onerror
 inline-box-with-overflow-words
 inline-style-allowed-while-cloning-objects
 interface-object-types-are-correct
diff --git a/src/cobalt/layout_tests/testdata/cobalt/window-onerror.html b/src/cobalt/layout_tests/testdata/cobalt/window-onerror.html
index 6baa77d..20bf0b7 100644
--- a/src/cobalt/layout_tests/testdata/cobalt/window-onerror.html
+++ b/src/cobalt/layout_tests/testdata/cobalt/window-onerror.html
@@ -28,26 +28,13 @@
 
 let windowOnErrorWasCalled = false;
 window.onerror = (message, filename, lineno, colno, error) => {
-  if (typeof message === 'object') {
-    // The code below is to work around Cobalt's window.onerror signature not being correct
-    // (although weird, the spec says you're supposed to expand the event into its attributes before
-    // dispatching it).  It is still important to test the rest of window.onerror support, despite
-    // this known issue.
-    const errorEvent = message;
-    message = errorEvent.message;
-    filename = errorEvent.filename;
-    lineno = errorEvent.lineno;
-    colno = errorEvent.colno;
-    error = errorEvent.error;
-  }
-
   // We allow anything that ends with "myCoolMessage" to pass, Chrome/Firefox behavior differs here
   // (Chrome: "Uncaught Error: myCoolMessage", Firefox: "Error: myCoolMessage").
   expect(/.*myCoolMessage/.test(message), message);
   // Allow any filename that ends with "window-onerror.html", in order to not couple too heavily to
   // the implementation of layout_tests.
   expect(/.*window-onerror.html/.test(filename), filename);
-  expect(lineno === 72, lineno);
+  expect(lineno === 83, lineno);
   // The value of the column number is not standard across major browsers.
   //   Chrome: 1 without devtools open, 7 with devtools open.
   //   Edge: Always 1.
@@ -60,11 +47,35 @@
   windowOnErrorWasCalled = true;
 };
 
+// Make sure that when an event handler is added for the 'error' event via
+// addEventListener, it assumes the usual function signature of just the event
+// object.
+let windowErrorEventHandlerCalled = false;
+window.addEventListener('error', (errorEvent) => {
+  expect(errorEvent.type == 'error', errorEvent.type);
+
+  // Check the same parameters as above in the onerror attribute event handler,
+  // just make sure they are passed through as an event object instead of
+  // expanded.
+  expect(/.*myCoolMessage/.test(errorEvent.message), errorEvent.message);
+  expect(/.*window-onerror.html/.test(errorEvent.filename),
+         errorEvent.filename);
+  expect(errorEvent.lineno === 83, errorEvent.lineno);
+  expect(errorEvent.colno === 1 || errorEvent.colno === 7 ||
+         errorEvent.colno === 18, errorEvent.colno);
+  expect(typeof errorEvent.error === 'object', typeof errorEvent.error);
+  expect(String(errorEvent.error) === 'Error: myCoolMessage',
+         String(errorEvent.error));
+
+  windowErrorEventHandlerCalled = true;
+});
+
 if (window.testRunner) {
   window.testRunner.waitUntilDone();
 }
 setTimeout(() => {
   expect(windowOnErrorWasCalled);
+  expect(windowErrorEventHandlerCalled);
   if (window.testRunner) {
     window.testRunner.notifyDone();
   }
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/html/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/html/web_platform_tests.txt
new file mode 100644
index 0000000..e7ee0c4
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/web-platform-tests/html/web_platform_tests.txt
@@ -0,0 +1,3 @@
+# HTML
+
+webappapis/atob/base64.html,PASS
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/web_platform_tests.cc b/src/cobalt/layout_tests/web_platform_tests.cc
index 281803e..78cf442 100644
--- a/src/cobalt/layout_tests/web_platform_tests.cc
+++ b/src/cobalt/layout_tests/web_platform_tests.cc
@@ -197,12 +197,14 @@
   std::vector<TestResult> test_results;
 
   scoped_ptr<base::Value> root;
-  base::JSONReader reader;
+  base::JSONReader reader(
+      base::JSONParserOptions::JSON_REPLACE_INVALID_CHARACTERS);
   root.reset(reader.ReadToValue(json_results));
   // Expect that parsing test result succeeded.
   EXPECT_EQ(base::JSONReader::JSON_NO_ERROR, reader.error_code());
   if (!root) {
     // Unparseable JSON, or empty string.
+    LOG(ERROR) << "Web Platform Tests returned unparseable JSON test result!";
     return test_results;
   }
 
@@ -317,6 +319,10 @@
     ::testing::ValuesIn(EnumerateWebPlatformTests("XMLHttpRequest")));
 
 INSTANTIATE_TEST_CASE_P(
+    cobalt_special, WebPlatformTest,
+    ::testing::ValuesIn(EnumerateWebPlatformTests("cobalt_special")));
+
+INSTANTIATE_TEST_CASE_P(
     csp, WebPlatformTest,
     ::testing::ValuesIn(EnumerateWebPlatformTests("content-security-policy")));
 
@@ -336,6 +342,9 @@
     ::testing::ValuesIn(EnumerateWebPlatformTests("fetch", "'fetch' in this")));
 #endif
 
+INSTANTIATE_TEST_CASE_P(html, WebPlatformTest,
+                        ::testing::ValuesIn(EnumerateWebPlatformTests("html")));
+
 INSTANTIATE_TEST_CASE_P(
     mediasession, WebPlatformTest,
     ::testing::ValuesIn(EnumerateWebPlatformTests("mediasession")));
@@ -347,10 +356,6 @@
                             "streams", "'ReadableStream' in this")));
 #endif
 
-INSTANTIATE_TEST_CASE_P(
-    cobalt_special, WebPlatformTest,
-    ::testing::ValuesIn(EnumerateWebPlatformTests("cobalt_special")));
-
 #endif  // !defined(COBALT_WIN)
 
 }  // namespace layout_tests
diff --git a/src/cobalt/loader/image/animated_webp_image.h b/src/cobalt/loader/image/animated_webp_image.h
index de9ca49..4f856e6 100644
--- a/src/cobalt/loader/image/animated_webp_image.h
+++ b/src/cobalt/loader/image/animated_webp_image.h
@@ -30,7 +30,7 @@
 #include "cobalt/loader/image/image.h"
 #include "cobalt/render_tree/color_rgba.h"
 #include "cobalt/render_tree/resource_provider.h"
-#include "third_party/libwebp/webp/demux.h"
+#include "third_party/libwebp/src/webp/demux.h"
 
 namespace cobalt {
 namespace loader {
diff --git a/src/cobalt/loader/image/image_encoder.cc b/src/cobalt/loader/image/image_encoder.cc
index da3f832..08ffc75 100644
--- a/src/cobalt/loader/image/image_encoder.cc
+++ b/src/cobalt/loader/image/image_encoder.cc
@@ -14,8 +14,9 @@
 
 #include "cobalt/loader/image/image_encoder.h"
 
+#include "base/debug/trace_event.h"
 #include "cobalt/renderer/test/png_utils/png_encode.h"
-#include "third_party/libwebp/webp/encode.h"
+#include "third_party/libwebp/src/webp/encode.h"
 
 namespace cobalt {
 namespace loader {
@@ -26,6 +27,7 @@
 scoped_array<uint8> WriteRGBAPixelsToPNG(const uint8* const pixel_data,
                                          const math::Size& dimensions,
                                          size_t* out_num_bytes) {
+  TRACE_EVENT0("cobalt::loader", "ImageEncoder::WriteRGBAPixelsToPNG()");
   const int kRGBABytesPerPixel = 4;
   const int kPitchSizeInBytes = dimensions.width() * kRGBABytesPerPixel;
   return EncodeRGBAToBuffer(pixel_data, dimensions.width(), dimensions.height(),
@@ -35,6 +37,7 @@
 scoped_refptr<loader::image::EncodedStaticImage> CompressRGBAImage(
     loader::image::EncodedStaticImage::ImageFormat desired_format,
     const uint8* const image_data, const math::Size& dimensions) {
+  TRACE_EVENT0("cobalt::loader", "ImageEncoder::CompressRGBAImage()");
   using ImageFormat = loader::image::EncodedStaticImage::ImageFormat;
   switch (desired_format) {
     case ImageFormat::kPNG: {
diff --git a/src/cobalt/loader/image/webp_image_decoder.cc b/src/cobalt/loader/image/webp_image_decoder.cc
index 5602ef6..c372790 100644
--- a/src/cobalt/loader/image/webp_image_decoder.cc
+++ b/src/cobalt/loader/image/webp_image_decoder.cc
@@ -39,8 +39,6 @@
   config_.options.no_fancy_upsampling = 1;
   // Don't use multi-threaded decoding.
   config_.options.use_threads = 0;
-  // Discard enhancement layer.
-  config_.options.no_enhancement = 1;
 }
 
 WEBPImageDecoder::~WEBPImageDecoder() {
diff --git a/src/cobalt/loader/image/webp_image_decoder.h b/src/cobalt/loader/image/webp_image_decoder.h
index aa93e42..45d773f 100644
--- a/src/cobalt/loader/image/webp_image_decoder.h
+++ b/src/cobalt/loader/image/webp_image_decoder.h
@@ -20,7 +20,7 @@
 
 #include "cobalt/loader/image/animated_webp_image.h"
 #include "cobalt/loader/image/image_data_decoder.h"
-#include "third_party/libwebp/webp/decode.h"
+#include "third_party/libwebp/src/webp/decode.h"
 
 namespace cobalt {
 namespace loader {
diff --git a/src/cobalt/loader/loader_factory.cc b/src/cobalt/loader/loader_factory.cc
index 5ef2229..0c7d695 100644
--- a/src/cobalt/loader/loader_factory.cc
+++ b/src/cobalt/loader/loader_factory.cc
@@ -97,6 +97,51 @@
   return loader.Pass();
 }
 
+scoped_ptr<Loader> LoaderFactory::CreateLinkLoader(
+    const GURL& url, const Origin& origin,
+    const csp::SecurityCallback& url_security_callback,
+    const loader::RequestMode cors_mode,
+    const TextDecoder::SuccessCallback& success_callback,
+    const Loader::OnErrorFunction& loader_error_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  scoped_ptr<loader::Decoder> decoder(
+      new loader::TextDecoder(success_callback));
+
+  Loader::FetcherCreator fetcher_creator =
+      MakeFetcherCreator(url, url_security_callback, cors_mode, origin);
+  scoped_ptr<Loader> loader(new Loader(
+      fetcher_creator, decoder.Pass(), loader_error_callback,
+
+      base::Bind(&LoaderFactory::OnLoaderDestroyed, base::Unretained(this)),
+      is_suspended_));
+
+  OnLoaderCreated(loader.get());
+  return loader.Pass();
+}
+
+scoped_ptr<Loader> LoaderFactory::CreateScriptLoader(
+    const GURL& url, const Origin& origin,
+    const csp::SecurityCallback& url_security_callback,
+    const TextDecoder::SuccessCallback& success_callback,
+    const Loader::OnErrorFunction& loader_error_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  scoped_ptr<loader::Decoder> decoder(
+      new loader::TextDecoder(success_callback));
+
+  Loader::FetcherCreator fetcher_creator =
+      MakeFetcherCreator(url, url_security_callback, kNoCORSMode, origin);
+  scoped_ptr<Loader> loader(new Loader(
+      fetcher_creator, decoder.Pass(), loader_error_callback,
+
+      base::Bind(&LoaderFactory::OnLoaderDestroyed, base::Unretained(this)),
+      is_suspended_));
+
+  OnLoaderCreated(loader.get());
+  return loader.Pass();
+}
+
 Loader::FetcherCreator LoaderFactory::MakeFetcherCreator(
     const GURL& url, const csp::SecurityCallback& url_security_callback,
     RequestMode request_mode, const Origin& origin) {
diff --git a/src/cobalt/loader/loader_factory.h b/src/cobalt/loader/loader_factory.h
index cfef2fc..cde4c56 100644
--- a/src/cobalt/loader/loader_factory.h
+++ b/src/cobalt/loader/loader_factory.h
@@ -19,11 +19,13 @@
 
 #include "base/threading/thread.h"
 #include "cobalt/csp/content_security_policy.h"
+#include "cobalt/loader/fetcher.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/font/typeface_decoder.h"
 #include "cobalt/loader/image/image_decoder.h"
 #include "cobalt/loader/loader.h"
 #include "cobalt/loader/mesh/mesh_decoder.h"
+#include "cobalt/loader/text_decoder.h"
 #include "cobalt/render_tree/resource_provider.h"
 #include "googleurl/src/gurl.h"
 
@@ -60,6 +62,21 @@
       const mesh::MeshDecoder::SuccessCallback& success_callback,
       const mesh::MeshDecoder::ErrorCallback& error_callback);
 
+  // Creates a loader that fetches and decodes a Javascript resource.
+  scoped_ptr<Loader> CreateScriptLoader(
+      const GURL& url, const Origin& origin,
+      const csp::SecurityCallback& url_security_callback,
+      const TextDecoder::SuccessCallback& success_callback,
+      const Loader::OnErrorFunction& loader_error_callback);
+
+  // Creates a loader that fetches and decodes a link resources.
+  scoped_ptr<Loader> CreateLinkLoader(
+      const GURL& url, const Origin& origin,
+      const csp::SecurityCallback& url_security_callback,
+      const loader::RequestMode cors_mode,
+      const TextDecoder::SuccessCallback& success_callback,
+      const Loader::OnErrorFunction& loader_error_callback);
+
   // Clears out the loader factory's resource provider, aborting any in-progress
   // loads.
   void Suspend();
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index 6185752..e25c19f 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -573,6 +573,10 @@
   ResourceSet non_callback_blocking_loading_resource_set_;
   // Resources that have completed loading and have callbacks pending.
   ResourceCallbackMap pending_callback_map_;
+  // Timer used to ensure that pending callbacks are handled in a timely manner
+  // when callbacks are being blocked by additional loading resources.
+  base::OneShotTimer<ResourceCache<CacheType>> process_pending_callback_timer_;
+
   // Whether or not ProcessPendingCallbacks() is running.
   bool is_processing_pending_callbacks_;
   // Whether or not callbacks are currently disabled.
@@ -709,6 +713,7 @@
 template <typename CacheType>
 void ResourceCache<CacheType>::ProcessPendingCallbacks() {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
+  process_pending_callback_timer_.Stop();
 
   // If callbacks are disabled, simply return.
   if (are_callbacks_disabled_) {
@@ -921,6 +926,10 @@
 
 template <typename CacheType>
 void ResourceCache<CacheType>::ProcessPendingCallbacksIfUnblocked() {
+  // If there are no callback blocking resources, then simply process any
+  // pending callbacks now; otherwise, start |process_pending_callback_timer_|,
+  // which ensures that the callbacks are handled in a timely manner while still
+  // allowing them to be batched.
   if (callback_blocking_loading_resource_set_.empty()) {
     ProcessPendingCallbacks();
 
@@ -931,6 +940,19 @@
       callback_blocking_loading_resource_set_.swap(
           non_callback_blocking_loading_resource_set_);
     }
+  } else if (!pending_callback_map_.empty() &&
+             !process_pending_callback_timer_.IsRunning()) {
+    // The maximum delay for a pending callback is set to 500ms. After that, the
+    // callback will be processed regardless of how many callback blocking
+    // loading resources remain. This specific value maximizes callback batching
+    // on fast networks while also keeping the callback delay on slow networks
+    // to a minimum and is based on significant testing.
+    const int64 kMaxPendingCallbackDelayInMilliseconds = 500;
+    process_pending_callback_timer_.Start(
+        FROM_HERE,
+        base::TimeDelta::FromMilliseconds(
+            kMaxPendingCallbackDelayInMilliseconds),
+        this, &ResourceCache::ProcessPendingCallbacks);
   }
 }
 
diff --git a/src/cobalt/loader/sync_loader.cc b/src/cobalt/loader/sync_loader.cc
index eb75db5..76b69da 100644
--- a/src/cobalt/loader/sync_loader.cc
+++ b/src/cobalt/loader/sync_loader.cc
@@ -33,7 +33,9 @@
 // decoder creators.
 class LoaderOnThread {
  public:
-  LoaderOnThread() : waitable_event_(false, false) {}
+  LoaderOnThread()
+      : start_waitable_event_(false, false),
+        end_waitable_event_(false, false) {}
 
   // Start() and End() should be called on the same thread, the sychronous load
   // thread, so the member objects are created, execute, and are destroyed,
@@ -44,15 +46,28 @@
       base::Callback<void(const std::string&)> error_callback);
   void End();
 
-  void Signal() { waitable_event_.Signal(); }
-  void Wait() { waitable_event_.Wait(); }
+  void SignalStartDone() { start_waitable_event_.Signal(); }
+  void SignalEndDone() { end_waitable_event_.Signal(); }
+
+  void WaitForStart(base::WaitableEvent* interrupt_event) {
+    base::WaitableEvent* event_array[] = {&start_waitable_event_,
+                                          interrupt_event};
+    size_t effective_size = arraysize(event_array);
+    if (!interrupt_event) {
+      --effective_size;
+    }
+    base::WaitableEvent::WaitMany(event_array, effective_size);
+  }
+
+  void WaitForEnd() { end_waitable_event_.Wait(); }
 
  private:
   scoped_ptr<Decoder> decoder_;
   scoped_ptr<FetcherToDecoderAdapter> fetcher_to_decoder_adaptor_;
   scoped_ptr<Fetcher> fetcher_;
 
-  base::WaitableEvent waitable_event_;
+  base::WaitableEvent start_waitable_event_;
+  base::WaitableEvent end_waitable_event_;
 };
 
 // This class is responsible for passing chunks of data from fetcher to decoder
@@ -75,12 +90,12 @@
     DCHECK(fetcher);
     decoder_->SetLastURLOrigin(fetcher->last_url_origin());
     decoder_->Finish();
-    loader_on_thread_->Signal();
+    loader_on_thread_->SignalStartDone();
   }
   void OnError(Fetcher* fetcher, const std::string& error) override {
     UNREFERENCED_PARAMETER(fetcher);
     error_callback_.Run(error);
-    loader_on_thread_->Signal();
+    loader_on_thread_->SignalStartDone();
   }
 
  private:
@@ -103,7 +118,7 @@
   fetcher_.reset();
   fetcher_to_decoder_adaptor_.reset();
   decoder_.reset();
-  Signal();
+  SignalEndDone();
 }
 
 }  // namespace
@@ -113,7 +128,7 @@
 //////////////////////////////////////////////////////////////////
 
 void LoadSynchronously(
-    MessageLoop* message_loop,
+    MessageLoop* message_loop, base::WaitableEvent* interrupt_trigger,
     base::Callback<scoped_ptr<Fetcher>(Fetcher::Handler*)> fetcher_creator,
     base::Callback<scoped_ptr<Decoder>()> decoder_creator,
     base::Callback<void(const std::string&)> error_callback) {
@@ -127,12 +142,16 @@
       FROM_HERE,
       base::Bind(&LoaderOnThread::Start, base::Unretained(&loader_on_thread),
                  fetcher_creator, decoder_creator, error_callback));
-  loader_on_thread.Wait();
+  loader_on_thread.WaitForStart(interrupt_trigger);
 
   message_loop->PostTask(
       FROM_HERE,
       base::Bind(&LoaderOnThread::End, base::Unretained(&loader_on_thread)));
-  loader_on_thread.Wait();
+
+  // Wait for a different event here, since it is possible that the first
+  // wait was interrupted, and the fetcher completion can still |Signal()|
+  // the start event.
+  loader_on_thread.WaitForEnd();
 }
 
 }  // namespace loader
diff --git a/src/cobalt/loader/sync_loader.h b/src/cobalt/loader/sync_loader.h
index dadc2b8..1280203 100644
--- a/src/cobalt/loader/sync_loader.h
+++ b/src/cobalt/loader/sync_loader.h
@@ -20,6 +20,7 @@
 #include "base/callback.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/message_loop.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "cobalt/loader/decoder.h"
 #include "cobalt/loader/fetcher.h"
@@ -31,7 +32,7 @@
 // and use the given message loop to load the resource. The fetcher and decoder
 // are responsible for setting up timeout for themselves.
 void LoadSynchronously(
-    MessageLoop* message_loop,
+    MessageLoop* message_loop, base::WaitableEvent* interrupt_trigger,
     base::Callback<scoped_ptr<Fetcher>(Fetcher::Handler*)> fetcher_creator,
     base::Callback<scoped_ptr<Decoder>()> decoder_creator,
     base::Callback<void(const std::string&)> error_callback);
diff --git a/src/cobalt/loader/text_decoder.h b/src/cobalt/loader/text_decoder.h
index bbb24a2..69b58fd 100644
--- a/src/cobalt/loader/text_decoder.h
+++ b/src/cobalt/loader/text_decoder.h
@@ -34,9 +34,10 @@
 // results.
 class TextDecoder : public Decoder {
  public:
-  explicit TextDecoder(
-      base::Callback<void(const loader::Origin&, scoped_ptr<std::string>)>
-          done_callback)
+  typedef base::Callback<void(const loader::Origin&, scoped_ptr<std::string>)>
+      SuccessCallback;
+
+  explicit TextDecoder(const SuccessCallback& done_callback)
       : done_callback_(done_callback), suspended_(false) {}
   ~TextDecoder() override {}
 
@@ -97,8 +98,7 @@
 
  private:
   base::ThreadChecker thread_checker_;
-  base::Callback<void(const loader::Origin&, scoped_ptr<std::string>)>
-      done_callback_;
+  SuccessCallback done_callback_;
   loader::Origin last_url_origin_;
   scoped_ptr<std::string> text_;
   bool suspended_;
diff --git a/src/cobalt/media/base/BUILD.gn b/src/cobalt/media/base/BUILD.gn
index 0c3ad22..8c223cf 100644
--- a/src/cobalt/media/base/BUILD.gn
+++ b/src/cobalt/media/base/BUILD.gn
@@ -147,8 +147,6 @@
     "media_client.h",
     "media_content_type.cc",
     "media_content_type.h",
-    "media_keys.cc",
-    "media_keys.h",
     "media_log.cc",
     "media_log.h",
     "media_log_event.h",
diff --git a/src/cobalt/media/base/drm_system.cc b/src/cobalt/media/base/drm_system.cc
index 8ecc2a3..f03ba30 100644
--- a/src/cobalt/media/base/drm_system.cc
+++ b/src/cobalt/media/base/drm_system.cc
@@ -93,11 +93,15 @@
 #if SB_HAS(DRM_KEY_STATUSES)
                                             ,
                                             OnSessionKeyStatusesChangedFunc
+#endif  // SB_HAS(DRM_KEY_STATUSES)
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+                                            ,
+                                            OnServerCertificateUpdatedFunc
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
 #if SB_HAS(DRM_SESSION_CLOSED)
                                             ,
                                             OnSessionClosedFunc
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
-#endif  // SB_HAS(DRM_KEY_STATUSES)
                                             )),  // NOLINT(whitespace/parens)
       message_loop_(MessageLoop::current()),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
@@ -130,6 +134,31 @@
                                      ));  // NOLINT(whitespace/parens)
 }
 
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+bool DrmSystem::IsServerCertificateUpdatable() {
+  return SbDrmIsServerCertificateUpdatable(wrapped_drm_system_);
+}
+
+void DrmSystem::UpdateServerCertificate(
+    const uint8_t* certificate, int certificate_size,
+    ServerCertificateUpdatedCallback server_certificate_updated_callback) {
+  DCHECK(IsServerCertificateUpdatable());
+  int ticket = next_session_update_request_ticket_++;
+  ticket_to_server_certificate_updated_map_.insert(
+      std::make_pair(ticket, server_certificate_updated_callback));
+  SbDrmUpdateServerCertificate(wrapped_drm_system_, ticket, certificate,
+                               certificate_size);
+}
+#else   // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+bool DrmSystem::IsServerCertificateUpdatable() { return false; }
+
+void DrmSystem::UpdateServerCertificate(
+    const uint8_t* certificate, int certificate_size,
+    ServerCertificateUpdatedCallback server_certificate_updated_callback) {
+  NOTREACHED();
+}
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+
 void DrmSystem::GenerateSessionUpdateRequest(
     Session* session, const std::string& type, const uint8_t* init_data,
     int init_data_length, const SessionUpdateRequestGeneratedCallback&
@@ -172,8 +201,11 @@
 }
 
 void DrmSystem::OnSessionUpdateRequestGenerated(
-    int ticket, const base::optional<std::string>& session_id,
+    SessionTicketAndOptionalId ticket_and_optional_id, SbDrmStatus status,
+    SbDrmSessionRequestType type, const std::string& error_message,
     scoped_array<uint8> message, int message_size) {
+  int ticket = ticket_and_optional_id.ticket;
+  const base::optional<std::string>& session_id = ticket_and_optional_id.id;
   if (SbDrmTicketIsValid(ticket)) {
     // Called back as a result of |SbDrmGenerateSessionUpdateRequest|.
 
@@ -197,11 +229,12 @@
       id_to_session_map_.insert(
           std::make_pair(*session_id, session_update_request.session));
 
-      session_update_request.generated_callback.Run(message.Pass(),
+      session_update_request.generated_callback.Run(type, message.Pass(),
                                                     message_size);
     } else {
       // Failure during request generation.
-      session_update_request.did_not_generate_callback.Run();
+      session_update_request.did_not_generate_callback.Run(status,
+                                                           error_message);
     }
 
     // Sweep the context of |GenerateSessionUpdateRequest|.
@@ -226,12 +259,13 @@
     }
     Session* session = session_iterator->second;
 
-    session->update_request_generated_callback().Run(message.Pass(),
+    session->update_request_generated_callback().Run(type, message.Pass(),
                                                      message_size);
   }
 }
 
-void DrmSystem::OnSessionUpdated(int ticket, bool succeeded) {
+void DrmSystem::OnSessionUpdated(int ticket, SbDrmStatus status,
+                                 const std::string& error_message) {
   // Restore the context of |UpdateSession|.
   TicketToSessionUpdateMap::iterator session_update_iterator =
       ticket_to_session_update_map_.find(ticket);
@@ -242,10 +276,10 @@
   const SessionUpdate& session_update = session_update_iterator->second;
 
   // Interpret the result.
-  if (succeeded) {
+  if (status == kSbDrmStatusSuccess) {
     session_update.updated_callback.Run();
   } else {
-    session_update.did_not_update_callback.Run();
+    session_update.did_not_update_callback.Run(status, error_message);
   }
 
   // Sweep the context of |UpdateSession|.
@@ -285,11 +319,36 @@
 }
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
 
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+void DrmSystem::OnServerCertificateUpdated(int ticket, SbDrmStatus status,
+                                           const std::string& error_message) {
+  auto iter = ticket_to_server_certificate_updated_map_.find(ticket);
+  if (iter == ticket_to_server_certificate_updated_map_.end()) {
+    LOG(ERROR) << "Unknown ticket: " << ticket << ".";
+    return;
+  }
+  iter->second.Run(status, error_message);
+  ticket_to_server_certificate_updated_map_.erase(iter);
+}
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+
 // static
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+void DrmSystem::OnSessionUpdateRequestGeneratedFunc(
+    SbDrmSystem wrapped_drm_system, void* context, int ticket,
+    SbDrmStatus status, SbDrmSessionRequestType type, const char* error_message,
+    const void* session_id, int session_id_size, const void* content,
+    int content_size, const char* url) {
+#else   // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
 void DrmSystem::OnSessionUpdateRequestGeneratedFunc(
     SbDrmSystem wrapped_drm_system, void* context, int ticket,
     const void* session_id, int session_id_size, const void* content,
     int content_size, const char* url) {
+  SbDrmStatus status =
+      session_id ? kSbDrmStatusSuccess : kSbDrmStatusUnknownError;
+  SbDrmSessionRequestType type = kSbDrmSessionRequestTypeLicenseRequest;
+  const char* error_message = NULL;
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
   DCHECK(context);
   DrmSystem* drm_system = static_cast<DrmSystem*>(context);
   DCHECK_EQ(wrapped_drm_system, drm_system->wrapped_drm_system_);
@@ -306,23 +365,39 @@
   }
 
   drm_system->message_loop_->PostTask(
-      FROM_HERE, base::Bind(&DrmSystem::OnSessionUpdateRequestGenerated,
-                            drm_system->weak_this_, ticket, session_id_copy,
-                            base::Passed(&content_copy), content_size));
+      FROM_HERE,
+      base::Bind(&DrmSystem::OnSessionUpdateRequestGenerated,
+                 drm_system->weak_this_,
+                 SessionTicketAndOptionalId{ticket, session_id_copy}, status,
+                 type, error_message ? std::string(error_message) : "",
+                 base::Passed(&content_copy), content_size));
 }
 
 // static
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+void DrmSystem::OnSessionUpdatedFunc(SbDrmSystem wrapped_drm_system,
+                                     void* context, int ticket,
+                                     SbDrmStatus status,
+                                     const char* error_message,
+                                     const void* /*session_id*/,
+                                     int /*session_id_size*/) {
+#else   // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
 void DrmSystem::OnSessionUpdatedFunc(SbDrmSystem wrapped_drm_system,
                                      void* context, int ticket,
                                      const void* /*session_id*/,
                                      int /*session_id_size*/, bool succeeded) {
+  SbDrmStatus status =
+      succeeded ? kSbDrmStatusSuccess : kSbDrmStatusUnknownError;
+  const char* error_message = NULL;
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
   DCHECK(context);
   DrmSystem* drm_system = static_cast<DrmSystem*>(context);
   DCHECK_EQ(wrapped_drm_system, drm_system->wrapped_drm_system_);
 
   drm_system->message_loop_->PostTask(
-      FROM_HERE, base::Bind(&DrmSystem::OnSessionUpdated,
-                            drm_system->weak_this_, ticket, succeeded));
+      FROM_HERE,
+      base::Bind(&DrmSystem::OnSessionUpdated, drm_system->weak_this_, ticket,
+                 status, error_message ? std::string(error_message) : ""));
 }
 
 #if SB_HAS(DRM_KEY_STATUSES)
@@ -359,6 +434,23 @@
 }
 #endif  // SB_HAS(DRM_KEY_STATUSES)
 
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+// static
+void DrmSystem::OnServerCertificateUpdatedFunc(SbDrmSystem wrapped_drm_system,
+                                               void* context, int ticket,
+                                               SbDrmStatus status,
+                                               const char* error_message) {
+  DCHECK(context);
+  DrmSystem* drm_system = static_cast<DrmSystem*>(context);
+  DCHECK_EQ(wrapped_drm_system, drm_system->wrapped_drm_system_);
+
+  drm_system->message_loop_->PostTask(
+      FROM_HERE, base::Bind(&DrmSystem::OnServerCertificateUpdated,
+                            drm_system->weak_this_, ticket, status,
+                            error_message ? std::string(error_message) : ""));
+}
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+
 #if SB_HAS(DRM_SESSION_CLOSED)
 // static
 void DrmSystem::OnSessionClosedFunc(SbDrmSystem wrapped_drm_system,
diff --git a/src/cobalt/media/base/drm_system.h b/src/cobalt/media/base/drm_system.h
index b1c85af..9a06ffc 100644
--- a/src/cobalt/media/base/drm_system.h
+++ b/src/cobalt/media/base/drm_system.h
@@ -35,11 +35,16 @@
 // from the same thread where |DrmSystem| was instantiated.
 class DrmSystem : public base::RefCounted<DrmSystem> {
  public:
-  typedef base::Callback<void(scoped_array<uint8> message, int message_size)>
+  typedef base::Callback<void(SbDrmSessionRequestType type,
+                              scoped_array<uint8> message, int message_size)>
       SessionUpdateRequestGeneratedCallback;
-  typedef base::Callback<void()> SessionUpdateRequestDidNotGenerateCallback;
+  typedef base::Callback<void(SbDrmStatus status,
+                              const std::string& error_message)>
+      SessionUpdateRequestDidNotGenerateCallback;
   typedef base::Callback<void()> SessionUpdatedCallback;
-  typedef base::Callback<void()> SessionDidNotUpdateCallback;
+  typedef base::Callback<void(SbDrmStatus status,
+                              const std::string& error_message)>
+      SessionDidNotUpdateCallback;
 #if SB_HAS(DRM_KEY_STATUSES)
   typedef base::Callback<void(const std::vector<std::string>& key_ids,
                               const std::vector<SbDrmKeyStatus>& key_statuses)>
@@ -48,6 +53,9 @@
 #if SB_HAS(DRM_SESSION_CLOSED)
   typedef base::Callback<void()> SessionClosedCallback;
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
+  typedef base::Callback<void(SbDrmStatus status,
+                              const std::string& error_message)>
+      ServerCertificateUpdatedCallback;
 
   // Flyweight that provides RAII semantics for sessions.
   // Most of logic is implemented by |DrmSystem| and thus sessions must be
@@ -150,6 +158,11 @@
 #endif    // SB_HAS(DRM_SESSION_CLOSED)
       );  // NOLINT(whitespace/parens)
 
+  bool IsServerCertificateUpdatable();
+  void UpdateServerCertificate(
+      const uint8_t* certificate, int certificate_size,
+      ServerCertificateUpdatedCallback server_certificate_updated_callback);
+
  private:
   // Stores context of |GenerateSessionUpdateRequest|.
   struct SessionUpdateRequest {
@@ -162,6 +175,9 @@
 
   typedef base::hash_map<std::string, Session*> IdToSessionMap;
 
+  typedef base::hash_map<int, ServerCertificateUpdatedCallback>
+      TicketToServerCertificateUpdatedMap;
+
   // Stores context of |Session::Update|.
   struct SessionUpdate {
     SessionUpdatedCallback updated_callback;
@@ -169,6 +185,13 @@
   };
   typedef base::hash_map<int, SessionUpdate> TicketToSessionUpdateMap;
 
+  // Defined to work around the limitation on number of parameters of
+  // base::Bind().
+  struct SessionTicketAndOptionalId {
+    int ticket;
+    base::optional<std::string> id;
+  };
+
   // Private API for |Session|.
   void GenerateSessionUpdateRequest(
       Session* session, const std::string& type, const uint8_t* init_data,
@@ -185,18 +208,39 @@
   // Called on the constructor thread, parameters are copied and owned by these
   // methods.
   void OnSessionUpdateRequestGenerated(
-      int ticket, const base::optional<std::string>& session_id,
+      SessionTicketAndOptionalId ticket_and_optional_id, SbDrmStatus status,
+      SbDrmSessionRequestType type, const std::string& error_message,
       scoped_array<uint8> message, int message_size);
-  void OnSessionUpdated(int ticket, bool succeeded);
+  void OnSessionUpdated(int ticket, SbDrmStatus status,
+                        const std::string& error_message);
+
 #if SB_HAS(DRM_KEY_STATUSES)
   void OnSessionKeyStatusChanged(
       const std::string& session_id, const std::vector<std::string>& key_ids,
       const std::vector<SbDrmKeyStatus>& key_statuses);
 #endif  // SB_HAS(DRM_KEY_STATUSES)
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+  void OnServerCertificateUpdated(int ticket, SbDrmStatus status,
+                                  const std::string& error_message);
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
 #if SB_HAS(DRM_SESSION_CLOSED)
   void OnSessionClosed(const std::string& session_id);
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
   // Called on any thread, parameters need to be copied immediately.
+
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+  static void OnSessionUpdateRequestGeneratedFunc(
+      SbDrmSystem wrapped_drm_system, void* context, int ticket,
+      SbDrmStatus status, SbDrmSessionRequestType type,
+      const char* error_message, const void* session_id, int session_id_size,
+      const void* content, int content_size, const char* url);
+  static void OnSessionUpdatedFunc(SbDrmSystem wrapped_drm_system,
+                                   void* context, int ticket,
+                                   SbDrmStatus status,
+                                   const char* error_message,
+                                   const void* session_id,
+                                   int session_id_length);
+#else   // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
   static void OnSessionUpdateRequestGeneratedFunc(
       SbDrmSystem wrapped_drm_system, void* context, int ticket,
       const void* session_id, int session_id_size, const void* content,
@@ -205,6 +249,8 @@
                                    void* context, int ticket,
                                    const void* session_id,
                                    int session_id_length, bool succeeded);
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+
 #if SB_HAS(DRM_KEY_STATUSES)
   static void OnSessionKeyStatusesChangedFunc(
       SbDrmSystem wrapped_drm_system, void* context, const void* session_id,
@@ -218,6 +264,14 @@
                                   const void* session_id,
                                   int session_id_size);
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
+
+#if SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+  static void OnServerCertificateUpdatedFunc(SbDrmSystem wrapped_drm_system,
+                                             void* context, int ticket,
+                                             SbDrmStatus status,
+                                             const char* error_message);
+#endif  // SB_API_VERSION >= SB_DRM_REFINEMENT_API_VERSION
+
   const SbDrmSystem wrapped_drm_system_;
   MessageLoop* const message_loop_;
 
@@ -234,6 +288,8 @@
   // Supports spontaneous invocations of |SbDrmSessionUpdateRequestFunc|.
   IdToSessionMap id_to_session_map_;
 
+  TicketToServerCertificateUpdatedMap ticket_to_server_certificate_updated_map_;
+
   // Supports concurrent calls to |Session::Update|.
   int next_session_update_ticket_;
   TicketToSessionUpdateMap ticket_to_session_update_map_;
diff --git a/src/cobalt/media/base/media_keys.cc b/src/cobalt/media/base/media_keys.cc
deleted file mode 100644
index ab878cb..0000000
--- a/src/cobalt/media/base/media_keys.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "cobalt/media/base/media_keys.h"
-
-namespace cobalt {
-namespace media {
-
-MediaKeys::MediaKeys() {}
-
-MediaKeys::~MediaKeys() {}
-
-CdmContext* MediaKeys::GetCdmContext() { return NULL; }
-
-void MediaKeys::DeleteOnCorrectThread() const { delete this; }
-
-// static
-void MediaKeysTraits::Destruct(const MediaKeys* media_keys) {
-  media_keys->DeleteOnCorrectThread();
-}
-
-}  // namespace media
-}  // namespace cobalt
diff --git a/src/cobalt/media/base/media_keys.h b/src/cobalt/media/base/media_keys.h
deleted file mode 100644
index cf66444..0000000
--- a/src/cobalt/media/base/media_keys.h
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COBALT_MEDIA_BASE_MEDIA_KEYS_H_
-#define COBALT_MEDIA_BASE_MEDIA_KEYS_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_vector.h"
-#include "cobalt/media/base/eme_constants.h"
-#include "cobalt/media/base/media_export.h"
-#include "googleurl/src/gurl.h"
-#include "starboard/types.h"
-
-namespace base {
-class Time;
-}
-
-namespace cobalt {
-namespace media {
-
-class CdmContext;
-struct CdmKeyInformation;
-struct MediaKeysTraits;
-
-template <typename... T>
-class CdmPromiseTemplate;
-
-typedef CdmPromiseTemplate<std::string> NewSessionCdmPromise;
-typedef CdmPromiseTemplate<> SimpleCdmPromise;
-typedef ScopedVector<CdmKeyInformation> CdmKeysInfo;
-
-// An interface that represents the Content Decryption Module (CDM) in the
-// Encrypted Media Extensions (EME) spec in Chromium.
-// See http://w3c.github.io/encrypted-media/#cdm
-//
-// * Ownership
-//
-// This class is ref-counted. However, a ref-count should only be held by:
-// - The owner of the CDM. This is usually some class in the EME stack, e.g.
-//   CdmSessionAdapter in the render process, or MojoCdmService in a non-render
-//   process.
-// - The media player that uses the CDM, to prevent the CDM from being
-//   destructed while still being used by the media player.
-//
-// When binding class methods into callbacks, prefer WeakPtr to using |this|
-// directly to avoid having a ref-count held by the callback.
-//
-// * Thread Safety
-//
-// Most CDM operations happen on one thread. However, it is not uncommon that
-// the media player lives on a different thread and may call into the CDM from
-// that thread. For example, if the CDM supports a Decryptor interface, the
-// Decryptor methods could be called on a different thread. The CDM
-// implementation should make sure it's thread safe for these situations.
-//
-// TODO(xhwang): Rename MediaKeys to ContentDecryptionModule. See
-// http://crbug.com/309237
-
-class MEDIA_EXPORT MediaKeys
-    : public base::RefCountedThreadSafe<MediaKeys, MediaKeysTraits> {
- public:
-  // TODO(xhwang): Remove after prefixed EME support is removed. See
-  // http://crbug.com/249976
-  // Must be a superset of cdm::MediaKeyException.
-  enum Exception {
-    NOT_SUPPORTED_ERROR,
-    INVALID_STATE_ERROR,
-    INVALID_ACCESS_ERROR,
-    QUOTA_EXCEEDED_ERROR,
-    UNKNOWN_ERROR,
-    CLIENT_ERROR,
-    OUTPUT_ERROR,
-    EXCEPTION_MAX = OUTPUT_ERROR
-  };
-
-  // Type of license required when creating/loading a session.
-  // Must be consistent with the values specified in the spec:
-  // https://w3c.github.io/encrypted-media/#idl-def-MediaKeySessionType
-  enum SessionType {
-    TEMPORARY_SESSION,
-    PERSISTENT_LICENSE_SESSION,
-    PERSISTENT_RELEASE_MESSAGE_SESSION,
-    SESSION_TYPE_MAX = PERSISTENT_RELEASE_MESSAGE_SESSION
-  };
-
-  // Type of message being sent to the application.
-  // Must be consistent with the values specified in the spec:
-  // https://w3c.github.io/encrypted-media/#idl-def-MediaKeyMessageType
-  enum MessageType {
-    LICENSE_REQUEST,
-    LICENSE_RENEWAL,
-    LICENSE_RELEASE,
-    MESSAGE_TYPE_MAX = LICENSE_RELEASE
-  };
-
-  // Provides a server certificate to be used to encrypt messages to the
-  // license server.
-  virtual void SetServerCertificate(
-      const std::vector<uint8_t>& certificate,
-      std::unique_ptr<SimpleCdmPromise> promise) = 0;
-
-  // Creates a session with |session_type|. Then generates a request with the
-  // |init_data_type| and |init_data|.
-  // Note:
-  // 1. The session ID will be provided when the |promise| is resolved.
-  // 2. The generated request should be returned through a SessionMessageCB,
-  //    which must be AFTER the |promise| is resolved. Otherwise, the session ID
-  //    in the callback will not be recognized.
-  // 3. UpdateSession(), CloseSession() and RemoveSession() should only be
-  //    called after the |promise| is resolved.
-  virtual void CreateSessionAndGenerateRequest(
-      SessionType session_type, EmeInitDataType init_data_type,
-      const std::vector<uint8_t>& init_data,
-      std::unique_ptr<NewSessionCdmPromise> promise) = 0;
-
-  // Loads a session with the |session_id| provided.
-  // Note: UpdateSession(), CloseSession() and RemoveSession() should only be
-  //       called after the |promise| is resolved.
-  virtual void LoadSession(SessionType session_type,
-                           const std::string& session_id,
-                           std::unique_ptr<NewSessionCdmPromise> promise) = 0;
-
-  // Updates a session specified by |session_id| with |response|.
-  virtual void UpdateSession(const std::string& session_id,
-                             const std::vector<uint8_t>& response,
-                             std::unique_ptr<SimpleCdmPromise> promise) = 0;
-
-  // Closes the session specified by |session_id|. The CDM should resolve or
-  // reject the |promise| when the call has been processed. This may be before
-  // the session is closed. Once the session is closed, a SessionClosedCB must
-  // also be called.
-  virtual void CloseSession(const std::string& session_id,
-                            std::unique_ptr<SimpleCdmPromise> promise) = 0;
-
-  // Removes stored session data associated with the session specified by
-  // |session_id|.
-  virtual void RemoveSession(const std::string& session_id,
-                             std::unique_ptr<SimpleCdmPromise> promise) = 0;
-
-  // Returns the CdmContext associated with |this|. The returned CdmContext is
-  // owned by |this| and the caller needs to make sure it is not used after
-  // |this| is destructed.
-  // Returns null if CdmContext is not supported. Instead the media player may
-  // use the CDM via some platform specific method.
-  // By default this method returns null.
-  // TODO(xhwang): Convert all SetCdm() implementations to use CdmContext so
-  // that this function should never return NULL.
-  virtual CdmContext* GetCdmContext();
-
-  // Deletes |this| on the correct thread. By default |this| is deleted
-  // immediately. Override this method if |this| needs to be deleted on a
-  // specific thread.
-  virtual void DeleteOnCorrectThread() const;
-
- protected:
-  friend class base::RefCountedThreadSafe<MediaKeys, MediaKeysTraits>;
-
-  MediaKeys();
-  virtual ~MediaKeys();
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MediaKeys);
-};
-
-struct MEDIA_EXPORT MediaKeysTraits {
-  // Destroys |media_keys| on the correct thread.
-  static void Destruct(const MediaKeys* media_keys);
-};
-
-// CDM session event callbacks.
-
-// Called when the CDM needs to queue a message event to the session object.
-// See http://w3c.github.io/encrypted-media/#dom-evt-message
-typedef base::Callback<void(
-    const std::string& session_id, MediaKeys::MessageType message_type,
-    const std::vector<uint8_t>& message)> SessionMessageCB;
-
-// Called when the session specified by |session_id| is closed. Note that the
-// CDM may close a session at any point, such as in response to a CloseSession()
-// call, when the session is no longer needed, or when system resources are
-// lost. See http://w3c.github.io/encrypted-media/#session-close
-typedef base::Callback<void(const std::string& session_id)> SessionClosedCB;
-
-// Called when there has been a change in the keys in the session or their
-// status. See http://w3c.github.io/encrypted-media/#dom-evt-keystatuseschange
-typedef base::Callback<void(const std::string& session_id,
-                            bool has_additional_usable_key,
-                            CdmKeysInfo keys_info)> SessionKeysChangeCB;
-
-// Called when the CDM changes the expiration time of a session.
-// See http://w3c.github.io/encrypted-media/#update-expiration
-typedef base::Callback<void(const std::string& session_id,
-                            const base::Time& new_expiry_time)>
-    SessionExpirationUpdateCB;
-
-}  // namespace media
-}  // namespace cobalt
-
-#endif  // COBALT_MEDIA_BASE_MEDIA_KEYS_H_
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index 028145b..7548977 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -805,7 +805,7 @@
   TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::CreatePlayer");
 
   DCHECK(message_loop_->BelongsToCurrentThread());
-  DCHECK(video_stream_);
+  DCHECK(audio_stream_ || video_stream_);
 
   if (stopped_) {
     return;
@@ -826,8 +826,10 @@
   const AudioDecoderConfig& audio_config =
       audio_stream_ ? audio_stream_->audio_decoder_config()
                     : invalid_audio_config;
+  VideoDecoderConfig invalid_video_config;
   const VideoDecoderConfig& video_config =
-      video_stream_->video_decoder_config();
+      video_stream_ ? video_stream_->video_decoder_config()
+                    : invalid_video_config;
 
   {
     base::AutoLock auto_lock(lock_);
@@ -851,7 +853,9 @@
     if (audio_stream_) {
       UpdateDecoderConfig(audio_stream_);
     }
-    UpdateDecoderConfig(video_stream_);
+    if (video_stream_) {
+      UpdateDecoderConfig(video_stream_);
+    }
     return;
   }
 
@@ -905,12 +909,22 @@
   }
 #endif  // !SB_HAS(AUDIOLESS_VIDEO)
 
+#if SB_API_VERSION < SB_AUDIO_ONLY_VIDEO_API_VERSION
   if (video_stream == NULL) {
     LOG(INFO) << "The video has to contain a video track.";
     ResetAndRunIfNotNull(&error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
                          "The video has to contain a video track.");
     return;
   }
+#endif  // SB_API_VERSION < SB_AUDIO_ONLY_VIDEO_API_VERSION
+
+  if (audio_stream == NULL && video_stream == NULL) {
+    LOG(INFO) << "The video has to contain an audio track or a video track.";
+    ResetAndRunIfNotNull(
+        &error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
+        "The video has to contain an audio track or a video track.");
+    return;
+  }
 
   {
     base::AutoLock auto_lock(lock_);
@@ -919,13 +933,17 @@
 
     bool is_encrypted =
         audio_stream_ && audio_stream_->audio_decoder_config().is_encrypted();
-    is_encrypted |= video_stream_->video_decoder_config().is_encrypted();
-    bool natural_size_changed =
-        (video_stream_->video_decoder_config().natural_size().width() !=
-             natural_size_.width() ||
-         video_stream_->video_decoder_config().natural_size().height() !=
-             natural_size_.height());
-    natural_size_ = video_stream_->video_decoder_config().natural_size();
+    is_encrypted |=
+        video_stream_ && video_stream_->video_decoder_config().is_encrypted();
+    bool natural_size_changed = false;
+    if (video_stream_) {
+      natural_size_changed =
+          (video_stream_->video_decoder_config().natural_size().width() !=
+               natural_size_.width() ||
+           video_stream_->video_decoder_config().natural_size().height() !=
+               natural_size_.height());
+      natural_size_ = video_stream_->video_decoder_config().natural_size();
+    }
     if (natural_size_changed) {
       content_size_change_cb_.Run();
     }
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index 35ea7fd..9b12235 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -135,7 +135,7 @@
       set_bounds_helper_(set_bounds_helper),
       allow_resume_after_suspend_(allow_resume_after_suspend),
       video_frame_provider_(video_frame_provider) {
-  DCHECK(video_config.IsValidConfig());
+  DCHECK(audio_config.IsValidConfig() || video_config.IsValidConfig());
   DCHECK(host_);
   DCHECK(set_bounds_helper_);
   DCHECK(video_frame_provider_);
@@ -429,13 +429,19 @@
 #if SB_API_VERSION < SB_DEPRECATE_SB_MEDIA_TIME_API_VERSION
   SbPlayerInfo info;
   SbPlayerGetInfo(player_, &info);
-  DCHECK_NE(info.duration_pts, SB_PLAYER_NO_DURATION);
+  if (info.duration_pts == SB_PLAYER_NO_DURATION) {
+    // URL-based player may not have loaded asset yet, so map no duration to 0.
+    return base::TimeDelta();
+  }
   return base::TimeDelta::FromMicroseconds(
       SB_MEDIA_TIME_TO_SB_TIME(info.duration_pts));
 #else   // SB_API_VERSION < SB_DEPRECATE_SB_MEDIA_TIME_API_VERSION
   SbPlayerInfo2 info;
   SbPlayerGetInfo2(player_, &info);
-  DCHECK_NE(info.duration, SB_PLAYER_NO_DURATION);
+  if (info.duration == SB_PLAYER_NO_DURATION) {
+    // URL-based player may not have loaded asset yet, so map no duration to 0.
+    return base::TimeDelta();
+  }
   return base::TimeDelta::FromMicroseconds(info.duration);
 #endif  // SB_API_VERSION < SB_DEPRECATE_SB_MEDIA_TIME_API_VERSION
 }
@@ -606,8 +612,10 @@
     audio_codec = MediaAudioCodecToSbMediaAudioCodec(audio_config_.codec());
   }
 
-  SbMediaVideoCodec video_codec =
-      MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
+  SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
+  if (video_config_.IsValidConfig()) {
+    video_codec = MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
+  }
 
   DCHECK(SbPlayerOutputModeSupported(output_mode_, video_codec, drm_system_));
 
diff --git a/src/cobalt/media/decoder_buffer_allocator.cc b/src/cobalt/media/decoder_buffer_allocator.cc
index 5421c15..1edc5f0 100644
--- a/src/cobalt/media/decoder_buffer_allocator.cc
+++ b/src/cobalt/media/decoder_buffer_allocator.cc
@@ -178,6 +178,7 @@
 
 void DecoderBufferAllocator::UpdateVideoConfig(
     const VideoDecoderConfig& config) {
+#if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL
   if (!reuse_allocator_) {
     return;
   }
@@ -185,8 +186,10 @@
   if (reuse_allocator_->max_capacity() && resolution > kVideoResolution1080p) {
     reuse_allocator_->set_max_capacity(COBALT_MEDIA_BUFFER_MAX_CAPACITY_4K);
   }
+#endif  // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL
 }
 
+#if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL
 DecoderBufferAllocator::ReuseAllocator::ReuseAllocator(
     Allocator* fallback_allocator, std::size_t initial_capacity,
     std::size_t allocation_increment, std::size_t max_capacity)
@@ -276,6 +279,6 @@
         // COBALT_MEDIA_BUFFER_MAX_CAPACITY_4K > 0
   return true;
 }
-
+#endif  // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media_capture/get_user_media_test.cc b/src/cobalt/media_capture/get_user_media_test.cc
new file mode 100644
index 0000000..07ceac5
--- /dev/null
+++ b/src/cobalt/media_capture/get_user_media_test.cc
@@ -0,0 +1,86 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/media_capture/media_devices.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/dom/dom_settings.h"
+#include "cobalt/dom/testing/stub_window.h"
+#include "cobalt/script/global_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int kMaxDomElementDepth = 8;
+
+scoped_ptr<cobalt::script::EnvironmentSettings> CreateDOMSettings() {
+  cobalt::dom::DOMSettings::Options options;
+#if defined(ENABLE_FAKE_MICROPHONE)
+  options.microphone_options.enable_fake_microphone = true;
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+
+  return make_scoped_ptr<cobalt::script::EnvironmentSettings>(
+      new cobalt::dom::DOMSettings(kMaxDomElementDepth, nullptr, nullptr,
+                                   nullptr, nullptr, nullptr, nullptr, nullptr,
+                                   nullptr, nullptr, options));
+}
+
+}  // namespace.
+
+namespace cobalt {
+namespace media_capture {
+
+class GetUserMediaTest : public ::testing::Test {
+ protected:
+  GetUserMediaTest()
+      : window_(CreateDOMSettings()),
+        media_devices_(new MediaDevices(
+            window_.global_environment()->script_value_factory())) {
+    media_devices_->SetEnvironmentSettings(window_.environment_settings());
+  }
+
+  dom::testing::StubWindow window_;
+  scoped_refptr<MediaDevices> media_devices_;
+};
+
+TEST_F(GetUserMediaTest, TestEmptyParameters) {
+  script::Handle<MediaDevices::MediaStreamPromise> media_stream_promise =
+      media_devices_->GetUserMedia();
+  ASSERT_FALSE(media_stream_promise.IsEmpty());
+  EXPECT_EQ(cobalt::script::PromiseState::kRejected,
+            media_stream_promise->State());
+}
+
+TEST_F(GetUserMediaTest, NoMediaSources) {
+  media_stream::MediaStreamConstraints constraints;
+  constraints.set_audio(false);
+  script::Handle<MediaDevices::MediaStreamPromise> media_stream_promise =
+      media_devices_->GetUserMedia(constraints);
+  ASSERT_FALSE(media_stream_promise.IsEmpty());
+  EXPECT_EQ(cobalt::script::PromiseState::kRejected,
+            media_stream_promise->State());
+}
+
+TEST_F(GetUserMediaTest, AudioMediaSources) {
+  media_stream::MediaStreamConstraints constraints;
+  constraints.set_audio(true);
+  script::Handle<MediaDevices::MediaStreamPromise> media_stream_promise =
+      media_devices_->GetUserMedia(constraints);
+  ASSERT_FALSE(media_stream_promise.IsEmpty());
+  EXPECT_EQ(cobalt::script::PromiseState::kFulfilled,
+            media_stream_promise->State());
+}
+
+}  // namespace media_capture
+}  // namespace cobalt
diff --git a/src/cobalt/media_capture/media_capture.gyp b/src/cobalt/media_capture/media_capture.gyp
index 364c272..12f1fb1 100644
--- a/src/cobalt/media_capture/media_capture.gyp
+++ b/src/cobalt/media_capture/media_capture.gyp
@@ -25,9 +25,14 @@
         'media_devices.h',
         'media_device_info.cc',
         'media_device_info.h',
+        'media_recorder.cc',
+        'media_recorder.h',
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+        '<(DEPTH)/cobalt/media_stream/media_stream.gyp:media_stream',
+        '<(DEPTH)/cobalt/script/engine.gyp:engine',
+        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
       ],
       'export_dependent_settings': [
         # Additionally, ensure that the include directories for generated
diff --git a/src/cobalt/media_capture/media_capture_test.gyp b/src/cobalt/media_capture/media_capture_test.gyp
new file mode 100644
index 0000000..c5903c7
--- /dev/null
+++ b/src/cobalt/media_capture/media_capture_test.gyp
@@ -0,0 +1,55 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'media_capture_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'get_user_media_test.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+        '<(DEPTH)/cobalt/dom/testing/dom_testing.gyp:dom_testing',
+        '<(DEPTH)/cobalt/media_capture/media_capture.gyp:media_capture',
+
+        # TODO: Remove the dependency below, it works around the fact that
+        #       ScriptValueFactory has non-virtual method CreatePromise().
+        '<(DEPTH)/cobalt/script/engine.gyp:engine',
+        # For Fake Microphone.
+        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
+
+        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+    },
+
+    {
+      'target_name': 'media_capture_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'media_capture_test',
+      ],
+      'variables': {
+        'executable_name': 'media_capture_test',
+      },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/cobalt/media_capture/media_devices.cc b/src/cobalt/media_capture/media_devices.cc
index b9a194b..e90199a 100644
--- a/src/cobalt/media_capture/media_devices.cc
+++ b/src/cobalt/media_capture/media_devices.cc
@@ -16,8 +16,11 @@
 
 #include <string>
 
+#include "cobalt/dom/dom_exception.h"
 #include "cobalt/media_capture/media_device_info.h"
+#include "cobalt/media_stream/media_stream.h"
 #include "cobalt/speech/microphone.h"
+#include "cobalt/speech/microphone_fake.h"
 #include "cobalt/speech/microphone_starboard.h"
 #include "starboard/string.h"
 
@@ -30,13 +33,28 @@
 
 namespace {
 
-scoped_ptr<speech::Microphone> CreateMicrophone() {
-  scoped_ptr<speech::Microphone> mic;
-#ifdef ENABLE_MICROPHONE_IDL
+using speech::Microphone;
+
+scoped_ptr<Microphone> CreateMicrophone(const Microphone::Options& options) {
+#if defined(ENABLE_FAKE_MICROPHONE)
+  if (options.enable_fake_microphone) {
+    return make_scoped_ptr<speech::Microphone>(
+        new speech::MicrophoneFake(options));
+  }
+#else
+  UNREFERENCED_PARAMETER(options);
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+
+  scoped_ptr<Microphone> mic;
+
+#if defined(ENABLE_MICROPHONE_IDL)
   mic.reset(new speech::MicrophoneStarboard(
       speech::MicrophoneStarboard::kDefaultSampleRate,
-      speech::MicrophoneStarboard::kDefaultSampleRate));
-#endif
+      /* Buffer for one second. */
+      speech::MicrophoneStarboard::kDefaultSampleRate *
+          speech::MicrophoneStarboard::kSbMicrophoneSampleSizeInBytes));
+#endif  // defined(ENABLE_MICROPHONE_IDL)
+
   return mic.Pass();
 }
 
@@ -48,20 +66,104 @@
 
 script::Handle<MediaDevices::MediaInfoSequencePromise>
 MediaDevices::EnumerateDevices() {
+  DCHECK(settings_);
+  DCHECK(script_value_factory_);
   script::Handle<MediaInfoSequencePromise> promise =
       script_value_factory_->CreateBasicPromise<MediaInfoSequence>();
   script::Sequence<scoped_refptr<Wrappable>> output;
-  scoped_ptr<speech::Microphone> microphone = CreateMicrophone();
+
+  scoped_ptr<speech::Microphone> microphone =
+      CreateMicrophone(settings_->microphone_options());
   if (microphone) {
     scoped_refptr<Wrappable> media_device(
         new MediaDeviceInfo(script_value_factory_, kMediaDeviceKindAudioinput,
                             microphone->Label()));
     output.push_back(media_device);
   }
-
   promise->Resolve(output);
   return promise;
 }
 
+script::Handle<MediaDevices::MediaStreamPromise> MediaDevices::GetUserMedia() {
+  DCHECK(script_value_factory_);
+  script::Handle<MediaDevices::MediaStreamPromise> promise =
+      script_value_factory_->CreateInterfacePromise<
+          script::ScriptValueFactory::WrappablePromise>();
+  // Per specification at
+  // https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia
+  //
+  // Step 3: If requestedMediaTypes is the empty set, return a promise rejected
+  // with a TypeError. The word "optional" occurs in the WebIDL due to WebIDL
+  // rules, but the argument must be supplied in order for the call to succeed.
+
+  promise->Reject(script::kTypeError);
+  return promise;
+}
+
+script::Handle<MediaDevices::MediaStreamPromise> MediaDevices::GetUserMedia(
+    const media_stream::MediaStreamConstraints& constraints) {
+  script::Handle<MediaDevices::MediaStreamPromise> promise =
+      script_value_factory_->CreateInterfacePromise<
+          script::ScriptValueFactory::WrappablePromise>();
+  if (!constraints.audio()) {
+    // Step 3: If requestedMediaTypes is the empty set, return a promise
+    // rejected with a TypeError. The word "optional" occurs in the WebIDL due
+    // to WebIDL rules, but the argument must be supplied in order for the call
+    // to succeed.
+    DLOG(INFO) << "Audio constraint must be true.";
+    promise->Reject(script::kTypeError);
+    return promise;
+  }
+  // Steps 4-7 are not needed for cobalt.
+  // Step 8 is to create a promise (which is already done).
+
+  // Step 9: Construct a list of MediaStreamTracks that we have permission.
+  CreateMicrophoneIfNeeded();
+  if (!microphone_) {
+    promise->Reject(new dom::DOMException(dom::DOMException::kNotFoundErr));
+    return promise;
+  }
+  bool open_success = microphone_->Open();
+  if (!open_success) {
+    // This typically happens if the user did not give permission.
+    // Step 9.8, handle "Permission Failure"
+    DLOG(INFO) << "Unable to open the microphone.";
+    promise->Reject(new dom::DOMException(dom::DOMException::kNotAllowedErr));
+  }
+  using media_stream::MediaStream;
+  scoped_refptr<media_stream::MediaStreamTrack> microphone_track;
+  // TODO: Attach a microphone device to the microphone track.
+  MediaStream::TrackSequences audio_tracks;
+  audio_tracks.push_back(microphone_track);
+  auto media_stream(make_scoped_refptr(new MediaStream(audio_tracks)));
+  promise->Resolve(media_stream);
+
+  // Step 10, return promise.
+  return promise;
+}
+
+void MediaDevices::CreateMicrophoneIfNeeded() {
+  DCHECK(settings_);
+  if (microphone_) {
+    return;
+  }
+
+  scoped_ptr<speech::Microphone> microphone =
+      CreateMicrophone(settings_->microphone_options());
+
+  if (!microphone) {
+    DLOG(INFO) << "Unable to create a microphone.";
+    return;
+  }
+
+  if (!microphone->IsValid()) {
+    DLOG(INFO) << "Ignoring created microphone because it is invalid.";
+    return;
+  }
+
+  DLOG(INFO) << "Created microphone: " << microphone->Label();
+  microphone_ = microphone.Pass();
+}
+
 }  // namespace media_capture
 }  // namespace cobalt
diff --git a/src/cobalt/media_capture/media_devices.h b/src/cobalt/media_capture/media_devices.h
index 83739d3..89c0e39 100644
--- a/src/cobalt/media_capture/media_devices.h
+++ b/src/cobalt/media_capture/media_devices.h
@@ -17,13 +17,18 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/event_target.h"
 #include "cobalt/media_capture/media_device_info.h"
+#include "cobalt/media_stream/media_stream_constraints.h"
+#include "cobalt/script/environment_settings.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/speech/microphone.h"
 
 namespace cobalt {
 namespace media_capture {
@@ -35,15 +40,29 @@
  public:
   using MediaInfoSequence = script::Sequence<scoped_refptr<script::Wrappable>>;
   using MediaInfoSequencePromise = script::Promise<MediaInfoSequence>;
+  using MediaStreamPromise =
+      script::Promise<script::ScriptValueFactory::WrappablePromise>;
 
   explicit MediaDevices(script::ScriptValueFactory* script_value_factory);
 
   script::Handle<MediaInfoSequencePromise> EnumerateDevices();
+  script::Handle<MediaStreamPromise> GetUserMedia();
+  script::Handle<MediaStreamPromise> GetUserMedia(
+      const media_stream::MediaStreamConstraints& constraints);
+
+  void SetEnvironmentSettings(script::EnvironmentSettings* settings) {
+    settings_ = base::polymorphic_downcast<dom::DOMSettings*>(settings);
+  }
 
   DEFINE_WRAPPABLE_TYPE(MediaDevices);
 
  private:
+  void CreateMicrophoneIfNeeded();
+
   script::ScriptValueFactory* script_value_factory_;
+  dom::DOMSettings* settings_ = nullptr;
+
+  scoped_ptr<speech::Microphone> microphone_;
 
   DISALLOW_COPY_AND_ASSIGN(MediaDevices);
 };
diff --git a/src/cobalt/media_capture/media_devices.idl b/src/cobalt/media_capture/media_devices.idl
index f697037..2b77fec 100644
--- a/src/cobalt/media_capture/media_devices.idl
+++ b/src/cobalt/media_capture/media_devices.idl
@@ -17,4 +17,5 @@
 [Exposed=Window]
 interface MediaDevices : EventTarget {
   Promise<sequence<MediaDeviceInfo>> enumerateDevices();
+  Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints);
 };
diff --git a/src/cobalt/media_capture/media_recorder.cc b/src/cobalt/media_capture/media_recorder.cc
new file mode 100644
index 0000000..638048c
--- /dev/null
+++ b/src/cobalt/media_capture/media_recorder.cc
@@ -0,0 +1,262 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/media_capture/media_recorder.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/message_loop.h"
+#include "base/string_piece.h"
+#include "cobalt/dom/array_buffer.h"
+#include "cobalt/dom/blob.h"
+#include "cobalt/dom/dom_exception.h"
+#include "cobalt/media_stream/media_stream_track.h"
+#include "cobalt/media_stream/media_track_settings.h"
+
+namespace {
+
+// See https://tools.ietf.org/html/rfc2586 for MIME type
+const char kLinear16MimeType[] = "audio/L16";
+
+const int32 kMinimumTimeSliceInMilliseconds = 1;
+
+// Read Microphone input every few milliseconds, so that
+// the input buffer doesn't fill up.
+const int32 kDefaultMicrophoneReadThresholdMilliseconds = 50;
+
+const double kSchedulingLatencyBufferSeconds = 0.20;
+
+}  // namespace
+
+namespace cobalt {
+namespace media_capture {
+
+void MediaRecorder::Start(int32 timeslice,
+                          script::ExceptionState* exception_state) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // Following the spec at
+  // https://www.w3.org/TR/mediastream-recording/#mediarecorder-methods:
+
+  // Step #1, not needed for Cobalt.
+
+  // Step #2, done by Cobalt bindings.
+
+  // Step #3
+  if (recording_state_ != kRecordingStateInactive) {
+    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+                             "Internal error: Unable to get DOM settings.",
+                             exception_state);
+    return;
+  }
+
+  // Step #4-5.3, not needed for Cobalt.
+  recording_state_ = kRecordingStateRecording;
+
+  // Step #5.4, create a new Blob, and start collecting data.
+
+  // If timeslice is not undefined, then once a minimum of timeslice
+  // milliseconds of data have been collected, or some minimum time slice
+  // imposed by the UA, whichever is greater, start gathering data into a new
+  // Blob blob, and queue a task, using the DOM manipulation task source, that
+  // fires a blob event named |dataavailable| at target.
+
+  // We need to drain the media frequently, so try to read atleast once every
+  // |read_frequency_| interval.
+  int32 effective_time_slice_milliseconds =
+      std::min(kDefaultMicrophoneReadThresholdMilliseconds, timeslice);
+  // Avoid rounding down to 0 milliseconds.
+  effective_time_slice_milliseconds = std::max(
+      kMinimumTimeSliceInMilliseconds, effective_time_slice_milliseconds);
+  read_frequency_ =
+      base::TimeDelta::FromMilliseconds(effective_time_slice_milliseconds);
+
+  // This is the frequency we will callback to Javascript.
+  callback_frequency_ = base::TimeDelta::FromMilliseconds(timeslice);
+
+  int64 buffer_size_hint = GetRecommendedBufferSize(callback_frequency_);
+  recorded_buffer_.HintTypicalSize(static_cast<size_t>(buffer_size_hint));
+
+  ResetLastCallbackTime();
+  ReadStreamAndDoCallback();
+
+  stream_reader_callback_.Reset(
+      base::Bind(&MediaRecorder::ReadStreamAndDoCallback, this));
+  MessageLoop::current()->PostTask(FROM_HERE,
+                                   stream_reader_callback_.callback());
+
+  // Step #5.5, not needed.
+
+  // Step #6, return undefined.
+}
+
+void MediaRecorder::Stop(script::ExceptionState* exception_state) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  UNREFERENCED_PARAMETER(exception_state);
+  NOTREACHED();
+}
+
+MediaRecorder::MediaRecorder(
+    script::EnvironmentSettings* settings,
+    const scoped_refptr<media_stream::MediaStream>& stream,
+    const MediaRecorderOptions& options)
+    : settings_(settings), stream_(stream) {
+  // Per W3C spec, the default value of this is platform-specific,
+  // so Linear16 was chosen. Spec url:
+  // https://www.w3.org/TR/mediastream-recording/#dom-mediarecorder-mediarecorder
+  mime_type_ =
+      options.has_mime_type() ? options.mime_type() : kLinear16MimeType;
+}
+
+void MediaRecorder::ReadStreamAndDoCallback() {
+  DCHECK(stream_);
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  size_t number_audio_tracks = stream_->GetAudioTracks().size();
+  if (number_audio_tracks == 0) {
+    LOG(WARNING) << "Audio Tracks are empty.";
+    return;
+  }
+  LOG_IF(WARNING, number_audio_tracks > 1)
+      << "Only recording the first audio track.";
+
+  base::TimeTicks current_time = base::TimeTicks::Now();
+  base::TimeDelta time_difference = last_callback_time_ - current_time;
+
+  int64 recommended_buffer_size = GetRecommendedBufferSize(time_difference);
+  base::StringPiece writeable_buffer = recorded_buffer_.GetWriteCursor(
+      static_cast<size_t>(recommended_buffer_size));
+
+  media_stream::MediaStreamTrack* track =
+      stream_->GetAudioTracks().begin()->get();
+
+  int64 bytes_read = track->Read(writeable_buffer);
+
+  if (bytes_read < 0) {
+    // An error occured, so do not post another read.
+    DoOnDataCallback();
+    return;
+  }
+
+  DCHECK_LE(bytes_read, static_cast<int64>(writeable_buffer.size()));
+  recorded_buffer_.IncrementCursorPosition(static_cast<size_t>(bytes_read));
+
+  if (current_time >= GetNextCallbackTime()) {
+    DoOnDataCallback();
+    ResetLastCallbackTime();
+  }
+
+  // Note that GetNextCallbackTime() should not be cached, since
+  // ResetLastCallbackTime() above can change its value.
+  base::TimeDelta time_until_expiration = GetNextCallbackTime() - current_time;
+
+  // Consider the scenario where |time_until_expiration| is 4ms, and
+  // read_frequency is 10ms.  In this case, just do the read 4 milliseconds
+  // later, and then do the callback.
+  base::TimeDelta delay_until_next_read =
+      std::min(time_until_expiration, read_frequency_);
+
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, stream_reader_callback_.callback(), delay_until_next_read);
+}
+
+void MediaRecorder::DoOnDataCallback() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (recorded_buffer_.GetWrittenChunk().empty()) {
+    DLOG(WARNING) << "No data was recorded.";
+    return;
+  }
+
+  base::StringPiece written_data = recorded_buffer_.GetWrittenChunk();
+  DCHECK_LE(written_data.size(), kuint32max);
+  uint32 number_of_written_bytes = static_cast<uint32>(written_data.size());
+
+  auto array_buffer = make_scoped_refptr(new dom::ArrayBuffer(
+      settings_, reinterpret_cast<const uint8*>(written_data.data()),
+      number_of_written_bytes));
+  recorded_buffer_.Reset();
+
+  auto blob = make_scoped_refptr(new dom::Blob(settings_, array_buffer));
+  // TODO: Post a task to fire BlobEvent (constructed out of |blob| and
+  // |array_buffer| at target.
+}
+
+void MediaRecorder::ResetLastCallbackTime() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  last_callback_time_ = base::TimeTicks::Now();
+}
+
+void MediaRecorder::CalculateStreamBitrate() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  media_stream::MediaStreamTrack* track =
+      stream_->GetAudioTracks().begin()->get();
+  const media_stream::MediaTrackSettings& settings = track->GetSettings();
+  DCHECK_GT(settings.sample_rate(), 0);
+  DCHECK_GT(settings.sample_size(), 0);
+  DCHECK_GT(settings.channel_count(), 0);
+  bitrate_bps_ = settings.sample_rate() * settings.sample_size() *
+                 settings.channel_count();
+  DCHECK_GT(bitrate_bps_, 0);
+}
+
+int64 MediaRecorder::GetRecommendedBufferSize(base::TimeDelta time_span) const {
+  DCHECK_GE(time_span, base::TimeDelta::FromSeconds(0));
+  // Increase buffer slightly to account for the fact that scheduling our
+  // tasks might be a little bit noisy.
+  double buffer_window_span_seconds =
+      time_span.InSecondsF() + kSchedulingLatencyBufferSeconds;
+  int64 recommended_buffer_size =
+      static_cast<int64>(std::ceil(buffer_window_span_seconds * bitrate_bps_));
+  DCHECK_GT(recommended_buffer_size, 0);
+  return recommended_buffer_size;
+}
+
+bool MediaRecorder::IsTypeSupported(const base::StringPiece mime_type) {
+  return mime_type == kLinear16MimeType;
+}
+
+base::StringPiece MediaRecorder::Buffer::GetWriteCursor(
+    size_t number_of_bytes) {
+  size_t minimim_required_size = current_position_ + number_of_bytes;
+  if (minimim_required_size > buffer_.size()) {
+    buffer_.resize(minimim_required_size);
+  }
+  return base::StringPiece(reinterpret_cast<const char*>(buffer_.data()),
+                           number_of_bytes);
+}
+
+void MediaRecorder::Buffer::IncrementCursorPosition(size_t number_of_bytes) {
+  size_t new_position = current_position_ + number_of_bytes;
+  DCHECK_LE(new_position, buffer_.size());
+  current_position_ = new_position;
+}
+
+base::StringPiece MediaRecorder::Buffer::GetWrittenChunk() const {
+  return base::StringPiece(reinterpret_cast<const char*>(buffer_.data()),
+                           current_position_);
+}
+
+void MediaRecorder::Buffer::Reset() {
+  current_position_ = 0;
+  buffer_.resize(0);
+}
+
+void MediaRecorder::Buffer::HintTypicalSize(size_t number_of_bytes) {
+  // Cap the hint size to be 1 Megabyte.
+  const size_t kMaxBufferSizeHintInBytes = 1024 * 1024;
+  buffer_.reserve(std::min(number_of_bytes, kMaxBufferSizeHintInBytes));
+}
+
+}  // namespace media_capture
+}  // namespace cobalt
diff --git a/src/cobalt/media_capture/media_recorder.h b/src/cobalt/media_capture/media_recorder.h
new file mode 100644
index 0000000..cebaa73
--- /dev/null
+++ b/src/cobalt/media_capture/media_recorder.h
@@ -0,0 +1,135 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_CAPTURE_MEDIA_RECORDER_H_
+#define COBALT_MEDIA_CAPTURE_MEDIA_RECORDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/cancelable_callback.h"
+#include "base/optional.h"
+#include "base/string_piece.h"
+#include "base/threading/thread_checker.h"
+#include "base/time.h"
+#include "cobalt/dom/event_target.h"
+#include "cobalt/media_capture/media_recorder_options.h"
+#include "cobalt/media_capture/recording_state.h"
+#include "cobalt/media_stream/media_stream.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace media_capture {
+
+// This class represents a MediaRecorder, and implements the specification at:
+// https://www.w3.org/TR/mediastream-recording/#mediarecorder-api
+class MediaRecorder : public dom::EventTarget {
+ public:
+  // Constructors.
+  explicit MediaRecorder(
+      script::EnvironmentSettings* settings,
+      const scoped_refptr<media_stream::MediaStream>& stream,
+      const MediaRecorderOptions& options = MediaRecorderOptions());
+
+  // Readonly attributes.
+  const std::string& mime_type() const { return mime_type_; }
+
+  // Functions
+  static bool IsTypeSupported(const base::StringPiece mime_type);
+
+  void Start(int32 timeslice, script::ExceptionState* exception_state);
+
+  void Start(script::ExceptionState* exception_state) {
+    Start(kint32max, exception_state);
+  }
+
+  void Stop(script::ExceptionState* exception_state);
+
+  // EventHandlers.
+  const EventListenerScriptValue* onerror() const {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    return GetAttributeEventListener(base::Tokens::error());
+  }
+
+  void set_onerror(const EventListenerScriptValue& event_listener) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    SetAttributeEventListener(base::Tokens::error(), event_listener);
+  }
+
+  const EventListenerScriptValue* ondataavailable() const {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    return GetAttributeEventListener(base::Tokens::dataavailable());
+  }
+
+  void set_ondataavailable(const EventListenerScriptValue& event_listener) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    SetAttributeEventListener(base::Tokens::dataavailable(), event_listener);
+  }
+
+  DEFINE_WRAPPABLE_TYPE(MediaRecorder);
+
+ private:
+  MediaRecorder(const MediaRecorder&) = delete;
+  MediaRecorder& operator=(const MediaRecorder&) = delete;
+
+  class Buffer {
+   public:
+    base::StringPiece GetWriteCursor(size_t number_of_bytes);
+    void IncrementCursorPosition(size_t number_of_bytes);
+    base::StringPiece GetWrittenChunk() const;
+    void Reset();
+    void HintTypicalSize(size_t number_of_bytes);
+
+   private:
+    size_t current_position_ = 0;
+    std::vector<uint8> buffer_;
+  };
+
+  void ReadStreamAndDoCallback();
+  void DoOnDataCallback();
+  void ScheduleOnDataAvailableCallback();
+  void ResetLastCallbackTime();
+  void CalculateStreamBitrate();
+  int64 GetRecommendedBufferSize(base::TimeDelta time_span) const;
+  base::TimeTicks GetNextCallbackTime() const {
+    return last_callback_time_ + callback_frequency_;
+  }
+
+  base::ThreadChecker thread_checker_;
+
+  RecordingState recording_state_ = kRecordingStateInactive;
+
+  script::EnvironmentSettings* settings_;
+  std::string mime_type_;
+  scoped_refptr<media_stream::MediaStream> stream_;
+
+  base::TimeTicks last_callback_time_;
+  // Frequency we will callback to Javascript.
+  base::TimeDelta callback_frequency_;
+
+  // Frequency we will read the stream.
+  base::TimeDelta read_frequency_;
+
+  int64 bitrate_bps_;
+  Buffer recorded_buffer_;
+
+  base::CancelableClosure stream_reader_callback_;
+};
+
+}  // namespace media_capture
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_CAPTURE_MEDIA_RECORDER_H_
diff --git a/src/cobalt/media_capture/media_recorder.idl b/src/cobalt/media_capture/media_recorder.idl
new file mode 100644
index 0000000..45b513f
--- /dev/null
+++ b/src/cobalt/media_capture/media_recorder.idl
@@ -0,0 +1,29 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Implements a subset of the specification at:
+// https://www.w3.org/TR/mediastream-recording/#mediarecorder-api
+[Constructor(MediaStream stream, optional MediaRecorderOptions options),
+ ConstructorCallWith = EnvironmentSettings]
+interface MediaRecorder : EventTarget {
+  readonly attribute DOMString mimeType;
+  attribute EventHandler ondataavailable;
+  attribute EventHandler onerror;
+
+  [RaisesException] void start(optional long timeslice);
+  [RaisesException] void stop();
+
+  static boolean isTypeSupported(DOMString type);
+};
+
diff --git a/src/cobalt/media_capture/media_recorder_options.idl b/src/cobalt/media_capture/media_recorder_options.idl
new file mode 100644
index 0000000..a0a9aa0
--- /dev/null
+++ b/src/cobalt/media_capture/media_recorder_options.idl
@@ -0,0 +1,19 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://www.w3.org/TR/mediastream-recording/#dictdef-mediarecorderoptions
+dictionary MediaRecorderOptions {
+  DOMString mimeType;
+};
+
diff --git a/src/cobalt/media_capture/recording_state.idl b/src/cobalt/media_capture/recording_state.idl
new file mode 100644
index 0000000..3f9a955
--- /dev/null
+++ b/src/cobalt/media_capture/recording_state.idl
@@ -0,0 +1,23 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Implements a subset of the specification at:
+// https://www.w3.org/TR/mediastream-recording/#mediarecorder-api
+
+enum RecordingState {
+  "inactive",
+  "recording",
+  "paused"
+};
+
diff --git a/src/cobalt/media_session/media_session.h b/src/cobalt/media_session/media_session.h
index ec74cef..9c838a1 100644
--- a/src/cobalt/media_session/media_session.h
+++ b/src/cobalt/media_session/media_session.h
@@ -24,9 +24,11 @@
 #include "base/logging.h"
 #include "base/message_loop_proxy.h"
 #include "cobalt/media_session/media_session_action.h"
+#include "cobalt/media_session/media_session_action_details.h"
 #include "cobalt/media_session/media_session_playback_state.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/script_value.h"
+#include "cobalt/script/wrappable.h"
 
 namespace cobalt {
 namespace media_session {
@@ -37,7 +39,9 @@
   friend class MediaSessionClient;
 
  public:
-  typedef script::CallbackFunction<void()> MediaSessionActionHandler;
+  typedef script::CallbackFunction<void(
+      const scoped_refptr<MediaSessionActionDetails>& action_details)>
+          MediaSessionActionHandler;
   typedef script::ScriptValue<MediaSessionActionHandler>
       MediaSessionActionHandlerHolder;
   typedef script::ScriptValue<MediaSessionActionHandler>::Reference
diff --git a/src/cobalt/media_session/media_session.idl b/src/cobalt/media_session/media_session.idl
index ffb06e2..5d8d1d7 100644
--- a/src/cobalt/media_session/media_session.idl
+++ b/src/cobalt/media_session/media_session.idl
@@ -15,7 +15,8 @@
 // MediaSession interface
 // https://wicg.github.io/mediasession
 
-callback MediaSessionActionHandler = void();
+// Cobalt customization - details parameter is not part of spec.
+callback MediaSessionActionHandler = void(MediaSessionActionDetails details);
 
 interface MediaSession {
   attribute MediaMetadata? metadata;
diff --git a/src/cobalt/media_session/media_session_action.idl b/src/cobalt/media_session/media_session_action.idl
index c57a01b..24dd691 100644
--- a/src/cobalt/media_session/media_session_action.idl
+++ b/src/cobalt/media_session/media_session_action.idl
@@ -18,6 +18,7 @@
 enum MediaSessionAction {
   "play",
   "pause",
+  "seek",  // Cobalt customization.
   "seekbackward",
   "seekforward",
   "previoustrack",
diff --git a/src/cobalt/media_session/media_session_action_details.h b/src/cobalt/media_session/media_session_action_details.h
new file mode 100644
index 0000000..2c910ee
--- /dev/null
+++ b/src/cobalt/media_session/media_session_action_details.h
@@ -0,0 +1,71 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_SESSION_MEDIA_SESSION_ACTION_DETAILS_H_
+#define COBALT_MEDIA_SESSION_MEDIA_SESSION_ACTION_DETAILS_H_
+
+#include "base/logging.h"
+#include "cobalt/media_session/media_session_action.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace media_session {
+
+class MediaSessionActionDetails : public script::Wrappable {
+ public:
+  // Non-Wrappable class holding the data needed for selected actions. This can
+  // be constructed on any thread, and then it's used internally to construct
+  // MediaSessionActionDetails on the web module thread.
+  class Data {
+   public:
+    explicit Data(MediaSessionAction action, double seek = 0.0)
+        : action_(action),
+          seek_time_(action == kMediaSessionActionSeek ? seek : 0.0),
+          seek_offset_(
+              (action == kMediaSessionActionSeekforward
+                  || action == kMediaSessionActionSeekbackward) ? seek : 0.0) {
+      DCHECK(seek >= 0.0);
+      DCHECK(seek == 0.0
+             || action == kMediaSessionActionSeek
+             || action == kMediaSessionActionSeekforward
+             || action == kMediaSessionActionSeekbackward);
+    }
+    Data(const Data&) = default;
+
+    MediaSessionAction action() const { return action_; }
+
+   private:
+    friend class MediaSessionActionDetails;
+
+    MediaSessionAction action_;
+    double seek_time_;
+    double seek_offset_;
+  };
+
+  explicit MediaSessionActionDetails(const Data& data) : data_(data) {}
+
+  MediaSessionAction action() const { return data_.action_; }
+  double seek_time() const { return data_.seek_time_; }
+  double seek_offset() const { return data_.seek_offset_; }
+
+  DEFINE_WRAPPABLE_TYPE(MediaSessionActionDetails);
+
+ private:
+  Data data_;
+};
+
+}  // namespace media_session
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_SESSION_MEDIA_SESSION_ACTION_DETAILS_H_
diff --git a/src/cobalt/media_session/media_session_action_details.idl b/src/cobalt/media_session/media_session_action_details.idl
new file mode 100644
index 0000000..8846df5
--- /dev/null
+++ b/src/cobalt/media_session/media_session_action_details.idl
@@ -0,0 +1,27 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// MediaSession interface (Cobalt customization)
+// https://wicg.github.io/mediasession
+
+interface MediaSessionActionDetails {
+  readonly attribute MediaSessionAction action;
+
+  // Time in seconds relative to the beginning of the media for "seek" action.
+  readonly attribute double seekTime;
+
+  // Time in seconds relative to the current media time for "seekforward" and
+  // "seekbackward" actions.
+  readonly attribute double seekOffset;
+};
diff --git a/src/cobalt/media_session/media_session_client.cc b/src/cobalt/media_session/media_session_client.cc
index 6cf769c..d141611 100644
--- a/src/cobalt/media_session/media_session_client.cc
+++ b/src/cobalt/media_session/media_session_client.cc
@@ -98,22 +98,23 @@
   }
 }
 
-void MediaSessionClient::InvokeAction(MediaSessionAction action) {
+void MediaSessionClient::InvokeActionInternal(
+    scoped_ptr<MediaSessionActionDetails::Data> data) {
   if (base::MessageLoopProxy::current() != media_session_->message_loop_) {
     media_session_->message_loop_->PostTask(
-        FROM_HERE, base::Bind(&MediaSessionClient::InvokeAction,
-                              base::Unretained(this), action));
+        FROM_HERE, base::Bind(&MediaSessionClient::InvokeActionInternal,
+                              base::Unretained(this), base::Passed(&data)));
     return;
   }
 
   MediaSession::ActionMap::iterator it =
-      media_session_->action_map_.find(action);
+      media_session_->action_map_.find(data->action());
 
   if (it == media_session_->action_map_.end()) {
     return;
   }
 
-  it->second->value().Run();
+  it->second->value().Run(new MediaSessionActionDetails(*data));
 }
 
 }  // namespace media_session
diff --git a/src/cobalt/media_session/media_session_client.h b/src/cobalt/media_session/media_session_client.h
index d838785..1763d6c 100644
--- a/src/cobalt/media_session/media_session_client.h
+++ b/src/cobalt/media_session/media_session_client.h
@@ -17,9 +17,11 @@
 
 #include <bitset>
 
+#include "base/memory/scoped_ptr.h"
 #include "base/threading/thread_checker.h"
 
 #include "cobalt/media_session/media_session.h"
+#include "cobalt/media_session/media_session_action_details.h"
 
 namespace cobalt {
 namespace media_session {
@@ -60,7 +62,15 @@
   // Invokes a given media session action
   // https://wicg.github.io/mediasession/#actions-model
   // Can be invoked from any thread.
-  void InvokeAction(MediaSessionAction action);
+  void InvokeAction(MediaSessionAction action) {
+    InvokeActionInternal(scoped_ptr<MediaSessionActionDetails::Data>(
+        new MediaSessionActionDetails::Data(action)));
+  }
+
+  // Invokes a given media session action that takes additional data.
+  void InvokeAction(scoped_ptr<MediaSessionActionDetails::Data> data) {
+    InvokeActionInternal(data.Pass());
+  }
 
   // Invoked on the browser thread when any metadata, playback state,
   // or supported session actions change.
@@ -71,6 +81,8 @@
   scoped_refptr<MediaSession> media_session_;
   MediaSessionPlaybackState platform_playback_state_;
 
+  void InvokeActionInternal(scoped_ptr<MediaSessionActionDetails::Data> data);
+
   DISALLOW_COPY_AND_ASSIGN(MediaSessionClient);
 };
 
diff --git a/src/cobalt/media_session/media_session_test.cc b/src/cobalt/media_session/media_session_test.cc
index 345dbd9..6c9f7bf 100644
--- a/src/cobalt/media_session/media_session_test.cc
+++ b/src/cobalt/media_session/media_session_test.cc
@@ -44,7 +44,8 @@
 
 class MockCallbackFunction : public MediaSession::MediaSessionActionHandler {
  public:
-  MOCK_CONST_METHOD0(Run, ReturnValue());
+  MOCK_CONST_METHOD1(Run, ReturnValue(
+      const scoped_refptr<MediaSessionActionDetails>& action_details));
 };
 
 class MockMediaSessionClient : public MediaSessionClient {
@@ -52,6 +53,16 @@
   MOCK_METHOD0(OnMediaSessionChanged, void());
 };
 
+MATCHER_P(SeekTime, time, "") {
+  return arg->action() ==  kMediaSessionActionSeek
+      && arg->seek_time() == time;
+}
+
+MATCHER_P2(SeekOffset, action, offset, "") {
+  return arg->action() ==  action
+      && arg->seek_offset() == offset;
+}
+
 TEST(MediaSessionTest, MediaSessionTest) {
   MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
   base::RunLoop run_loop;
@@ -130,7 +141,7 @@
   EXPECT_EQ(0, client.GetAvailableActions().to_ulong());
 
   MockCallbackFunction cf;
-  EXPECT_CALL(cf, Run())
+  EXPECT_CALL(cf, Run(_))
       .Times(1)
       .WillRepeatedly(Return(CallbackResult<void>()));
   FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
@@ -162,7 +173,7 @@
   EXPECT_EQ(0, client.GetAvailableActions().to_ulong());
 
   MockCallbackFunction cf;
-  EXPECT_CALL(cf, Run()).Times(0);
+  EXPECT_CALL(cf, Run(_)).Times(0);
   FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
 
   session->SetActionHandler(kMediaSessionActionPlay, holder);
@@ -173,7 +184,7 @@
 
   EXPECT_EQ(1, client.GetAvailableActions().to_ulong());
 
-  session->SetActionHandler(kMediaSessionActionSeekbackward, holder);
+  session->SetActionHandler(kMediaSessionActionSeek, holder);
 
   EXPECT_EQ(5, client.GetAvailableActions().to_ulong());
 
@@ -220,6 +231,55 @@
   run_loop.Run();
 }
 
+TEST(MediaSessionTest, SeekDetails) {
+  MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
+  base::RunLoop run_loop;
+
+  MockMediaSessionClient client;
+
+  ON_CALL(client, OnMediaSessionChanged())
+      .WillByDefault(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+  EXPECT_CALL(client, OnMediaSessionChanged()).Times(AtLeast(0));
+
+  scoped_refptr<MediaSession> session = client.GetMediaSession();
+
+  MockCallbackFunction cf;
+  FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
+
+  session->SetActionHandler(kMediaSessionActionSeek, holder);
+  session->SetActionHandler(kMediaSessionActionSeekforward, holder);
+  session->SetActionHandler(kMediaSessionActionSeekbackward, holder);
+
+  EXPECT_CALL(cf, Run(SeekTime(0.0)))
+      .WillOnce(Return(CallbackResult<void>()));
+  client.InvokeAction(kMediaSessionActionSeek);
+
+  EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekforward, 0.0)))
+      .WillOnce(Return(CallbackResult<void>()));
+  client.InvokeAction(kMediaSessionActionSeekforward);
+
+  EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekbackward, 0.0)))
+      .WillOnce(Return(CallbackResult<void>()));
+  client.InvokeAction(kMediaSessionActionSeekbackward);
+
+  EXPECT_CALL(cf, Run(SeekTime(1.2)))
+      .WillOnce(Return(CallbackResult<void>()));
+  client.InvokeAction(scoped_ptr<MediaSessionActionDetails::Data>(
+      new MediaSessionActionDetails::Data(kMediaSessionActionSeek, 1.2)));
+
+  EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekforward, 3.4)))
+      .WillOnce(Return(CallbackResult<void>()));
+  client.InvokeAction(scoped_ptr<MediaSessionActionDetails::Data>(
+      new MediaSessionActionDetails::Data(
+          kMediaSessionActionSeekforward, 3.4)));
+
+  EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekbackward, 5.6)))
+      .WillOnce(Return(CallbackResult<void>()));
+  client.InvokeAction(scoped_ptr<MediaSessionActionDetails::Data>(
+      new MediaSessionActionDetails::Data(
+          kMediaSessionActionSeekbackward, 5.6)));
+}
+
 }  // namespace
 }  // namespace media_session
 }  // namespace cobalt
diff --git a/src/cobalt/media_stream/audio_parameters.cc b/src/cobalt/media_stream/audio_parameters.cc
new file mode 100644
index 0000000..8973f6f
--- /dev/null
+++ b/src/cobalt/media_stream/audio_parameters.cc
@@ -0,0 +1,31 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/media_stream/audio_parameters.h"
+
+#include <sstream>
+
+namespace cobalt {
+namespace media_stream {
+
+std::string AudioParameters::AsHumanReadableString() const {
+  std::ostringstream os;
+  os << "channel_count: " << channel_count()
+     << " sample_rate: " << sample_rate()
+     << " bits_per_sample: " << bits_per_sample();
+  return os.str();
+}
+
+}  // namespace media_stream
+}  // namespace cobalt
diff --git a/src/cobalt/media_stream/audio_parameters.h b/src/cobalt/media_stream/audio_parameters.h
new file mode 100644
index 0000000..5f1e012
--- /dev/null
+++ b/src/cobalt/media_stream/audio_parameters.h
@@ -0,0 +1,76 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_STREAM_AUDIO_PARAMETERS_H_
+#define COBALT_MEDIA_STREAM_AUDIO_PARAMETERS_H_
+
+#include <string>
+
+#include "base/logging.h"
+
+namespace cobalt {
+namespace media_stream {
+
+class AudioParameters {
+ public:
+  AudioParameters(int channel_count, int sample_rate, int bits_per_sample)
+      : channel_count_(channel_count),
+        sample_rate_(sample_rate),
+        bits_per_sample_(bits_per_sample) {
+    DCHECK(IsValid());
+  }
+
+  AudioParameters(const AudioParameters&) = default;
+  AudioParameters& operator=(const AudioParameters&) = default;
+
+  int channel_count() const { return channel_count_; }
+  int sample_rate() const { return sample_rate_; }
+  int bits_per_sample() const { return bits_per_sample_; }
+
+  std::string AsHumanReadableString() const;
+
+  bool IsValid() const {
+    return (channel_count_ > 0) && (bits_per_sample_ > 0) && (sample_rate_ > 0);
+  }
+
+  int GetBitsPerSecond() const {
+    return sample_rate_ * channel_count_ * bits_per_sample_;
+  }
+
+ private:
+  int channel_count_;
+  int sample_rate_;
+  int bits_per_sample_;
+};
+
+inline bool operator==(const AudioParameters& lhs, const AudioParameters& rhs) {
+  return ((lhs.channel_count() == rhs.channel_count()) &&
+          (lhs.sample_rate() == rhs.sample_rate()) &&
+          (lhs.bits_per_sample() == rhs.bits_per_sample()));
+}
+
+inline bool operator!=(const AudioParameters& lhs, const AudioParameters& rhs) {
+  return !(lhs == rhs);
+}
+
+inline std::ostream& operator<<(std::ostream& os,
+                                const AudioParameters& params) {
+  os << params.AsHumanReadableString();
+  return os;
+}
+
+}  // namespace media_stream
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_STREAM_AUDIO_PARAMETERS_H_
diff --git a/src/cobalt/media_stream/audio_parameters_test.cc b/src/cobalt/media_stream/audio_parameters_test.cc
new file mode 100644
index 0000000..107f784
--- /dev/null
+++ b/src/cobalt/media_stream/audio_parameters_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/media_stream/audio_parameters.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace media_stream {
+
+TEST(AudioParameters, Constructor_ParametersValues) {
+  int channel_count = 6;
+  int sample_rate = 20000;
+  int bits_per_sample = 24;
+  AudioParameters params(channel_count, sample_rate, bits_per_sample);
+
+  EXPECT_EQ(channel_count, params.channel_count());
+  EXPECT_EQ(bits_per_sample, params.bits_per_sample());
+  EXPECT_EQ(sample_rate, params.sample_rate());
+}
+
+TEST(AudioParameters, AsHumanReadableString) {
+  int channel_count = 6;
+  int sample_rate = 20000;
+  int bits_per_sample = 24;
+  AudioParameters params(channel_count, sample_rate, bits_per_sample);
+  std::string params_string = params.AsHumanReadableString();
+  EXPECT_EQ("channel_count: 6 sample_rate: 20000 bits_per_sample: 24",
+            params_string);
+}
+
+TEST(AudioParameters, IsValid_Parameterized) {
+  int channel_count = 1;
+  int sample_rate = 44100;
+  int bits_per_sample = 16;
+  AudioParameters params(channel_count, sample_rate, bits_per_sample);
+  EXPECT_TRUE(params.IsValid());
+}
+
+TEST(AudioParameters, GetBitsPerSecond) {
+  int channel_count = 2;
+  int sample_rate = 44100;
+  int bits_per_sample = 16;
+  AudioParameters params(channel_count, sample_rate, bits_per_sample);
+  EXPECT_EQ(2 * 44100 * 16, params.GetBitsPerSecond());
+}
+
+TEST(AudioParameters, Compare) {
+  int channel_count = 1;
+  int sample_rate = 44100;
+  int bits_per_sample = 16;
+  AudioParameters params1(channel_count, sample_rate, bits_per_sample);
+  AudioParameters params2(channel_count, sample_rate, bits_per_sample);
+  AudioParameters params3(channel_count, sample_rate + 1, bits_per_sample);
+  EXPECT_EQ(params1, params2);
+  EXPECT_NE(params2, params3);
+}
+
+}  // namespace media_stream
+}  // namespace cobalt
diff --git a/src/cobalt/media_stream/media_stream.gyp b/src/cobalt/media_stream/media_stream.gyp
new file mode 100644
index 0000000..8581291
--- /dev/null
+++ b/src/cobalt/media_stream/media_stream.gyp
@@ -0,0 +1,38 @@
+# Copyright 2018 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'media_stream',
+      'type': 'static_library',
+      'sources': [
+        'audio_parameters.cc',
+        'audio_parameters.h',
+        'media_stream.h',
+        'media_stream_track.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+      ],
+      'export_dependent_settings': [
+        '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+      ],
+    },
+  ],
+}
+
diff --git a/src/cobalt/media_stream/media_stream.h b/src/cobalt/media_stream/media_stream.h
new file mode 100644
index 0000000..491072b
--- /dev/null
+++ b/src/cobalt/media_stream/media_stream.h
@@ -0,0 +1,65 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_STREAM_MEDIA_STREAM_H_
+#define COBALT_MEDIA_STREAM_MEDIA_STREAM_H_
+
+#include "base/memory/ref_counted.h"
+#include "cobalt/dom/event_target.h"
+#include "cobalt/media_stream/media_stream_track.h"
+#include "cobalt/script/sequence.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace media_stream {
+
+// This class represents a MediaStream, and implements the specification at:
+// https://www.w3.org/TR/mediacapture-streams/#dom-mediastream
+class MediaStream : public dom::EventTarget {
+ public:
+  using TrackSequences = script::Sequence<scoped_refptr<MediaStreamTrack>>;
+
+  // Constructors.
+  MediaStream() = default;
+
+  explicit MediaStream(TrackSequences tracks): tracks_(std::move(tracks)) {
+  }
+
+  // Functions.
+  script::Sequence<scoped_refptr<MediaStreamTrack>>& GetAudioTracks() {
+    return tracks_;
+  }
+
+  script::Sequence<scoped_refptr<MediaStreamTrack>>& GetTracks() {
+    return GetAudioTracks();
+  }
+
+  void TraceMembers(script::Tracer* tracer) override {
+    EventTarget::TraceMembers(tracer);
+    tracer->TraceItems(tracks_);
+  }
+
+  DEFINE_WRAPPABLE_TYPE(MediaStream);
+
+ private:
+  MediaStream(const MediaStream&) = delete;
+  MediaStream& operator=(const MediaStream&) = delete;
+
+  script::Sequence<scoped_refptr<MediaStreamTrack>> tracks_;
+};
+
+}  // namespace media_stream
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_STREAM_MEDIA_STREAM_H_
diff --git a/src/cobalt/media_stream/media_stream.idl b/src/cobalt/media_stream/media_stream.idl
new file mode 100644
index 0000000..d98bade
--- /dev/null
+++ b/src/cobalt/media_stream/media_stream.idl
@@ -0,0 +1,23 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Implements a subset of the specification at:
+// https://www.w3.org/TR/mediacapture-streams/#dom-mediastream
+[Exposed=Window,
+ Constructor
+]
+interface MediaStream : EventTarget {
+  sequence<MediaStreamTrack> getAudioTracks();
+  sequence<MediaStreamTrack> getTracks();
+};
diff --git a/src/cobalt/media_stream/media_stream_constraints.idl b/src/cobalt/media_stream/media_stream_constraints.idl
new file mode 100644
index 0000000..72bf8f8
--- /dev/null
+++ b/src/cobalt/media_stream/media_stream_constraints.idl
@@ -0,0 +1,19 @@
+// Copyright 2018 Google Inc. All Rights Reserved