Import Cobalt 3.11337
diff --git a/src/base/file_util_starboard.cc b/src/base/file_util_starboard.cc
index 0c6511a..a2e9b2b 100644
--- a/src/base/file_util_starboard.cc
+++ b/src/base/file_util_starboard.cc
@@ -193,8 +193,8 @@
   }
 
   base::PlatformFile destination_file = base::CreatePlatformFileUnsafe(
-      to_path, base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, NULL,
-      NULL);
+      to_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
+      NULL, NULL);
   if (destination_file == base::kInvalidPlatformFileValue) {
     DPLOG(ERROR) << "CopyFile(): Unable to open destination file: "
                  << to_path.value();
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp
index a2f0dda..a2822ab 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -31,6 +31,7 @@
         'c_val.h',
         'c_val_time_interval_entry_stats.h',
         'c_val_time_interval_timer.h',
+        'deep_link_event.h',
         'do_main.h',
         'do_main_starboard.h',
         'event.h',
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h
index c4bcc0c..7331781 100644
--- a/src/cobalt/base/c_val.h
+++ b/src/cobalt/base/c_val.h
@@ -249,6 +249,7 @@
       {10LL * 1024LL * 1024LL * 1024LL, 1024LL * 1024LL * 1024LL, "GB"},
       {10LL * 1024LL * 1024LL, 1024LL * 1024LL, "MB"},
       {10LL * 1024LL, 1024LL, "KB"},
+      {0LL, 1L, "B"},
   };
   return ThresholdList<cval::SizeInBytes>(thresholds, arraysize(thresholds));
 }
@@ -291,21 +292,23 @@
   if (negative) {
     oss << "-";
   }
+
+  oss << std::fixed << std::setprecision(1) << std::setfill('0');
   if (value_in_us > kHour) {
-    oss << value_in_us / kHour << ":" << std::setfill('0') << std::setw(2)
-        << (value_in_us % kHour) / kMinute << ":" << std::setfill('0')
+    oss << value_in_us / kHour << ":" << std::setw(2)
+        << (value_in_us % kHour) / kMinute << ":"
         << std::setw(2) << (value_in_us % kMinute) / kSecond << "h";
   } else if (value_in_us > kMinute) {
-    oss << value_in_us / kMinute << ":" << std::setfill('0') << std::setw(2)
+    oss << value_in_us / kMinute << ":" << std::setw(2)
         << (value_in_us % kMinute) / kSecond << "m";
-  } else if (value_in_us > kSecond * 10) {
-    oss << value_in_us / kSecond << "s";
-  } else if (value_in_us > kMillisecond * 2) {
-    oss << value_in_us / kMillisecond << "ms";
-  } else if (value_in_us > 0) {
-    oss << value_in_us << "us";
+  } else if (value_in_us > kSecond) {
+    oss << std::setw(1)
+        << static_cast<double>(value_in_us) / kSecond << "s";
+  } else if (value_in_us > kMillisecond) {
+    oss << std::setw(1)
+        << static_cast<double>(value_in_us) / kMillisecond << "ms";
   } else {
-    oss << value_in_us;
+    oss << value_in_us << "us";
   }
 
   return oss.str();
diff --git a/src/cobalt/base/c_val_test.cc b/src/cobalt/base/c_val_test.cc
index 9751266..c1941d3 100644
--- a/src/cobalt/base/c_val_test.cc
+++ b/src/cobalt/base/c_val_test.cc
@@ -16,6 +16,7 @@
 
 #include <limits>
 
+#include "base/time.h"
 #include "cobalt/base/c_val.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -64,7 +65,27 @@
   base::CValManager* cvm = base::CValManager::GetInstance();
   base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
   EXPECT_TRUE(result);
-  EXPECT_EQ(*result, "4095MB");
+  EXPECT_EQ(*result, "4294M");
+}
+
+TEST(CValTest, RegisterAndPrintU32Zero) {
+  const std::string cval_name = "32-bit unsigned int";
+  const uint32_t cval_value = 0;
+  base::CVal<uint32_t> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "0");
+}
+
+TEST(CValTest, RegisterAndPrintU32K) {
+  const std::string cval_name = "32-bit unsigned int";
+  const uint32_t cval_value = 50000;
+  base::CVal<uint32_t> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50K");
 }
 
 TEST(CValTest, RegisterAndPrintU64) {
@@ -74,7 +95,7 @@
   base::CValManager* cvm = base::CValManager::GetInstance();
   base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
   EXPECT_TRUE(result);
-  EXPECT_EQ(*result, "17592186044415MB");
+  EXPECT_EQ(*result, "18446744073709M");
 }
 
 TEST(CValTest, RegisterAndPrintS32) {
@@ -97,6 +118,266 @@
   EXPECT_EQ(*result, "-9223372036854775808");
 }
 
+TEST(CValTest, RegisterAndPrintSizeInBytesB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(500);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "500B");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesZero) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(0);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "0B");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesKB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(50000UL);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "48KB");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesMB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(50000000UL);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "47MB");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesGB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(50000000000UL);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "46GB");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMicroseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50us");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaZero) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(0));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "0us");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMilliseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50.0ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMillisecondsSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.0ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMillisecondsFraction) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(5500));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.5ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50.0s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaSecondsSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.0s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaSecondsFraction) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(5500));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.5s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMinutes) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50:00m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMinutesSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5:00m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMinutesAndSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(92));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:32m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHours) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromHours(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50:00:00h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromHours(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5:00:00h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndMinutes) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(92));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:32:00h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndMinutesAndSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(92 * 60 + 32));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:32:32h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(60 * 60 + 32));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:00:32h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMicroseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3us");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMilliseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3.0ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3.0s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMinutes) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3:00m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeHours) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromHours(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3:00:00h");
+}
+
 TEST(CValTest, RegisterAndPrintFloat) {
   const std::string cval_name = "float";
   const float cval_value = 3.14159f;
diff --git a/src/cobalt/base/deep_link_event.h b/src/cobalt/base/deep_link_event.h
new file mode 100644
index 0000000..5e3df4b
--- /dev/null
+++ b/src/cobalt/base/deep_link_event.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef COBALT_BASE_DEEP_LINK_EVENT_H_
+#define COBALT_BASE_DEEP_LINK_EVENT_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "cobalt/base/event.h"
+
+namespace base {
+
+class DeepLinkEvent : public Event {
+ public:
+  explicit DeepLinkEvent(const std::string& link) : link_(link) {}
+  const std::string& link() const { return link_; }
+
+  BASE_EVENT_SUBCLASS(DeepLinkEvent);
+
+ private:
+  std::string link_;
+};
+
+}  // namespace base
+
+#endif  // COBALT_BASE_DEEP_LINK_EVENT_H_
diff --git a/src/cobalt/base/init_cobalt.cc b/src/cobalt/base/init_cobalt.cc
index f9ed397..9cad4b4 100644
--- a/src/cobalt/base/init_cobalt.cc
+++ b/src/cobalt/base/init_cobalt.cc
@@ -16,6 +16,8 @@
 
 #include "cobalt/base/init_cobalt.h"
 
+#include <string>
+
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/i18n/icu_util.h"
@@ -51,10 +53,15 @@
 
 #endif
 
+base::LazyInstance<std::string> s_initial_deep_link = LAZY_INSTANCE_INITIALIZER;
+
 }  // namespace
 
-void InitCobalt(int argc, char* argv[]) {
+void InitCobalt(int argc, char* argv[], const char* link) {
   CommandLine::Init(argc, argv);
+  if (link) {
+    s_initial_deep_link.Get() = link;
+  }
   deprecated::PlatformDelegate::Init();
 
   // Register a callback to be called during program termination.
@@ -74,4 +81,6 @@
   LOG_IF(ERROR, !icu_initialized) << "ICU initialization failed.";
 }
 
+const char* GetInitialDeepLink() { return s_initial_deep_link.Get().c_str(); }
+
 }  // namespace cobalt
diff --git a/src/cobalt/base/init_cobalt.h b/src/cobalt/base/init_cobalt.h
index 14aed0c..1ba9cdf 100644
--- a/src/cobalt/base/init_cobalt.h
+++ b/src/cobalt/base/init_cobalt.h
@@ -32,7 +32,10 @@
 //
 //   ..RunSomeCode..
 // }
-void InitCobalt(int argc, char* argv[]);
+void InitCobalt(int argc, char* argv[], const char* initial_deep_link);
+
+// Get the |initial_deep_link| string specified in |InitCobalt|.
+const char* GetInitialDeepLink();
 
 }  // namespace cobalt
 
diff --git a/src/cobalt/base/wrap_main.h b/src/cobalt/base/wrap_main.h
index 10aacce..193e379 100644
--- a/src/cobalt/base/wrap_main.h
+++ b/src/cobalt/base/wrap_main.h
@@ -42,7 +42,7 @@
 typedef int (*MainFunction)(int argc, char** argv);
 
 // A start-style function.
-typedef void (*StartFunction)(int argc, char** argv,
+typedef void (*StartFunction)(int argc, char** argv, const char* link,
                               const base::Closure& quit_closure);
 
 // A function type that can be called at shutdown.
@@ -53,7 +53,7 @@
 
 // No-operation function that can be passed into start_function if no start work
 // is needed.
-void NoopStartFunction(int /*argc*/, char** /*argv*/,
+void NoopStartFunction(int /*argc*/, char** /*argv*/, const char* /*link*/,
                        const base::Closure& /*quit_closure*/) {}
 
 // No-operation function that can be passed into event_function if no other
@@ -75,16 +75,16 @@
 namespace wrap_main {
 
 template <MainFunction main_function>
-int SimpleMain(int argc, char **argv) {
+int SimpleMain(int argc, char** argv) {
   base::AtExitManager at_exit;
-  InitCobalt(argc, argv);
+  InitCobalt(argc, argv, NULL);
   return main_function(argc, argv);
 }
 
 template <StartFunction start_function, StopFunction stop_function>
-int BaseMain(int argc, char **argv) {
+int BaseMain(int argc, char** argv) {
   base::AtExitManager at_exit;
-  InitCobalt(argc, argv);
+  InitCobalt(argc, argv, NULL);
 
   MessageLoopForUI message_loop;
   base::PlatformThread::SetName("Main");
@@ -93,7 +93,7 @@
   DCHECK(!message_loop.is_running());
   base::RunLoop run_loop;
 
-  start_function(argc, argv, run_loop.QuitClosure());
+  start_function(argc, argv, NULL, run_loop.QuitClosure());
   run_loop.Run();
   stop_function();
 
@@ -103,7 +103,7 @@
 // Calls |main_function| at startup, creates an AtExitManager and calls
 // InitCobalt, and terminates once it is completed.
 #define COBALT_WRAP_SIMPLE_MAIN(main_function)                         \
-  int main(int argc, char **argv) {                                    \
+  int main(int argc, char** argv) {                                    \
     return ::cobalt::wrap_main::SimpleMain<main_function>(argc, argv); \
   }
 
@@ -111,7 +111,7 @@
 // terminates once the MessageLoop terminates, calling |stop_function| on the
 // way out.
 #define COBALT_WRAP_BASE_MAIN(start_function, stop_function)                   \
-  int main(int argc, char **argv) {                                            \
+  int main(int argc, char** argv) {                                            \
     return ::cobalt::wrap_main::BaseMain<start_function, stop_function>(argc,  \
                                                                         argv); \
   }
diff --git a/src/cobalt/base/wrap_main_starboard.h b/src/cobalt/base/wrap_main_starboard.h
index 1a72bd7..dad3708 100644
--- a/src/cobalt/base/wrap_main_starboard.h
+++ b/src/cobalt/base/wrap_main_starboard.h
@@ -42,14 +42,14 @@
 
       DCHECK(!g_at_exit);
       g_at_exit = new base::AtExitManager();
-      InitCobalt(data->argument_count, data->argument_values);
+      InitCobalt(data->argument_count, data->argument_values, data->link);
 
       DCHECK(!g_loop);
       g_loop = new MessageLoopForUI();
       g_loop->set_thread_name("Main");
       g_loop->Start();
 
-      start_function(data->argument_count, data->argument_values,
+      start_function(data->argument_count, data->argument_values, data->link,
                      base::Bind(&SbSystemRequestStop, 0));
       break;
     }
@@ -78,7 +78,7 @@
 template <MainFunction main_function>
 int CobaltMainAddOns(int argc, char** argv) {
   base::AtExitManager at_exit;
-  cobalt::InitCobalt(argc, argv);
+  cobalt::InitCobalt(argc, argv, NULL);
   return main_function(argc, argv);
 }
 
diff --git a/src/cobalt/bindings/testing/get_opaque_root_test.cc b/src/cobalt/bindings/testing/get_opaque_root_test.cc
index ef72e1f..e60db42 100644
--- a/src/cobalt/bindings/testing/get_opaque_root_test.cc
+++ b/src/cobalt/bindings/testing/get_opaque_root_test.cc
@@ -24,8 +24,19 @@
 namespace testing {
 
 namespace {
-typedef InterfaceBindingsTest<GetOpaqueRootInterface>
-    GetOpaqueRootInterfaceTest;
+class GetOpaqueRootInterfaceTest : public BindingsTestBase {
+ public:
+  GetOpaqueRootInterfaceTest()
+      : test_mock_(new ::testing::NiceMock<GetOpaqueRootInterface>()) {
+    global_environment_->Bind(
+        "test", make_scoped_refptr<GetOpaqueRootInterface>((test_mock_)));
+  }
+
+  GetOpaqueRootInterface& test_mock() { return *test_mock_.get(); }
+
+ private:
+  const scoped_refptr<GetOpaqueRootInterface> test_mock_;
+};
 }  // namespace
 
 TEST_F(GetOpaqueRootInterfaceTest, CallsFunction) {
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index ac19432..27c9f1a 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -29,6 +29,8 @@
 #include "build/build_config.h"
 #include "cobalt/account/account_event.h"
 #include "cobalt/base/cobalt_paths.h"
+#include "cobalt/base/deep_link_event.h"
+#include "cobalt/base/init_cobalt.h"
 #include "cobalt/base/localized_strings.h"
 #include "cobalt/base/user_log.h"
 #include "cobalt/browser/switches.h"
@@ -290,6 +292,7 @@
   BrowserModule::Options options;
   options.web_module_options.name = "MainWebModule";
   options.language = language;
+  options.initial_deep_link = GetInitialDeepLink();
   options.network_module_options.preferred_language = language;
 
   ApplyCommandLineSettingsToRendererOptions(&options.renderer_module_options);
diff --git a/src/cobalt/browser/browser_bindings.gyp b/src/cobalt/browser/browser_bindings.gyp
index 80049c1..cb9d50b 100644
--- a/src/cobalt/browser/browser_bindings.gyp
+++ b/src/cobalt/browser/browser_bindings.gyp
@@ -155,6 +155,7 @@
         '../h5vcc/H5vccAudioConfigArray.idl',
         '../h5vcc/H5vccCVal.idl',
         '../h5vcc/H5vccCValKeyList.idl',
+        '../h5vcc/H5vccDeepLinkEventTarget.idl',
         '../h5vcc/H5vccRuntime.idl',
         '../h5vcc/H5vccRuntimeEventTarget.idl',
         '../h5vcc/H5vccSettings.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index cd3c605..692ee5d 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -16,12 +16,16 @@
 
 #include "cobalt/browser/browser_module.h"
 
+#include <vector>
+
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/debug/trace_event.h"
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/base/tokens.h"
@@ -53,6 +57,15 @@
     "activated or not.  While activated, input will constantly and randomly be "
     "generated and passed directly into the main web module.";
 
+const char kSetMediaConfigCommand[] = "set_media_config";
+const char kSetMediaConfigCommandShortHelp[] =
+    "Sets media module configuration.";
+const char kSetMediaConfigCommandLongHelp[] =
+    "This can be called in the form of set_media_config('name=value'), where "
+    "name is a string and value is an int.  Refer to the implementation of "
+    "MediaModule::SetConfiguration() on individual platform for settings "
+    "supported on the particular platform.";
+
 #if defined(ENABLE_SCREENSHOT)
 // Command to take a screenshot.
 const char kScreenshotCommand[] = "screenshot";
@@ -134,6 +147,10 @@
           kFuzzerToggleCommand,
           base::Bind(&BrowserModule::OnFuzzerToggle, base::Unretained(this)),
           kFuzzerToggleCommandShortHelp, kFuzzerToggleCommandLongHelp)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(set_media_config_command_handler_(
+          kSetMediaConfigCommand,
+          base::Bind(&BrowserModule::OnSetMediaConfig, base::Unretained(this)),
+          kSetMediaConfigCommandShortHelp, kSetMediaConfigCommandLongHelp)),
 #if defined(ENABLE_SCREENSHOT)
       ALLOW_THIS_IN_INITIALIZER_LIST(screenshot_command_handler_(
           kScreenshotCommand,
@@ -156,6 +173,7 @@
   h5vcc_settings.network_module = &network_module_;
   h5vcc_settings.account_manager = account_manager;
   h5vcc_settings.event_dispatcher = system_window->event_dispatcher();
+  h5vcc_settings.initial_deep_link = options.initial_deep_link;
   web_module_options_.injected_window_attributes["h5vcc"] =
       base::Bind(&CreateH5VCC, h5vcc_settings);
 
@@ -330,6 +348,30 @@
   }
 }
 
+void BrowserModule::OnSetMediaConfig(const std::string& config) {
+  if (MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BrowserModule::OnSetMediaConfig, weak_this_, config));
+    return;
+  }
+
+  std::vector<std::string> tokens;
+  base::SplitString(config, '=', &tokens);
+
+  int value;
+  if (tokens.size() != 2 || !base::StringToInt(tokens[1], &value)) {
+    LOG(WARNING) << "Media configuration '" << config << "' is not in the"
+                 << " form of '<string name>=<int value>'.";
+    return;
+  }
+  if (media_module_->SetConfiguration(tokens[0], value)) {
+    LOG(INFO) << "Successfully setting " << tokens[0] << " to " << value;
+  } else {
+    LOG(WARNING) << "Failed to set " << tokens[0] << " to " << value;
+  }
+}
+
 void BrowserModule::OnDebugConsoleRenderTreeProduced(
     const browser::WebModule::LayoutResults& layout_results) {
   TRACE_EVENT0("cobalt::browser",
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 742a4d3..df6da73 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -64,6 +64,7 @@
     WebModule::Options web_module_options;
     media::MediaModule::Options media_module_options;
     std::string language;
+    std::string initial_deep_link;
     base::Closure web_module_recreated_callback;
   };
 
@@ -166,6 +167,10 @@
   // Toggles the input fuzzer on/off.  Ignores the parameter.
   void OnFuzzerToggle(const std::string&);
 
+  // Use the config in the form of '<string name>=<int value>' to call
+  // MediaModule::SetConfiguration().
+  void OnSetMediaConfig(const std::string& config);
+
   // Glue function to deal with the production of the debug console render tree,
   // and will manage handing it off to the renderer.
   void OnDebugConsoleRenderTreeProduced(
@@ -266,9 +271,12 @@
 
   TraceManager trace_manager;
 
-  // Command handler object for toggline the input fuzzer on/off.
+  // Command handler object for toggling the input fuzzer on/off.
   base::ConsoleCommandManager::CommandHandler fuzzer_toggle_command_handler_;
 
+  // Command handler object for setting media module config.
+  base::ConsoleCommandManager::CommandHandler set_media_config_command_handler_;
+
 #if defined(ENABLE_SCREENSHOT)
   // Command handler object for screenshot command from the debug console.
   base::ConsoleCommandManager::CommandHandler screenshot_command_handler_;
diff --git a/src/cobalt/browser/main.cc b/src/cobalt/browser/main.cc
index ef98aff..6b5fef2 100644
--- a/src/cobalt/browser/main.cc
+++ b/src/cobalt/browser/main.cc
@@ -27,7 +27,7 @@
 
 cobalt::browser::Application* g_application = NULL;
 
-void StartApplication(int /*argc*/, char** /*argv*/,
+void StartApplication(int /*argc*/, char** /*argv*/, const char* /*link*/,
                       const base::Closure& quit_closure) {
   DCHECK(!g_application);
   g_application = cobalt::browser::CreateApplication(quit_closure).release();
diff --git a/src/cobalt/browser/snapshot_app_stats.cc b/src/cobalt/browser/snapshot_app_stats.cc
index 757e784..050914f 100644
--- a/src/cobalt/browser/snapshot_app_stats.cc
+++ b/src/cobalt/browser/snapshot_app_stats.cc
@@ -130,7 +130,7 @@
 cobalt::browser::Application* g_application = NULL;
 base::Thread* g_snapshot_thread = NULL;
 
-void StartApplication(int /*argc*/, char* /*argv*/ [],
+void StartApplication(int /*argc*/, char* /*argv*/ [], const char* /*link*/,
                       const base::Closure& quit_closure) {
   logging::SetMinLogLevel(100);
 
diff --git a/src/cobalt/browser/starboard/event_handler.cc b/src/cobalt/browser/starboard/event_handler.cc
index 00808d7..be5e494 100644
--- a/src/cobalt/browser/starboard/event_handler.cc
+++ b/src/cobalt/browser/starboard/event_handler.cc
@@ -18,6 +18,7 @@
 
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
+#include "cobalt/base/deep_link_event.h"
 #include "cobalt/network/network_event.h"
 #include "cobalt/system_window/application_event.h"
 #include "cobalt/system_window/starboard/system_window.h"
@@ -65,6 +66,9 @@
   } else if (starboard_event->type == kSbEventTypeNetworkDisconnect) {
     cobalt_event.reset(
         new network::NetworkEvent(network::NetworkEvent::kDisconnection));
+  } else if (starboard_event->type == kSbEventTypeLink) {
+    const char* link = static_cast<const char*>(starboard_event->data);
+    cobalt_event.reset(new base::DeepLinkEvent(link));
   }
 
   // Dispatch the Cobalt event, if created.
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 0e34392..eb49ee4 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-11130
\ No newline at end of file
+11337
\ No newline at end of file
diff --git a/src/cobalt/dom/Window.idl b/src/cobalt/dom/Window.idl
index 3094309..b6a3e20 100644
--- a/src/cobalt/dom/Window.idl
+++ b/src/cobalt/dom/Window.idl
@@ -25,6 +25,7 @@
   [Unforgeable] readonly attribute Document document;
   [PutForwards=href, Unforgeable] readonly attribute Location location;
   readonly attribute History history;
+  void close();
 
   // other browsing contexts
   [Replaceable] readonly attribute Window frames;
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index fcfdf35..b94ee93 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -53,7 +53,8 @@
       load_option_(0),
       inline_script_location_(GetSourceLocationName(), 1, 1),
       is_sync_load_successful_(false),
-      prevent_garbage_collection_count_(0) {
+      prevent_garbage_collection_count_(0),
+      should_execute_(true) {
   DCHECK(document->html_element_context()->script_runner());
 }
 
@@ -260,12 +261,9 @@
                      base::Unretained(this)));
 
       if (is_sync_load_successful_) {
-        html_element_context()->script_runner()->Execute(
-            content_, base::SourceLocation(url_.spec(), 1, 1));
-
-        // If the script is from an external file, fire a simple event named
-        // load at the script element.
-        DispatchEvent(new Event(base::Tokens::load()));
+        PreventGarbageCollection();
+        ExecuteExternal();
+        AllowGarbageCollection();
       } else {
         // Executing the script block must just consist of firing a simple event
         // named error at the element.
@@ -497,6 +495,16 @@
 void HTMLScriptElement::Execute(const std::string& content,
                                 const base::SourceLocation& script_location,
                                 bool is_external) {
+  // If should_execute_ is set to false for whatever reason, then we
+  // immediately exit.
+  // When inserted using the document.write() method, script elements execute
+  // (typically synchronously), but when inserted using innerHTML and
+  // outerHTML attributes, they do not execute at all.
+  // https://www.w3.org/TR/html5/scripting-1.html#the-script-element.
+  if (!should_execute_) {
+    return;
+  }
+
   TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::Execute()");
   // Since error is already handled, it is guaranteed the load is successful.
 
diff --git a/src/cobalt/dom/html_script_element.h b/src/cobalt/dom/html_script_element.h
index 17ab6d4..aa523db 100644
--- a/src/cobalt/dom/html_script_element.h
+++ b/src/cobalt/dom/html_script_element.h
@@ -62,6 +62,10 @@
     SetAttributeEventListener(base::Tokens::readystatechange(), event_listener);
   }
 
+  void set_should_execute(bool should_execute) {
+    should_execute_ = should_execute;
+  }
+
   // Custom, not in any spec.
   //
   // From Node.
@@ -133,6 +137,9 @@
   std::string content_;
   // Active requests disabling garbage collection.
   int prevent_garbage_collection_count_;
+
+  // Whether or not the script should execute at all.
+  bool should_execute_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/parser.h b/src/cobalt/dom/parser.h
index 554643e..3bf7392 100644
--- a/src/cobalt/dom/parser.h
+++ b/src/cobalt/dom/parser.h
@@ -45,7 +45,8 @@
   virtual ~Parser() {}
   // Synchronous parsing interfaces.
   //
-  // Parses an HTML document and returns the created Document.
+  // Parses an HTML document and returns the created Document.  No
+  // script elements within the HTML document will be executed.
   virtual scoped_refptr<Document> ParseDocument(
       const std::string& input, HTMLElementContext* html_element_context,
       const base::SourceLocation& input_location) = 0;
@@ -56,7 +57,8 @@
       const base::SourceLocation& input_location) = 0;
 
   // Parses an HTML input and inserts new nodes in document under parent_node
-  // before reference_node.
+  // before reference_node.  No script elements within the HTML document will
+  // be executed.
   virtual void ParseDocumentFragment(
       const std::string& input, const scoped_refptr<Document>& document,
       const scoped_refptr<Node>& parent_node,
@@ -74,7 +76,8 @@
   // Asynchronous parsing interfaces.
   //
   // Parses an HTML document asynchronously, returns the decoder that can be
-  // used in the parsing.
+  // used in the parsing.  Script elements in the HTML document will be
+  // executed.
   virtual scoped_ptr<loader::Decoder> ParseDocumentAsync(
       const scoped_refptr<Document>& document,
       const base::SourceLocation& input_location) = 0;
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 8a57e10..3e23ed9 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -39,6 +39,9 @@
 #include "cobalt/dom/storage.h"
 #include "cobalt/dom/window_timers.h"
 #include "cobalt/script/javascript_engine.h"
+#if defined(OS_STARBOARD)
+#include "starboard/system.h"
+#endif
 
 namespace cobalt {
 namespace dom {
@@ -135,6 +138,15 @@
 
 const scoped_refptr<History>& Window::history() const { return history_; }
 
+// https://www.w3.org/TR/html5/browsers.html#dom-window-close
+void Window::Close() {
+#if defined(OS_STARBOARD)
+  SbSystemRequestStop(0);
+#else
+  LOG(WARNING) << "window.close is not supported on this platform.";
+#endif
+}
+
 const scoped_refptr<Navigator>& Window::navigator() const { return navigator_; }
 
 scoped_refptr<cssom::CSSStyleDeclaration> Window::GetComputedStyle(
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 6b2e776..8240112 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -116,6 +116,7 @@
   const scoped_refptr<Document>& document() const;
   const scoped_refptr<Location>& location() const;
   const scoped_refptr<History>& history() const;
+  void Close();
 
   scoped_refptr<Window> frames() { return this; }
   unsigned int length() { return 0; }
diff --git a/src/cobalt/dom_parser/html_decoder.cc b/src/cobalt/dom_parser/html_decoder.cc
index ddda365..f0b5585 100644
--- a/src/cobalt/dom_parser/html_decoder.cc
+++ b/src/cobalt/dom_parser/html_decoder.cc
@@ -30,12 +30,14 @@
     const scoped_refptr<dom::Node>& reference_node,
     const int dom_max_element_depth, const base::SourceLocation& input_location,
     const base::Closure& done_callback,
-    const base::Callback<void(const std::string&)>& error_callback)
+    const base::Callback<void(const std::string&)>& error_callback,
+    const bool should_run_scripts)
     : libxml_html_parser_wrapper_(new LibxmlHTMLParserWrapper(
           document, parent_node, reference_node, dom_max_element_depth,
-          input_location, error_callback)),
+          input_location, error_callback, should_run_scripts)),
       document_(document),
-      done_callback_(done_callback) {}
+      done_callback_(done_callback),
+      should_run_scripts_(should_run_scripts) {}
 
 HTMLDecoder::~HTMLDecoder() {}
 
diff --git a/src/cobalt/dom_parser/html_decoder.h b/src/cobalt/dom_parser/html_decoder.h
index 853fd47..db142e7 100644
--- a/src/cobalt/dom_parser/html_decoder.h
+++ b/src/cobalt/dom_parser/html_decoder.h
@@ -49,7 +49,8 @@
               const int dom_max_element_depth,
               const base::SourceLocation& input_location,
               const base::Closure& done_callback,
-              const base::Callback<void(const std::string&)>& error_callback);
+              const base::Callback<void(const std::string&)>& error_callback,
+              const bool should_run_scripts);
 
   ~HTMLDecoder();
 
@@ -68,6 +69,8 @@
   base::ThreadChecker thread_checker_;
   const base::Closure done_callback_;
 
+  const bool should_run_scripts_;
+
   DISALLOW_COPY_AND_ASSIGN(HTMLDecoder);
 };
 
diff --git a/src/cobalt/dom_parser/html_decoder_test.cc b/src/cobalt/dom_parser/html_decoder_test.cc
index ab5f6a1..92736ea 100644
--- a/src/cobalt/dom_parser/html_decoder_test.cc
+++ b/src/cobalt/dom_parser/html_decoder_test.cc
@@ -82,7 +82,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -104,7 +105,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(reinterpret_cast<char*>(temp), sizeof(temp));
   html_decoder_->Finish();
 
@@ -122,7 +124,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -140,7 +143,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -158,7 +162,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -176,7 +181,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -199,7 +205,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -214,7 +221,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -238,7 +246,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -258,7 +267,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -276,7 +286,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -295,7 +306,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(reinterpret_cast<char*>(temp), sizeof(temp));
   html_decoder_->Finish();
 
@@ -308,7 +320,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -335,7 +348,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -354,7 +368,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -376,7 +391,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -402,7 +418,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -420,7 +437,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -434,7 +452,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -457,7 +476,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
diff --git a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
index a50c295..e65ef8f 100644
--- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
@@ -18,6 +18,8 @@
 
 #include "base/basictypes.h"
 #include "base/string_util.h"
+#include "cobalt/dom/element.h"
+#include "cobalt/dom/html_script_element.h"
 
 namespace cobalt {
 namespace dom_parser {
@@ -89,6 +91,17 @@
   if (!IsFullDocument() && (name == "html" || name == "body")) {
     return;
   }
+
+  // If the top if the node stack is an html script element, then set its
+  // should_execute_ field to be our should_run_scripts_ field.
+  DCHECK(!node_stack().empty());
+  if (name == "script") {
+    scoped_refptr<dom::HTMLScriptElement> html_script_element =
+        node_stack().top()->AsElement()->AsHTMLElement()->AsHTMLScriptElement();
+    DCHECK(html_script_element);
+    html_script_element->set_should_execute(should_run_scripts_);
+  }
+
   LibxmlParserWrapper::OnEndElement(name);
 }
 
diff --git a/src/cobalt/dom_parser/libxml_html_parser_wrapper.h b/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
index f553818..7114443 100644
--- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
+++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
@@ -40,11 +40,13 @@
       const scoped_refptr<dom::Node>& reference_node,
       const int dom_max_element_depth,
       const base::SourceLocation& input_location,
-      const base::Callback<void(const std::string&)>& error_callback)
+      const base::Callback<void(const std::string&)>& error_callback,
+      const bool should_run_scripts)
       : LibxmlParserWrapper(document, parent_node, reference_node,
                             dom_max_element_depth, input_location,
                             error_callback),
-        html_parser_context_(NULL) {
+        html_parser_context_(NULL),
+        should_run_scripts_(should_run_scripts) {
     DCHECK(!document->IsXMLDocument());
   }
   ~LibxmlHTMLParserWrapper() OVERRIDE;
@@ -61,6 +63,8 @@
 
   htmlParserCtxtPtr html_parser_context_;
 
+  const bool should_run_scripts_;
+
   DISALLOW_COPY_AND_ASSIGN(LibxmlHTMLParserWrapper);
 };
 
diff --git a/src/cobalt/dom_parser/parser.cc b/src/cobalt/dom_parser/parser.cc
index 507c187..5be05a6 100644
--- a/src/cobalt/dom_parser/parser.cc
+++ b/src/cobalt/dom_parser/parser.cc
@@ -32,7 +32,8 @@
   scoped_refptr<dom::Document> document =
       new dom::Document(html_element_context, dom::Document::Options());
   HTMLDecoder html_decoder(document, document, NULL, dom_max_element_depth_,
-                           input_location, base::Closure(), error_callback_);
+                           input_location, base::Closure(), error_callback_,
+                           false);
   html_decoder.DecodeChunk(input.c_str(), input.length());
   html_decoder.Finish();
   return document;
@@ -60,7 +61,7 @@
     const base::SourceLocation& input_location) {
   HTMLDecoder html_decoder(document, parent_node, reference_node,
                            dom_max_element_depth_, input_location,
-                           base::Closure(), error_callback_);
+                           base::Closure(), error_callback_, false);
   html_decoder.DecodeChunk(input.c_str(), input.length());
   html_decoder.Finish();
 }
@@ -84,7 +85,7 @@
     const base::SourceLocation& input_location) {
   return scoped_ptr<loader::Decoder>(
       new HTMLDecoder(document, document, NULL, dom_max_element_depth_,
-                      input_location, base::Closure(), error_callback_));
+                      input_location, base::Closure(), error_callback_, true));
 }
 
 scoped_ptr<loader::Decoder> Parser::ParseXMLDocumentAsync(
diff --git a/src/cobalt/h5vcc/H5vccDeepLinkEventTarget.idl b/src/cobalt/h5vcc/H5vccDeepLinkEventTarget.idl
new file mode 100644
index 0000000..8f823a9
--- /dev/null
+++ b/src/cobalt/h5vcc/H5vccDeepLinkEventTarget.idl
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+// Custom interface to dispatch deep link events.
+// Modeled after Chrome's runtime onMessage target (and similar):
+// https://developer.chrome.com/apps/runtime#event-onMessage
+
+interface H5vccDeepLinkEventTarget {
+  void addListener(H5vccDeepLinkEventCallback callback);
+};
+
+callback H5vccDeepLinkEventCallback = void(DOMString link);
diff --git a/src/cobalt/h5vcc/H5vccRuntime.idl b/src/cobalt/h5vcc/H5vccRuntime.idl
index 18b8acb..02ed8ad 100644
--- a/src/cobalt/h5vcc/H5vccRuntime.idl
+++ b/src/cobalt/h5vcc/H5vccRuntime.idl
@@ -15,6 +15,8 @@
  */
 
 interface H5vccRuntime {
+  readonly attribute DOMString initialDeepLink;
+  readonly attribute H5vccDeepLinkEventTarget onDeepLink;
   readonly attribute H5vccRuntimeEventTarget onPause;
   readonly attribute H5vccRuntimeEventTarget onResume;
 };
diff --git a/src/cobalt/h5vcc/h5vcc.cc b/src/cobalt/h5vcc/h5vcc.cc
index b0cf530..9792fcb 100644
--- a/src/cobalt/h5vcc/h5vcc.cc
+++ b/src/cobalt/h5vcc/h5vcc.cc
@@ -23,7 +23,8 @@
   account_info_ = new H5vccAccountInfo(settings.account_manager);
   audio_config_array_ = new H5vccAudioConfigArray();
   c_val_ = new H5vccCVal();
-  runtime_ = new H5vccRuntime(settings.event_dispatcher);
+  runtime_ =
+      new H5vccRuntime(settings.event_dispatcher, settings.initial_deep_link);
   settings_ = new H5vccSettings(settings.media_module);
   storage_ = new H5vccStorage(settings.network_module);
   system_ = new H5vccSystem();
diff --git a/src/cobalt/h5vcc/h5vcc.gyp b/src/cobalt/h5vcc/h5vcc.gyp
index 835c4f9..c208fd0 100644
--- a/src/cobalt/h5vcc/h5vcc.gyp
+++ b/src/cobalt/h5vcc/h5vcc.gyp
@@ -39,6 +39,9 @@
         'h5vcc_c_val.h',
         'h5vcc_c_val_key_list.cc',
         'h5vcc_c_val_key_list.h',
+        'h5vcc_deep_link_event_target.cc',
+        'h5vcc_deep_link_event_target.h',
+        'h5vcc_event_listener_container.h',
         'h5vcc_runtime.cc',
         'h5vcc_runtime.h',
         'h5vcc_runtime_event_target.cc',
diff --git a/src/cobalt/h5vcc/h5vcc.h b/src/cobalt/h5vcc/h5vcc.h
index e8395b4..369cad3 100644
--- a/src/cobalt/h5vcc/h5vcc.h
+++ b/src/cobalt/h5vcc/h5vcc.h
@@ -17,6 +17,8 @@
 #ifndef COBALT_H5VCC_H5VCC_H_
 #define COBALT_H5VCC_H5VCC_H_
 
+#include <string>
+
 #include "cobalt/base/event_dispatcher.h"
 #include "cobalt/h5vcc/h5vcc_account_info.h"
 #include "cobalt/h5vcc/h5vcc_audio_config_array.h"
@@ -42,6 +44,7 @@
     network::NetworkModule* network_module;
     account::AccountManager* account_manager;
     base::EventDispatcher* event_dispatcher;
+    std::string initial_deep_link;
   };
 
   explicit H5vcc(const Settings& config);
diff --git a/src/cobalt/h5vcc/h5vcc_deep_link_event_target.cc b/src/cobalt/h5vcc/h5vcc_deep_link_event_target.cc
new file mode 100644
index 0000000..697688d
--- /dev/null
+++ b/src/cobalt/h5vcc/h5vcc_deep_link_event_target.cc
@@ -0,0 +1,40 @@
+/*
+ * 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/h5vcc/h5vcc_deep_link_event_target.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+namespace {
+const std::string& OnGetArgument(const std::string& link) { return link; }
+}  // namespace
+
+H5vccDeepLinkEventTarget::H5vccDeepLinkEventTarget()
+    : ALLOW_THIS_IN_INITIALIZER_LIST(listeners_(this)) {}
+
+void H5vccDeepLinkEventTarget::AddListener(
+    const H5vccDeepLinkEventTarget::H5vccDeepLinkEventCallbackHolder&
+        callback_holder) {
+  listeners_.AddListener(callback_holder);
+}
+
+void H5vccDeepLinkEventTarget::DispatchEvent(const std::string& link) {
+  listeners_.DispatchEvent(base::Bind(OnGetArgument, link));
+}
+
+}  // namespace h5vcc
+}  // namespace cobalt
diff --git a/src/cobalt/h5vcc/h5vcc_deep_link_event_target.h b/src/cobalt/h5vcc/h5vcc_deep_link_event_target.h
new file mode 100644
index 0000000..4a6b706
--- /dev/null
+++ b/src/cobalt/h5vcc/h5vcc_deep_link_event_target.h
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifndef COBALT_H5VCC_H5VCC_DEEP_LINK_EVENT_TARGET_H_
+#define COBALT_H5VCC_H5VCC_DEEP_LINK_EVENT_TARGET_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "cobalt/h5vcc/h5vcc_event_listener_container.h"
+#include "cobalt/script/callback_function.h"
+#include "cobalt/script/script_object.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+// This class implements the onDeepLink attribute of the h5vcc.runtime protocol,
+// modeled after the events in Chrome's runtime extension, e.g.
+// https://developer.chrome.com/apps/runtime#event-onMessage
+
+class H5vccDeepLinkEventTarget : public script::Wrappable {
+ public:
+  // Type for JavaScript event callback.
+  typedef script::CallbackFunction<void(const std::string&)>
+      H5vccDeepLinkEventCallback;
+  typedef script::ScriptObject<H5vccDeepLinkEventCallback>
+      H5vccDeepLinkEventCallbackHolder;
+
+  H5vccDeepLinkEventTarget();
+
+  // Called from JavaScript to register an event listener callback.
+  // May be called from any thread.
+  void AddListener(const H5vccDeepLinkEventCallbackHolder& callback_holder);
+
+  // Dispatches an event to the registered listeners.
+  // May be called from any thread.
+  void DispatchEvent(const std::string& link);
+
+  DEFINE_WRAPPABLE_TYPE(H5vccDeepLinkEventTarget);
+
+ private:
+  H5vccEventListenerContainer<const std::string&, H5vccDeepLinkEventCallback>
+      listeners_;
+
+  DISALLOW_COPY_AND_ASSIGN(H5vccDeepLinkEventTarget);
+};
+
+}  // namespace h5vcc
+}  // namespace cobalt
+
+#endif  // COBALT_H5VCC_H5VCC_DEEP_LINK_EVENT_TARGET_H_
diff --git a/src/cobalt/h5vcc/h5vcc_event_listener_container.h b/src/cobalt/h5vcc/h5vcc_event_listener_container.h
new file mode 100644
index 0000000..6f91143
--- /dev/null
+++ b/src/cobalt/h5vcc/h5vcc_event_listener_container.h
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#ifndef COBALT_H5VCC_H5VCC_EVENT_LISTENER_CONTAINER_H_
+#define COBALT_H5VCC_H5VCC_EVENT_LISTENER_CONTAINER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "cobalt/script/callback_function.h"
+#include "cobalt/script/script_object.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+// Template class to implement a container of event listeners where the
+// listener callback can take an argument of any type, including none (void).
+// Callback type must be specified in addition to callback argument type,
+// as we cannot typedef a callback taking a void argument.
+template <class CallbackArgType, class CallbackType>
+class H5vccEventListenerContainer {
+ public:
+  typedef script::ScriptObject<CallbackType> CallbackHolderType;
+
+  // Type for a callback that returns the value of the argument to be passed
+  // to the callback for each listener.
+  typedef base::Callback<CallbackArgType()> GetArgumentCallback;
+
+  // Type for a listener.
+  // We store the message loop from which the listener was registered,
+  // so we can run the callback on the same loop.
+  struct Listener {
+    Listener(script::Wrappable* owner, const CallbackHolderType& cb)
+        : callback(owner, cb),
+          message_loop(base::MessageLoopProxy::current()) {}
+
+    // Notifies listener. Must be called on the same message loop the
+    // listener registered its callback from.
+    void Notify(GetArgumentCallback on_notify) {
+      DCHECK_EQ(base::MessageLoopProxy::current(), message_loop);
+      CallbackArgType arg = on_notify.Run();
+      callback.value().Run(arg);
+    }
+
+    typename CallbackHolderType::Reference callback;
+    scoped_refptr<base::MessageLoopProxy> message_loop;
+  };
+
+  explicit H5vccEventListenerContainer(script::Wrappable* owner)
+      : owner_(owner) {}
+
+  ~H5vccEventListenerContainer() {
+    // Delete all registered listeners.
+    for (typename ListenerVector::const_iterator it = listeners_.begin();
+         it != listeners_.end(); ++it) {
+      delete *it;
+    }
+  }
+
+  // Called from JavaScript to register an event listener. May be called from
+  // any thread, and event notification will be called on the same thread.
+  void AddListener(const CallbackHolderType& callback_holder) {
+    base::AutoLock auto_lock(lock_);
+    listeners_.push_back(new Listener(owner_, callback_holder));
+  }
+
+  // Dispatches an event to the registered listeners. May be called from any
+  // thread, and the callbacks will be invoked on the same thread each listener
+  // was registered on. |get_argument_callback| must be a function that
+  // returns the argument value for this event.
+  void DispatchEvent(GetArgumentCallback get_argument_callback) {
+    base::AutoLock auto_lock(lock_);
+    for (typename ListenerVector::iterator it = listeners_.begin();
+         it != listeners_.end(); ++it) {
+      Listener* listener = *it;
+      listener->message_loop->PostTask(
+          FROM_HERE, base::Bind(&Listener::Notify, base::Unretained(listener),
+                                get_argument_callback));
+    }
+  }
+
+ private:
+  typedef std::vector<Listener*> ListenerVector;
+
+  script::Wrappable* owner_;
+  ListenerVector listeners_;
+  base::Lock lock_;
+};
+
+// Explicit template specialization for the no callback argument case, where
+// we don't need to call the |GetArgumentCallback| callback.
+template <>
+inline void
+    H5vccEventListenerContainer<void, script::CallbackFunction<void()> >::
+        Listener::Notify(GetArgumentCallback) {
+  DCHECK_EQ(base::MessageLoopProxy::current(), message_loop);
+  callback.value().Run();
+}
+
+}  // namespace h5vcc
+}  // namespace cobalt
+
+#endif  // COBALT_H5VCC_H5VCC_EVENT_LISTENER_CONTAINER_H_
diff --git a/src/cobalt/h5vcc/h5vcc_runtime.cc b/src/cobalt/h5vcc/h5vcc_runtime.cc
index 09acb30..8b0f871 100644
--- a/src/cobalt/h5vcc/h5vcc_runtime.cc
+++ b/src/cobalt/h5vcc/h5vcc_runtime.cc
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
+#include "cobalt/base/deep_link_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/h5vcc/h5vcc_runtime.h"
 #include "cobalt/system_window/application_event.h"
 
 namespace cobalt {
 namespace h5vcc {
-H5vccRuntime::H5vccRuntime(base::EventDispatcher* event_dispatcher)
+H5vccRuntime::H5vccRuntime(base::EventDispatcher* event_dispatcher,
+                           const std::string& initial_deep_link)
     : event_dispatcher_(event_dispatcher) {
+  initial_deep_link_ = initial_deep_link;
+  on_deep_link_ = new H5vccDeepLinkEventTarget;
   on_pause_ = new H5vccRuntimeEventTarget;
   on_resume_ = new H5vccRuntimeEventTarget;
 
@@ -30,11 +34,26 @@
       base::Bind(&H5vccRuntime::OnApplicationEvent, base::Unretained(this));
   event_dispatcher_->AddEventCallback(system_window::ApplicationEvent::TypeId(),
                                       application_event_callback_);
+  deep_link_event_callback_ =
+      base::Bind(&H5vccRuntime::OnDeepLinkEvent, base::Unretained(this));
+  event_dispatcher_->AddEventCallback(base::DeepLinkEvent::TypeId(),
+                                      deep_link_event_callback_);
 }
 
 H5vccRuntime::~H5vccRuntime() {
   event_dispatcher_->RemoveEventCallback(
       system_window::ApplicationEvent::TypeId(), application_event_callback_);
+  event_dispatcher_->RemoveEventCallback(base::DeepLinkEvent::TypeId(),
+                                         deep_link_event_callback_);
+}
+
+const std::string& H5vccRuntime::initial_deep_link() const {
+  return initial_deep_link_;
+}
+
+const scoped_refptr<H5vccDeepLinkEventTarget>& H5vccRuntime::on_deep_link()
+    const {
+  return on_deep_link_;
 }
 
 const scoped_refptr<H5vccRuntimeEventTarget>& H5vccRuntime::on_pause() const {
@@ -56,5 +75,12 @@
     on_resume()->DispatchEvent();
   }
 }
+
+void H5vccRuntime::OnDeepLinkEvent(const base::Event* event) {
+  const base::DeepLinkEvent* deep_link_event =
+      base::polymorphic_downcast<const base::DeepLinkEvent*>(event);
+  DLOG(INFO) << "Got deep link event: " << deep_link_event->link();
+  on_deep_link()->DispatchEvent(deep_link_event->link());
+}
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/src/cobalt/h5vcc/h5vcc_runtime.h b/src/cobalt/h5vcc/h5vcc_runtime.h
index 8c14def..e51e87b 100644
--- a/src/cobalt/h5vcc/h5vcc_runtime.h
+++ b/src/cobalt/h5vcc/h5vcc_runtime.h
@@ -17,7 +17,10 @@
 #ifndef COBALT_H5VCC_H5VCC_RUNTIME_H_
 #define COBALT_H5VCC_H5VCC_RUNTIME_H_
 
+#include <string>
+
 #include "cobalt/base/event_dispatcher.h"
+#include "cobalt/h5vcc/h5vcc_deep_link_event_target.h"
 #include "cobalt/h5vcc/h5vcc_runtime_event_target.h"
 #include "cobalt/script/wrappable.h"
 
@@ -26,26 +29,33 @@
 
 class H5vccRuntime : public script::Wrappable {
  public:
-  explicit H5vccRuntime(base::EventDispatcher* event_dispatcher);
+  explicit H5vccRuntime(base::EventDispatcher* event_dispatcher,
+                        const std::string& initial_deep_link);
   ~H5vccRuntime();
 
+  const std::string& initial_deep_link() const;
+  const scoped_refptr<H5vccDeepLinkEventTarget>& on_deep_link() const;
   const scoped_refptr<H5vccRuntimeEventTarget>& on_pause() const;
   const scoped_refptr<H5vccRuntimeEventTarget>& on_resume() const;
 
   DEFINE_WRAPPABLE_TYPE(H5vccRuntime);
 
  private:
-  // Called by the event dispatcher to handle an application event.
+  // Called by the event dispatcher to handle various event types.
   void OnApplicationEvent(const base::Event* event);
+  void OnDeepLinkEvent(const base::Event* event);
 
+  std::string initial_deep_link_;
+  scoped_refptr<H5vccDeepLinkEventTarget> on_deep_link_;
   scoped_refptr<H5vccRuntimeEventTarget> on_pause_;
   scoped_refptr<H5vccRuntimeEventTarget> on_resume_;
 
   // Non-owned reference used to receive application event callbacks.
   base::EventDispatcher* event_dispatcher_;
 
-  // Application event callback.
+  // Event callbacks.
   base::EventCallback application_event_callback_;
+  base::EventCallback deep_link_event_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(H5vccRuntime);
 };
diff --git a/src/cobalt/h5vcc/h5vcc_runtime_event_target.cc b/src/cobalt/h5vcc/h5vcc_runtime_event_target.cc
index 32902e0..4174201 100644
--- a/src/cobalt/h5vcc/h5vcc_runtime_event_target.cc
+++ b/src/cobalt/h5vcc/h5vcc_runtime_event_target.cc
@@ -16,43 +16,23 @@
 
 #include "cobalt/h5vcc/h5vcc_runtime_event_target.h"
 
-#include "base/location.h"
-
 namespace cobalt {
 namespace h5vcc {
 
-H5vccRuntimeEventTarget::H5vccRuntimeEventTarget() {}
+namespace {
+void OnGetArgument() {}
+}  // namespace
 
-H5vccRuntimeEventTarget::~H5vccRuntimeEventTarget() {
-  // Delete all registered listeners.
-  for (ListenerVector::const_iterator it = listeners_.begin();
-       it != listeners_.end(); ++it) {
-    delete *it;
-  }
-}
+H5vccRuntimeEventTarget::H5vccRuntimeEventTarget()
+    : ALLOW_THIS_IN_INITIALIZER_LIST(listeners_(this)) {}
 
 void H5vccRuntimeEventTarget::AddListener(
-    const H5vccRuntimeEventTarget::H5vccRuntimeEventCallbackHolder& callback) {
-  base::AutoLock auto_lock(lock_);
-  listeners_.push_back(new ListenerInfo(this, callback));
+    const H5vccRuntimeEventCallbackHolder& callback_holder) {
+  listeners_.AddListener(callback_holder);
 }
 
 void H5vccRuntimeEventTarget::DispatchEvent() {
-  base::AutoLock auto_lock(lock_);
-
-  for (ListenerVector::const_iterator it = listeners_.begin();
-       it != listeners_.end(); ++it) {
-    const ListenerInfo* listener = *it;
-    listener->message_loop->PostTask(
-        FROM_HERE,
-        base::Bind(&H5vccRuntimeEventTarget::NotifyListener, this, listener));
-  }
-}
-
-void H5vccRuntimeEventTarget::NotifyListener(
-    const H5vccRuntimeEventTarget::ListenerInfo* listener) {
-  DCHECK_EQ(base::MessageLoopProxy::current(), listener->message_loop);
-  listener->callback.value().Run();
+  listeners_.DispatchEvent(base::Bind(OnGetArgument));
 }
 
 }  // namespace h5vcc
diff --git a/src/cobalt/h5vcc/h5vcc_runtime_event_target.h b/src/cobalt/h5vcc/h5vcc_runtime_event_target.h
index 344623e..fc2b491 100644
--- a/src/cobalt/h5vcc/h5vcc_runtime_event_target.h
+++ b/src/cobalt/h5vcc/h5vcc_runtime_event_target.h
@@ -22,6 +22,7 @@
 #include "base/callback.h"
 #include "base/message_loop_proxy.h"
 #include "base/synchronization/lock.h"
+#include "cobalt/h5vcc/h5vcc_event_listener_container.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/script_object.h"
 #include "cobalt/script/wrappable.h"
@@ -40,21 +41,7 @@
   typedef script::ScriptObject<H5vccRuntimeEventCallback>
       H5vccRuntimeEventCallbackHolder;
 
-  // Type for listener info.
-  // We store the message loop from which the listener was registered,
-  // so we can run the callback on the same loop.
-  struct ListenerInfo {
-    ListenerInfo(H5vccRuntimeEventTarget* const pause_event,
-                 const H5vccRuntimeEventCallbackHolder& cb)
-        : callback(pause_event, cb),
-          message_loop(base::MessageLoopProxy::current()) {}
-
-    H5vccRuntimeEventCallbackHolder::Reference callback;
-    scoped_refptr<base::MessageLoopProxy> message_loop;
-  };
-
   H5vccRuntimeEventTarget();
-  ~H5vccRuntimeEventTarget();
 
   // Called from JavaScript to register an event listener callback.
   // May be called from any thread.
@@ -67,14 +54,7 @@
   DEFINE_WRAPPABLE_TYPE(H5vccRuntimeEventTarget);
 
  private:
-  typedef std::vector<ListenerInfo*> ListenerVector;
-
-  // Notifies a particular listener. Must be called on the same message loop the
-  // listener registered its callback from.
-  void NotifyListener(const ListenerInfo* listener);
-
-  ListenerVector listeners_;
-  base::Lock lock_;
+  H5vccEventListenerContainer<void, H5vccRuntimeEventCallback> listeners_;
 
   DISALLOW_COPY_AND_ASSIGN(H5vccRuntimeEventTarget);
 };
diff --git a/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-positioning-area-is-smaller-than-image-size.html b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-positioning-area-is-smaller-than-image-size.html
index 53de4b8..d727d74 100644
--- a/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-positioning-area-is-smaller-than-image-size.html
+++ b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-positioning-area-is-smaller-than-image-size.html
@@ -20,7 +20,7 @@
     }
 
     var image = new Image();
-    var image_name = 'legend-sprite-ps3.png';
+    var image_name = 'legend-sprite.png';
 
     image.onload = function() {
       var cobalt = document.getElementById('image');
diff --git a/src/cobalt/layout_tests/testdata/css3-background/legend-sprite.png b/src/cobalt/layout_tests/testdata/css3-background/legend-sprite.png
new file mode 100644
index 0000000..f566171
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/legend-sprite.png
Binary files differ
diff --git a/src/cobalt/network/cookie_jar_impl.cc b/src/cobalt/network/cookie_jar_impl.cc
index 5ad5205..6e573de 100644
--- a/src/cobalt/network/cookie_jar_impl.cc
+++ b/src/cobalt/network/cookie_jar_impl.cc
@@ -18,7 +18,6 @@
 
 #include "base/bind.h"
 #include "base/synchronization/waitable_event.h"
-#include "base/threading/thread.h"
 
 namespace cobalt {
 namespace network {
@@ -27,10 +26,10 @@
 
 class CookiesGetter {
  public:
-  CookiesGetter(const GURL& origin, net::CookieStore* cookie_store)
-      : getter_thread_("CookiesGetter"), event_(true, false) {
-    getter_thread_.Start();
-    getter_thread_.message_loop()->PostTask(
+  CookiesGetter(const GURL& origin, net::CookieStore* cookie_store,
+                MessageLoop* get_cookies_message_loop)
+      : event_(true, false) {
+    get_cookies_message_loop->PostTask(
         FROM_HERE, base::Bind(&net::CookieStore::GetCookiesWithOptionsAsync,
                               cookie_store, origin, net::CookieOptions(),
                               base::Bind(&CookiesGetter::CompletionCallback,
@@ -49,19 +48,20 @@
   }
 
   std::string cookies_;
-  base::Thread getter_thread_;
   base::WaitableEvent event_;
 };
 
 }  // namespace
 
 CookieJarImpl::CookieJarImpl(net::CookieStore* cookie_store)
-    : cookie_store_(cookie_store) {
+    : get_cookies_thread_("CookiesGetter"), cookie_store_(cookie_store) {
   DCHECK(cookie_store_);
+  get_cookies_thread_.Start();
 }
 
 std::string CookieJarImpl::GetCookies(const GURL& origin) {
-  CookiesGetter cookies_getter(origin, cookie_store_);
+  CookiesGetter cookies_getter(
+      origin, cookie_store_, get_cookies_thread_.message_loop());
   return cookies_getter.WaitForCookies();
 }
 
diff --git a/src/cobalt/network/cookie_jar_impl.h b/src/cobalt/network/cookie_jar_impl.h
index 9d68e39..6c87a3f 100644
--- a/src/cobalt/network/cookie_jar_impl.h
+++ b/src/cobalt/network/cookie_jar_impl.h
@@ -20,6 +20,7 @@
 #include <string>
 
 #include "base/compiler_specific.h"
+#include "base/threading/thread.h"
 #include "cobalt/network_bridge/cookie_jar.h"
 #include "net/cookies/cookie_store.h"
 
@@ -34,6 +35,11 @@
   void SetCookie(const GURL& origin, const std::string& cookie_line) OVERRIDE;
 
  private:
+  // We use a dedicated thread for making GetCookiesWithOptionsAsync() calls in
+  // order to workaround GetCookiesWithOptionsAsync()'s default behavior of
+  // PostTask()ing the completion callback to the message loop that the
+  // GetCookiesWithOptionsAsync() call was made from.
+  base::Thread get_cookies_thread_;
   net::CookieStore* cookie_store_;
 
   DISALLOW_COPY_AND_ASSIGN(CookieJarImpl);
diff --git a/src/cobalt/renderer/backend/egl/texture_data_pbo.cc b/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
index edc0e40..86fc108 100644
--- a/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
+++ b/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
@@ -32,7 +32,7 @@
 TextureDataPBO::TextureDataPBO(ResourceContext* resource_context,
                                const math::Size& size, GLenum format)
     : resource_context_(resource_context), size_(size), format_(format) {
-  data_size_ = GetPitchInBytes() * size_.height();
+  data_size_ = static_cast<int64>(GetPitchInBytes()) * size_.height();
 
   resource_context_->RunSynchronouslyWithinResourceContext(
       base::Bind(&TextureDataPBO::InitAndMapPBO, base::Unretained(this)));
@@ -57,7 +57,7 @@
       GL_PIXEL_UNPACK_BUFFER, 0, data_size_, GL_MAP_WRITE_BIT |
                                                  GL_MAP_INVALIDATE_BUFFER_BIT |
                                                  GL_MAP_UNSYNCHRONIZED_BIT));
-  DCHECK(mapped_data_);
+  CHECK(mapped_data_);
   DCHECK_EQ(GL_NO_ERROR, glGetError());
   GL_CALL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
 }
diff --git a/src/cobalt/renderer/backend/egl/texture_data_pbo.h b/src/cobalt/renderer/backend/egl/texture_data_pbo.h
index 56754ea..d99ca54 100644
--- a/src/cobalt/renderer/backend/egl/texture_data_pbo.h
+++ b/src/cobalt/renderer/backend/egl/texture_data_pbo.h
@@ -63,7 +63,7 @@
   math::Size size_;
   GLenum format_;
   GLuint pixel_buffer_;
-  int data_size_;
+  int64 data_size_;
   GLubyte* mapped_data_;
 };
 
diff --git a/src/cobalt/renderer/sandbox/renderer_sandbox_main.cc b/src/cobalt/renderer/sandbox/renderer_sandbox_main.cc
index 3fddb14..60e72e6 100644
--- a/src/cobalt/renderer/sandbox/renderer_sandbox_main.cc
+++ b/src/cobalt/renderer/sandbox/renderer_sandbox_main.cc
@@ -80,7 +80,7 @@
 
 RendererSandbox* g_renderer_sandbox = NULL;
 
-void StartApplication(int /*argc*/, char** /*argv*/,
+void StartApplication(int /*argc*/, char** /*argv*/, const char* /*link*/,
                       const base::Closure& quit_closure) {
   DCHECK(!g_renderer_sandbox);
   g_renderer_sandbox = new RendererSandbox();
diff --git a/src/cobalt/script/exception_message.cc b/src/cobalt/script/exception_message.cc
index 73ac903..939b6b1 100644
--- a/src/cobalt/script/exception_message.cc
+++ b/src/cobalt/script/exception_message.cc
@@ -40,7 +40,7 @@
     {kConvertToEnumFailed, kTypeError, "Failed to convert JS value to enum."},
     {kStringifierProblem, kTypeError, "Stringifier problem."},
     {kNotFunctionValue, kTypeError, "Value is not a function."},
-    {kInvalidNumberOfArguments, kRangeError, "Invalid number of arguments."},
+    {kInvalidNumberOfArguments, kTypeError, "Invalid number of arguments."},
     {kNotUnionType, kTypeError, "Value is not a member of the union type."},
     {kOutsideBounds, kRangeError, "Offset is outside the object's bounds."},
     {kInvalidLength, kRangeError, "Invalid length."},
diff --git a/src/cobalt/script/mozjs/mozjs_engine.cc b/src/cobalt/script/mozjs/mozjs_engine.cc
index b4d16ec..1b79cf7 100644
--- a/src/cobalt/script/mozjs/mozjs_engine.cc
+++ b/src/cobalt/script/mozjs/mozjs_engine.cc
@@ -28,8 +28,18 @@
 namespace {
 // After this many bytes have been allocated, the garbage collector will run.
 const uint32_t kGarbageCollectionThresholdBytes = 8 * 1024 * 1024;
+
+JSBool CheckAccessStub(JSContext*, JS::Handle<JSObject*>, JS::Handle<jsid>,
+                       JSAccessMode, JS::MutableHandle<JS::Value>) {
+  return true;
 }
 
+JSSecurityCallbacks security_callbacks = {
+  CheckAccessStub,
+  MozjsGlobalEnvironment::CheckEval
+};
+}  // namespace
+
 MozjsEngine::MozjsEngine() {
   // TODO: Investigate the benefit of helper threads and things like
   // parallel compilation.
@@ -39,6 +49,8 @@
 
   JS_SetRuntimePrivate(runtime_, this);
 
+  JS_SetSecurityCallbacks(runtime_, &security_callbacks);
+
   // Use incremental garbage collection.
   JS_SetGCParameter(runtime_, JSGC_MODE, JSGC_MODE_INCREMENTAL);
 
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.cc b/src/cobalt/script/mozjs/mozjs_global_environment.cc
index bf2648c..25fa34b 100644
--- a/src/cobalt/script/mozjs/mozjs_global_environment.cc
+++ b/src/cobalt/script/mozjs/mozjs_global_environment.cc
@@ -227,8 +227,10 @@
       MozjsExceptionState exception_state(context_);
       FromJSValue(context_, result_value, kNoConversionFlags, &exception_state,
                   out_result_utf8);
-    } else {
+    } else if (last_error_message_) {
       *out_result_utf8 = *last_error_message_;
+    } else {
+      DLOG(ERROR) << "Script execution failed.";
     }
   }
   JS_RestoreExceptionState(context_, previous_exception_state);
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.h b/src/cobalt/script/mozjs/mozjs_global_environment.h
index a774cb2..54f75de 100644
--- a/src/cobalt/script/mozjs/mozjs_global_environment.h
+++ b/src/cobalt/script/mozjs/mozjs_global_environment.h
@@ -128,17 +128,17 @@
 
   static MozjsGlobalEnvironment* GetFromContext(JSContext* context);
 
+  // This will be called every time an attempt is made to use eval() and
+  // friends. If it returns false, then the ReportErrorHandler will be fired
+  // with an error that eval() is disabled.
+  static JSBool CheckEval(JSContext* context);
+
  protected:
   static void ReportErrorHandler(JSContext* context, const char* message,
                                  JSErrorReport* report);
 
   static void TraceFunction(JSTracer* trace, void* data);
 
-  // This will be called every time an attempt is made to use eval() and
-  // friends. If it returns false, then the ReportErrorHandler will be fired
-  // with an error that eval() is disabled.
-  static JSBool CheckEval(JSContext* context);
-
   // Helper struct to ensure the context is destroyed in the correct order
   // relative to the MozjsGlobalEnvironment's other members.
   struct ContextDestructor {
diff --git a/src/cobalt/storage/savegame_thread.cc b/src/cobalt/storage/savegame_thread.cc
index 423e52f..a36cf48 100644
--- a/src/cobalt/storage/savegame_thread.cc
+++ b/src/cobalt/storage/savegame_thread.cc
@@ -26,7 +26,8 @@
 SavegameThread::SavegameThread(const Savegame::Options& options)
     : options_(options),
       initialized_(true /* manual reset */, false /* initially signalled */),
-      thread_(new base::Thread("Savegame I/O")) {
+      thread_(new base::Thread("Savegame I/O")),
+      num_consecutive_flush_failures_(0) {
   TRACE_EVENT0("cobalt::storage", __FUNCTION__);
   thread_->Start();
   thread_->message_loop()->PostTask(
@@ -88,7 +89,15 @@
   DCHECK_EQ(thread_->message_loop(), MessageLoop::current());
   if (raw_bytes_ptr->size() > 0) {
     bool ret = savegame_->Write(*raw_bytes_ptr);
-    DCHECK(ret);
+    if (ret) {
+      num_consecutive_flush_failures_ = 0;
+    } else {
+      DLOG(ERROR) << "Save failed.";
+      const int kMaxConsecutiveFlushFailures = 2;
+      DCHECK_LT(++num_consecutive_flush_failures_,
+                kMaxConsecutiveFlushFailures);
+      return;
+    }
   }
 
   if (!on_flush_complete.is_null()) {
diff --git a/src/cobalt/storage/savegame_thread.h b/src/cobalt/storage/savegame_thread.h
index 1f9b90f..1ffbd77 100644
--- a/src/cobalt/storage/savegame_thread.h
+++ b/src/cobalt/storage/savegame_thread.h
@@ -52,7 +52,7 @@
   // I/O thread.
   void ShutdownOnIOThread();
 
-  // runs on the I/O thread to write the database to the savegame's persistent
+  // Runs on the I/O thread to write the database to the savegame's persistent
   // storage.
   void FlushOnIOThread(scoped_ptr<Savegame::ByteVector> raw_bytes_ptr,
                        const base::Closure& on_flush_complete);
@@ -74,6 +74,11 @@
 
   // Interface to platform-specific savegame data.
   scoped_ptr<Savegame> savegame_;
+
+  // How many flush failures have occurred since the last successful flush.
+  // Flushes (storage writes) may sometimes fail, but we want to make sure
+  // they're not consistently failing.
+  int num_consecutive_flush_failures_;
 };
 
 }  // namespace storage
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 28e8c13..7c390aa 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -17,6 +17,6 @@
 #define COBALT_VERSION_H_
 
 // Cobalt release number.
-#define COBALT_VERSION "2"
+#define COBALT_VERSION "3"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder.cc b/src/net/tools/dump_cache/url_to_filename_encoder.cc
new file mode 100644
index 0000000..e928ee9
--- /dev/null
+++ b/src/net/tools/dump_cache/url_to_filename_encoder.cc
@@ -0,0 +1,292 @@
+// Copyright (c) 2011 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 <stdlib.h>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/base/net_util.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+using std::string;
+
+namespace {
+
+// Returns 1 if buf is prefixed by "num_digits" of hex digits
+// Teturns 0 otherwise.
+// The function checks for '\0' for string termination.
+int HexDigitsPrefix(const char* buf, int num_digits) {
+  for (int i = 0; i < num_digits; i++) {
+    if (!IsHexDigit(buf[i]))
+      return 0;  // This also detects end of string as '\0' is not xdigit.
+  }
+  return 1;
+}
+
+#if defined(WIN32) || defined(__LB_XB1__) || defined(__LB_XB360__)
+#define strtoull _strtoui64
+#endif
+
+// A simple parser for long long values. Returns the parsed value if a
+// valid integer is found; else returns deflt
+// UInt64 and Int64 cannot handle decimal numbers with leading 0s.
+uint64 ParseLeadingHex64Value(const char *str, uint64 deflt) {
+  char *error = NULL;
+  const uint64 value = strtoull(str, &error, 16);
+  return (error == str) ? deflt : value;
+}
+
+}
+
+namespace net {
+
+// The escape character choice is made here -- all code and tests in this
+// directory are based off of this constant.  However, our testdata
+// has tons of dependencies on this, so it cannot be changed without
+// re-running those tests and fixing them.
+const char UrlToFilenameEncoder::kEscapeChar = ',';
+const char UrlToFilenameEncoder::kTruncationChar = '-';
+const size_t UrlToFilenameEncoder::kMaximumSubdirectoryLength = 128;
+
+void UrlToFilenameEncoder::AppendSegment(string* segment, string* dest) {
+  CHECK(!segment->empty());
+  if ((*segment == ".") || (*segment == "..")) {
+    dest->append(1, kEscapeChar);
+    dest->append(*segment);
+    segment->clear();
+  } else {
+    size_t segment_size = segment->size();
+    if (segment_size > kMaximumSubdirectoryLength) {
+      // We need to inject ",-" at the end of the segment to signify that
+      // we are inserting an artificial '/'.  This means we have to chop
+      // off at least two characters to make room.
+      segment_size = kMaximumSubdirectoryLength - 2;
+
+      // But we don't want to break up an escape sequence that happens to lie at
+      // the end.  Escape sequences are at most 2 characters.
+      if ((*segment)[segment_size - 1] == kEscapeChar) {
+        segment_size -= 1;
+      } else if ((*segment)[segment_size - 2] == kEscapeChar) {
+        segment_size -= 2;
+      }
+      dest->append(segment->data(), segment_size);
+      dest->append(1, kEscapeChar);
+      dest->append(1, kTruncationChar);
+      segment->erase(0, segment_size);
+
+      // At this point, if we had segment_size=3, and segment="abcd",
+      // then after this erase, we will have written "abc,-" and set segment="d"
+    } else {
+      dest->append(*segment);
+      segment->clear();
+    }
+  }
+}
+
+void UrlToFilenameEncoder::EncodeSegment(const string& filename_prefix,
+                                         const string& escaped_ending,
+                                         char dir_separator,
+                                         string* encoded_filename) {
+  string filename_ending = UrlUtilities::Unescape(escaped_ending);
+
+  char encoded[3];
+  int encoded_len;
+  string segment;
+
+  // TODO(jmarantz): This code would be a bit simpler if we disallowed
+  // Instaweb allowing filename_prefix to not end in "/".  We could
+  // then change the is routine to just take one input string.
+  size_t start_of_segment = filename_prefix.find_last_of(dir_separator);
+  if (start_of_segment == string::npos) {
+    segment = filename_prefix;
+  } else {
+    segment = filename_prefix.substr(start_of_segment + 1);
+    *encoded_filename = filename_prefix.substr(0, start_of_segment + 1);
+  }
+
+  size_t index = 0;
+  // Special case the first / to avoid adding a leading kEscapeChar.
+  if (!filename_ending.empty() && (filename_ending[0] == dir_separator)) {
+    encoded_filename->append(segment);
+    segment.clear();
+    encoded_filename->append(1, dir_separator);
+    ++index;
+  }
+
+  for (; index < filename_ending.length(); ++index) {
+    unsigned char ch = static_cast<unsigned char>(filename_ending[index]);
+
+    // Note: instead of outputing an empty segment, we let the second slash
+    // be escaped below.
+    if ((ch == dir_separator) && !segment.empty()) {
+      AppendSegment(&segment, encoded_filename);
+      encoded_filename->append(1, dir_separator);
+      segment.clear();
+    } else {
+      // After removing unsafe chars the only safe ones are _.=+- and alphanums.
+      if ((ch == '_') || (ch == '.') || (ch == '=') || (ch == '+') ||
+          (ch == '-') || (('0' <= ch) && (ch <= '9')) ||
+          (('A' <= ch) && (ch <= 'Z')) || (('a' <= ch) && (ch <= 'z'))) {
+        encoded[0] = ch;
+        encoded_len = 1;
+      } else {
+        encoded[0] = kEscapeChar;
+        encoded[1] = ch / 16;
+        encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+        encoded[2] = ch % 16;
+        encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+        encoded_len = 3;
+      }
+      segment.append(encoded, encoded_len);
+
+      // If segment is too big, we must chop it into chunks.
+      if (segment.size() > kMaximumSubdirectoryLength) {
+        AppendSegment(&segment, encoded_filename);
+        encoded_filename->append(1, dir_separator);
+      }
+    }
+  }
+
+  // Append "," to the leaf filename so the leaf can also be a branch., e.g.
+  // allow http://a/b/c and http://a/b/c/d to co-exist as files "/a/b/c," and
+  // /a/b/c/d".  So we will rename the "d" here to "d,".  If doing that pushed
+  // us over the 128 char limit, then we will need to append "/" and the
+  // remaining chars.
+  segment += kEscapeChar;
+  AppendSegment(&segment, encoded_filename);
+  if (!segment.empty()) {
+    // The last overflow segment is special, because we appended in
+    // kEscapeChar above.  We won't need to check it again for size
+    // or further escaping.
+    encoded_filename->append(1, dir_separator);
+    encoded_filename->append(segment);
+  }
+}
+
+// Note: this decoder is not the exact inverse of the EncodeSegment above,
+// because it does not take into account a prefix.
+bool UrlToFilenameEncoder::Decode(const string& encoded_filename,
+                                  char dir_separator,
+                                  string* decoded_url) {
+  enum State {
+    kStart,
+    kEscape,
+    kFirstDigit,
+    kTruncate,
+    kEscapeDot
+  };
+  State state = kStart;
+  char hex_buffer[3];
+  hex_buffer[2] = '\0';
+  for (size_t i = 0; i < encoded_filename.size(); ++i) {
+    char ch = encoded_filename[i];
+    switch (state) {
+      case kStart:
+        if (ch == kEscapeChar) {
+          state = kEscape;
+        } else if (ch == dir_separator) {
+          decoded_url->append(1, '/');  // URLs only use '/' not '\\'
+        } else {
+          decoded_url->append(1, ch);
+        }
+        break;
+      case kEscape:
+        if (HexDigitsPrefix(&ch, 1) == 1) {
+          hex_buffer[0] = ch;
+          state = kFirstDigit;
+        } else if (ch == kTruncationChar) {
+          state = kTruncate;
+        } else if (ch == '.') {
+          decoded_url->append(1, '.');
+          state = kEscapeDot;  // Look for at most one more dot.
+        } else if (ch == dir_separator) {
+          // Consider url "//x".  This was once encoded to "/,/x,".
+          // This code is what skips the first Escape.
+          decoded_url->append(1, '/');  // URLs only use '/' not '\\'
+          state = kStart;
+        } else {
+          return false;
+        }
+        break;
+      case kFirstDigit:
+        if (HexDigitsPrefix(&ch, 1) == 1) {
+          hex_buffer[1] = ch;
+          uint64 hex_value = ParseLeadingHex64Value(hex_buffer, 0);
+          decoded_url->append(1, static_cast<char>(hex_value));
+          state = kStart;
+        } else {
+          return false;
+        }
+        break;
+      case kTruncate:
+        if (ch == dir_separator) {
+          // Skip this separator, it was only put in to break up long
+          // path segments, but is not part of the URL.
+          state = kStart;
+        } else {
+          return false;
+        }
+        break;
+      case kEscapeDot:
+        decoded_url->append(1, ch);
+        state = kStart;
+        break;
+    }
+  }
+
+  // All legal encoded filenames end in kEscapeChar.
+  return (state == kEscape);
+}
+
+// Escape the given input |path| and chop any individual components
+// of the path which are greater than kMaximumSubdirectoryLength characters
+// into two chunks.
+//
+// This legacy version has several issues with aliasing of different URLs,
+// inability to represent both /a/b/c and /a/b/c/d, and inability to decode
+// the filenames back into URLs.
+//
+// But there is a large body of slurped data which depends on this format,
+// so leave it as the default for spdy_in_mem_edsm_server.
+string UrlToFilenameEncoder::LegacyEscape(const string& path) {
+  string output;
+
+  // Note:  We also chop paths into medium sized 'chunks'.
+  //        This is due to the incompetence of the windows
+  //        filesystem, which still hasn't figured out how
+  //        to deal with long filenames.
+  int last_slash = 0;
+  for (size_t index = 0; index < path.length(); index++) {
+    char ch = path[index];
+    if (ch == 0x5C)
+      last_slash = index;
+    if ((ch == 0x2D) ||                    // hyphen
+        (ch == 0x5C) || (ch == 0x5F) ||    // backslash, underscore
+        ((0x30 <= ch) && (ch <= 0x39)) ||  // Digits [0-9]
+        ((0x41 <= ch) && (ch <= 0x5A)) ||  // Uppercase [A-Z]
+        ((0x61 <= ch) && (ch <= 0x7A))) {  // Lowercase [a-z]
+      output.append(&path[index], 1);
+    } else {
+      char encoded[3];
+      encoded[0] = 'x';
+      encoded[1] = ch / 16;
+      encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+      encoded[2] = ch % 16;
+      encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+      output.append(encoded, 3);
+    }
+    if (index - last_slash > kMaximumSubdirectoryLength) {
+#ifdef WIN32
+      char slash = '\\';
+#else
+      char slash = '/';
+#endif
+      output.append(&slash, 1);
+      last_slash = index;
+    }
+  }
+  return output;
+}
+
+}  // namespace net
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder.h b/src/net/tools/dump_cache/url_to_filename_encoder.h
new file mode 100644
index 0000000..b81a854
--- /dev/null
+++ b/src/net/tools/dump_cache/url_to_filename_encoder.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2010 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.
+
+// URL filename encoder goals:
+//
+// 1. Allow URLs with arbitrary path-segment length, generating filenames
+//    with a maximum of 128 characters.
+// 2. Provide a somewhat human readable filenames, for easy debugging flow.
+// 3. Provide reverse-mapping from filenames back to URLs.
+// 4. Be able to distinguish http://x from http://x/ from http://x/index.html.
+//    Those can all be different URLs.
+// 5. Be able to represent http://a/b/c and http://a/b/c/d, a pattern seen
+//    with Facebook Connect.
+//
+// We need an escape-character for representing characters that are legal
+// in URL paths, but not in filenames, such as '?'.
+//
+// We can pick any legal character as an escape, as long as we escape it too.
+// But as we have a goal of having filenames that humans can correlate with
+// URLs, we should pick one that doesn't show up frequently in URLs. Candidates
+// are ~`!@#$%^&()-=_+{}[],. but we would prefer to avoid characters that are
+// shell escapes or that various build tools use.
+//
+// .#&%-=_+ occur frequently in URLs.
+// <>:"/\|?* are illegal in Windows
+//   See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+// ~`!$^&(){}[]'; are special to Unix shells
+// In addition, build tools do not like ^@#%
+//
+// Josh took a quick look at the frequency of some special characters in
+// Sadeesh's slurped directory from Fall 09 and found the following occurances:
+//
+//   ^   3               build tool doesn't like ^ in testdata filenames
+//   @   10              build tool doesn't like @ in testdata filenames
+//   .   1676            too frequent in URLs
+//   ,   76              THE WINNER
+//   #   0               build tool doesn't like it
+//   &   487             Prefer to avoid shell escapes
+//   %   374             g4 doesn't like it
+//   =   579             very frequent in URLs -- leave unmodified
+//   -   464             very frequent in URLs -- leave unmodified
+//   _   798             very frequent in URLs -- leave unmodified
+//
+//
+// The escaping algorithm is:
+//  1) Escape all unfriendly symbols as ,XX where XX is the hex code.
+//  2) Add a ',' at the end (We do not allow ',' at end of any directory name,
+//     so this assures that e.g. /a and /a/b can coexist in the filesystem).
+//  3) Go through the path segment by segment (where a segment is one directory
+//     or leaf in the path) and
+//     3a) If the segment is empty, escape the second slash. i.e. if it was
+//         www.foo.com//a then we escape the second / like www.foo.com/,2Fa,
+//     3a) If it is "." or ".." prepend with ',' (so that we have a non-
+//         empty and non-reserved filename).
+//     3b) If it is over 128 characters, break it up into smaller segments by
+//         inserting ,-/ (Windows limits paths to 128 chars, other OSes also
+//         have limits that would restrict us)
+//
+// For example:
+//     URL               File
+//     /                 /,
+//     /index.html       /index.html,
+//     /.                /.,
+//     /a/b              /a/b,
+//     /a/b/             /a/b/,
+//     /a/b/c            /a/b/c,   Note: no prefix problem
+//     /u?foo=bar        /u,3Ffoo=bar,
+//     //                /,2F,
+//     /./               /,./,
+//     /../              /,../,
+//     /,                /,2C,
+//     /,./              /,2C./,
+//     /very...longname/ /very...long,-/name   If very...long is about 126 long.
+
+// NOTE: we avoid using some classes here (like FilePath and GURL) because we
+//       share this code with other projects externally.
+
+#ifndef NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
+#define NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
+
+#include <string>
+
+#include "base/string_util.h"
+#include "net/tools/dump_cache/url_utilities.h"
+
+namespace net {
+
+// Helper class for converting a URL into a filename.
+class UrlToFilenameEncoder {
+ public:
+  // Given a |url| and a |base_path|, returns a filename which represents this
+  // |url|. |url| may include URL escaping such as %21 for !
+  // |legacy_escape| indicates that this function should use the old-style
+  // of encoding.
+  // TODO(mbelshe): delete the legacy_escape code.
+  static std::string Encode(const std::string& url, std::string base_path,
+                            bool legacy_escape) {
+    std::string filename;
+    if (!legacy_escape) {
+      std::string url_no_scheme = UrlUtilities::GetUrlHostPath(url);
+      EncodeSegment(base_path, url_no_scheme, '/', &filename);
+#ifdef WIN32
+      ReplaceAll(&filename, "/", "\\");
+#endif
+    } else {
+      std::string clean_url(url);
+      if (clean_url.length() && clean_url[clean_url.length()-1] == '/')
+        clean_url.append("index.html");
+
+      std::string host = UrlUtilities::GetUrlHost(clean_url);
+      filename.append(base_path);
+      filename.append(host);
+#ifdef WIN32
+      filename.append("\\");
+#else
+      filename.append("/");
+#endif
+
+      std::string url_filename = UrlUtilities::GetUrlPath(clean_url);
+      // Strip the leading '/'.
+      if (url_filename[0] == '/')
+        url_filename = url_filename.substr(1);
+
+      // Replace '/' with '\'.
+      ConvertToSlashes(&url_filename);
+
+      // Strip double back-slashes ("\\\\").
+      StripDoubleSlashes(&url_filename);
+
+      // Save path as filesystem-safe characters.
+      url_filename = LegacyEscape(url_filename);
+      filename.append(url_filename);
+
+#ifndef WIN32
+      // Last step - convert to native slashes.
+      const std::string slash("/");
+      const std::string backslash("\\");
+      ReplaceAll(&filename, backslash, slash);
+#endif
+    }
+
+    return filename;
+  }
+
+  // Rewrite HTML in a form that the SPDY in-memory server
+  // can read.
+  // |filename_prefix| is prepended without escaping.
+  // |escaped_ending| is the URL to be encoded into a filename. It may have URL
+  // escaped characters (like %21 for !).
+  // |dir_separator| is "/" on Unix, "\" on Windows.
+  // |encoded_filename| is the resultant filename.
+  static void EncodeSegment(
+      const std::string& filename_prefix,
+      const std::string& escaped_ending,
+      char dir_separator,
+      std::string* encoded_filename);
+
+  // Decodes a filename that was encoded with EncodeSegment,
+  // yielding back the original URL.
+  static bool Decode(const std::string& encoded_filename,
+                     char dir_separator,
+                     std::string* decoded_url);
+
+  static const char kEscapeChar;
+  static const char kTruncationChar;
+  static const size_t kMaximumSubdirectoryLength;
+
+  friend class UrlToFilenameEncoderTest;
+
+ private:
+  // Appends a segment of the path, special-casing "." and "..", and
+  // ensuring that the segment does not exceed the path length.  If it does,
+  // it chops the end off the segment, writes the segment with a separator of
+  // ",-/", and then rewrites segment to contain just the truncated piece so
+  // it can be used in the next iteration.
+  // |segment| is a read/write parameter containing segment to write
+  // Note: this should not be called with empty segment.
+  static void AppendSegment(std::string* segment, std::string* dest);
+
+  // Allow reading of old slurped files.
+  static std::string LegacyEscape(const std::string& path);
+
+  // Replace all instances of |from| within |str| as |to|.
+  static void ReplaceAll(std::string* str, const std::string& from,
+                         const std::string& to) {
+    std::string::size_type pos(0);
+    while ((pos = str->find(from, pos)) != std::string::npos) {
+      str->replace(pos, from.size(), to);
+      pos += from.size();
+    }
+  }
+
+  // Replace all instances of "/" with "\" in |path|.
+  static void ConvertToSlashes(std::string* path) {
+    const std::string slash("/");
+    const std::string backslash("\\");
+    ReplaceAll(path, slash, backslash);
+  }
+
+  // Replace all instances of "\\" with "%5C%5C" in |path|.
+  static void StripDoubleSlashes(std::string* path) {
+    const std::string doubleslash("\\\\");
+    const std::string escaped_doubleslash("%5C%5C");
+    ReplaceAll(path, doubleslash, escaped_doubleslash);
+  }
+};
+
+}  // namespace net
+
+#endif  // NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc b/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
new file mode 100644
index 0000000..2e09e0b
--- /dev/null
+++ b/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2010 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 "net/tools/dump_cache/url_to_filename_encoder.h"
+
+#include <string>
+#include <vector>
+
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+#ifdef WIN32
+char kDirSeparator = '\\';
+char kOtherDirSeparator = '/';
+#else
+char kDirSeparator = '/';
+char kOtherDirSeparator = '\\';
+#endif
+
+class UrlToFilenameEncoderTest : public ::testing::Test {
+ protected:
+  UrlToFilenameEncoderTest() : escape_(1, UrlToFilenameEncoder::kEscapeChar),
+                               dir_sep_(1, kDirSeparator) {
+  }
+
+  void CheckSegmentLength(const StringPiece& escaped_word) {
+    std::vector<StringPiece> components;
+    Tokenize(escaped_word, StringPiece("/"), &components);
+    for (size_t i = 0; i < components.size(); ++i) {
+      EXPECT_GE(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+                components[i].size());
+    }
+  }
+
+  void CheckValidChars(const StringPiece& escaped_word, char invalid_slash) {
+    // These characters are invalid in Windows.  We add in ', as that's pretty
+    // inconvenient in a Unix filename.
+    //
+    // See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+    const string kInvalidChars = "<>:\"|?*'";
+    for (size_t i = 0; i < escaped_word.size(); ++i) {
+      char c = escaped_word[i];
+      EXPECT_EQ(string::npos, kInvalidChars.find(c));
+      EXPECT_NE(invalid_slash, c);
+      EXPECT_NE('\0', c);  // only invalid character in Posix
+      EXPECT_GT(0x7E, c);  // only English printable characters
+    }
+  }
+
+  void Validate(const string& in_word, const string& gold_word) {
+    string escaped_word, url;
+    UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word);
+    EXPECT_EQ(gold_word, escaped_word);
+    CheckSegmentLength(escaped_word);
+    CheckValidChars(escaped_word, '\\');
+    UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+    EXPECT_EQ(in_word, url);
+  }
+
+  void ValidateAllSegmentsSmall(const string& in_word) {
+    string escaped_word, url;
+    UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word);
+    CheckSegmentLength(escaped_word);
+    CheckValidChars(escaped_word, '\\');
+    UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+    EXPECT_EQ(in_word, url);
+  }
+
+  void ValidateNoChange(const string& word) {
+    // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+    Validate(word, word + escape_);
+  }
+
+  void ValidateEscaped(unsigned char ch) {
+    // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+    char escaped[100];
+    const char escape = UrlToFilenameEncoder::kEscapeChar;
+    base::snprintf(escaped, sizeof(escaped), "%c%02X%c", escape, ch, escape);
+    Validate(string(1, ch), escaped);
+  }
+
+  void ValidateUrl(const string& url, const string& base_path,
+                   bool legacy_escape, const string& gold_filename) {
+    string encoded_filename = UrlToFilenameEncoder::Encode(
+        url, base_path, legacy_escape);
+    EXPECT_EQ(gold_filename, encoded_filename);
+    if (!legacy_escape) {
+      CheckSegmentLength(encoded_filename);
+      CheckValidChars(encoded_filename, kOtherDirSeparator);
+      string decoded_url;
+      UrlToFilenameEncoder::Decode(encoded_filename, kDirSeparator,
+                                   &decoded_url);
+      if (url != decoded_url) {
+        EXPECT_EQ(url, "http://" + decoded_url);
+      }
+    }
+  }
+
+  void ValidateUrlOldNew(const string& url, const string& gold_old_filename,
+                         const string& gold_new_filename) {
+    ValidateUrl(url, "", true, gold_old_filename);
+    ValidateUrl(url, "", false, gold_new_filename);
+  }
+
+  void ValidateEncodeSame(const string& url1, const string& url2) {
+    string filename1 = UrlToFilenameEncoder::Encode(url1, "", false);
+    string filename2 = UrlToFilenameEncoder::Encode(url2, "", false);
+    EXPECT_EQ(filename1, filename2);
+  }
+
+  string escape_;
+  string dir_sep_;
+};
+
+TEST_F(UrlToFilenameEncoderTest, DoesNotEscape) {
+  ValidateNoChange("");
+  ValidateNoChange("abcdefg");
+  ValidateNoChange("abcdefghijklmnopqrstuvwxyz");
+  ValidateNoChange("ZYXWVUT");
+  ValidateNoChange("ZYXWVUTSRQPONMLKJIHGFEDCBA");
+  ValidateNoChange("01234567689");
+  ValidateNoChange("_.=+-");
+  ValidateNoChange("abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIHGFEDCBA"
+                   "01234567689_.=+-");
+  ValidateNoChange("index.html");
+  ValidateNoChange("/");
+  ValidateNoChange("/.");
+  ValidateNoChange(".");
+  ValidateNoChange("..");
+}
+
+TEST_F(UrlToFilenameEncoderTest, Escapes) {
+  const string bad_chars =
+      "<>:\"\\|?*"      // Illegal on Windows
+      "~`!$^&(){}[]';"  // Bad for Unix shells
+      "^@"              // Build tool doesn't like
+      "#%"              // Tool doesn't like
+      ",";              // The escape char has to be escaped
+
+  for (size_t i = 0; i < bad_chars.size(); ++i) {
+    ValidateEscaped(bad_chars[i]);
+  }
+
+  // Check non-printable characters.
+  ValidateEscaped('\0');
+  for (size_t i = 127; i < 256; ++i) {
+    ValidateEscaped(static_cast<char>(i));
+  }
+}
+
+TEST_F(UrlToFilenameEncoderTest, DoesEscapeCorrectly) {
+  Validate("mysite.com&x", "mysite.com" + escape_ + "26x" + escape_);
+  Validate("/./", "/" + escape_ + "./" + escape_);
+  Validate("/../", "/" + escape_ + "../" + escape_);
+  Validate("//", "/" + escape_ + "2F" + escape_);
+  Validate("/./leaf", "/" + escape_ + "./leaf" + escape_);
+  Validate("/../leaf", "/" + escape_ + "../leaf" + escape_);
+  Validate("//leaf", "/" + escape_ + "2Fleaf" + escape_);
+  Validate("mysite/u?param1=x&param2=y",
+           "mysite/u" + escape_ + "3Fparam1=x" + escape_ + "26param2=y" +
+           escape_);
+  Validate("search?q=dogs&go=&form=QBLH&qs=n",  // from Latency Labs bing test.
+           "search" + escape_ + "3Fq=dogs" + escape_ + "26go=" + escape_ +
+           "26form=QBLH" + escape_ + "26qs=n" + escape_);
+  Validate("~joebob/my_neeto-website+with_stuff.asp?id=138&content=true",
+           "" + escape_ + "7Ejoebob/my_neeto-website+with_stuff.asp" + escape_ +
+           "3Fid=138" + escape_ + "26content=true" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, EncodeUrlCorrectly) {
+  ValidateUrlOldNew("http://www.google.com/index.html",
+                    "www.google.com" + dir_sep_ + "indexx2Ehtml",
+                    "www.google.com" + dir_sep_ + "index.html" + escape_);
+  ValidateUrlOldNew("http://www.google.com/x/search?hl=en&q=dogs&oq=",
+                    "www.google.com" + dir_sep_ + "x" + dir_sep_ +
+                    "searchx3Fhlx3Denx26qx3Ddogsx26oqx3D",
+
+                    "www.google.com" + dir_sep_ + "x" + dir_sep_ + "search" +
+                    escape_ + "3Fhl=en" + escape_ + "26q=dogs" + escape_ +
+                    "26oq=" + escape_);
+  ValidateUrlOldNew("http://www.foo.com/a//",
+                    "www.foo.com" + dir_sep_ + "ax255Cx255Cindexx2Ehtml",
+                    "www.foo.com" + dir_sep_ + "a" + dir_sep_ + escape_ + "2F" +
+                    escape_);
+
+  // From bug: Double slash preserved.
+  ValidateUrl("http://www.foo.com/u?site=http://www.google.com/index.html",
+              "", false,
+              "www.foo.com" + dir_sep_ + "u" + escape_ + "3Fsite=http" +
+              escape_ + "3A" + dir_sep_ + escape_ + "2Fwww.google.com" +
+              dir_sep_ + "index.html" + escape_);
+  ValidateUrlOldNew(
+      "http://blogutils.net/olct/online.php?"
+      "site=http://thelwordfanfics.blogspot.&interval=600",
+
+      "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "onlinex2Ephpx3F"
+      "sitex3Dhttpx3Ax255Cx255Cthelwordfanficsx2Eblogspotx2Ex26intervalx3D600",
+
+      "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "online.php" + escape_ +
+      "3Fsite=http" + escape_ + "3A" + dir_sep_ + escape_ +
+      "2Fthelwordfanfics.blogspot." + escape_ + "26interval=600" + escape_);
+}
+
+// From bug: Escapes treated the same as normal char.
+TEST_F(UrlToFilenameEncoderTest, UnescapeUrlsBeforeEncode) {
+  for (int i = 0; i < 128; ++i) {
+    string unescaped(1, static_cast<char>(i));
+    string escaped = base::StringPrintf("%%%02X", i);
+    ValidateEncodeSame(unescaped, escaped);
+  }
+
+  ValidateEncodeSame(
+      "http://www.blogger.com/navbar.g?bName=God!&Mode=FOO&searchRoot"
+      "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch",
+
+      "http://www.blogger.com/navbar.g?bName=God%21&Mode=FOO&searchRoot"
+      "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch");
+}
+
+// From bug: Filename encoding is not prefix-free.
+TEST_F(UrlToFilenameEncoderTest, EscapeSecondSlash) {
+  Validate("/", "/" + escape_);
+  Validate("//", "/" + escape_ + "2F" + escape_);
+  Validate("///", "/" + escape_ + "2F" + "/" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTail) {
+  static char long_word[] =
+      "~joebob/briggs/12345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890";
+
+  // the long lines in the string below are 64 characters, so we can see
+  // the slashes every 128.
+  string gold_long_word =
+      escape_ + "7Ejoebob/briggs/"
+      "1234567890123456789012345678901234567890123456789012345678901234"
+      "56789012345678901234567890123456789012345678901234567890123456" +
+      escape_ + "-/"
+      "7890123456789012345678901234567890123456789012345678901234567890"
+      "12345678901234567890123456789012345678901234567890123456789012" +
+      escape_ + "-/"
+      "3456789012345678901234567890123456789012345678901234567890123456"
+      "78901234567890123456789012345678901234567890123456789012345678" +
+      escape_ + "-/"
+      "9012345678901234567890" + escape_;
+  EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+            sizeof(long_word));
+  Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTailQuestion) {
+  // Here the '?' in the last path segment expands to @3F, making
+  // it hit 128 chars before the input segment gets that big.
+  static char long_word[] =
+      "~joebob/briggs/1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?";
+
+  // Notice that at the end of the third segment, we avoid splitting
+  // the (escape_ + "3F") that was generated from the "?", so that segment is
+  // only 127 characters.
+  string pattern = "1234567" + escape_ + "3F";  // 10 characters
+  string gold_long_word =
+      escape_ + "7Ejoebob/briggs/" +
+      pattern + pattern + pattern + pattern + pattern + pattern + "1234"
+      "567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+       "123456" + escape_ + "-/"
+      "7" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+      pattern + pattern + pattern + pattern + pattern + pattern + pattern +
+      "12" +
+      escape_ + "-/"
+      "34567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern
+      + "1234567" + escape_ + "3F" + pattern + pattern + pattern + pattern
+      + pattern + "1234567" +
+      escape_ + "-/" +
+      escape_ + "3F" + pattern + pattern + escape_;
+  EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+            sizeof(long_word));
+  Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenNoEscape) {
+  // hit corner cases, +/- 4 characters from kMaxLen
+  for (int i = -4; i <= 4; ++i) {
+    string input;
+    input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength, 'x');
+    ValidateAllSegmentsSmall(input);
+  }
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenWithEscape) {
+  // hit corner cases, +/- 4 characters from kMaxLen.  This time we
+  // leave off the last 'x' and put in a '.', which ensures that we
+  // are truncating with '/' *after* the expansion.
+  for (int i = -4; i <= 4; ++i) {
+    string input;
+    input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength - 1, 'x');
+    input.append(1, '.');  // this will expand to 3 characters.
+    ValidateAllSegmentsSmall(input);
+  }
+}
+
+TEST_F(UrlToFilenameEncoderTest, LeafBranchAlias) {
+  Validate("/a/b/c", "/a/b/c" + escape_);        // c is leaf file "c,"
+  Validate("/a/b/c/d", "/a/b/c/d" + escape_);    // c is directory "c"
+  Validate("/a/b/c/d/", "/a/b/c/d/" + escape_);
+}
+
+
+TEST_F(UrlToFilenameEncoderTest, BackslashSeparator) {
+  string long_word;
+  string escaped_word;
+  long_word.append(UrlToFilenameEncoder::kMaximumSubdirectoryLength + 1, 'x');
+  UrlToFilenameEncoder::EncodeSegment("", long_word, '\\', &escaped_word);
+
+  // check that one backslash, plus the escape ",-", and the ending , got added.
+  EXPECT_EQ(long_word.size() + 4, escaped_word.size());
+  ASSERT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+            escaped_word.size());
+  // Check that the backslash got inserted at the correct spot.
+  EXPECT_EQ('\\', escaped_word[
+      UrlToFilenameEncoder::kMaximumSubdirectoryLength]);
+}
+
+}  // namespace net
+
diff --git a/src/net/tools/dump_cache/url_utilities.cc b/src/net/tools/dump_cache/url_utilities.cc
new file mode 100644
index 0000000..fe64bd9
--- /dev/null
+++ b/src/net/tools/dump_cache/url_utilities.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2010 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 "net/tools/dump_cache/url_utilities.h"
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+
+namespace net {
+
+std::string UrlUtilities::GetUrlHost(const std::string& url) {
+  size_t b = url.find("//");
+  if (b == std::string::npos)
+    b = 0;
+  else
+    b += 2;
+  size_t next_slash = url.find_first_of('/', b);
+  size_t next_colon = url.find_first_of(':', b);
+  if (next_slash != std::string::npos
+      && next_colon != std::string::npos
+      && next_colon < next_slash) {
+    return std::string(url, b, next_colon - b);
+  }
+  if (next_slash == std::string::npos) {
+    if (next_colon != std::string::npos) {
+      return std::string(url, b, next_colon - b);
+    } else {
+      next_slash = url.size();
+    }
+  }
+  return std::string(url, b, next_slash - b);
+}
+
+std::string UrlUtilities::GetUrlHostPath(const std::string& url) {
+  size_t b = url.find("//");
+  if (b == std::string::npos)
+    b = 0;
+  else
+    b += 2;
+  return std::string(url, b);
+}
+
+std::string UrlUtilities::GetUrlPath(const std::string& url) {
+  size_t b = url.find("//");
+  if (b == std::string::npos)
+    b = 0;
+  else
+    b += 2;
+  b = url.find("/", b);
+  if (b == std::string::npos)
+    return "/";
+
+  size_t e = url.find("#", b+1);
+  if (e != std::string::npos)
+    return std::string(url, b, (e - b));
+  return std::string(url, b);
+}
+
+namespace {
+
+// Parsing states for UrlUtilities::Unescape
+enum UnescapeState {
+  NORMAL,   // We are not in the middle of parsing an escape.
+  ESCAPE1,  // We just parsed % .
+  ESCAPE2   // We just parsed %X for some hex digit X.
+};
+
+}  // namespace
+
+std::string UrlUtilities::Unescape(const std::string& escaped_url) {
+  std::string unescaped_url, escape_text;
+  int escape_value;
+  UnescapeState state = NORMAL;
+  std::string::const_iterator iter = escaped_url.begin();
+  while (iter < escaped_url.end()) {
+    char c = *iter;
+    switch (state) {
+      case NORMAL:
+        if (c == '%') {
+          escape_text.clear();
+          state = ESCAPE1;
+        } else {
+          unescaped_url.push_back(c);
+        }
+        ++iter;
+        break;
+      case ESCAPE1:
+        if (IsHexDigit(c)) {
+          escape_text.push_back(c);
+          state = ESCAPE2;
+          ++iter;
+        } else {
+          // Unexpected, % followed by non-hex chars, pass it through.
+          unescaped_url.push_back('%');
+          state = NORMAL;
+        }
+        break;
+      case ESCAPE2:
+        if (IsHexDigit(c)) {
+          escape_text.push_back(c);
+          bool ok = base::HexStringToInt(escape_text, &escape_value);
+          DCHECK(ok);
+          unescaped_url.push_back(static_cast<unsigned char>(escape_value));
+          state = NORMAL;
+          ++iter;
+        } else {
+          // Unexpected, % followed by non-hex chars, pass it through.
+          unescaped_url.push_back('%');
+          unescaped_url.append(escape_text);
+          state = NORMAL;
+        }
+        break;
+    }
+  }
+  // Unexpected, % followed by end of string, pass it through.
+  if (state == ESCAPE1 || state == ESCAPE2) {
+    unescaped_url.push_back('%');
+    unescaped_url.append(escape_text);
+  }
+  return unescaped_url;
+}
+
+}  // namespace net
+
diff --git a/src/net/tools/dump_cache/url_utilities.h b/src/net/tools/dump_cache/url_utilities.h
new file mode 100644
index 0000000..c9d8ea5
--- /dev/null
+++ b/src/net/tools/dump_cache/url_utilities.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2010 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 NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+#define NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+
+#include <string>
+
+namespace net {
+
+struct UrlUtilities {
+  // Gets the host from an url, strips the port number as well if the url
+  // has one.
+  // For example: calling GetUrlHost(www.foo.com:8080/boo) returns www.foo.com
+  static std::string GetUrlHost(const std::string& url);
+
+  // Get the host + path portion of an url
+  // e.g   http://www.foo.com/path
+  //       returns www.foo.com/path
+  static std::string GetUrlHostPath(const std::string& url);
+
+  // Gets the path portion of an url.
+  // e.g   http://www.foo.com/path
+  //       returns /path
+  static std::string GetUrlPath(const std::string& url);
+
+  // Unescape a url, converting all %XX to the the actual char 0xXX.
+  // For example, this will convert "foo%21bar" to "foo!bar".
+  static std::string Unescape(const std::string& escaped_url);
+};
+
+}  // namespace net
+
+#endif  // NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
diff --git a/src/net/tools/dump_cache/url_utilities_unittest.cc b/src/net/tools/dump_cache/url_utilities_unittest.cc
new file mode 100644
index 0000000..0f9cb06
--- /dev/null
+++ b/src/net/tools/dump_cache/url_utilities_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2010 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 "net/tools/dump_cache/url_utilities.h"
+
+#include <string>
+
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(UrlUtilitiesTest, GetUrlHost) {
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com:80"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com:80/"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com/news"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("www.foo.com:80/news?q=hello"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("www.foo.com/news?q=a:b"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, GetUrlHostPath) {
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com"));
+  EXPECT_EQ("www.foo.com:80",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com:80"));
+  EXPECT_EQ("www.foo.com:80/",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com:80/"));
+  EXPECT_EQ("www.foo.com/news",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com/news"));
+  EXPECT_EQ("www.foo.com:80/news?q=hello",
+            UrlUtilities::GetUrlHostPath("www.foo.com:80/news?q=hello"));
+  EXPECT_EQ("www.foo.com/news?q=a:b",
+            UrlUtilities::GetUrlHostPath("www.foo.com/news?q=a:b"));
+  EXPECT_EQ("www.foo.com:80",
+            UrlUtilities::GetUrlHostPath("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, GetUrlPath) {
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("http://www.foo.com"));
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("http://www.foo.com:80"));
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("http://www.foo.com:80/"));
+  EXPECT_EQ("/news",
+            UrlUtilities::GetUrlPath("http://www.foo.com/news"));
+  EXPECT_EQ("/news?q=hello",
+            UrlUtilities::GetUrlPath("www.foo.com:80/news?q=hello"));
+  EXPECT_EQ("/news?q=a:b",
+            UrlUtilities::GetUrlPath("www.foo.com/news?q=a:b"));
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, Unescape) {
+  // Basic examples are left alone.
+  EXPECT_EQ("http://www.foo.com",
+            UrlUtilities::Unescape("http://www.foo.com"));
+  EXPECT_EQ("www.foo.com:80/news?q=hello",
+            UrlUtilities::Unescape("www.foo.com:80/news?q=hello"));
+
+  // All chars can be unescaped.
+  EXPECT_EQ("~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/",
+            UrlUtilities::Unescape("%7E%60%21%40%23%24%25%5E%26%2A%28%29%5F%2D"
+                                   "%2B%3D%7B%5B%7D%5D%7C%5C%3A%3B%22%27%3C%2C"
+                                   "%3E%2E%3F%2F"));
+  for (int c = 0; c < 256; ++c) {
+    std::string unescaped_char(1, implicit_cast<unsigned char>(c));
+    std::string escaped_char = base::StringPrintf("%%%02X", c);
+    EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char))
+        << "escaped_char = " << escaped_char;
+    escaped_char = base::StringPrintf("%%%02x", c);
+    EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char))
+        << "escaped_char = " << escaped_char;
+  }
+
+  // All non-% chars are left alone.
+  EXPECT_EQ("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/",
+            UrlUtilities::Unescape("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/"));
+  for (int c = 0; c < 256; ++c) {
+    if (c != '%') {
+      std::string just_char(1, implicit_cast<unsigned char>(c));
+      EXPECT_EQ(just_char, UrlUtilities::Unescape(just_char));
+    }
+  }
+
+  // Some examples to unescape.
+  EXPECT_EQ("Hello, world!", UrlUtilities::Unescape("Hello%2C world%21"));
+
+  // Not actually escapes.
+  EXPECT_EQ("%", UrlUtilities::Unescape("%"));
+  EXPECT_EQ("%www", UrlUtilities::Unescape("%www"));
+  EXPECT_EQ("%foo", UrlUtilities::Unescape("%foo"));
+  EXPECT_EQ("%1", UrlUtilities::Unescape("%1"));
+  EXPECT_EQ("%1x", UrlUtilities::Unescape("%1x"));
+  EXPECT_EQ("%%", UrlUtilities::Unescape("%%"));
+  // Escapes following non-escapes.
+  EXPECT_EQ("%!", UrlUtilities::Unescape("%%21"));
+  EXPECT_EQ("%2!", UrlUtilities::Unescape("%2%21"));
+}
+
+}  // namespace net
+
diff --git a/src/starboard/linux/x64directfb/xephyr_run.sh b/src/starboard/linux/x64directfb/xephyr_run.sh
index dc3181c..77bf969 100755
--- a/src/starboard/linux/x64directfb/xephyr_run.sh
+++ b/src/starboard/linux/x64directfb/xephyr_run.sh
@@ -13,44 +13,123 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This script can be used as a convenience to launch an app under a Xephyr
-# X server.  It will first launch Xephyr, and then launch the executable given
-# on the command line such that it targets and will appear within the Xephyr
-# window.  It shuts down Xephyr after the main executable finishes.
+# This script can be used as a convenience to launch an app under a new
+# X server.  Xephyr requires a base X server, so will be launched if there is a
+# DISPLAY environment variable set. Xvfb does not, so it will be launched if
+# there is no DISPLAY variable set, which is likely to come from a remote shell
+# or background process. It will first launch the X server, and then launch the
+# executable given on the command line such that it targets the newly-launched
+# X server. It shuts down the X server after the main executable finishes.
 
-if [[ $# = 0 ]]; then
-  echo
-  echo "$0 will launch a given executable file under a Xephyr X server."
-  echo
-  echo "Usage:"
-  echo "  $0 PATH_TO_EXECUTABLE_FILE  "
-  echo
-  exit 0
+# Standard stuff to get the real script name.
+script_file="$(readlink -f "${BASH_SOURCE[0]}")"
+script_name="$(basename "${script_file}")"
+
+if [[ -n "$DISPLAY" ]]; then
+  # The binary to run to start the X Server.
+  xserver_bin=Xephyr
+  # The command-line args to set the screen dimensions.
+  declare -a xserver_screen=(-screen 1920x1080)
+  # The package to install for this X Server.
+  xserver_package=xserver-xephyr
+else
+  xserver_bin=Xvfb
+  declare -a xserver_screen=(-screen 0 1920x1080x24)
+  xserver_package=xvfb
 fi
 
-if ! hash Xephyr 2>/dev/null ; then
-  echo
-  echo "Xephyr is not installed.  Please run:"
-  echo "  sudo apt-get install xserver-xephyr"
-  echo
-  exit 0
-fi
+# Use display 42 as it is unlikely to be used by another process on this host.
+# TODO: Scan displays for a free one.
+xserver_display=":42"
 
-# Open up a Xephyr display.
-Xephyr -screen 1920x1080 :10 2> /dev/null &
-xephyr_pid=$!
+function log() {
+  echo "${script_name}: $@"
+}
 
-# Setup a trap to clean up Xephyr upon termination of this script
-function kill1() {
+function deleteTempTrap() {
+  # If something interrupted the script, it might have printed a partial line.
+  echo
+  deleteDirectory "${temp_dir}"
+}
+
+function killServerTrap() {
+  echo
   # Kill the Xephyr process.
-  kill $xephyr_pid 2> /dev/null
+  kill "${xserver_pid}" &> /dev/null
   # Waits for the kill to finish.
   wait
+  log "${xserver_bin} (pid ${xserver_pid}) terminated."
+  deleteDirectory "${temp_dir}"
 }
-trap kill1 EXIT
 
-# Give Xephyr some time to setup.
-sleep 0.5
+function deleteDirectory() {
+  if [[ -z "$1" ]]; then
+    log "deleteDirectory with no argument"
+    exit 1
+  fi
 
-# Launch the executable passed as a parameter on the Xephyr display.
-DISPLAY=:10 $@
\ No newline at end of file
+  # Only delete target if it is an existing directory.
+  if [[ -d "$1" ]]; then
+    rm -rf "$1"
+  fi
+}
+
+function main() {
+  if [[ "$#" = "0" ]]; then
+    echo
+    echo "${script_name}: Launches a given executable file under a ${xserver_bin} X server."
+    echo
+    echo "Usage:"
+    echo "  ${script_name} PATH_TO_EXECUTABLE_FILE [ARGS...] "
+    echo
+    exit 1
+  fi
+
+  if ! hash "${xserver_bin}" 2>/dev/null ; then
+    log "${xserver_bin} is not installed.  Please run:"
+    log "  sudo apt-get install ${xserver_package}"
+    exit 1
+  fi
+
+  # Create an auth file that will allow the current user to access the display.
+  temp_dir="$(mktemp -dt "${script_name}.XXXXXXXXXX")"
+
+  # Delete the temporary directory on exit.
+  trap deleteTempTrap EXIT
+
+  # In this case, we don't want to try to tunnel authority through to remote
+  # connections, we want to use this X server with the client. So we create
+  # our own auth and forcibly pass it through both sides of the client and
+  # server. Apologies for the X11 magic here.
+  xserver_auth="${temp_dir}/XAuthority"
+  touch "${xserver_auth}"
+  token="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | cut -f1 -d' ')"
+  xauth -qf "${xserver_auth}" add "${xserver_display}" . "${token}"
+
+  xserver_log="${temp_dir}/${xserver_bin}.txt"
+  # Launch an X Server in the background at a new display.
+  "${xserver_bin}" "${xserver_display}" \
+    -auth "${xserver_auth}" \
+    "${xserver_screen[@]}" \
+    &> "${xserver_log}" &
+  xserver_pid=$!
+  log "${xserver_bin} (pid ${xserver_pid}) running on ${xserver_display}."
+
+  # Setup a trap to clean up Xephyr upon termination of this script
+  trap killServerTrap EXIT
+
+  # Give Xephyr some time to setup.
+  sleep 0.5
+
+  # Launch the executable passed as a parameter on the new display.
+  DISPLAY="${xserver_display}" XAUTHORITY="${xserver_auth}" "$@"
+  result=$?
+  if [[ "${result}" != "0" ]]; then
+    log "$1 result: ${result}"
+    log "--- ${xserver_bin} log ---------------------------------------"
+    cat "${xserver_log}"
+  fi
+  return ${result}
+}
+
+main "$@"
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 6c7aec8..5bf56cd 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -150,6 +150,8 @@
     SbThreadJoin(decoder_thread_, NULL);
   }
 
+  avcodec_flush_buffers(codec_context_);
+
   decoder_thread_ = kSbThreadInvalid;
   stream_ended_ = false;
 }
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc
index 5bab32b..ddb425f 100644
--- a/src/starboard/shared/starboard/player/player_worker.cc
+++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 
+#include "starboard/memory.h"
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/drm/drm_system_internal.h"
 #include "starboard/shared/starboard/player/audio_decoder_internal.h"
@@ -113,8 +114,11 @@
     } else if (event->type == Event::kSetPause) {
       running &= ProcessSetPauseEvent(event->data.set_pause);
     } else if (event->type == Event::kSetBounds) {
-      bounds = event->data.set_bounds;
-      ProcessUpdateEvent(bounds);
+      if (SbMemoryCompare(&bounds, &event->data.set_bounds, sizeof(bounds)) !=
+          0) {
+        bounds = event->data.set_bounds;
+        ProcessUpdateEvent(bounds);
+      }
     } else if (event->type == Event::kStop) {
       ProcessStopEvent();
       running = false;
diff --git a/src/starboard/shared/starboard/player/video_frame_internal.cc b/src/starboard/shared/starboard/player/video_frame_internal.cc
index f13dc3b..d1a0099 100644
--- a/src/starboard/shared/starboard/player/video_frame_internal.cc
+++ b/src/starboard/shared/starboard/player/video_frame_internal.cc
@@ -30,7 +30,7 @@
 int s_u_to_g[256];

 int s_v_to_g[256];

 int s_u_to_b[256];

-uint8_t s_clamp_table[256 * 3];

+uint8_t s_clamp_table[256 * 5];

 

 void EnsureYUVToRGBLookupTableInitialized() {

   if (s_yuv_to_rgb_lookup_table_initialized) {

@@ -47,10 +47,13 @@
   //

   // We optimize the conversion algorithm by creating two kinds of lookup

   // tables.  The color component table contains pre-calculated color component

-  // values.  The clamp table contains a map between |v| + 256 to the clamped

+  // values.  The clamp table contains a map between |v| + 512 to the clamped

   // |v| to avoid conditional operation.

-  SbMemorySet(s_clamp_table, 0, 256);

-  SbMemorySet(s_clamp_table + 512, 0xff, 256);

+  // The minimum value of |v| can be 2.112f * (-128) = -271, the maximum value

+  // of |v| can be 1.164f * 255 + 2.112f * 127 = 565.  So we need 512 bytes at

+  // each side of the clamp buffer.

+  SbMemorySet(s_clamp_table, 0, 512);

+  SbMemorySet(s_clamp_table + 768, 0xff, 512);

 

   for (int i = 0; i < 256; ++i) {

     s_y_to_rgb[i] = (static_cast<uint8_t>(i) - 16) * 1.164f;

@@ -58,14 +61,14 @@
     s_u_to_g[i] = (static_cast<uint8_t>(i) - 128) * -0.213;

     s_v_to_g[i] = (static_cast<uint8_t>(i) - 128) * -0.533f;

     s_u_to_b[i] = (static_cast<uint8_t>(i) - 128) * 2.112f;

-    s_clamp_table[256 + i] = i;

+    s_clamp_table[512 + i] = i;

   }

 

   s_yuv_to_rgb_lookup_table_initialized = true;

 }

 

 uint8_t ClampColorComponent(int component) {

-  return s_clamp_table[component + 256];

+  return s_clamp_table[component + 512];

 }

 

 }  // namespace

diff --git a/src/starboard/shared/starboard/player/video_renderer_internal.cc b/src/starboard/shared/starboard/player/video_renderer_internal.cc
index da9ccf8..d3faa9f 100644
--- a/src/starboard/shared/starboard/player/video_renderer_internal.cc
+++ b/src/starboard/shared/starboard/player/video_renderer_internal.cc
@@ -70,6 +70,9 @@
   seeking_ = true;
   end_of_stream_written_ = false;
 
+  if (!frames_.empty()) {
+    seeking_frame_ = frames_.front();
+  }
   frames_.clear();
 }
 
@@ -77,8 +80,7 @@
   ScopedLock lock(mutex_);
 
   if (frames_.empty()) {
-    // We cannot paint anything if there is no frames.
-    return empty_frame_;
+    return seeking_frame_;
   }
   // Remove any frames with timestamps earlier than |media_time|, but always
   // keep at least one of the frames.
@@ -122,7 +124,7 @@
       frames_.push_back(*frame);
     }
 
-    if (seeking_ && frames_.size() > kPrerollFrames) {
+    if (seeking_ && frames_.size() >= kPrerollFrames) {
       seeking_ = false;
     }
   }
diff --git a/src/starboard/shared/starboard/player/video_renderer_internal.h b/src/starboard/shared/starboard/player/video_renderer_internal.h
index f62cfda..0cf815a 100644
--- a/src/starboard/shared/starboard/player/video_renderer_internal.h
+++ b/src/starboard/shared/starboard/player/video_renderer_internal.h
@@ -70,7 +70,11 @@
   bool seeking_;
   Frames frames_;
 
-  VideoFrame empty_frame_;
+  // During seeking, all frames inside |frames_| will be cleared but the app
+  // should still display the last frame it is rendering.  This frame will be
+  // kept inside |seeking_frame_|.  It is an empty/black frame before the video
+  // is started.
+  VideoFrame seeking_frame_;
 
   SbMediaTime seeking_to_pts_;
   bool end_of_stream_written_;
diff --git a/src/starboard/shared/x11/application_x11.cc b/src/starboard/shared/x11/application_x11.cc
index 3499bd8..bfde70b 100644
--- a/src/starboard/shared/x11/application_x11.cc
+++ b/src/starboard/shared/x11/application_x11.cc
@@ -587,6 +587,7 @@
   // XLib is not thread-safe. Since we may be coming from another thread, we
   // have to open another connection to the display to inject the wake-up event.
   Display* display = XOpenDisplay(NULL);
+  SB_DCHECK(display);
   XClientMessageEvent event = {0};
   event.type = ClientMessage;
   event.message_type = atom;
@@ -640,7 +641,9 @@
 }
 
 SbWindow ApplicationX11::CreateWindow(const SbWindowOptions* options) {
-  EnsureX();
+  if (!EnsureX()) {
+    return kSbWindowInvalid;
+  }
 
   SbWindow window = new SbWindowPrivate(display_, options);
   windows_.push_back(window);
@@ -787,17 +790,25 @@
   XSendAtom((*windows_.begin())->window, wake_up_atom_);
 }
 
-void ApplicationX11::EnsureX() {
+bool ApplicationX11::EnsureX() {
   // TODO: Consider thread-safety.
   if (display_) {
-    return;
+    return true;
   }
 
   XInitThreads();
   XSetIOErrorHandler(IOErrorHandler);
   XSetErrorHandler(ErrorHandler);
   display_ = XOpenDisplay(NULL);
-  SB_DCHECK(display_);
+  if (!display_) {
+    const char *display_environment = getenv("DISPLAY");
+    if (display_environment == NULL) {
+      SB_LOG(ERROR) << "Unable to open display, DISPLAY not set.";
+    } else {
+      SB_LOG(ERROR) << "Unable to open display " << display_environment << ".";
+    }
+    return false;
+  }
 
   // Disable keyup events on auto-repeat to match Windows.
   // Otherwise when holding down a key, we get a keyup event before
@@ -812,6 +823,7 @@
 #if SB_IS(PLAYER_PUNCHED_OUT)
   Composite();
 #endif  // SB_IS(PLAYER_PUNCHED_OUT)
+  return true;
 }
 
 void ApplicationX11::StopX() {
diff --git a/src/starboard/shared/x11/application_x11.h b/src/starboard/shared/x11/application_x11.h
index 4be2cab..11b4aca 100644
--- a/src/starboard/shared/x11/application_x11.h
+++ b/src/starboard/shared/x11/application_x11.h
@@ -80,8 +80,9 @@
   };
 #endif  // SB_IS(PLAYER_PUNCHED_OUT)
 
-  // Ensures that X is up, display is populated and connected.
-  void EnsureX();
+  // Ensures that X is up, display is populated and connected, returning whether
+  // it succeeded.
+  bool EnsureX();
 
   // Shuts X down.
   void StopX();
diff --git a/src/third_party/mozjs/js/src/js.msg b/src/third_party/mozjs/js/src/js.msg
index 4047035..ff04140 100644
--- a/src/third_party/mozjs/js/src/js.msg
+++ b/src/third_party/mozjs/js/src/js.msg
@@ -297,7 +297,11 @@
 MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX,  244, 0, JSEXN_ERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 245, 1, JSEXN_ERR, "argument {0} must be >= 0")
 MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS,   246, 0, JSEXN_ERR, "invalid arguments")
+#if defined(STARBOARD)
+MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION,   247, 0, JSEXN_EVALERR, "call to Function() blocked by CSP")
+#else
 MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION,   247, 0, JSEXN_ERR, "call to Function() blocked by CSP")
+#endif
 MSG_DEF(JSMSG_BAD_GET_SET_FIELD,      248, 1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function")
 MSG_DEF(JSMSG_BAD_PROXY_FIX,          249, 0, JSEXN_TYPEERR, "proxy was fixed while executing the handler")
 MSG_DEF(JSMSG_INVALID_EVAL_SCOPE_ARG, 250, 0, JSEXN_EVALERR, "invalid eval scope argument")
@@ -338,7 +342,11 @@
 MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 285, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object")
 MSG_DEF(JSMSG_DEBUG_NOT_SCRIPT_FRAME, 286, 0, JSEXN_ERR, "stack frame is not running JavaScript code")
 MSG_DEF(JSMSG_CANT_WATCH_PROP,        287, 0, JSEXN_TYPEERR, "properties whose names are objects can't be watched")
+#if defined(STARBOARD)
+MSG_DEF(JSMSG_CSP_BLOCKED_EVAL,       288, 0, JSEXN_EVALERR, "call to eval() blocked by CSP")
+#else
 MSG_DEF(JSMSG_CSP_BLOCKED_EVAL,       288, 0, JSEXN_ERR, "call to eval() blocked by CSP")
+#endif
 MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT,  289, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects")
 MSG_DEF(JSMSG_EMPTY_CONSEQUENT,       290, 0, JSEXN_SYNTAXERR, "mistyped ; after conditional?")
 MSG_DEF(JSMSG_NOT_ITERABLE,           291, 1, JSEXN_TYPEERR, "{0} is not iterable")
diff --git a/src/third_party/mozjs/js/src/jsapi.cpp b/src/third_party/mozjs/js/src/jsapi.cpp
index 1fae091..33b7991 100644
--- a/src/third_party/mozjs/js/src/jsapi.cpp
+++ b/src/third_party/mozjs/js/src/jsapi.cpp
@@ -93,6 +93,8 @@
 #include "jit/Ion.h"
 #endif
 
+#include "starboard/file.h"
+
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
@@ -5119,6 +5121,27 @@
     return script;
 }
 
+#if defined(STARBOARD)
+JSScript *
+JS::Compile(JSContext *cx, HandleObject obj, CompileOptions options,
+            SbFile file)
+{
+    SbFileInfo info;
+    bool success = SbFileGetInfo(file, &info);
+    if (!success) {
+        return NULL;
+    }
+    const int64_t kFileSize = info.size;
+    FileContents buffer(cx);
+    buffer.resize(kFileSize);
+    if (SbFileRead(file, buffer.begin(), kFileSize) < 0) {
+        return NULL;
+    }
+    JSScript *script = Compile(cx, obj, options, buffer.begin(),
+                               buffer.length());
+    return script;
+}
+#else
 JSScript *
 JS::Compile(JSContext *cx, HandleObject obj, CompileOptions options, FILE *fp)
 {
@@ -5129,15 +5152,26 @@
     JSScript *script = Compile(cx, obj, options, buffer.begin(), buffer.length());
     return script;
 }
+#endif
 
 JSScript *
 JS::Compile(JSContext *cx, HandleObject obj, CompileOptions options, const char *filename)
 {
+#if defined(STARBOARD)
+    starboard::ScopedFile file(filename, kSbFileOpenOnly | kSbFileRead, NULL,
+                               NULL);
+    if (!file.IsValid()) {
+        return NULL;
+    }
+    options = options.setFileAndLine(filename, 1);
+    JSScript *script = Compile(cx, obj, options, file.file());
+#else
     AutoFile file;
     if (!file.open(cx, filename))
         return NULL;
     options = options.setFileAndLine(filename, 1);
     JSScript *script = Compile(cx, obj, options, file.fp());
+#endif
     return script;
 }
 
@@ -7082,4 +7116,3 @@
 
     return JSObject::preventExtensions(cx, obj);
 }
-
diff --git a/src/third_party/mozjs/js/src/jsapi.h b/src/third_party/mozjs/js/src/jsapi.h
index 9fb1cec..768260e 100644
--- a/src/third_party/mozjs/js/src/jsapi.h
+++ b/src/third_party/mozjs/js/src/jsapi.h
@@ -31,6 +31,8 @@
 #include "js/Value.h"
 #include "js/Vector.h"
 
+#include "starboard/file.h"
+
 /************************************************************************/
 
 namespace JS {
@@ -3990,8 +3992,14 @@
 Compile(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options,
         const jschar *chars, size_t length);
 
+#if defined(STARBOARD)
+extern JS_PUBLIC_API(JSScript *)
+Compile(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options,
+        SbFile file);
+#else
 extern JS_PUBLIC_API(JSScript *)
 Compile(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options, FILE *file);
+#endif
 
 extern JS_PUBLIC_API(JSScript *)
 Compile(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options, const char *filename);
diff --git a/src/third_party/mozjs/js/src/jsstarboard-time.cpp b/src/third_party/mozjs/js/src/jsstarboard-time.cpp
index bed499f..d5049e2 100644
--- a/src/third_party/mozjs/js/src/jsstarboard-time.cpp
+++ b/src/third_party/mozjs/js/src/jsstarboard-time.cpp
@@ -17,11 +17,11 @@
 #include "mozilla/Assertions.h"
 #include "unicode/timezone.h"
 
-SbTime get_dst_offset(SbTime utc_time) {
+SbTime getDSTOffset(int64_t utc_time_us) {
   // UDate is in milliseconds from the epoch.
-  UDate udate = utc_time / kSbTimeMillisecond;
+  UDate udate = utc_time_us / kSbTimeMillisecond;
 
-  icu_46::TimeZone* current_zone = icu_46::TimeZone::createDefault();
+  icu::TimeZone* current_zone = icu::TimeZone::createDefault();
   int32_t raw_offset_ms, dst_offset_ms;
   UErrorCode error_code = U_ZERO_ERROR;
   current_zone->getOffset(
@@ -30,7 +30,15 @@
 
   if (U_SUCCESS(error_code)) {
     MOZ_ASSERT(dst_offset_ms >= 0);
-    return (raw_offset_ms + dst_offset_ms) * kSbTimeMillisecond;
+    return dst_offset_ms * kSbTimeMillisecond;
   }
   return 0;
 }
+
+SbTime getTZOffset() {
+  icu::TimeZone* current_zone = icu::TimeZone::createDefault();
+  int32_t raw_offset_ms = current_zone->getRawOffset();
+  delete current_zone;
+  return raw_offset_ms * kSbTimeMillisecond;
+}
+
diff --git a/src/third_party/mozjs/js/src/jsstarboard-time.h b/src/third_party/mozjs/js/src/jsstarboard-time.h
index 0d20ad7..038dd2d 100644
--- a/src/third_party/mozjs/js/src/jsstarboard-time.h
+++ b/src/third_party/mozjs/js/src/jsstarboard-time.h
@@ -18,10 +18,14 @@
 #include "starboard/time.h"
 
 /*
- * Get the local time offset with DST applied using the current system's
- * timezone such that:
- *     local_time = utc_time + get_dst_offset(utc_time)
+ * Get the DST offset at the given time. |utc_time_us| is relative to the posix
+ * epoch.
  */
-SbTime get_dst_offset(SbTime utc_time);
+SbTime getDSTOffset(int64_t utc_time_us);
+
+/*
+ * Get the TZ offset based on the system's current timezone setting.
+ */
+SbTime getTZOffset();
 
 #endif  // jsstarboardtime_h
diff --git a/src/third_party/mozjs/js/src/prmjtime.cpp b/src/third_party/mozjs/js/src/prmjtime.cpp
index 12b2f40..c1e67b5 100644
--- a/src/third_party/mozjs/js/src/prmjtime.cpp
+++ b/src/third_party/mozjs/js/src/prmjtime.cpp
@@ -193,11 +193,11 @@
 int64_t
 PRMJ_Now(void)
 {
-    // SbTime is in microseconds since the epoch.
-    SbTime utcNowMicroseconds = SbTimeGetNow();
-    SbTime dstOffsetMicroseconds = get_dst_offset(utcNowMicroseconds);
-    return utcNowMicroseconds + dstOffsetMicroseconds;
+    // SbTime is in microseconds since the Starboard/Windows epoch, and PRMJ_Now
+    // should return microseconds since the Posix epoch.
+    return SbTimeToPosix(SbTimeGetNow());
 }
+
 #elif defined(XP_OS2)
 int64_t
 PRMJ_Now(void)
diff --git a/src/third_party/mozjs/js/src/shell/js.cpp b/src/third_party/mozjs/js/src/shell/js.cpp
index 7b1cf6b..4af5e8d 100644
--- a/src/third_party/mozjs/js/src/shell/js.cpp
+++ b/src/third_party/mozjs/js/src/shell/js.cpp
@@ -8,7 +8,9 @@
 #include <errno.h>
 #include <locale.h>
 #include <math.h>
+#if !defined(STARBOARD)
 #include <signal.h>
+#endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -72,7 +74,7 @@
 # include <direct.h>
 # include "jswin.h"
 # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
-#else
+#elif !defined(STARBOARD)
 # include <libgen.h>
 #endif
 
@@ -89,6 +91,8 @@
 #include "zlib.h"
 #endif
 
+#include "starboard/client_porting/wrap_main/wrap_main.h"
+
 using namespace js;
 using namespace js::cli;
 
@@ -413,6 +417,13 @@
 }
 
 static void
+#if defined(STARBOARD)
+RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, SbFile file,
+        bool compileOnly)
+{
+    // We may not handle lines starting with # correctly without
+    // adding more here.
+#else
 RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, FILE *file, bool compileOnly)
 {
     SkipUTF8BOM(file);
@@ -427,6 +438,7 @@
         }
     }
     ungetc(ch, file);
+#endif
 
     int64_t t1 = PRMJ_Now();
     uint32_t oldopts = JS_GetOptions(cx);
@@ -531,8 +543,7 @@
         if (hitEOF && buffer.empty())
             break;
 
-        if (!EvalAndPrint(cx, global, buffer.begin(), buffer.length(), startline, compileOnly,
-                          out))
+        if (!EvalAndPrint(cx, global, buffer.begin(), buffer.length(), startline, compileOnly, out))
         {
             // Catch the error, report it, and keep going.
             JS_ReportPendingException(cx);
@@ -554,11 +565,31 @@
     }
 };
 
+#if defined(STARBOARD)
+static void
+Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY)
+{
+    // No interactive mode for Starboard : stdin not guaranteed.
+    if (forceTTY) {
+        return;
+    }
+    RootedObject obj(cx, obj_);
+    SbFile file = SbFileOpen(filename, kSbFileOpenOnly | kSbFileRead, NULL,
+                             NULL);
+    if (!SbFileIsValid(file)) {
+        SbFileClose(file);
+        return;
+    }
+
+    SetContextOptions(cx);
+    RunFile(cx, obj, filename, file, compileOnly);
+    SbFileClose(file);
+}
+#else
 static void
 Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY)
 {
     RootedObject obj(cx, obj_);
-
     FILE *file;
     if (forceTTY || !filename || strcmp(filename, "-") == 0) {
         file = stdin;
@@ -572,9 +603,7 @@
         }
     }
     AutoCloseInputFile autoClose(file);
-
     SetContextOptions(cx);
-
     if (!forceTTY && !isatty(fileno(file))) {
         // It's not interactive - just execute it.
         RunFile(cx, obj, filename, file, compileOnly);
@@ -583,6 +612,7 @@
         ReadEvalPrintLoop(cx, obj, file, gOutFile, compileOnly);
     }
 }
+#endif // STARBOARD
 
 /*
  * JSContext option name to flag map. The option names are in alphabetical
@@ -706,22 +736,37 @@
 
     static char buffer[PATH_MAX+1];
     if (scriptRelative) {
-#ifdef XP_WIN
+#if defined(STARBOARD)
+        // We do not expect relative path scripts to work in Starboard.
+        return NULL;
+#elif defined(XP_WIN)
         // The docs say it can return EINVAL, but the compiler says it's void
         _splitpath(script->filename(), NULL, buffer, NULL, NULL);
 #else
         strncpy(buffer, script->filename(), PATH_MAX+1);
         if (buffer[PATH_MAX] != '\0')
             return NULL;
-
         // dirname(buffer) might return buffer, or it might return a
         // statically-allocated string
         memmove(buffer, dirname(buffer), strlen(buffer) + 1);
 #endif
     } else {
+#if defined(STARBOARD)
+        // For Starboard, the tests scripts are in
+        // content/dir_source_root/mozjs/tests
+        // so we let that play the role of the cwd.
+        bool result = SbSystemGetPath(kSbSystemPathSourceDirectory,
+                                      buffer, PATH_MAX + 1);
+        if (!result) {
+            return NULL;
+        }
+        // Append subdirectory /mozjs/tests.
+        SbStringConcat(buffer, "/mozjs/tests", PATH_MAX + 1);
+#else
         const char *cwd = getcwd(buffer, PATH_MAX);
         if (!cwd)
             return NULL;
+#endif
     }
 
     size_t len = strlen(buffer);
@@ -771,7 +816,7 @@
         }
     }
     if (!found)
-        names = js_strdup("");
+        names = js_strdup((char*)"");
     if (!names) {
         JS_ReportOutOfMemory(cx);
         return false;
@@ -2987,7 +3032,9 @@
 static bool
 ScheduleWatchdog(JSRuntime *rt, double t)
 {
-#ifdef XP_WIN
+#if defined(STARBOARD)
+    return (t <= 0);
+#elif defined(XP_WIN)
     if (gTimerHandle) {
         DeleteTimerQueueTimer(NULL, gTimerHandle, NULL);
         gTimerHandle = 0;
@@ -3311,6 +3358,7 @@
     return ReadFile(cx, argc, vp, true);
 }
 
+#if !defined(STARBOARD)
 static JSBool
 System(JSContext *cx, unsigned argc, jsval *vp)
 {
@@ -3334,6 +3382,7 @@
     JS_SET_RVAL(cx, vp, Int32Value(result));
     return true;
 }
+#endif
 
 static bool
 DecompileFunctionSomehow(JSContext *cx, unsigned argc, Value *vp,
@@ -3885,11 +3934,11 @@
     JS_FN_HELP("setThrowHook", SetThrowHook, 1, 0,
 "setThrowHook(f)",
 "  Set throw hook to f."),
-
+#if !defined(STARBOARD)
     JS_FN_HELP("system", System, 1, 0,
 "system(command)",
 "  Execute command on the current host, returning result code."),
-
+#endif
     JS_FN_HELP("trap", Trap, 3, 0,
 "trap([fun, [pc,]] exp)",
 "  Trap bytecode execution."),
@@ -4450,6 +4499,8 @@
         JS_smprintf_free(waste);
 #endif
     }
+#elif defined(STARBOARD)
+    rv = -1;
 #else
     rv = setenv(idstr.getBytes(), valstr.getBytes(), 1);
 #endif
@@ -4502,6 +4553,7 @@
         return false;
 
     name = idstr.getBytes();
+#if !defined(STARBOARD)
     value = getenv(name);
     if (value) {
         valstr = JS_NewStringCopyZ(cx, value);
@@ -4513,6 +4565,7 @@
         }
         objp.set(obj);
     }
+#endif
     return true;
 }
 
@@ -5160,10 +5213,14 @@
                             FILE* defaultOut,
                             FILE** outFile)
 {
+#if defined(STARBOARD)
+    *outFile = defaultOut;
+#else
     const char* outPath = getenv(envVar);
     if (!outPath || !*outPath || !(*outFile = fopen(outPath, "w"))) {
         *outFile = defaultOut;
     }
+#endif
 }
 
 /* Set the initial counter to 1 so the principal will never be destroyed. */
@@ -5189,7 +5246,7 @@
 }
 
 int
-main(int argc, char **argv, char **envp)
+ShellMain(int argc, char **argv, char **envp)
 {
     int stackDummy;
     JSRuntime *rt;
@@ -5382,7 +5439,6 @@
     /* Must be done before creating the global object */
     if (op.getBoolOption('D'))
         JS_ToggleOptions(cx, JSOPTION_PCCOUNT);
-
     result = Shell(cx, &op, envp);
 
 #if defined(DEBUG) && !defined(JS_USE_CUSTOM_ALLOCATOR)
@@ -5401,3 +5457,9 @@
     JS_ShutDown();
     return result;
 }
+
+int ShellMainNoEnv(int argc, char** argv) {
+    return ShellMain(argc, argv, NULL);
+}
+
+STARBOARD_WRAP_SIMPLE_MAIN(ShellMainNoEnv);
diff --git a/src/third_party/mozjs/js/src/shell/jsoptparse.cpp b/src/third_party/mozjs/js/src/shell/jsoptparse.cpp
index 204efa1..18be826 100644
--- a/src/third_party/mozjs/js/src/shell/jsoptparse.cpp
+++ b/src/third_party/mozjs/js/src/shell/jsoptparse.cpp
@@ -253,7 +253,7 @@
     char *eq = strchr(argv[*i], '=');
     if (eq) {
         *value = eq + 1;
-        if (value[0] == '\0')
+        if (*value[0] == '\0')
             return error("A value is required for option %.*s", eq - argv[*i], argv[*i]);
         return Okay;
     }
diff --git a/src/third_party/mozjs/js/src/vm/DateTime.cpp b/src/third_party/mozjs/js/src/vm/DateTime.cpp
index 9c26bd9..cfca247 100644
--- a/src/third_party/mozjs/js/src/vm/DateTime.cpp
+++ b/src/third_party/mozjs/js/src/vm/DateTime.cpp
@@ -17,16 +17,7 @@
 
 using mozilla::UnspecifiedNaN;
 
-#if defined(STARBOARD)
-static int32_t
-UTCToLocalStandardOffsetSeconds()
-{
-    SbTimeZone timeZone = SbTimeZoneGetCurrent();
-    // timeZone is in minutes
-    return timeZone * 60;
-}
-
-#else
+#if !defined(STARBOARD)
 static bool
 ComputeLocalTime(time_t local, struct tm *ptm)
 {
@@ -149,7 +140,11 @@
      * The difference between local standard time and UTC will never change for
      * a given time zone.
      */
+#if defined(STARBOARD)
+    utcToLocalStandardOffsetSeconds = getTZOffset() / kSbTimeSecond;
+#else
     utcToLocalStandardOffsetSeconds = UTCToLocalStandardOffsetSeconds();
+#endif
 
     double newTZA = utcToLocalStandardOffsetSeconds * msPerSecond;
     if (newTZA == localTZA_)
@@ -192,7 +187,7 @@
     MOZ_ASSERT(utcSeconds <= MaxUnixTimeT);
 
     SbTime utcTimeMicroseconds = utcSeconds * kSbTimeSecond;
-    SbTime offsetMicroseconds = get_dst_offset(utcTimeMicroseconds);
+    SbTime offsetMicroseconds = getDSTOffset(utcTimeMicroseconds);
     return offsetMicroseconds / kSbTimeMillisecond;
 }
 
diff --git a/src/third_party/mozjs/mozjs.gyp b/src/third_party/mozjs/mozjs.gyp
index c646032..b0c4497 100644
--- a/src/third_party/mozjs/mozjs.gyp
+++ b/src/third_party/mozjs/mozjs.gyp
@@ -205,7 +205,30 @@
         'mozjs_lib',
         '<(DEPTH)/starboard/starboard.gyp:starboard',
         '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
-      ]
+      ],
+      'actions': [
+      {
+        'action_name': 'copy_test_data',
+          'variables': {
+            'input_files': [
+              'js/src/tests/',
+            ],
+            'output_dir': 'mozjs/tests/',
+            },
+            'includes': ['../../starboard/build/copy_test_data.gypi'],
+        },
+      ],
+    },
+    {
+      'target_name': 'mozjs_shell_deploy',
+      'type': 'none',
+      'dependencies': [
+        'mozjs_shell',
+      ],
+      'variables': {
+        'executable_name': 'mozjs_shell',
+      },
+      'includes': [ '../../starboard/build/deploy.gypi' ],
     },
     {
       # SpiderMonkey source expects to include files from a certain directory