Import Cobalt 23.master.0.309699
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 08dba15..12bbd12 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -168,7 +168,7 @@
         types: [text]
         # TODO: Remove docker-compose-windows.yml after internal check evaluates
         # properly on it.
-        exclude: '(^docker-compose-windows.yml|EXCLUDE\.FILES(\.RECURSIVE)?|codereview\.settings)$'
+        exclude: '(^docker-compose-windows(-internal)?.yml|EXCLUDE\.FILES(\.RECURSIVE)?|codereview\.settings)$'
     -   id: gn-format
         name: GN format
         entry: gn format
diff --git a/base/single_thread_task_runner.cc b/base/single_thread_task_runner.cc
index 755a0fc..d4baf23 100644
--- a/base/single_thread_task_runner.cc
+++ b/base/single_thread_task_runner.cc
@@ -40,11 +40,15 @@
   base::WaitableEvent task_finished(
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED);
-  PostTask(from_here,
+  bool task_may_run = PostTask(from_here,
            base::Bind(&RunAndSignal, task, base::Unretained(&task_finished)));
+  DCHECK(task_may_run)
+      << "Task that will never run posted with PostBlockingTask.";
 
-  // Wait for the task to complete before proceeding.
-  task_finished.Wait();
+  if (task_may_run) {
+    // Wait for the task to complete before proceeding.
+    task_finished.Wait();
+  }
 }
 #endif
 }  // namespace base
diff --git a/base/values.cc b/base/values.cc
index 49440e7..ade6b6f 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -272,6 +272,11 @@
   return list_;
 }
 
+Value::ListStorage Value::TakeList() {
+  CHECK(is_list());
+  return std::exchange(list_, {});
+}
+
 Value* Value::FindKey(StringPiece key) {
   return const_cast<Value*>(static_cast<const Value*>(this)->FindKey(key));
 }
diff --git a/base/values.h b/base/values.h
index c9964a8..0f9558d 100644
--- a/base/values.h
+++ b/base/values.h
@@ -178,6 +178,11 @@
   ListStorage& GetList();
   const ListStorage& GetList() const;
 
+  // Transfers ownership of the underlying list to the caller. Subsequent
+  // calls to `GetList()` will return an empty list.
+  // Note: This requires that `type()` is Type::LIST.
+  ListStorage TakeList();
+
   // |FindKey| looks up |key| in the underlying dictionary. If found, it returns
   // a pointer to the element. Otherwise it returns nullptr.
   // returned. Callers are expected to perform a check against null before using
diff --git a/cobalt/base/statistics.h b/cobalt/base/statistics.h
index 90dbe3e..b154573 100644
--- a/cobalt/base/statistics.h
+++ b/cobalt/base/statistics.h
@@ -17,8 +17,10 @@
 
 #include <algorithm>
 #include <functional>
+#include <string>
 #include <vector>
 
+#include "cobalt/base/c_val.h"
 #include "starboard/types.h"
 
 namespace base {
@@ -66,7 +68,7 @@
               internal::DefaultSampleToValueFunc>
 class Statistics {
  public:
-  constexpr Statistics() = default;
+  explicit Statistics(const std::string& metric_name) {}
 
   void AddSample(DividendType dividend, DivisorType divisor) {}
   int64_t accumulated_dividend() const { return 0; }
@@ -85,7 +87,11 @@
               internal::DefaultSampleToValueFunc>
 class Statistics {
  public:
-  constexpr Statistics() = default;
+  explicit Statistics(const std::string& metric_name)
+      : last_sample_value_(metric_name + ".Latest", 0,
+                           metric_name + " most recent value."),
+        median_sample_value_(metric_name + ".Median", 0,
+                             metric_name + " median value.") {}
 
   void AddSample(DividendType dividend, DivisorType divisor) {
     static_assert(MaxSamples > 0, "MaxSamples has to be greater than 0.");
@@ -111,6 +117,9 @@
       min_ = max_ = value;
       first_sample_added_ = true;
     }
+    last_sample_value_ = value;
+    // TODO(b/258531018) this should be optimized in future.
+    median_sample_value_ = GetMedian();
   }
 
   int64_t accumulated_dividend() const { return accumulated_dividend_; }
@@ -174,6 +183,9 @@
   Sample samples_[MaxSamples] = {};  // Ring buffer for samples.
   size_t number_of_samples_ = 0;     // Number of samples in |samples_|.
   size_t first_sample_index_ = 0;    // Index of the first sample in |samples_|.
+
+  base::CVal<int64_t> last_sample_value_;
+  base::CVal<int64_t> median_sample_value_;
 };
 
 #endif  // defined(COBALT_BUILD_TYPE_GOLD)
diff --git a/cobalt/base/statistics_test.cc b/cobalt/base/statistics_test.cc
index beefdb9..60fcd09 100644
--- a/cobalt/base/statistics_test.cc
+++ b/cobalt/base/statistics_test.cc
@@ -20,7 +20,7 @@
 namespace {
 
 TEST(StatisticsTest, ZeroSamples) {
-  Statistics<int, int, 1> statistics;
+  Statistics<int, int, 1> statistics("test1");
 
   EXPECT_EQ(statistics.accumulated_dividend(), 0);
   EXPECT_EQ(statistics.accumulated_divisor(), 0);
@@ -31,7 +31,7 @@
 }
 
 TEST(StatisticsTest, SingleSample) {
-  Statistics<int, int, 1> statistics;
+  Statistics<int, int, 1> statistics("test2");
 
   statistics.AddSample(100, 10);
 
@@ -44,7 +44,7 @@
 }
 
 TEST(StatisticsTest, NotCrashOnZeroDivisor) {
-  Statistics<int, int, 4> statistics;
+  Statistics<int, int, 4> statistics("test3");
 
   statistics.AddSample(100, 0);
 
@@ -58,7 +58,7 @@
 }
 
 TEST(StatisticsTest, MultipleSamples) {
-  Statistics<int, int, 4> statistics;
+  Statistics<int, int, 4> statistics("test4");
 
   statistics.AddSample(10, 2);
   statistics.AddSample(20, 2);
@@ -83,8 +83,8 @@
 }
 
 TEST(StatisticsTest, MultipleSamplesDifferentOrders) {
-  Statistics<int, int, 10> statistics;
-  Statistics<int, int, 10> statistics_reversed;
+  Statistics<int, int, 10> statistics("test5");
+  Statistics<int, int, 10> statistics_reversed("reverse test5");
 
   for (int i = 0; i < 10; ++i) {
     statistics.AddSample(i * i, i * 3);
@@ -105,16 +105,16 @@
 }
 
 TEST(StatisticsTest, MoreSamplesThanCapacity) {
-  Statistics<int, int, 1> statistics_1;
-  Statistics<int, int, 10> statistics_10;
+  Statistics<int, int, 1> statistics_1("test6");
+  Statistics<int, int, 10> statistics_10("test7");
 
   for (int i = 0; i < 5; ++i) {
     statistics_1.AddSample(i * i, i * 3);
     statistics_10.AddSample(i * i, i * 3);
   }
 
-  Statistics<int, int, 1> statistics_1_median_reference;
-  Statistics<int, int, 10> statistics_10_median_reference;
+  Statistics<int, int, 1> statistics_1_median_reference("test8");
+  Statistics<int, int, 10> statistics_10_median_reference("test9");
 
   for (int i = 0; i < 10; ++i) {
     statistics_1.AddSample(i * i * i, i * 5);
@@ -145,7 +145,7 @@
 }
 
 TEST(StatisticsTest, MedianWithOverflow) {
-  Statistics<int, int, 3> statistics;
+  Statistics<int, int, 3> statistics("test10");
 
   statistics.AddSample(1, 1);
   statistics.AddSample(11, 1);
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index 34b2ffe..2ede496 100644
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -62,6 +62,7 @@
 _TESTS_NO_SIGNAL = [
     'allow_eval',
     'compression_test',
+    'default_site_can_load',
     'disable_eval_with_csp',
     'http_cache',
     'persistent_cookie',
@@ -71,6 +72,8 @@
     'web_platform_tests',
     'web_worker_test',
     'worker_csp_test',
+    'service_worker_get_registrations_test',
+    'service_worker_fetch_test',
     'service_worker_message_test',
     'service_worker_test',
 ]
@@ -109,7 +112,7 @@
     super(BlackBoxTestCase, cls).tearDownClass()
     logging.info('Done %s', cls.__name__)
 
-  def CreateCobaltRunner(self, url, target_params=None):
+  def CreateCobaltRunner(self, url=None, target_params=None):
     all_target_params = list(target_params) if target_params else []
     if _launcher_params.target_params is not None:
       all_target_params += _launcher_params.target_params
diff --git a/cobalt/black_box_tests/testdata/black_box_js_test_utils.js b/cobalt/black_box_tests/testdata/black_box_js_test_utils.js
index ed1d883..2227ff5 100644
--- a/cobalt/black_box_tests/testdata/black_box_js_test_utils.js
+++ b/cobalt/black_box_tests/testdata/black_box_js_test_utils.js
@@ -1,3 +1,17 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // This file provides JavaScript-side environment and test utility functions.
 // The following constants and logics are used in communication with the python
 // tests. Anyone making changes here should also ensure corresponding changes
@@ -9,59 +23,102 @@
 const SETUP_DONE_MESSAGE = 'JavaScript_setup_done';
 const EFFECT_AFTER_VISIBILITY_CHANGE_TIMEOUT_SECONDS = 5;
 
-function notReached() {
-  document.body.setAttribute(TEST_STATUS_ELEMENT_NAME, FAILURE_MESSAGE);
+let tearDown = () => {};
+function setTearDown(fn) {
+  tearDown = fn;
 }
 
-function assertTrue(result) {
-  if (!result){
-    notReached();
+function failed() {
+  return document.body.getAttribute(TEST_STATUS_ELEMENT_NAME) === FAILURE_MESSAGE;
+}
+
+function printError(error) {
+  if (!error) {
+    return;
+  }
+  if (!error.message && !error.stack) {
+    console.error(error);
+    return;
+  }
+  if (error.stack) {
+    console.error('\n' + error.stack);
+    return;
+  }
+  console.error(error.message);
+}
+
+function notReached(error) {
+  if (failed()) {
+    console.error('Already failed.');
+    return;
+  }
+  Promise.resolve(tearDown()).then(() => {
+    printError(error);
+    document.body.setAttribute(TEST_STATUS_ELEMENT_NAME, FAILURE_MESSAGE);
+  });
+}
+
+function assertTrue(result, msg) {
+  if (!result) {
+    const errorMessage = '\n' +
+      'Black Box Test Assertion failed: \n' +
+      'expected: true\n' +
+      'but got:  ' + result +
+      (msg ? `\n${msg}` : '');
+    notReached(new Error(errorMessage));
   }
 }
 
 function assertFalse(result) {
-  if (result){
-    notReached();
+  if (result) {
+    const errorMessage = '\n' +
+      'Black Box Test Assertion failed: \n' +
+      'expected: false\n' +
+      'but got:  ' + result +
+      (msg ? `\n${msg}` : '');
+    notReached(new Error(errorMessage));
   }
 }
 
-function assertEqual(expected, result) {
+function assertEqual(expected, result, msg) {
   if (expected !== result) {
-    console.log('\n' +
+    const errorMessage = '\n' +
       'Black Box Test Equal Test Assertion failed: \n' +
       'expected: ' + expected + '\n' +
-      'but got:  ' + result);
-    notReached();
+      'but got:  ' + result +
+      (msg ? `\n${msg}` : '');
+    notReached(new Error(errorMessage));
   }
 }
 
-function assertIncludes(expected, result) {
+function assertIncludes(expected, result, msg) {
   if (!result || !result.includes(expected)) {
-    console.log('\n' +
+    const errorMessage = '\n' +
       'Black Box Test Equal Test Assertion failed: \n' +
       'expected includes: ' + expected + '\n' +
-      'but got:  ' + result);
-    notReached();
+      'but got:  ' + result +
+      (msg ? `\n${msg}` : '');
+    notReached(new Error(errorMessage));
   }
 }
 
-function assertNotEqual(expected, result) {
+function assertNotEqual(expected, result, msg) {
   if (expected === result) {
-    console.log('\n' +
+    const errorMessage = '\n' +
       'Black Box Test Unequal Assertion failed: \n' +
-      'both are: ' + expected);
-    notReached();
+      'both are: ' + expected +
+      (msg ? `\n${msg}` : '');
+    notReached(new Error(errorMessage));
   }
 }
 
 function onEndTest() {
-  if (document.body.getAttribute(TEST_STATUS_ELEMENT_NAME) === FAILURE_MESSAGE) {
+  if (failed()) {
     return;
   }
   document.body.setAttribute(TEST_STATUS_ELEMENT_NAME, SUCCESS_MESSAGE);
 }
 
-
 class TimerTestCase {
   constructor(name, ExpectedCallTimes) {
     this.name = name;
diff --git a/cobalt/black_box_tests/testdata/http_cache.html b/cobalt/black_box_tests/testdata/http_cache.html
index e1f2e48..71dda62 100644
--- a/cobalt/black_box_tests/testdata/http_cache.html
+++ b/cobalt/black_box_tests/testdata/http_cache.html
@@ -50,6 +50,9 @@
     // the same url is used both times.
     var timestampString = '?t=' + initialLoadTime;
 
+    // Ensure that the cache is enabled.
+    h5vcc.storage.enableCache();
+
     // Validate the tranferSize attribute of each performance entry specified
     // in CACHED_ELEMENT_LOCATIONS and UNCACHED_ELEMENT_LOCATIONS.
     function checkTransferSizes() {
diff --git a/cobalt/black_box_tests/testdata/service_worker_fetch_resource.js b/cobalt/black_box_tests/testdata/service_worker_fetch_resource.js
new file mode 100644
index 0000000..ccee89c
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_fetch_resource.js
@@ -0,0 +1 @@
+window.activeServiceWorker.postMessage("script-not-intercepted");
diff --git a/cobalt/black_box_tests/testdata/service_worker_fetch_test.html b/cobalt/black_box_tests/testdata/service_worker_fetch_test.html
new file mode 100644
index 0000000..a325b69
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_fetch_test.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<!--
+  Copyright 2022 The Cobalt Authors. All Rights Reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!--
+  This is a basic test of the fetch event for service workers.
+-->
+
+<html>
+<head>
+  <title>Cobalt Service Worker Fetch Test</title>
+  <script src='black_box_js_test_utils.js'></script>
+</head>
+<body>
+  <script>
+    const unregisterAll = () => navigator.serviceWorker.getRegistrations().then(registrations =>
+        Promise.all(registrations.map(r => r.unregister())));
+    const unregisterAndNotReached = () => unregisterAll().then(notReached);
+
+    const fetch_resource = () => fetch('service_worker_fetch_resource.js').then(r => r.text());
+    const timeoutId = window.setTimeout(unregisterAndNotReached, 10000);
+    window.activeServiceWorker = null;
+    navigator.serviceWorker.onmessage = event => {
+      if (event.data.test && event.data.test.indexOf('check-script') === 0) {
+        const script = document.createElement('script');
+        script.src = 'service_worker_fetch_resource.js';
+        document.body.appendChild(script);
+        return;
+      }
+      if (event.data.test && event.data.test.indexOf('check-fetch') === 0) {
+        if (event.data.result === 'failed') {
+          unregisterAndNotReached();
+          return;
+        }
+        fetch_resource().then(body => {
+          window.activeServiceWorker.postMessage({test: event.data.test, result: body});
+        });
+        return;
+      }
+      if (event.data === 'end-test') {
+        unregisterAll().then(() => {
+          clearTimeout(timeoutId);
+          onEndTest();
+        });
+        return;
+      }
+      unregisterAndNotReached();
+    };
+
+    navigator.serviceWorker.ready.then(registration => {
+      window.activeServiceWorker = registration.active;
+      window.activeServiceWorker.postMessage('start-test');
+    });
+
+    unregisterAll().then(fetch_resource).then(body => {
+      if ('window.activeServiceWorker.postMessage("script-not-intercepted");\n' !== body) {
+        unregisterAndNotReached();
+        return;
+      }
+      navigator.serviceWorker.register('service_worker_fetch_test.js').catch(() => {
+        unregisterAndNotReached();
+      });
+    });
+
+    setupFinished();
+  </script>
+</body>
+</html>
diff --git a/cobalt/black_box_tests/testdata/service_worker_fetch_test.js b/cobalt/black_box_tests/testdata/service_worker_fetch_test.js
new file mode 100644
index 0000000..22da79e
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_fetch_test.js
@@ -0,0 +1,94 @@
+// Copyright 2022 The Cobalt Authors. 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.
+
+const interceptedBody = 'window.activeServiceWorker.postMessage("script-intercepted");';
+let shouldIntercept = false;
+
+const postMessage = message => {
+  const options = {
+    includeUncontrolled: false, type: 'window'
+  };
+  self.clients.matchAll(options).then(clients => {
+    clients.forEach(c => {
+      c.postMessage(message);
+    });
+  });
+};
+
+self.addEventListener("install", event => {
+  self.skipWaiting();
+});
+
+self.addEventListener('activate', event => {
+  self.clients.claim();
+});
+
+let fetchEventCount = 0;
+let exceptScriptIntercepted = false;
+
+self.addEventListener('message', event => {
+  if (event.data === 'start-test') {
+    shouldIntercept = true;
+    postMessage({test: 'check-fetch-intercepted'});
+    return;
+  }
+  if (event.data.test === 'check-fetch-intercepted') {
+    if (interceptedBody !== event.data.result) {
+      postMessage({test: 'check-fetch-intercepted', result: 'failed'});
+      return;
+    }
+    shouldIntercept = false;
+    postMessage({test: 'check-fetch-not-intercepted'});
+    return;
+  }
+  if (event.data.test === 'check-fetch-not-intercepted') {
+    if (interceptedBody === event.data.result) {
+      postMessage({test: 'check-fetch-not-intercepted', result: 'failed'});
+      return;
+    }
+    shouldIntercept = true;
+    exceptScriptIntercepted = true;
+    postMessage({test: 'check-script-intercepted'});
+    return;
+  }
+  if (event.data === 'script-intercepted') {
+    if (!exceptScriptIntercepted) {
+      postMessage('script-intercepted-unexpected');
+    }
+    shouldIntercept = false;
+    exceptScriptIntercepted = false;
+    postMessage({test: 'check-script-not-intercepted'})
+    return;
+  }
+  if (event.data === 'script-not-intercepted') {
+    if (exceptScriptIntercepted) {
+      postMessage('script-not-intercepted-unexpected');
+      return;
+    }
+    if (fetchEventCount !== 4) {
+      postMessage('fetch-event-count-incorrect');
+      return;
+    }
+    postMessage('end-test');
+    return;
+  }
+  postMessage(`${JSON.stringify(event.data)}-unexpected-message`);
+});
+
+self.addEventListener('fetch', event => {
+  fetchEventCount++;
+  if (shouldIntercept) {
+    event.respondWith(Promise.resolve(new Response(interceptedBody)));
+  }
+});
diff --git a/cobalt/black_box_tests/testdata/service_worker_get_registrations_test.html b/cobalt/black_box_tests/testdata/service_worker_get_registrations_test.html
new file mode 100644
index 0000000..21cf71d
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_get_registrations_test.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<!--
+  Copyright 2022 The Cobalt Authors. All Rights Reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!--
+  This is a basic test of the getRegistrations() for service workers.
+-->
+
+<html>
+<head>
+  <title>Cobalt Service Worker getRegistrations() Test</title>
+  <script src='black_box_js_test_utils.js'></script>
+</head>
+<body>
+  <script>
+    // TODO: check multiple registrations.
+    const unregisterAll = () => navigator.serviceWorker.getRegistrations().then(registrations =>
+        Promise.all(registrations.map(r => r.unregister())));
+    const fail = msg => {
+      if (msg) {
+        console.error(msg);
+      }
+      unregisterAll().then(notReached);
+    };
+    const timeoutId = window.setTimeout(fail, 10000);
+    const success = () => unregisterAll().then(() => {
+      clearTimeout(timeoutId);
+      onEndTest();
+    });
+    const assertEquals = (expected, actual, msg) => {
+      if (expected !== actual) {
+        const errorMessage = `Expected: '${expected}', but was '${actual}'`;
+        fail(msg ? `${msg}(${errorMessage})` : errorMessage);
+      }
+    };
+    const workerPostMessage = message => navigator.serviceWorker.getRegistrations()
+      .then(registrations => {
+        let sent = false;
+        registrations.forEach(registration => {
+          if (registration.active) {
+            registration.active.postMessage(message);
+            sent = true;
+          }
+        });
+        if (!sent) {
+          fail('Unable to post message to active service worker.');
+        }
+      });
+    let activeServiceWorker = null;
+    navigator.serviceWorker.onmessage = event => {
+      if (event.data === 'check-get-registrations') {
+        navigator.serviceWorker.getRegistrations().then(registrations => {
+          assertEquals(1, registrations.length);
+          console.log(JSON.stringify(registrations[0]));
+          workerPostMessage('check-get-registrations-ok');
+        });
+        return;
+      }
+      if (event.data === 'end-test') {
+        success();
+        return;
+      }
+      fail();
+    };
+
+    unregisterAll()
+      .then(() => navigator.serviceWorker.getRegistrations())
+      .then(registrations => {
+        assertEquals(0, registrations.length);
+      })
+      .then(() => navigator.serviceWorker.register('service_worker_get_registrations_test.js'))
+      .catch(fail);
+
+    navigator.serviceWorker.ready.then(() => {
+      workerPostMessage('start-test');
+    });
+
+    setupFinished();
+  </script>
+</body>
+</html>
diff --git a/cobalt/black_box_tests/testdata/service_worker_get_registrations_test.js b/cobalt/black_box_tests/testdata/service_worker_get_registrations_test.js
new file mode 100644
index 0000000..7d1be14
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_get_registrations_test.js
@@ -0,0 +1,44 @@
+// Copyright 2022 The Cobalt Authors. 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.
+
+const postMessage = message => {
+  const options = {
+    includeUncontrolled: false, type: 'window'
+  };
+  self.clients.matchAll(options).then(clients => {
+    clients.forEach(c => {
+      c.postMessage(message);
+    });
+  });
+};
+
+self.addEventListener("install", event => {
+  self.skipWaiting();
+});
+
+self.addEventListener('activate', event => {
+  self.clients.claim();
+});
+
+self.addEventListener('message', event => {
+  if (event.data === 'start-test') {
+    postMessage('check-get-registrations');
+    return;
+  }
+  if (event.data === 'check-get-registrations-ok') {
+    postMessage('end-test');
+    return;
+  }
+  postMessage(`${JSON.stringify(event.data)}-unexpected-message`);
+});
diff --git a/cobalt/black_box_tests/testdata/service_worker_message_test.html b/cobalt/black_box_tests/testdata/service_worker_message_test.html
index ac8f3f4..8b74c05 100644
--- a/cobalt/black_box_tests/testdata/service_worker_message_test.html
+++ b/cobalt/black_box_tests/testdata/service_worker_message_test.html
@@ -50,24 +50,48 @@
                     assertEqual('BarString', event.data.bar);
                     break;
                 case 4:
-                    assertEqual('Service Worker received a message',
+                    assertEqual('Service Worker received a message from source',
                         event.data.text);
                     assertEqual('{"message":"Registration ready resolved."}',
                         JSON.stringify(event.data.data));
                     assertEqual('[object ExtendableMessageEvent]',
                         event.data.event_type);
+                    assertEqual('visible', event.data.visibilityState);
+                    assertEqual(true, event.data.focused);
                     break;
                 case 5:
+                    assertEqual('Service Worker received a message from client',
+                        event.data.text);
+                    assertEqual('{"message":"Registration ready resolved."}',
+                        JSON.stringify(event.data.data));
+                    assertEqual('[object ExtendableMessageEvent]',
+                        event.data.event_type);
+                    assertEqual('visible', event.data.visibilityState);
+                    assertEqual(true, event.data.focused);
+                    break;
+                case 6:
                     assertEqual('Foo', event.data.text);
                     assertEqual('BarString', event.data.bar);
                     break;
-                case 6:
-                    assertEqual('Service Worker received a message',
+                case 7:
+                    assertEqual('Service Worker received a message from source',
                         event.data.text);
                     assertEqual('"Controllerchange event received."',
                         JSON.stringify(event.data.data));
                     assertEqual('[object ExtendableMessageEvent]',
                         event.data.event_type);
+                    assertEqual('visible', event.data.visibilityState);
+                    assertEqual(true, event.data.focused);
+                    break;
+                case 8:
+                    assertEqual('Service Worker received a message from client',
+                        event.data.text);
+                    assertEqual('"Controllerchange event received."',
+                        JSON.stringify(event.data.data));
+                    assertEqual('[object ExtendableMessageEvent]',
+                        event.data.event_type);
+                    assertEqual('visible', event.data.visibilityState);
+                    assertEqual(true, event.data.focused);
                     break;
                 default:
                     notReached();
@@ -96,7 +120,7 @@
                         .then(function (success) {
                             console.log('(Expected) unregister success :',
                                 success);
-                            count_event(7);
+                            count_event(9);
                         }, function (error) {
                             console.log('(Unexpected) unregister ' +
                                 `${error}`, error);
@@ -119,7 +143,7 @@
         window.setTimeout(
             () => {
                 console.log('Events:', expected_event_count)
-                assertEqual(7, expected_event_count);
+                assertEqual(9, expected_event_count);
                 onEndTest();
             }, 2000);
 
diff --git a/cobalt/black_box_tests/testdata/service_worker_message_test.js b/cobalt/black_box_tests/testdata/service_worker_message_test.js
index 20a3334..956924a 100644
--- a/cobalt/black_box_tests/testdata/service_worker_message_test.js
+++ b/cobalt/black_box_tests/testdata/service_worker_message_test.js
@@ -27,10 +27,21 @@
       }
     }));
     event.waitUntil(self.clients.matchAll(options).then(function (clients) {
+      message = {
+        text: 'Service Worker received a message from source',
+        data: event.data,
+        visibilityState: event.source.visibilityState,
+        focused: event.source.focused,
+        event_type: `${event}`
+      }
+      console.log('Posting to client:', JSON.stringify(message));
+      event.source.postMessage(message);
       for (var i = 0; i < clients.length; i++) {
         message = {
-          text: 'Service Worker received a message',
+          text: 'Service Worker received a message from client',
           data: event.data,
+          visibilityState: clients[i].visibilityState,
+          focused: clients[i].focused,
           event_type: `${event}`
         }
         console.log('Posting to client:', JSON.stringify(message));
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.html b/cobalt/black_box_tests/testdata/service_worker_test.html
index 7450faf..34782ec 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test.html
+++ b/cobalt/black_box_tests/testdata/service_worker_test.html
@@ -27,401 +27,7 @@
 </head>
 
 <body>
-    <script>
-        var expected_event_count = 0;
-
-        function count_event(expected_value) {
-            expected_event_count += 1;
-            console.log('Expected events counted:', expected_event_count)
-            if (expected_value) {
-                assertEqual(expected_value, expected_event_count)
-            }
-        }
-
-        function test_claimable_worker() {
-            console.log('Adding ready promise.');
-            navigator.serviceWorker.ready.then(function (
-                registration) {
-                assertNotEqual(null, registration);
-                console.log('(Expected) Registration ready promise',
-                    registration, ' with active worker ',
-                    registration.active);
-                assertNotEqual(null, registration.active);
-                registration.active.postMessage(
-                    'Registration ready received for claimable worker.');
-                count_event();
-                registration.unregister()
-                    .then(function (success) {
-                        // Even a claimed registration will successfully
-                        // unregister because unregistration takes effect after
-                        // the page is unloaded, so it's not blocked by being
-                        // the active service worker.
-                        console.log('(Expected) unregister success :',
-                            success);
-                        count_event(41);
-                    }, function (error) {
-                        console.log('(Unexpected) unregister ' +
-                            `${error}`, error);
-                        assertIncludes('SecurityError: ', `${error}`);
-                        notReached();
-                    });
-                console.log('unregister started.');
-
-            });
-
-            navigator.serviceWorker.register('service_worker_test_claimable.js', {
-                scope: './',
-            }).then(function (registration) {
-                worker = registration.installing;
-                if (worker) {
-                    console.log('claimable worker registered (installing).');
-                    worker.postMessage(
-                        'Registration successful, current worker state is ' +
-                        'installing.');
-                }
-                worker = registration.waiting;
-                if (worker) {
-                    console.log('claimable worker registered (waiting).');
-                    worker.postMessage(
-                        'Registration successful, current worker state is ' +
-                        'waiting.');
-                }
-                worker = registration.active;
-                if (worker) {
-                    console.log('claimable worker registered (active).');
-                    worker.postMessage(
-                        'Registration successful, current worker state is ' +
-                        'active.');
-                }
-             }, function (error) {
-                console.log('(Unexpected) :', error);
-                notReached();
-            });
-        }
-
-        console.log('Starting tests');
-        if ('serviceWorker' in navigator) {
-            navigator.serviceWorker.ready.then(function (registration) {
-                assertNotEqual(null, registration);
-                console.log('(Expected) Registration ready promise',
-                    registration, 'with active worker ', registration.active);
-                assertNotEqual(null, registration.active);
-                count_event();
-            });
-
-            navigator.serviceWorker.oncontrollerchange = function (event) {
-                console.log('Got oncontrollerchange event', event.target);
-                count_event(40);
-            }
-
-            navigator.serviceWorker.onmessage = function (event) {
-                console.log('Got onmessage event', event.target, event.data);
-            }
-
-            navigator.serviceWorker.register('http://..:..')
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test invalid script URL:', error);
-                    assertIncludes('TypeError', `${error}`);
-                    count_event(1);
-                });
-            navigator.serviceWorker.register('arg:service_worker_test.js')
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test script URL that is not http ' +
-                        'or https:', error);
-                    assertIncludes('TypeError', `${error}`);
-                    count_event(2);
-                });
-            navigator.serviceWorker.register('http:%2fservice_worker_test.js')
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test script URL with escaped slash:',
-                        error);
-                    assertIncludes('TypeError', `${error}`);
-                    count_event(3);
-                });
-            navigator.serviceWorker.register('service_worker_test.js', {
-                scope: 'http://..:..',
-            })
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test invalid scope URL:', error);
-                    assertIncludes('TypeError', `${error}`);
-                    count_event(4);
-                });
-            navigator.serviceWorker.register('service_worker_test.js', {
-                scope: 'arg:/',
-            })
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test scope URL that is not http ' +
-                        'or https:', error);
-                    assertIncludes('TypeError', `${error}`);
-                    count_event(5);
-                });
-            navigator.serviceWorker.register('service_worker_test.js', {
-                scope: '/%5c',
-            })
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test scope URL with escaped slash:',
-                        error);
-                    assertIncludes('TypeError', `${error}`);
-                    count_event(6);
-                });
-            // Repeat a few times to test the 'equivalent job' and finish job
-            // logic.
-            for (let repeated = 0; repeated < 15; repeated++) {
-                navigator.serviceWorker.register('http://www.example.com/', {
-                    scope: '/',
-                })
-                    .then(function (registration) {
-                        console.log('(Unexpected) :', registration);
-                        notReached();
-                    }, function (error) {
-                        console.log('(Expected) Test Script URL is not ' +
-                            `trusted: ${error}`, error);
-                        assertIncludes('SecurityError: ', `${error}`);
-                        count_event();
-                    });
-            }
-            navigator.serviceWorker.register('http://127.0.0.100:2345/')
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test script URL with different ' +
-                        `origin: ${error}`, error);
-                    assertIncludes('SecurityError: ', `${error}`);
-                    count_event();
-                });
-
-            navigator.serviceWorker.register('service_worker_test.js', {
-                scope: 'http://127.0.0.100:2345/',
-            })
-                .then(function (registration) {
-                    console.log('(Unexpected) :', registration);
-                    notReached();
-                }, function (error) {
-                    console.log('(Expected) Test scope URL with different ' +
-                        `origin: ${error}`, error);
-                    assertIncludes('SecurityError: ', `${error}`);
-                    count_event();
-                });
-
-            // Finally, test a succeeding registration.
-            navigator.serviceWorker.register('service_worker_test.js', {
-                scope: 'registration/scope',
-            })
-                .then(function (registration) {
-                    console.log('(Expected) Registration Succeeded:',
-                        registration);
-                    assertNotEqual(null, registration);
-                    // The default value for RegistrationOptions.type is
-                    // 'imports'.
-                    assertEqual('imports', registration.updateViaCache);
-                    assertTrue(registration.scope.endsWith(
-                        'registration/scope'));
-                    count_event(24);
-
-                    worker = registration.installing;
-                    if (worker) {
-                        worker.postMessage(
-                            'Registration with scope successful, current ' +
-                            'worker state is installing.');
-                    }
-                    worker = registration.waiting;
-                    if (worker) {
-                        worker.postMessage(
-                            'Registration with scope successful, current ' +
-                            'worker state is waiting.');
-                    }
-                    worker = registration.active;
-                    if (worker) {
-                        worker.postMessage(
-                            'Registration with scope successful, current ' +
-                            'worker state is active.');
-                    }
-
-                    registration.onupdatefound = function (event) {
-                        console.log('Got onupdatefound event',
-                            event.target.scope);
-                        assertTrue(event.target.scope.endsWith(
-                            'registration/scope'));
-                    }
-
-                    // Check that the registration has an activated worker after
-                    // some time. The delay used here should be long enough for
-                    // the service worker to complete activating and have the
-                    // state 'activated'. It has to be longer than the combined
-                    // delays in the install or activate event handlers.
-                    window.setTimeout(function () {
-                        // Since these events are asynchronous, the service
-                        // worker can be either of these states.
-                        console.log(
-                            'Registration active check after timeout',
-                            registration);
-                        assertNotEqual(null, registration.active);
-                        registration.active.postMessage(
-                            'Registration is active after waiting.');
-                        console.log('Registration active',
-                            registration.active, 'state:',
-                            registration.active.state);
-                        assertEqual('activated', registration.active.state);
-                        count_event(31);
-                        registration.active.onstatechange = function (event) {
-                            console.log('Got onstatechange event',
-                                event.target.state);
-                        }
-
-                        // Repeat a few times to test the 'equivalent job' and
-                        // finish job logic.
-                        for (let repeated = 0; repeated < 5; repeated++) {
-                            registration.update().then(function (registration) {
-                                console.log('(Expected) Registration update ' +
-                                    'Succeeded:', registration);
-                                assertNotEqual(null, registration);
-                                count_event();
-                            }, function (error) {
-                                console.log('(Unexpected) :', error);
-                                notReached();
-                            });
-                        }
-
-                        registration.unregister()
-                            .then(function (success) {
-                                console.log('(Expected) unregister success :',
-                                    success);
-                                count_event(37);
-                                // Finally, test getRegistration for the unregistered scope.
-                                navigator.serviceWorker.getRegistration(
-                                    'registration/scope')
-                                    .then(function (registration) {
-                                        console.log('(Expected) getRegistration Succeeded:',
-                                            registration);
-                                        assertTrue(null == registration ||
-                                            undefined == registration);
-                                        count_event(38);
-                                    }, function (error) {
-                                        console.log('(Unexpected) :', error);
-                                        notReached();
-                                    });
-
-                                test_claimable_worker();
-                            }, function (error) {
-                                console.log('(Unexpected) unregister ' +
-                                    `${error}`, error);
-                                assertIncludes('SecurityError: ', `${error}`);
-                                notReached();
-                            });
-                    }, 1000);
-
-                    // Test getRegistration for a non-registered scope.
-                    navigator.serviceWorker.getRegistration('/bo/gus')
-                        .then(function (registration) {
-                            console.log('(Expected) getRegistration Succeeded:',
-                                registration);
-                            assertTrue(null == registration ||
-                                undefined == registration);
-                            count_event();
-                        }, function (error) {
-                            console.log('(Unexpected) :', error);
-                            notReached();
-                        });
-
-                    // Test getRegistration for a deeper registered scope.
-                    navigator.serviceWorker.getRegistration(
-                        'registration/scope/deeper')
-                        .then(function (registration) {
-                            console.log('(Expected) getRegistration Succeeded:',
-                                registration);
-                            assertNotEqual(null, registration);
-                            assertEqual('imports', registration.updateViaCache);
-                            assertTrue(registration.scope.endsWith(
-                                'registration/scope'));
-                            count_event();
-                        }, function (error) {
-                            console.log('(Unexpected) :', error);
-                            notReached();
-                        });
-
-                    // Test getRegistration for a shallower registered scope.
-                    navigator.serviceWorker.getRegistration('registration')
-                        .then(function (registration) {
-                            console.log('(Expected) getRegistration Succeeded:',
-                                registration);
-                            assertTrue(null == registration ||
-                                undefined == registration);
-                            count_event();
-                        }, function (error) {
-                            console.log('(Unexpected) :', error);
-                            notReached();
-                        });
-
-                    // Test getRegistration for a non-registered scope.
-                    navigator.serviceWorker.getRegistration()
-                        .then(function (registration) {
-                            console.log('(Expected) getRegistration Succeeded:',
-                                registration);
-                            // TODO(b/234659851): Investigate whether this
-                            // should return a registration or not, in this case
-                            // where there is a registration with a scope.
-                            assertTrue(null == registration ||
-                                undefined == registration);
-                            count_event();
-                        }, function (error) {
-                            console.log('(Unexpected) :', error);
-                            notReached();
-                        });
-
-                    // Finally, test getRegistration for a registered scope.
-                    navigator.serviceWorker.getRegistration(
-                        'registration/scope')
-                        .then(function (registration) {
-                            console.log('(Expected) getRegistration Succeeded:',
-                                registration);
-                            assertNotEqual(null, registration);
-                            assertEqual('imports', registration.updateViaCache);
-                            assertTrue(registration.scope.endsWith(
-                                'registration/scope'));
-                            count_event();
-                        }, function (error) {
-                            console.log('(Unexpected) :', error);
-                            notReached();
-                        });
-                }, function (error) {
-                    console.log('(Unexpected) :', error);
-                    notReached();
-                });
-        }
-
-        assertEqual(undefined, self.clients);
-
-        console.log('Done starting tests');
-        setupFinished();
-        // This delay has to be long enough to guarantee that the test has
-        // finished.
-        window.setTimeout(
-            () => {
-                console.log('Events:', expected_event_count)
-                assertEqual(41, expected_event_count);
-                onEndTest();
-            }, 7000);
-
-    </script>
+    <script src='service_worker_test_script.js'></script>
 </body>
 
 </html>
diff --git a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
index 191c964..de15b34 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
+++ b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
@@ -37,7 +37,7 @@
   console.log('oninstall event received', e);
   // Using a delay long enough to make it clearly visible in the log that the
   // event is extended, and is delaying the activate event and ready promise.
-  e.waitUntil(delay_promise(1000).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n')));
+  e.waitUntil(delay_promise(100).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n')));
 }
 
 self.onactivate = function (e) {
@@ -53,7 +53,7 @@
     };
   // Using a delay long enough to make it clearly visible in the log that the
   // event is extended.
-    e.waitUntil(delay_promise(1000).then(function () {
+    e.waitUntil(delay_promise(100).then(function () {
       console.log('self.clients.matchAll(options)');
       e.waitUntil(self.clients.matchAll(options).then(function (clients) {
         console.log('(Expected) self.clients.matchAll():', clients.length, clients);
diff --git a/cobalt/black_box_tests/testdata/service_worker_test_script.js b/cobalt/black_box_tests/testdata/service_worker_test_script.js
new file mode 100644
index 0000000..20ce1f7
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_test_script.js
@@ -0,0 +1,295 @@
+// Copyright 2022 The Cobalt Authors. 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.
+
+const eventCounts = {};
+const countEvent = (fnName, expectedEventCount) => {
+  if (fnName in eventCounts) {
+    eventCounts[fnName]++;
+  } else {
+    eventCounts[fnName] = 1;
+  }
+  if (expectedEventCount) {
+    assertEqual(expectedEventCount, eventCounts[fnName])
+  }
+};
+
+const unregisterAll = () => navigator.serviceWorker.getRegistrations().then(registrations =>
+        Promise.all(registrations.map(r => r.unregister())));
+setTearDown(unregisterAll);
+const err = msg => new Error(msg);
+const fail = msg => {
+  const userDefinedError = new Error(msg);
+  return error => {
+    printError(userDefinedError);
+    return unregisterAll().then(() => notReached(error));
+  };
+};
+const timeoutId = window.setTimeout(fail('timeout'), 10000);
+const done = () => {
+  window.clearTimeout(timeoutId);
+  console.log('Done!');
+  onEndTest();
+};
+
+const expectEquals = (expected, actual, msg) => {
+  window.assertEqual(expected, actual, new Error(msg || ''));
+};
+
+setupFinished();
+
+assertFalse(!!self.clients);
+assertTrue(!!navigator.serviceWorker);
+
+navigator.serviceWorker.ready.then(registration => {
+  assertTrue(!!registration);
+  assertTrue(!!registration.active);
+  countEvent('testMain', /*expectedEventCount=*/1);
+});
+
+navigator.serviceWorker.oncontrollerchange = event => {
+ countEvent('testMain', /*expectedEventCount=*/2);
+};
+
+const promiseForEach = (xs, fn) => new Promise((resolve, reject) => {
+  let index = -1;
+  const doNext = () => {
+    if (failed()) {
+      reject(new Error(`Tests failed. Current sequence index is ${index}.`));
+    }
+    index++;
+    if (index === xs.length) {
+      resolve();
+      return;
+    }
+    fn(xs[index]).then(doNext).catch(reject);
+  };
+  doNext();
+});
+
+const promiseSequence = fns => promiseForEach(fns, fn => fn());
+
+const promiseMap = (xs, fn) => Promise.all(xs.map(fn));
+
+const checkInvalidUrls = () => promiseForEach(
+  [
+    'http://..:..',
+    'arg:service_worker_test_worker.js',
+    'http:%2fservice_worker_test_worker.js',
+  ],
+  url => navigator.serviceWorker.register(url)
+      .then(fail(`URL ${url} did not raise error.`))
+      .catch(error => {
+        assertEqual('TypeError', error.name, `url: ${url}`);
+      })
+);
+
+const checkInvalidScopes = () => promiseForEach(
+  [
+    ['service_worker_test_worker.js', {scope: 'arg:/'}, 'TypeError'],
+    ['service_worker_test_worker.js', {scope: '/foo'}, 'SecurityError'],
+    ['service_worker_test_worker.js', {scope: '/%5c'}, 'TypeError'],
+  ],
+  args => navigator.serviceWorker.register(args[0], args[1])
+      .then(fail(`Scope ${args[1].scope} did not raise error.`))
+      .catch(error => {
+        assertEqual(args[2], error.name);
+      })
+);
+
+const checkNotTrustedAndEquivalentJob = () => promiseMap(
+  Array.from({length: 15}),
+  () => navigator.serviceWorker.register('http://www.example.com/', {scope: '/'})
+      .then(fail('Untrusted URL did not raise error'))
+      .catch(error => {
+        assertEqual('SecurityError', error.name);
+      })
+);
+
+const checkSuccessfulRegistration = () => Promise.all([
+  navigator.serviceWorker.register('service_worker_test_worker.js', {
+    scope: '/bar/registration/scope',
+  })
+  .then(registration => {
+    assertTrue(!!registration);
+    assertEqual('imports', registration.updateViaCache);
+    assertTrue(registration.scope.endsWith('bar/registration/scope'));
+
+    let worker = registration.installing;
+    if (worker) {
+      worker.postMessage(
+          'Registration with scope successful, ' +
+          'current worker state is installing.');
+    }
+    worker = registration.waiting;
+    if (worker) {
+      worker.postMessage(
+          'Registration with scope successful, ' +
+          'current worker state is waiting.');
+    }
+    worker = registration.active;
+    if (worker) {
+      worker.postMessage(
+          'Registration with scope successful, ' +
+          'current worker state is active.');
+    }
+
+    registration.onupdatefound = event => {
+      assertTrue(event.target.scope.endsWith('bar/registration/scope'));
+    };
+
+    return navigator.serviceWorker.ready.then(registration => {
+      assertTrue(!!registration.active);
+      registration.active.postMessage('Registration is active after waiting.');
+      assertEqual('activating', registration.active.state);
+
+      return promiseSequence([
+        () => promiseMap(
+          Array.from({length: 5}),
+          () => registration.update()
+              .then(registration => {
+                assertTrue(!!registration);
+              })
+        ).catch(fail('getRegistration() failed.')),
+        () => navigator.serviceWorker.getRegistration('/bo/gus')
+          .then(registration => {
+            assertFalse(!!registration);
+          })
+          .catch(fail('getRegistration() failed.')),
+        () => navigator.serviceWorker.getRegistration(
+            '/bar/registration/scope/deeper')
+          .then(registration => {
+            assertTrue(!!registration);
+            assertEqual('imports', registration.updateViaCache);
+            assertTrue(registration.scope.endsWith(
+                'bar/registration/scope'));
+          })
+          .catch(fail('getRegistration() failed.')),
+        () => navigator.serviceWorker.getRegistration('registration')
+          .then(registration => {
+            assertFalse(!!registration);
+          })
+          .catch(fail('getRegistration() failed.')),
+        () => navigator.serviceWorker.getRegistration()
+          .then(registration => {
+            assertFalse(!!registration);
+          })
+          .catch(fail),
+        () => navigator.serviceWorker.getRegistration(
+            '/bar/registration/scope')
+          .then(registration => {
+            assertTrue(!!registration);
+            assertEqual('imports', registration.updateViaCache);
+            assertTrue(registration.scope.endsWith(
+                'bar/registration/scope'));
+          })
+          .catch(fail('getRegistration() failed.')),
+        () => registration.unregister()
+            .then(success => {
+              assertTrue(success);
+            })
+            .then(() =>
+                navigator.serviceWorker.getRegistration('bar/registration/scope'))
+            .then(registration => {
+              assertFalse(!!registration)
+            })
+            .catch(fail('getRegistration() failed after unregister.')),
+      ]);
+    });
+  }),
+]);
+
+const checkClaimableWorker = () => Promise.all([
+  new Promise((resolve, reject) => {
+    let messageCount = 0;
+    navigator.serviceWorker.onmessage = event => {
+      console.log('onmessage: ' + messageCount)
+      messageCount++;
+      // Expecting two messages:
+      //   - when sent a message
+      //   - when claimed
+      if (messageCount === 2) {
+        setTimeout(resolve);
+        return;
+      }
+    };
+  }),
+  navigator.serviceWorker.ready
+    .then(registration => {
+      assertTrue(!!registration);
+      assertTrue(!!registration.active);
+      registration.
+      registration.active.postMessage(
+          'Registration ready received for claimable worker.');
+      return registration.unregister();
+    })
+    .then(success => {
+      assertTrue(success);
+    })
+    .catch(fail),
+  navigator.serviceWorker.register(
+      'service_worker_test_claimable.js', {scope: './'})
+    .then(registration => {
+      let worker = registration.installing;
+      if (worker) {
+        worker.postMessage(
+            'Registration successful, current worker state is ' +
+            'installing.');
+      }
+      worker = registration.waiting;
+      if (worker) {
+        worker.postMessage(
+            'Registration successful, current worker state is ' +
+              'waiting.');
+      }
+      worker = registration.active;
+      if (worker) {
+        worker.postMessage(
+            'Registration successful, current worker state is ' +
+            'active.');
+      }
+    })
+    .catch(fail),
+]);
+
+promiseSequence([
+  unregisterAll,
+  checkInvalidUrls,
+  checkInvalidScopes,
+  checkNotTrustedAndEquivalentJob,
+  () => navigator.serviceWorker.register('http://127.0.0.100:2345/')
+      .then(fail(`Invalid URL did not raise error.`))
+      .catch(error => {
+        assertEqual('SecurityError', error.name);
+      }),
+  () => navigator.serviceWorker.register('service_worker_test_worker.js', {
+        scope: 'http://127.0.0.100:2345/',
+      })
+      .then(fail(`Invalid scope did not raise error.`))
+      .catch(error => {
+        assertEqual('SecurityError', error.name);
+      }),
+  () => navigator.serviceWorker.register('service_worker_test.html')
+      .then(fail(`HTML did not raise error.`))
+      .catch(error => {
+        assertEqual('SecurityError', error.name);
+      }),
+  () => navigator.serviceWorker.register('service_worker_test_nonexist.js')
+      .then(fail(`Unknown script did not raise error.`))
+      .catch(error => {
+        assertEqual('NetworkError', error.name);
+      }),
+  checkSuccessfulRegistration,
+  unregisterAll,
+  checkClaimableWorker,
+]).then(done).catch(fail('Error not caught.'));
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.js b/cobalt/black_box_tests/testdata/service_worker_test_worker.js
similarity index 100%
rename from cobalt/black_box_tests/testdata/service_worker_test.js
rename to cobalt/black_box_tests/testdata/service_worker_test_worker.js
diff --git a/cobalt/black_box_tests/tests/allow_eval.py b/cobalt/black_box_tests/tests/allow_eval.py
index 503ffbb..4f5137d 100644
--- a/cobalt/black_box_tests/tests/allow_eval.py
+++ b/cobalt/black_box_tests/tests/allow_eval.py
@@ -24,7 +24,7 @@
 class AllowEvalTest(black_box_tests.BlackBoxTestCase):
   """Ensure that client page can use eval() when CSP is missing."""
 
-  def test_simple(self):
+  def test_allow_eval(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/allow_eval.html')
diff --git a/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py b/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py
index 4036853..a40956a 100644
--- a/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py
+++ b/cobalt/black_box_tests/tests/cancel_sync_loads_when_suspended.py
@@ -89,7 +89,7 @@
     except:  # pylint: disable=bare-except
       traceback.print_exc()
 
-  def test_simple(self):
+  def test_cancel_sync_loads_when_suspended(self):
 
     # Step 2. Start Cobalt, and point it to the socket created in Step 1.
     try:
diff --git a/cobalt/black_box_tests/tests/conceal_visibility.py b/cobalt/black_box_tests/tests/conceal_visibility.py
index 5df63de..b4d76f1 100644
--- a/cobalt/black_box_tests/tests/conceal_visibility.py
+++ b/cobalt/black_box_tests/tests/conceal_visibility.py
@@ -24,7 +24,7 @@
 class ConcealVisibilityTest(black_box_tests.BlackBoxTestCase):
   """Verify correct visibility changes during and after conceal event."""
 
-  def test_simple(self):
+  def test_conceal_visibility(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/conceal_visibility.html')
diff --git a/cobalt/black_box_tests/tests/default_site_can_load.py b/cobalt/black_box_tests/tests/default_site_can_load.py
new file mode 100644
index 0000000..591bd2b
--- /dev/null
+++ b/cobalt/black_box_tests/tests/default_site_can_load.py
@@ -0,0 +1,62 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests that Cobalt can load the default site."""
+
+import logging
+import traceback
+import time
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class DefaultSiteCanLoadTest(black_box_tests.BlackBoxTestCase):
+  """Let Cobalt load the default site and perform some basic sanity checks."""
+
+  def test_default_site_can_load(self):
+    try:
+      with ThreadedWebServer(binding_address=self.GetBindingAddress()):
+        # Note: This test will fail when the Internet can not be reached from
+        # the device running Cobalt.
+
+        with self.CreateCobaltRunner() as runner:
+          # Allow for app startup time.
+          logging.info('Wait to allow app startup.')
+          time.sleep(10)
+          logging.info('Wait for html element.')
+          # Wait until there is at least the root element.
+          runner.PollUntilFound('html')
+          # Check that certain elements that should always be there are there,
+          # and remain there for a few seconds.
+          for i in range(10):
+            time.sleep(1)
+            logging.info(
+                ('Checking for consistent html, head, body, script, and div'
+                 ' elements: %d'), i)
+            # Expect at least one <html> (root) element in the page
+            self.assertTrue(runner.FindElements('html'))
+            # Expect at least one <head> element in the page
+            self.assertTrue(runner.FindElements('head'))
+            # Expect at least one <body> element in the page
+            self.assertTrue(runner.FindElements('body'))
+            # Expect at least one <script> element in the page
+            self.assertTrue(runner.FindElements('script'))
+            # Expect at least three <div> elements in the page
+            self.assertTrue(len(runner.FindElements('div')) > 2)
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+      # Consider an exception being thrown as a test failure.
+      self.fail('Test failure')
+    finally:
+      logging.info('Cleaning up.')
diff --git a/cobalt/black_box_tests/tests/disable_eval_with_csp.py b/cobalt/black_box_tests/tests/disable_eval_with_csp.py
index 3fd19bb..82d1076 100644
--- a/cobalt/black_box_tests/tests/disable_eval_with_csp.py
+++ b/cobalt/black_box_tests/tests/disable_eval_with_csp.py
@@ -24,7 +24,7 @@
 class DisableEvalWithCSPTest(black_box_tests.BlackBoxTestCase):
   """Ensure that Cobalt prohibits eval() when CSP is present in client page."""
 
-  def test_simple(self):
+  def test_disable_eval_with_csp(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/disable_eval_with_csp.html')
diff --git a/cobalt/black_box_tests/tests/freeze_timers.py b/cobalt/black_box_tests/tests/freeze_timers.py
index cbcbce3..ba56d58 100644
--- a/cobalt/black_box_tests/tests/freeze_timers.py
+++ b/cobalt/black_box_tests/tests/freeze_timers.py
@@ -26,7 +26,7 @@
 class FreezeVisibilityTest(black_box_tests.BlackBoxTestCase):
   """Verify correct timer suspend and resume during and after freeze event."""
 
-  def test_simple(self):
+  def test_freeze_visibility(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/freeze_timers.html')
diff --git a/cobalt/black_box_tests/tests/freeze_visibility.py b/cobalt/black_box_tests/tests/freeze_visibility.py
index d10c95c..e3b9f51 100644
--- a/cobalt/black_box_tests/tests/freeze_visibility.py
+++ b/cobalt/black_box_tests/tests/freeze_visibility.py
@@ -24,7 +24,7 @@
 class FreezeVisibilityTest(black_box_tests.BlackBoxTestCase):
   """Verify correct visibility changes during and after freeze event."""
 
-  def test_simple(self):
+  def test_freeze_visibility(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/freeze_visibility.html')
diff --git a/cobalt/black_box_tests/tests/http_cache.py b/cobalt/black_box_tests/tests/http_cache.py
index 63719ff..43958ec 100644
--- a/cobalt/black_box_tests/tests/http_cache.py
+++ b/cobalt/black_box_tests/tests/http_cache.py
@@ -43,7 +43,7 @@
 class HttpCacheTest(black_box_tests.BlackBoxTestCase):
   """Load resources, then reload the page and verify."""
 
-  def test_simple(self):
+  def test_http_cache(self):
 
     with ThreadedWebServer(
         binding_address=self.GetBindingAddress(),
diff --git a/cobalt/black_box_tests/tests/override_ua_parameters.py b/cobalt/black_box_tests/tests/override_ua_parameters.py
index 01768ae..3dec188 100644
--- a/cobalt/black_box_tests/tests/override_ua_parameters.py
+++ b/cobalt/black_box_tests/tests/override_ua_parameters.py
@@ -53,7 +53,7 @@
 class OverrideUAParametersTest(black_box_tests.BlackBoxTestCase):
   """Test overriding UA parameters."""
 
-  def test_simple(self):
+  def test_override_ua_parameters(self):
     """Set UA parameters when launching Cobalt."""
 
     with ThreadedWebServer(
diff --git a/cobalt/black_box_tests/tests/persistent_cookie.py b/cobalt/black_box_tests/tests/persistent_cookie.py
index 209bc09..ab30bb4 100644
--- a/cobalt/black_box_tests/tests/persistent_cookie.py
+++ b/cobalt/black_box_tests/tests/persistent_cookie.py
@@ -29,7 +29,7 @@
 
   # The same page has to be used since cookie are stored per URL.
 
-  def test_simple(self):
+  def test_persistent_cookie(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/persistent_cookie.html')
diff --git a/cobalt/black_box_tests/tests/preload_font.py b/cobalt/black_box_tests/tests/preload_font.py
index 5f8a5ae..b43a00a 100644
--- a/cobalt/black_box_tests/tests/preload_font.py
+++ b/cobalt/black_box_tests/tests/preload_font.py
@@ -28,7 +28,7 @@
 class PreloadFontTest(black_box_tests.BlackBoxTestCase):
   """Verify that fonts are loaded correctly after preload event."""
 
-  def test_simple(self):
+  def test_preload_font(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/preload_font.html')
diff --git a/cobalt/black_box_tests/tests/preload_launch_parameter.py b/cobalt/black_box_tests/tests/preload_launch_parameter.py
index a0675af..e23fb38 100644
--- a/cobalt/black_box_tests/tests/preload_launch_parameter.py
+++ b/cobalt/black_box_tests/tests/preload_launch_parameter.py
@@ -24,7 +24,7 @@
 class PreloadLaunchParameterTest(black_box_tests.BlackBoxTestCase):
   """Set a JS timer that expires after exiting preload mode."""
 
-  def test_simple(self):
+  def test_preload_launch_parameter(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/preload_launch_parameter.html')
diff --git a/cobalt/black_box_tests/tests/preload_visibility.py b/cobalt/black_box_tests/tests/preload_visibility.py
index 7d75050..6eab66f 100644
--- a/cobalt/black_box_tests/tests/preload_visibility.py
+++ b/cobalt/black_box_tests/tests/preload_visibility.py
@@ -24,7 +24,7 @@
 class PreloadVisibilityTest(black_box_tests.BlackBoxTestCase):
   """Set a JS timer that expires after exiting preload mode."""
 
-  def test_simple(self):
+  def test_preload_visibility(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/preload_visibility.html')
diff --git a/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py b/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py
index 3359b4f..1420463 100644
--- a/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py
+++ b/cobalt/black_box_tests/tests/retry_async_script_loads_after_suspend.py
@@ -100,7 +100,7 @@
     except:  # pylint: disable=bare-except
       traceback.print_exc()
 
-  def test_simple(self):
+  def test_retry_async_script_loads_after_suspend(self):
 
     # Step 2. Start Cobalt, and point it to the socket created in Step 1.
     try:
diff --git a/cobalt/black_box_tests/tests/service_worker_fetch_test.py b/cobalt/black_box_tests/tests/service_worker_fetch_test.py
new file mode 100644
index 0000000..27dac0e
--- /dev/null
+++ b/cobalt/black_box_tests/tests/service_worker_fetch_test.py
@@ -0,0 +1,27 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests Service Worker Fetch functionality."""
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class ServiceWorkerFetchTest(black_box_tests.BlackBoxTestCase):
+
+  def test_service_worker_fetch(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(file_name='testdata/service_worker_fetch_test.html')
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/service_worker_get_registrations_test.py b/cobalt/black_box_tests/tests/service_worker_get_registrations_test.py
new file mode 100644
index 0000000..ebe059e
--- /dev/null
+++ b/cobalt/black_box_tests/tests/service_worker_get_registrations_test.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests Service Worker getRegistrations() functionality."""
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class ServiceWorkerGetRegistrationsTest(black_box_tests.BlackBoxTestCase):
+
+  def test_service_worker_get_registrations(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(
+          file_name='testdata/service_worker_get_registrations_test.html')
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/service_worker_message_test.py b/cobalt/black_box_tests/tests/service_worker_message_test.py
index 9e92654..b4c4675 100644
--- a/cobalt/black_box_tests/tests/service_worker_message_test.py
+++ b/cobalt/black_box_tests/tests/service_worker_message_test.py
@@ -20,7 +20,7 @@
 class ServiceWorkerMessageTest(black_box_tests.BlackBoxTestCase):
   """Test basic Service Worker functionality."""
 
-  def test_simple(self):
+  def test_service_worker_message(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/service_worker_message_test.html')
diff --git a/cobalt/black_box_tests/tests/service_worker_test.py b/cobalt/black_box_tests/tests/service_worker_test.py
index 72fa974..f66b2dc 100644
--- a/cobalt/black_box_tests/tests/service_worker_test.py
+++ b/cobalt/black_box_tests/tests/service_worker_test.py
@@ -13,16 +13,69 @@
 # limitations under the License.
 """Tests basic Service Worker functionality."""
 
+import os
+from six.moves import SimpleHTTPServer
+
 from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass
 from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
 
+# The base path of the requested assets is the parent directory.
+_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
+
+
+class ServiceWorkerRequestDetector(MakeRequestHandlerClass(_SERVER_ROOT_PATH)):
+  """Proxies everything to SimpleHTTPRequestHandler, except some paths."""
+
+  def end_headers(self):
+    self.send_my_headers()
+
+    SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
+
+  def send_header(self, header, value):
+    # Ensure that the Content-Type for paths ending in '.js' are always
+    # 'text/javascript'.
+    if header == 'Content-Type' and self.path.endswith('.js'):
+      SimpleHTTPServer.SimpleHTTPRequestHandler.send_header(
+          self, header, 'text/javascript')
+    else:
+      SimpleHTTPServer.SimpleHTTPRequestHandler.send_header(self, header, value)
+
+  def send_my_headers(self):
+    # Add 'Service-Worker-Allowed' header for the main service worker scripts.
+    if self.path.endswith('/service_worker_test_worker.js'):
+      self.send_header('Service-Worker-Allowed', '/bar')
+
+  def do_GET(self):  # pylint: disable=invalid-name
+    """Handles HTTP GET requests for resources."""
+    # Check if the request for the main service worker script has the
+    # 'Service-Worker' header.
+    if self.path.endswith(
+        '/service_worker_test_worker.js') or self.path.endswith(
+            '/service_worker_test_claimable.js'):
+      service_worker_request_header = self.headers.get('Service-Worker', '')
+      expected_service_worker_request_header = 'script'
+
+      if not (service_worker_request_header
+              == expected_service_worker_request_header):
+        raise ValueError('Service-Worker HTTP request header value does not '
+                         'match with expected value.\n'
+                         'Header value:%s\n'
+                         'Expected value:%s' %
+                         (service_worker_request_header,
+                          expected_service_worker_request_header))
+
+    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
 
 class ServiceWorkerTest(black_box_tests.BlackBoxTestCase):
   """Test basic Service Worker functionality."""
 
-  def test_simple(self):
+  def test_service_worker(self):
 
-    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+    with ThreadedWebServer(
+        ServiceWorkerRequestDetector,
+        binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/service_worker_test.html')
 
       with self.CreateCobaltRunner(url=url) as runner:
diff --git a/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py b/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py
index 8374c3f..ce2cc14 100644
--- a/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py
+++ b/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py
@@ -30,7 +30,7 @@
     if 'linux' not in self.launcher_params.platform:  # pylint: disable=unsupported-membership-test
       self.skipTest('This test needs POSIX system signal handlers')
 
-  def test_simple(self):
+  def test_signal_handler_doesnt_crash(self):
     with self.CreateCobaltRunner(url='', target_params=[]) as runner:
       runner.WaitForUrlLoadedEvents()
 
diff --git a/cobalt/black_box_tests/tests/suspend_visibility.py b/cobalt/black_box_tests/tests/suspend_visibility.py
index 7055d3f..33205a1 100644
--- a/cobalt/black_box_tests/tests/suspend_visibility.py
+++ b/cobalt/black_box_tests/tests/suspend_visibility.py
@@ -24,7 +24,7 @@
 class SuspendVisibilityTest(black_box_tests.BlackBoxTestCase):
   """Verify correct visibility changes during and after suspend event."""
 
-  def test_simple(self):
+  def test_suspend_visibility(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/suspend_visibility.html')
diff --git a/cobalt/black_box_tests/tests/text_encoding_test.py b/cobalt/black_box_tests/tests/text_encoding_test.py
index 56d58ae..e1c3563 100644
--- a/cobalt/black_box_tests/tests/text_encoding_test.py
+++ b/cobalt/black_box_tests/tests/text_encoding_test.py
@@ -20,7 +20,7 @@
 class TextEncodingTest(black_box_tests.BlackBoxTestCase):
   """Test basic TextEncoder and TextDecoder functionality."""
 
-  def test_simple(self):
+  def test_text_encoding(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/text_encoding_test.html')
diff --git a/cobalt/black_box_tests/tests/timer_hit_after_preload.py b/cobalt/black_box_tests/tests/timer_hit_after_preload.py
index f48a93f..5a97dfc 100644
--- a/cobalt/black_box_tests/tests/timer_hit_after_preload.py
+++ b/cobalt/black_box_tests/tests/timer_hit_after_preload.py
@@ -24,7 +24,7 @@
 class TimerAfterPreloadTest(black_box_tests.BlackBoxTestCase):
   """Set a JS timer that expires after exiting preload mode."""
 
-  def test_simple(self):
+  def test_timer_after_preload(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/timer_hit_after_preload.html')
diff --git a/cobalt/black_box_tests/tests/timer_hit_in_preload.py b/cobalt/black_box_tests/tests/timer_hit_in_preload.py
index edb0c8f..fc88ec7 100644
--- a/cobalt/black_box_tests/tests/timer_hit_in_preload.py
+++ b/cobalt/black_box_tests/tests/timer_hit_in_preload.py
@@ -24,7 +24,7 @@
 class TimerInPreloadTest(black_box_tests.BlackBoxTestCase):
   """Set a JS timer that expires during preload mode."""
 
-  def test_simple(self):
+  def test_timer_in_preload(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/timer_hit_in_preload.html')
diff --git a/cobalt/black_box_tests/tests/web_platform_tests.py b/cobalt/black_box_tests/tests/web_platform_tests.py
index 2c4fe56..4b64734 100644
--- a/cobalt/black_box_tests/tests/web_platform_tests.py
+++ b/cobalt/black_box_tests/tests/web_platform_tests.py
@@ -31,7 +31,7 @@
     if self.launcher_params.config not in ['debug', 'devel']:
       self.skipTest('Can only run web platform tests on debug or devel config.')
 
-  def test_simple(self):
+  def test_web_platform(self):
     with WebPlatformTestServer(
         binding_address=self.GetBindingAddress(),
         wpt_http_port=self.GetWptHttpPort()):
diff --git a/cobalt/black_box_tests/tests/web_worker_test.py b/cobalt/black_box_tests/tests/web_worker_test.py
index 1ac8046..0dcb550 100644
--- a/cobalt/black_box_tests/tests/web_worker_test.py
+++ b/cobalt/black_box_tests/tests/web_worker_test.py
@@ -20,7 +20,7 @@
 class WebWorkerTest(black_box_tests.BlackBoxTestCase):
   """Test basic Web Worker functionality."""
 
-  def test_simple(self):
+  def test_web_worker(self):
 
     with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
       url = server.GetURL(file_name='testdata/web_worker_test.html')
diff --git a/cobalt/black_box_tests/tests/worker_csp_test.py b/cobalt/black_box_tests/tests/worker_csp_test.py
index 1af8735..18b6511 100644
--- a/cobalt/black_box_tests/tests/worker_csp_test.py
+++ b/cobalt/black_box_tests/tests/worker_csp_test.py
@@ -32,7 +32,7 @@
 class WorkerCspTest(black_box_tests.BlackBoxTestCase):
   """Verify correct correct CSP behavior."""
 
-  def test_simple(self):
+  def test_worker_csp(self):
     path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
     with ThreadedWebServer(
         binding_address=self.GetBindingAddress(),
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index 855b484..c64b761 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -671,7 +671,7 @@
   // Initializes persistent settings.
   persistent_settings_ =
       std::make_unique<persistent_storage::PersistentSettings>(
-          kPersistentSettingsJson, message_loop_->task_runner());
+          kPersistentSettingsJson);
 
   // Initializes Watchdog.
   watchdog::Watchdog* watchdog =
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index 780f86d..0873200 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -707,8 +707,8 @@
     const base::Optional<math::Rect>& clip_rect,
     const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToMemory()");
-  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   DCHECK(screen_shot_writer_);
+  // Note: This does not have to be called from self_message_loop_.
 
   scoped_refptr<render_tree::Node> render_tree;
   web_module_->DoSynchronousLayoutAndGetRenderTree(&render_tree);
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index db2d7c5..e9dcf26 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -201,6 +201,9 @@
   "//cobalt/subtlecrypto/subtle_crypto.idl",
 
   "//cobalt/web/blob.idl",
+  "//cobalt/web/cache.idl",
+  "//cobalt/web/cache_request.idl",
+  "//cobalt/web/cache_storage.idl",
   "//cobalt/web/cobalt_ua_data_values_interface.idl",
   "//cobalt/web/crypto.idl",
   "//cobalt/web/custom_event.idl",
@@ -236,6 +239,7 @@
   "//cobalt/worker/dedicated_worker_global_scope.idl",
   "//cobalt/worker/extendable_event.idl",
   "//cobalt/worker/extendable_message_event.idl",
+  "//cobalt/worker/fetch_event.idl",
   "//cobalt/worker/navigation_preload_manager.idl",
   "//cobalt/worker/service_worker.idl",
   "//cobalt/worker/service_worker_container.idl",
@@ -341,6 +345,7 @@
   "//cobalt/web/error_event_init.idl",
   "//cobalt/web/event_init.idl",
   "//cobalt/web/message_event_init.idl",
+  "//cobalt/web/multi_cache_query_options.idl",
   "//cobalt/web/navigator_ua_brand_version.idl",
   "//cobalt/web/ua_data_values.idl",
   "//cobalt/web/ua_low_entropy_json.idl",
@@ -351,6 +356,7 @@
   "//cobalt/worker/client_query_options.idl",
   "//cobalt/worker/extendable_event_init.idl",
   "//cobalt/worker/extendable_message_event_init.idl",
+  "//cobalt/worker/fetch_event_init.idl",
   "//cobalt/worker/frame_type.idl",
   "//cobalt/worker/navigation_preload_state.idl",
   "//cobalt/worker/registration_options.idl",
@@ -405,6 +411,7 @@
   "//cobalt/media_capture/navigator.idl",
   "//cobalt/media_session/navigator_media_session.idl",
   "//cobalt/web/buffer_source.idl",
+  "//cobalt/web/global_cache_storage.idl",
   "//cobalt/web/global_crypto.idl",
   "//cobalt/web/navigator_id.idl",
   "//cobalt/web/navigator_language.idl",
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc
index c0a4e00..44027a4 100644
--- a/cobalt/browser/service_worker_registry.cc
+++ b/cobalt/browser/service_worker_registry.cc
@@ -71,6 +71,7 @@
   // Ensure that the destruction observer got added before stopping the thread.
   // Stop the thread. This will cause the destruction observer to be notified.
   destruction_observer_added_.Wait();
+  service_worker_jobs_->Stop();
   thread_.Stop();
 }
 
diff --git a/cobalt/browser/web_module.cc b/cobalt/browser/web_module.cc
index a7cfd00..6cb0ea4 100644
--- a/cobalt/browser/web_module.cc
+++ b/cobalt/browser/web_module.cc
@@ -782,10 +782,13 @@
   local_storage_database_.reset();
   mesh_cache_.reset();
   remote_typeface_cache_.reset();
+  // Warning: The image cache may contain animated images that rely on the
+  // thread in AnimatedImageTracker, so it's important to destroy the image
+  // cache before the animated image tracker object.
+  image_cache_.reset();
   animated_image_tracker_.reset();
   dom_parser_.reset();
   css_parser_.reset();
-  image_cache_.reset();
   web_module_stat_tracker_.reset();
 }
 
diff --git a/cobalt/build/cobalt_configuration.py b/cobalt/build/cobalt_configuration.py
index c70dfdf..342f025 100644
--- a/cobalt/build/cobalt_configuration.py
+++ b/cobalt/build/cobalt_configuration.py
@@ -167,6 +167,7 @@
         'nb_test',
         'net_unittests',
         'network_test',
+        'persistent_settings_test',
         'poem_unittests',
         'render_tree_test',
         'renderer_test',
diff --git a/cobalt/cache/BUILD.gn b/cobalt/cache/BUILD.gn
index edc1daf..237a810 100644
--- a/cobalt/cache/BUILD.gn
+++ b/cobalt/cache/BUILD.gn
@@ -11,6 +11,7 @@
 # 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.
+
 static_library("cache") {
   sources = [
     "cache.cc",
@@ -25,5 +26,6 @@
     "//cobalt/persistent_storage:persistent_settings",
     "//net",
     "//starboard:starboard_headers_only",
+    "//starboard/common",
   ]
 }
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc
index 867db60..aeb1132 100644
--- a/cobalt/cache/cache.cc
+++ b/cobalt/cache/cache.cc
@@ -27,6 +27,7 @@
 #include "cobalt/extension/javascript_cache.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/cobalt_backend_impl.h"
+#include "starboard/common/murmurhash2.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/system.h"
 
@@ -45,6 +46,8 @@
 base::Optional<std::string> GetSubdirectory(
     disk_cache::ResourceType resource_type) {
   switch (resource_type) {
+    case disk_cache::ResourceType::kCacheApi:
+      return "cache_api";
     case disk_cache::ResourceType::kCompiledScript:
       return "compiled_js";
     default:
@@ -89,24 +92,54 @@
   return base::Singleton<Cache, base::LeakySingletonTraits<Cache>>::get();
 }
 
-void Cache::Delete(disk_cache::ResourceType resource_type, uint32_t key) {
-  auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
-  if (memory_capped_directory) {
-    memory_capped_directory->Delete(key);
-  }
+// static
+uint32_t Cache::CreateKey(const std::string& s) {
+  return starboard::MurmurHash2_32(s.c_str(), s.size());
 }
 
-void Cache::DeleteAll() {
-  auto* memory_capped_directory =
-      GetMemoryCappedDirectory(disk_cache::ResourceType::kCompiledScript);
+bool Cache::Delete(disk_cache::ResourceType resource_type, uint32_t key) {
+  auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
+  if (memory_capped_directory) {
+    return memory_capped_directory->Delete(key);
+  }
+  return false;
+}
+
+void Cache::Delete(disk_cache::ResourceType resource_type) {
+  auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
   if (memory_capped_directory) {
     memory_capped_directory->DeleteAll();
   }
 }
 
+void Cache::DeleteAll() {
+  Delete(disk_cache::ResourceType::kCompiledScript);
+  Delete(disk_cache::ResourceType::kCacheApi);
+}
+
+std::vector<uint32_t> Cache::KeysWithMetadata(
+    disk_cache::ResourceType resource_type) {
+  auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
+  if (memory_capped_directory) {
+    return memory_capped_directory->KeysWithMetadata();
+  }
+  return std::vector<uint32_t>();
+}
+
+std::unique_ptr<base::Value> Cache::Metadata(
+    disk_cache::ResourceType resource_type, uint32_t key) {
+  auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
+  if (memory_capped_directory) {
+    return memory_capped_directory->Metadata(key);
+  }
+  return nullptr;
+}
+
 std::unique_ptr<std::vector<uint8_t>> Cache::Retrieve(
     disk_cache::ResourceType resource_type, uint32_t key,
-    std::function<std::unique_ptr<std::vector<uint8_t>>()> generate) {
+    std::function<std::pair<std::unique_ptr<std::vector<uint8_t>>,
+                            base::Optional<base::Value>>()>
+        generate) {
   base::ScopedClosureRunner notifier(base::BindOnce(
       &Cache::Notify, base::Unretained(this), resource_type, key));
   auto* e = GetWaitableEvent(resource_type, key);
@@ -128,12 +161,12 @@
         javascript_cache_extension->ReleaseCachedScriptData(cache_data_buf);
         return data;
       }
-      auto data = generate();
+      auto data = generate().first;
       if (data) {
         javascript_cache_extension->StoreCachedScript(
             key, data->size(), data->data(), data->size());
       }
-      return data;
+      return std::move(data);
     }
   }
 
@@ -145,10 +178,27 @@
     }
   }
   auto data = generate();
-  if (data) {
-    TryStore(resource_type, key, *data);
+  if (data.first) {
+    Store(resource_type, key, /*data=*/*(data.first), /*metadata=*/data.second);
   }
-  return data;
+  return std::move(data.first);
+}
+
+std::unique_ptr<std::vector<uint8_t>> Cache::Retrieve(
+    disk_cache::ResourceType resource_type, uint32_t key) {
+  base::ScopedClosureRunner notifier(base::BindOnce(
+      &Cache::Notify, base::Unretained(this), resource_type, key));
+  auto* e = GetWaitableEvent(resource_type, key);
+  if (e) {
+    e->Wait();
+    delete e;
+  }
+
+  auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
+  if (memory_capped_directory) {
+    return memory_capped_directory->Retrieve(key);
+  }
+  return nullptr;
 }
 
 void Cache::set_enabled(bool enabled) { enabled_ = enabled; }
@@ -196,7 +246,9 @@
 }
 
 void Cache::Resize(disk_cache::ResourceType resource_type, uint32_t bytes) {
-  if (resource_type != disk_cache::ResourceType::kCompiledScript) return;
+  if (resource_type != disk_cache::ResourceType::kCacheApi &&
+      resource_type != disk_cache::ResourceType::kCompiledScript)
+    return;
   if (bytes == disk_cache::kTypeMetadata[resource_type].max_size_bytes) return;
 
   if (persistent_settings_) {
@@ -209,11 +261,15 @@
   if (memory_capped_directory) {
     memory_capped_directory->Resize(bytes);
   }
+  if (bytes == 0) {
+    Delete(resource_type);
+  }
 }
 
 base::Optional<uint32_t> Cache::GetMaxCacheStorageInBytes(
     disk_cache::ResourceType resource_type) {
   switch (resource_type) {
+    case disk_cache::ResourceType::kCacheApi:
     case disk_cache::ResourceType::kCompiledScript:
       return disk_cache::kTypeMetadata[resource_type].max_size_bytes;
     default:
@@ -252,14 +308,15 @@
   pending_[resource_type].erase(key);
 }
 
-void Cache::TryStore(disk_cache::ResourceType resource_type, uint32_t key,
-                     const std::vector<uint8_t>& data) {
+void Cache::Store(disk_cache::ResourceType resource_type, uint32_t key,
+                  const std::vector<uint8_t>& data,
+                  const base::Optional<base::Value>& metadata) {
   if (!CanCache(resource_type, data.size())) {
     return;
   }
   auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
   if (memory_capped_directory) {
-    memory_capped_directory->Store(key, data);
+    memory_capped_directory->Store(key, data, metadata);
   }
 }
 
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h
index fd106f1..5b57be6 100644
--- a/cobalt/cache/cache.h
+++ b/cobalt/cache/cache.h
@@ -18,14 +18,18 @@
 #include <functional>
 #include <map>
 #include <memory>
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "base/basictypes.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/values.h"
 #include "cobalt/cache/memory_capped_directory.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/resource_type.h"
@@ -41,12 +45,26 @@
 class Cache {
  public:
   static Cache* GetInstance();
-  void Delete(disk_cache::ResourceType resource_type, uint32_t key);
+  static uint32_t CreateKey(const std::string& s);
+
+  bool Delete(disk_cache::ResourceType resource_type, uint32_t key);
+  void Delete(disk_cache::ResourceType resource_type);
   void DeleteAll();
+  std::vector<uint32_t> KeysWithMetadata(
+      disk_cache::ResourceType resource_type);
+  std::unique_ptr<base::Value> Metadata(disk_cache::ResourceType resource_type,
+                                        uint32_t key);
   std::unique_ptr<std::vector<uint8_t>> Retrieve(
       disk_cache::ResourceType resource_type, uint32_t key,
-      std::function<std::unique_ptr<std::vector<uint8_t>>()> generate);
+      std::function<std::pair<std::unique_ptr<std::vector<uint8_t>>,
+                              base::Optional<base::Value>>()>
+          generate);
+  std::unique_ptr<std::vector<uint8_t>> Retrieve(
+      disk_cache::ResourceType resource_type, uint32_t key);
   void Resize(disk_cache::ResourceType resource_type, uint32_t bytes);
+  void Store(disk_cache::ResourceType resource_type, uint32_t key,
+             const std::vector<uint8_t>& data,
+             const base::Optional<base::Value>& metadata);
   base::Optional<uint32_t> GetMaxCacheStorageInBytes(
       disk_cache::ResourceType resource_type);
 
@@ -64,8 +82,6 @@
   base::WaitableEvent* GetWaitableEvent(disk_cache::ResourceType resource_type,
                                         uint32_t key);
   void Notify(disk_cache::ResourceType resource_type, uint32_t key);
-  void TryStore(disk_cache::ResourceType resource_type, uint32_t key,
-                const std::vector<uint8_t>& data);
   bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size);
 
   mutable base::Lock lock_;
diff --git a/cobalt/cache/memory_capped_directory.cc b/cobalt/cache/memory_capped_directory.cc
index 3dc064c..de70439 100644
--- a/cobalt/cache/memory_capped_directory.cc
+++ b/cobalt/cache/memory_capped_directory.cc
@@ -15,14 +15,23 @@
 #include "cobalt/cache/memory_capped_directory.h"
 
 #include <algorithm>
+#include <string>
 
 #include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
 #include "base/strings/string_number_conversions.h"
 #include "starboard/directory.h"
 
 namespace cobalt {
 namespace cache {
 
+namespace {
+
+const char* kMetadataExtension = ".json";
+
+}  // namespace
+
 MemoryCappedDirectory::FileInfo::FileInfo(
     base::FilePath directory_path, base::FileEnumerator::FileInfo file_info)
     : file_path_(directory_path.Append(file_info.GetName())),
@@ -58,6 +67,13 @@
   while (!file_enumerator.Next().empty()) {
     MemoryCappedDirectory::FileInfo file_info(directory_path,
                                               file_enumerator.GetInfo());
+    // TODO: include metadata size in file sizes.
+    if (file_info.file_path_.Extension() == kMetadataExtension) {
+      memory_capped_directory->file_keys_with_metadata_[file_info.file_path_] =
+          std::stoul(
+              file_info.file_path_.BaseName().RemoveExtension().MaybeAsASCII());
+      continue;
+    }
     heap->push_back(file_info);
     memory_capped_directory->file_sizes_[file_info.file_path_] =
         file_info.size_;
@@ -70,13 +86,18 @@
   return memory_capped_directory;
 }
 
-void MemoryCappedDirectory::Delete(uint32_t key) {
+bool MemoryCappedDirectory::Delete(uint32_t key) {
   base::AutoLock auto_lock(lock_);
   auto file_path = GetFilePath(key);
   if (base::PathExists(file_path)) {
     base::DeleteFile(file_path, false);
   }
+  auto metadata_path = file_path.AddExtension(kMetadataExtension);
+  if (base::PathExists(metadata_path)) {
+    base::DeleteFile(metadata_path, false);
+  }
   file_sizes_.erase(file_path);
+  file_keys_with_metadata_.erase(file_path);
   auto* heap = &file_info_heap_;
   for (auto it = heap->begin(); it != heap->end(); ++it) {
     if (it->file_path_ == file_path) {
@@ -86,9 +107,10 @@
         std::make_heap(heap->begin(), heap->end(),
                        MemoryCappedDirectory::FileInfo::OldestFirst());
       }
-      return;
+      return true;
     }
   }
+  return false;
 }
 
 void MemoryCappedDirectory::DeleteAll() {
@@ -99,9 +121,29 @@
   SbDirectoryCreate(directory_path_.value().c_str());
   file_info_heap_.clear();
   file_sizes_.clear();
+  file_keys_with_metadata_.clear();
   size_ = 0;
 }
 
+std::vector<uint32_t> MemoryCappedDirectory::KeysWithMetadata() {
+  std::vector<uint32_t> keys(file_keys_with_metadata_.size());
+  for (auto it = file_keys_with_metadata_.begin();
+       it != file_keys_with_metadata_.end(); ++it) {
+    keys.push_back(it->second);
+  }
+  return keys;
+}
+
+std::unique_ptr<base::Value> MemoryCappedDirectory::Metadata(uint32_t key) {
+  auto metadata_path = GetFilePath(key).AddExtension(kMetadataExtension);
+  if (!base::PathExists(metadata_path)) {
+    return nullptr;
+  }
+  std::string serialized_metadata;
+  base::ReadFileToString(metadata_path, &serialized_metadata);
+  return base::JSONReader::Read(serialized_metadata);
+}
+
 std::unique_ptr<std::vector<uint8_t>> MemoryCappedDirectory::Retrieve(
     uint32_t key) {
   auto file_path = GetFilePath(key);
@@ -109,6 +151,10 @@
   if (it == file_sizes_.end()) {
     return nullptr;
   }
+  if (!base::PathExists(file_path)) {
+    Delete(key);
+    return nullptr;
+  }
   auto size = it->second;
   auto data = std::make_unique<std::vector<uint8_t>>(static_cast<size_t>(size));
   int bytes_read = base::ReadFile(
@@ -120,17 +166,27 @@
 }
 
 void MemoryCappedDirectory::Store(uint32_t key,
-                                  const std::vector<uint8_t>& data) {
+                                  const std::vector<uint8_t>& data,
+                                  const base::Optional<base::Value>& metadata) {
   base::AutoLock auto_lock(lock_);
   auto file_path = GetFilePath(key);
   uint32_t new_entry_size = static_cast<uint32_t>(data.size());
   if (!EnsureEnoughSpace(new_entry_size)) {
     return;
   }
+  if (metadata) {
+    std::string serialized_metadata;
+    base::JSONWriter::Write(metadata.value(), &serialized_metadata);
+    base::WriteFile(file_path.AddExtension(kMetadataExtension),
+                    serialized_metadata.data(), serialized_metadata.size());
+  }
   int bytes_written = base::WriteFile(
       file_path, reinterpret_cast<const char*>(data.data()), data.size());
   if (bytes_written != data.size()) {
     base::DeleteFile(file_path, false);
+    if (metadata) {
+      base::DeleteFile(file_path.AddExtension(kMetadataExtension), false);
+    }
     return;
   }
   size_ += new_entry_size;
@@ -140,6 +196,7 @@
   std::push_heap(heap->begin(), heap->end(),
                  MemoryCappedDirectory::FileInfo::OldestFirst());
   file_sizes_[file_path] = new_entry_size;
+  file_keys_with_metadata_[file_path] = key;
 }
 
 void MemoryCappedDirectory::Resize(uint32_t size) {
@@ -173,7 +230,12 @@
     auto removed = heap->back();
     size_ -= removed.size_;
     base::DeleteFile(removed.file_path_, false);
+    auto metadata_path = removed.file_path_.AddExtension(kMetadataExtension);
+    if (base::PathExists(metadata_path)) {
+      base::DeleteFile(metadata_path, false);
+    }
     file_sizes_.erase(removed.file_path_);
+    file_keys_with_metadata_.erase(removed.file_path_);
     heap->pop_back();
   }
   return true;
diff --git a/cobalt/cache/memory_capped_directory.h b/cobalt/cache/memory_capped_directory.h
index 941ab19..39d227d 100644
--- a/cobalt/cache/memory_capped_directory.h
+++ b/cobalt/cache/memory_capped_directory.h
@@ -25,6 +25,7 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
+#include "base/values.h"
 
 namespace cobalt {
 namespace cache {
@@ -50,10 +51,13 @@
  public:
   static std::unique_ptr<MemoryCappedDirectory> Create(
       const base::FilePath& directory_path, uint32_t max_size);
-  void Delete(uint32_t key);
+  bool Delete(uint32_t key);
   void DeleteAll();
+  std::vector<uint32_t> KeysWithMetadata();
+  std::unique_ptr<base::Value> Metadata(uint32_t key);
   std::unique_ptr<std::vector<uint8_t>> Retrieve(uint32_t key);
-  void Store(uint32_t key, const std::vector<uint8_t>& data);
+  void Store(uint32_t key, const std::vector<uint8_t>& data,
+             const base::Optional<base::Value>& metadata);
   void Resize(uint32_t size);
 
  private:
@@ -68,6 +72,7 @@
   // Min-heap determined by last modified time.
   std::vector<FileInfo> file_info_heap_;
   std::map<base::FilePath, uint32_t> file_sizes_;
+  std::map<base::FilePath, uint32_t> file_keys_with_metadata_;
   uint32_t size_;
   uint32_t max_size_;
 
diff --git a/cobalt/content/fonts/config/android/fonts.xml b/cobalt/content/fonts/config/android/fonts.xml
index 578c124..9ced68c 100644
--- a/cobalt/content/fonts/config/android/fonts.xml
+++ b/cobalt/content/fonts/config/android/fonts.xml
@@ -113,6 +113,18 @@
         <font weight="700" style="italic" font_name="Roboto Bold Italic" postscript_name="Roboto-BoldItalic">Roboto-BoldItalic.ttf</font>
         <font weight="900" style="normal" font_name="Roboto Black" postscript_name="Roboto-Black">Roboto-Black.ttf</font>
         <font weight="900" style="italic" font_name="Roboto Black Italic" postscript_name="Roboto-Black-Italic">Roboto-BlackItalic.ttf</font>
+        <!-- Android 12+ uses font variations -->
+        <font weight="100" style="normal" tag_wght="100" tag_ital="0" font_name="Roboto Thin" postscript_name="Roboto-Thin">Roboto-Regular.ttf</font>
+        <font weight="100" style="italic" tag_wght="100" tag_ital="1" font_name="Roboto Thin Italic" postscript_name="Roboto-Thin-Italic">Roboto-Regular.ttf</font>
+        <font weight="300" style="normal" tag_wght="300" tag_ital="0" font_name="Roboto Light" postscript_name="Roboto-Light">Roboto-Regular.ttf</font>
+        <font weight="300" style="italic" tag_wght="300" tag_ital="1" font_name="Roboto Light Italic" postscript_name="Roboto-Light-Italic">Roboto-Regular.ttf</font>
+        <font weight="400" style="italic" tag_wght="400" tag_ital="1" font_name="Roboto Regular Italic" postscript_name="Roboto-Italic">Roboto-Regular.ttf</font>
+        <font weight="500" style="normal" tag_wght="500" tag_ital="0" font_name="Roboto Medium" postscript_name="Roboto-Medium">Roboto-Regular.ttf</font>
+        <font weight="500" style="italic" tag_wght="500" tag_ital="1" font_name="Roboto Medium Italic" postscript_name="Roboto-Medium-Italic">Roboto-Regular.ttf</font>
+        <font weight="700" style="normal" tag_wght="700" tag_ital="0" font_name="Roboto Bold" postscript_name="Roboto-Bold">Roboto-Regular.ttf</font>
+        <font weight="700" style="italic" tag_wght="700" tag_ital="1" font_name="Roboto Bold Italic" postscript_name="Roboto-BoldItalic">Roboto-Regular.ttf</font>
+        <font weight="900" style="normal" tag_wght="900" tag_ital="0" font_name="Roboto Black" postscript_name="Roboto-Black">Roboto-Regular.ttf</font>
+        <font weight="900" style="italic" tag_wght="900" tag_ital="1" font_name="Roboto Black Italic" postscript_name="Roboto-Black-Italic">Roboto-Regular.ttf</font>
     </family>
     <!-- Note that aliases must come after the fonts they reference. -->
     <alias name="roboto" to="sans-serif" />
diff --git a/cobalt/content/licenses/platform/android/licenses_cobalt.txt b/cobalt/content/licenses/platform/android/licenses_cobalt.txt
index 022c76b..6754136 100644
--- a/cobalt/content/licenses/platform/android/licenses_cobalt.txt
+++ b/cobalt/content/licenses/platform/android/licenses_cobalt.txt
@@ -1884,87 +1884,6 @@
 
 
 
-  libjpeg
-
-
-  (Copied from the README.)
-
-  --------------------------------------------------------------------------------
-
-  The authors make NO WARRANTY or representation, either express or implied,
-  with respect to this software, its quality, accuracy, merchantability, or
-  fitness for a particular purpose.  This software is provided "AS IS", and you,
-  its user, assume the entire risk as to its quality and accuracy.
-
-  This software is copyright (C) 1991-1998, Thomas G. Lane.
-  All Rights Reserved except as specified below.
-
-  Permission is hereby granted to use, copy, modify, and distribute this
-  software (or portions thereof) for any purpose, without fee, subject to these
-  conditions:
-  (1) If any part of the source code for this software is distributed, then this
-  README file must be included, with this copyright and no-warranty notice
-  unaltered; and any additions, deletions, or changes to the original files
-  must be clearly indicated in accompanying documentation.
-  (2) If only executable code is distributed, then the accompanying
-  documentation must state that "this software is based in part on the work of
-  the Independent JPEG Group".
-  (3) Permission for use of this software is granted only if the user accepts
-  full responsibility for any undesirable consequences; the authors accept
-  NO LIABILITY for damages of any kind.
-
-  These conditions apply to any software derived from or based on the IJG code,
-  not just to the unmodified library.  If you use our work, you ought to
-  acknowledge us.
-
-  Permission is NOT granted for the use of any IJG author's name or company name
-  in advertising or publicity relating to this software or products derived from
-  it.  This software may be referred to only as "the Independent JPEG Group's
-  software".
-
-  We specifically permit and encourage the use of this software as the basis of
-  commercial products, provided that all warranty or liability claims are
-  assumed by the product vendor.
-
-
-  ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
-  sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
-  ansi2knr.c is NOT covered by the above copyright and conditions, but instead
-  by the usual distribution terms of the Free Software Foundation; principally,
-  that you must include source code if you redistribute it.  (See the file
-  ansi2knr.c for full details.)  However, since ansi2knr.c is not needed as part
-  of any program generated from the IJG code, this does not limit you more than
-  the foregoing paragraphs do.
-
-  The Unix configuration script "configure" was produced with GNU Autoconf.
-  It is copyright by the Free Software Foundation but is freely distributable.
-  The same holds for its supporting scripts (config.guess, config.sub,
-  ltconfig, ltmain.sh).  Another support script, install-sh, is copyright
-  by M.I.T. but is also freely distributable.
-
-  It appears that the arithmetic coding option of the JPEG spec is covered by
-  patents owned by IBM, AT&T, and Mitsubishi.  Hence arithmetic coding cannot
-  legally be used without obtaining one or more licenses.  For this reason,
-  support for arithmetic coding has been removed from the free JPEG software.
-  (Since arithmetic coding provides only a marginal gain over the unpatented
-  Huffman mode, it is unlikely that very many implementations will support it.)
-  So far as we are aware, there are no patent restrictions on the remaining
-  code.
-
-  The IJG distribution formerly included code to read and write GIF files.
-  To avoid entanglement with the Unisys LZW patent, GIF reading support has
-  been removed altogether, and the GIF writer has been simplified to produce
-  "uncompressed GIFs".  This technique does not use the LZW algorithm; the
-  resulting GIF files are larger than usual, but are readable by all standard
-  GIF decoders.
-
-  We are required to state that
-      "The Graphics Interchange Format(c) is the Copyright property of
-      CompuServe Incorporated.  GIF(sm) is a Service Mark property of
-      CompuServe Incorporated."
-
-
-
   libpng
 
 
diff --git a/cobalt/content/licenses/platform/default/licenses_cobalt.txt b/cobalt/content/licenses/platform/default/licenses_cobalt.txt
index 7ab54cf..fd93a89 100644
--- a/cobalt/content/licenses/platform/default/licenses_cobalt.txt
+++ b/cobalt/content/licenses/platform/default/licenses_cobalt.txt
@@ -2064,86 +2064,6 @@
 
 
 
-  libjpeg
-
-
-  (Copied from the README.)
-
-  --------------------------------------------------------------------------------
-
-  The authors make NO WARRANTY or representation, either express or implied,
-  with respect to this software, its quality, accuracy, merchantability, or
-  fitness for a particular purpose.  This software is provided "AS IS", and you,
-  its user, assume the entire risk as to its quality and accuracy.
-
-  This software is copyright (C) 1991-1998, Thomas G. Lane.
-  All Rights Reserved except as specified below.
-
-  Permission is hereby granted to use, copy, modify, and distribute this
-  software (or portions thereof) for any purpose, without fee, subject to these
-  conditions:
-  (1) If any part of the source code for this software is distributed, then this
-  README file must be included, with this copyright and no-warranty notice
-  unaltered; and any additions, deletions, or changes to the original files
-  must be clearly indicated in accompanying documentation.
-  (2) If only executable code is distributed, then the accompanying
-  documentation must state that "this software is based in part on the work of
-  the Independent JPEG Group".
-  (3) Permission for use of this software is granted only if the user accepts
-  full responsibility for any undesirable consequences; the authors accept
-  NO LIABILITY for damages of any kind.
-
-  These conditions apply to any software derived from or based on the IJG code,
-  not just to the unmodified library.  If you use our work, you ought to
-  acknowledge us.
-
-  Permission is NOT granted for the use of any IJG author's name or company name
-  in advertising or publicity relating to this software or products derived from
-  it.  This software may be referred to only as "the Independent JPEG Group's
-  software".
-
-  We specifically permit and encourage the use of this software as the basis of
-  commercial products, provided that all warranty or liability claims are
-  assumed by the product vendor.
-
-
-  ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
-  sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
-  ansi2knr.c is NOT covered by the above copyright and conditions, but instead
-  by the usual distribution terms of the Free Software Foundation; principally,
-  that you must include source code if you redistribute it.  (See the file
-  ansi2knr.c for full details.)  However, since ansi2knr.c is not needed as part
-  of any program generated from the IJG code, this does not limit you more than
-  the foregoing paragraphs do.
-
-  The Unix configuration script "configure" was produced with GNU Autoconf.
-  It is copyright by the Free Software Foundation but is freely distributable.
-  The same holds for its supporting scripts (config.guess, config.sub,
-  ltconfig, ltmain.sh).  Another support script, install-sh, is copyright
-  by M.I.T. but is also freely distributable.
-
-  It appears that the arithmetic coding option of the JPEG spec is covered by
-  patents owned by IBM, AT&T, and Mitsubishi.  Hence arithmetic coding cannot
-  legally be used without obtaining one or more licenses.  For this reason,
-  support for arithmetic coding has been removed from the free JPEG software.
-  (Since arithmetic coding provides only a marginal gain over the unpatented
-  Huffman mode, it is unlikely that very many implementations will support it.)
-  So far as we are aware, there are no patent restrictions on the remaining
-  code.
-
-  The IJG distribution formerly included code to read and write GIF files.
-  To avoid entanglement with the Unisys LZW patent, GIF reading support has
-  been removed altogether, and the GIF writer has been simplified to produce
-  "uncompressed GIFs".  This technique does not use the LZW algorithm; the
-  resulting GIF files are larger than usual, but are readable by all standard
-  GIF decoders.
-
-  We are required to state that
-      "The Graphics Interchange Format(c) is the Copyright property of
-      CompuServe Incorporated.  GIF(sm) is a Service Mark property of
-      CompuServe Incorporated."
-
-
   libjpeg-turbo
 
   libjpeg-turbo Licenses libjpeg-turbo is covered by three compatible BSD-style
diff --git a/cobalt/content/licenses/platform/evergreen/licenses_cobalt.txt b/cobalt/content/licenses/platform/evergreen/licenses_cobalt.txt
index a85d482..8063535 100644
--- a/cobalt/content/licenses/platform/evergreen/licenses_cobalt.txt
+++ b/cobalt/content/licenses/platform/evergreen/licenses_cobalt.txt
@@ -2064,87 +2064,6 @@
 
 
 
-  libjpeg
-
-
-  (Copied from the README.)
-
-  --------------------------------------------------------------------------------
-
-  The authors make NO WARRANTY or representation, either express or implied,
-  with respect to this software, its quality, accuracy, merchantability, or
-  fitness for a particular purpose.  This software is provided "AS IS", and you,
-  its user, assume the entire risk as to its quality and accuracy.
-
-  This software is copyright (C) 1991-1998, Thomas G. Lane.
-  All Rights Reserved except as specified below.
-
-  Permission is hereby granted to use, copy, modify, and distribute this
-  software (or portions thereof) for any purpose, without fee, subject to these
-  conditions:
-  (1) If any part of the source code for this software is distributed, then this
-  README file must be included, with this copyright and no-warranty notice
-  unaltered; and any additions, deletions, or changes to the original files
-  must be clearly indicated in accompanying documentation.
-  (2) If only executable code is distributed, then the accompanying
-  documentation must state that "this software is based in part on the work of
-  the Independent JPEG Group".
-  (3) Permission for use of this software is granted only if the user accepts
-  full responsibility for any undesirable consequences; the authors accept
-  NO LIABILITY for damages of any kind.
-
-  These conditions apply to any software derived from or based on the IJG code,
-  not just to the unmodified library.  If you use our work, you ought to
-  acknowledge us.
-
-  Permission is NOT granted for the use of any IJG author's name or company name
-  in advertising or publicity relating to this software or products derived from
-  it.  This software may be referred to only as "the Independent JPEG Group's
-  software".
-
-  We specifically permit and encourage the use of this software as the basis of
-  commercial products, provided that all warranty or liability claims are
-  assumed by the product vendor.
-
-
-  ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
-  sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
-  ansi2knr.c is NOT covered by the above copyright and conditions, but instead
-  by the usual distribution terms of the Free Software Foundation; principally,
-  that you must include source code if you redistribute it.  (See the file
-  ansi2knr.c for full details.)  However, since ansi2knr.c is not needed as part
-  of any program generated from the IJG code, this does not limit you more than
-  the foregoing paragraphs do.
-
-  The Unix configuration script "configure" was produced with GNU Autoconf.
-  It is copyright by the Free Software Foundation but is freely distributable.
-  The same holds for its supporting scripts (config.guess, config.sub,
-  ltconfig, ltmain.sh).  Another support script, install-sh, is copyright
-  by M.I.T. but is also freely distributable.
-
-  It appears that the arithmetic coding option of the JPEG spec is covered by
-  patents owned by IBM, AT&T, and Mitsubishi.  Hence arithmetic coding cannot
-  legally be used without obtaining one or more licenses.  For this reason,
-  support for arithmetic coding has been removed from the free JPEG software.
-  (Since arithmetic coding provides only a marginal gain over the unpatented
-  Huffman mode, it is unlikely that very many implementations will support it.)
-  So far as we are aware, there are no patent restrictions on the remaining
-  code.
-
-  The IJG distribution formerly included code to read and write GIF files.
-  To avoid entanglement with the Unisys LZW patent, GIF reading support has
-  been removed altogether, and the GIF writer has been simplified to produce
-  "uncompressed GIFs".  This technique does not use the LZW algorithm; the
-  resulting GIF files are larger than usual, but are readable by all standard
-  GIF decoders.
-
-  We are required to state that
-      "The Graphics Interchange Format(c) is the Copyright property of
-      CompuServe Incorporated.  GIF(sm) is a Service Mark property of
-      CompuServe Incorporated."
-
-
-
   libpng
 
 
diff --git a/cobalt/css_parser/parser.cc b/cobalt/css_parser/parser.cc
index f0903f4..49a1e2d 100644
--- a/cobalt/css_parser/parser.cc
+++ b/cobalt/css_parser/parser.cc
@@ -581,8 +581,6 @@
       message_verbosity_(message_verbosity),
       supports_map_to_mesh_(supports_map_to_mesh) {}
 
-Parser::~Parser() {}
-
 scoped_refptr<cssom::CSSStyleSheet> Parser::ParseStyleSheet(
     const std::string& input, const ::base::SourceLocation& input_location) {
   ParserImpl parser_impl(input, input_location, this, on_warning_callback_,
diff --git a/cobalt/css_parser/parser.h b/cobalt/css_parser/parser.h
index 7740788..9b96fe8 100644
--- a/cobalt/css_parser/parser.h
+++ b/cobalt/css_parser/parser.h
@@ -44,7 +44,7 @@
   // logging or map-to-mesh.
   static std::unique_ptr<Parser> Create();
 
-  ~Parser();
+  ~Parser() override {}
 
   scoped_refptr<cssom::CSSStyleSheet> ParseStyleSheet(
       const std::string& input,
@@ -88,7 +88,7 @@
     kShort,
 
     // With message verbosity set to kVerbose, expect to see information such
-    // as the line that caused the error to be output, allong with an arrow
+    // as the line that caused the error to be output, along with an arrow
     // pointing to where the error occurred on the line.
     kVerbose,
   };
diff --git a/cobalt/cssom/computed_style.cc b/cobalt/cssom/computed_style.cc
index 480e437..9f9212d 100644
--- a/cobalt/cssom/computed_style.cc
+++ b/cobalt/cssom/computed_style.cc
@@ -15,6 +15,7 @@
 #include "cobalt/cssom/computed_style.h"
 
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "cobalt/base/polymorphic_downcast.h"
@@ -1445,8 +1446,7 @@
     }
   }
 
-  void VisitDefault(PropertyValue* property_value) override {
-  }
+  void VisitDefault(PropertyValue* property_value) override {}
 
   bool computed_length_is_negative() { return computed_length_is_negative_; }
 
@@ -2526,14 +2526,14 @@
 
 void ComputedTransformFunctionProvider::VisitCobaltUiNavFocusTransform(
     const CobaltUiNavFocusTransformFunction* focus_function) {
-  computed_transform_function_.reset(new CobaltUiNavFocusTransformFunction(
-      *focus_function));
+  computed_transform_function_.reset(
+      new CobaltUiNavFocusTransformFunction(*focus_function));
 }
 
 void ComputedTransformFunctionProvider::VisitCobaltUiNavSpotlightTransform(
     const CobaltUiNavSpotlightTransformFunction* spotlight_function) {
-  computed_transform_function_.reset(new CobaltUiNavSpotlightTransformFunction(
-      *spotlight_function));
+  computed_transform_function_.reset(
+      new CobaltUiNavSpotlightTransformFunction(*spotlight_function));
 }
 
 // Absolutizes the value of "text-indent" property.
@@ -2679,7 +2679,7 @@
       base::polymorphic_downcast<TransformFunctionListValue*>(
           transform_property_value);
   if (!transform_function_list->value().HasTrait(
-      TransformFunction::kTraitUsesRelativeUnits)) {
+          TransformFunction::kTraitUsesRelativeUnits)) {
     // If the transform list contains no transforms that use relative units,
     // then we do not need to do anything and we can pass through the existing
     // transform.
@@ -3496,10 +3496,13 @@
   DCHECK(cascaded_style);
   DCHECK(parent_computed_style_declaration);
   DCHECK(root_computed_style);
-
-  // Create a context for calculating the computed style.  This object is useful
-  // because it can cache computed style values that are depended upon by other
-  // properties' computed style calculations.
+  if (!cascaded_style || !root_computed_style ||
+      !parent_computed_style_declaration) {
+    return;
+  }
+  // Create a context for calculating the computed style.  This object is
+  // useful because it can cache computed style values that are depended upon
+  // by other properties' computed style calculations.
   CalculateComputedStyleContext calculate_computed_style_context(
       cascaded_style.get(), parent_computed_style_declaration,
       root_computed_style, viewport_size, property_key_to_base_url_map);
diff --git a/cobalt/cssom/css_parser.h b/cobalt/cssom/css_parser.h
index d56809b..b1e7e61 100644
--- a/cobalt/cssom/css_parser.h
+++ b/cobalt/cssom/css_parser.h
@@ -41,6 +41,7 @@
 // This class is not a part of any specification.
 class CSSParser {
  public:
+  virtual ~CSSParser() {}
   //
   // Parser entry points
   // (see http://dev.w3.org/csswg/css-syntax/#parser-entry-points):
@@ -102,9 +103,6 @@
   virtual scoped_refptr<MediaQuery> ParseMediaQuery(
       const std::string& media_query,
       const base::SourceLocation& input_location) = 0;
-
- protected:
-  virtual ~CSSParser() {}
 };
 
 }  // namespace cssom
diff --git a/cobalt/cssom/user_agent_style_sheet.cc b/cobalt/cssom/user_agent_style_sheet.cc
index cdb4164..f0c3876 100644
--- a/cobalt/cssom/user_agent_style_sheet.cc
+++ b/cobalt/cssom/user_agent_style_sheet.cc
@@ -22,6 +22,9 @@
 namespace cssom {
 
 scoped_refptr<CSSStyleSheet> ParseUserAgentStyleSheet(CSSParser* css_parser) {
+  if (!css_parser) {
+    return nullptr;
+  }
   const char kUserAgentStyleSheetFileName[] = "user_agent_style_sheet.css";
 
   // Parse the user agent style sheet from the given file that was compiled
@@ -41,8 +44,10 @@
               reinterpret_cast<const char*>(html_css_file_contents.data),
               static_cast<size_t>(html_css_file_contents.size)),
           base::SourceLocation(kUserAgentStyleSheetFileName, 1, 1));
-  user_agent_style_sheet->set_origin(kNormalUserAgent);
-  user_agent_style_sheet->SetOriginClean(true);
+  if (user_agent_style_sheet) {
+    user_agent_style_sheet->set_origin(kNormalUserAgent);
+    user_agent_style_sheet->SetOriginClean(true);
+  }
   return user_agent_style_sheet;
 }
 
diff --git a/cobalt/dom/abort_controller.idl b/cobalt/dom/abort_controller.idl
index 36ebebd..0280347 100644
--- a/cobalt/dom/abort_controller.idl
+++ b/cobalt/dom/abort_controller.idl
@@ -15,6 +15,7 @@
 // https://dom.spec.whatwg.org/#interface-abortcontroller
 
 [
+  Exposed=(Window,Worker),
   Constructor,
   ConstructorCallWith=EnvironmentSettings,
 ]
diff --git a/cobalt/dom/comment_test.cc b/cobalt/dom/comment_test.cc
index b68580d..0fb5554 100644
--- a/cobalt/dom/comment_test.cc
+++ b/cobalt/dom/comment_test.cc
@@ -14,10 +14,15 @@
 
 #include "cobalt/dom/comment.h"
 
+#include <memory>
+
 #include "cobalt/dom/document.h"
+#include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
+#include "cobalt/dom/testing/fake_document.h"
+#include "cobalt/dom/testing/stub_window.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -34,18 +39,27 @@
   CommentTest();
   ~CommentTest() override;
 
-  base::MessageLoop message_loop_;
-  HTMLElementContext html_element_context_;
+  std::unique_ptr<testing::StubWindow> window_;
+  std::unique_ptr<HTMLElementContext> html_element_context_;
+  std::unique_ptr<DomStatTracker> dom_stat_tracker_;
   scoped_refptr<Document> document_;
 };
 
-CommentTest::CommentTest() {
+CommentTest::CommentTest()
+    : window_(new testing::StubWindow),
+      dom_stat_tracker_(new DomStatTracker("CommentTest")) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
-  document_ = new Document(&html_element_context_);
+  window_->InitializeWindow();
+  html_element_context_.reset(new HTMLElementContext(
+      window_->web_context()->environment_settings(), NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+      dom_stat_tracker_.get(), "", base::kApplicationStateStarted, NULL, NULL));
+  document_ = new testing::FakeDocument(html_element_context_.get());
 }
 
 CommentTest::~CommentTest() {
-  document_ = NULL;
+  window_.reset();
+  document_ = nullptr;
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
 }
 
diff --git a/cobalt/dom/document.cc b/cobalt/dom/document.cc
index 41bfa41a..c35472c 100644
--- a/cobalt/dom/document.cc
+++ b/cobalt/dom/document.cc
@@ -82,12 +82,11 @@
 }  // namespace
 
 Document::Document(HTMLElementContext* html_element_context,
-                   const Options& options)
+                   const Options& options, web::CspDelegate* csp_delegate)
     : ALLOW_THIS_IN_INITIALIZER_LIST(Node(html_element_context, this)),
       html_element_context_(html_element_context),
       application_lifecycle_state_(
           html_element_context_->application_lifecycle_state()),
-      window_(options.window),
       implementation_(new DOMImplementation(html_element_context)),
       style_sheets_(new cssom::StyleSheetList()),
       loading_counter_(0),
@@ -124,9 +123,13 @@
 
   cookie_jar_ = options.cookie_jar;
 
+  if (!csp_delegate) {
+    csp_delegate = Document::GetCSPDelegate();
+  }
+
   location_ = new Location(
       options.url, options.hashchange_callback, options.navigation_callback,
-      CreateSecurityCallback(csp_delegate(), web::CspDelegate::kLocation),
+      CreateSecurityCallback(csp_delegate, web::CspDelegate::kLocation),
       base::Bind(&Document::SetNavigationType, base::Unretained(this)));
 
   font_cache_.reset(new FontCache(
@@ -135,18 +138,16 @@
       base::Bind(&Document::OnTypefaceLoadEvent, base::Unretained(this)),
       html_element_context_->font_language_script(), location_));
 
-  if (HasBrowsingContext()) {
-    if (html_element_context_->remote_typeface_cache()) {
-      html_element_context_->remote_typeface_cache()->set_security_callback(
-          CreateSecurityCallback(csp_delegate(), web::CspDelegate::kFont));
-    }
-    if (html_element_context_->image_cache()) {
-      html_element_context_->image_cache()->set_security_callback(
-          CreateSecurityCallback(csp_delegate(), web::CspDelegate::kImage));
-    }
-
-    ready_state_ = kDocumentReadyStateLoading;
+  if (html_element_context_->remote_typeface_cache()) {
+    html_element_context_->remote_typeface_cache()->set_security_callback(
+        CreateSecurityCallback(csp_delegate, web::CspDelegate::kFont));
   }
+  if (html_element_context_->image_cache()) {
+    html_element_context_->image_cache()->set_security_callback(
+        CreateSecurityCallback(csp_delegate, web::CspDelegate::kImage));
+  }
+
+  ready_state_ = kDocumentReadyStateLoading;
 
   // Sample the timeline upon initialization.
   SampleTimelineTime();
@@ -164,7 +165,7 @@
   return first_element_child();
 }
 
-scoped_refptr<Window> Document::default_view() const { return window_; }
+scoped_refptr<Window> Document::default_view() const { return window(); }
 
 std::string Document::title() const {
   const char kTitleTag[] = "title";
@@ -571,7 +572,25 @@
   }
 }
 
-const scoped_refptr<Window> Document::window() { return window_; }
+web::CspDelegate* Document::GetCSPDelegate() const {
+  web::WindowOrWorkerGlobalScope* window_or_worker_global_scope =
+      environment_settings()->context()
+          ? environment_settings()->context()->GetWindowOrWorkerGlobalScope()
+          : nullptr;
+  return window_or_worker_global_scope
+             ? window_or_worker_global_scope->csp_delegate()
+             : nullptr;
+}
+
+const scoped_refptr<Window> Document::window() const {
+  web::WindowOrWorkerGlobalScope* window_or_worker_global_scope =
+      environment_settings()->context()
+          ? environment_settings()->context()->GetWindowOrWorkerGlobalScope()
+          : nullptr;
+  return window_or_worker_global_scope
+             ? window_or_worker_global_scope->AsWindow()
+             : nullptr;
+}
 
 void Document::IncreaseLoadingCounter() { ++loading_counter_; }
 
@@ -637,7 +656,7 @@
 
 void Document::NotifyUrlChanged(const GURL& url) {
   location_->set_url(url);
-  csp_delegate()->NotifyUrlChanged(url);
+  GetCSPDelegate()->NotifyUrlChanged(url);
 }
 
 void Document::OnFocusChange() {
@@ -1011,10 +1030,7 @@
 }
 
 void Document::DisableJit() {
-  window_->html_element_context()
-      ->script_runner()
-      ->GetGlobalEnvironment()
-      ->DisableJit();
+  environment_settings()->context()->global_environment()->DisableJit();
 }
 
 void Document::OnWindowFocusChanged(bool has_focus) {
@@ -1212,7 +1228,7 @@
 }
 
 void Document::OnRootElementUnableToProvideOffsetDimensions() {
-  window_->OnDocumentRootElementUnableToProvideOffsetDimensions();
+  window()->OnDocumentRootElementUnableToProvideOffsetDimensions();
 }
 
 void Document::DispatchOnLoadEvent() {
diff --git a/cobalt/dom/document.h b/cobalt/dom/document.h
index 89c65c0..0a7a4d9 100644
--- a/cobalt/dom/document.h
+++ b/cobalt/dom/document.h
@@ -103,11 +103,10 @@
                  public ApplicationLifecycleState::Observer {
  public:
   struct Options {
-    Options() : window(NULL), cookie_jar(NULL) {}
+    Options() : cookie_jar(NULL) {}
     explicit Options(const GURL& url_value)
-        : url(url_value), window(NULL), cookie_jar(NULL) {}
-    Options(const GURL& url_value, Window* window,
-            const base::Closure& hashchange_callback,
+        : url(url_value), cookie_jar(NULL) {}
+    Options(const GURL& url_value, const base::Closure& hashchange_callback,
             const scoped_refptr<base::BasicClock>& navigation_start_clock_value,
             const base::Callback<void(const GURL&)>& navigation_callback,
             const scoped_refptr<cssom::CSSStyleSheet> user_agent_style_sheet,
@@ -115,7 +114,6 @@
             network_bridge::CookieJar* cookie_jar,
             int dom_max_element_depth = 0)
         : url(url_value),
-          window(window),
           hashchange_callback(hashchange_callback),
           navigation_start_clock(navigation_start_clock_value),
           navigation_callback(navigation_callback),
@@ -125,7 +123,6 @@
           dom_max_element_depth(dom_max_element_depth) {}
 
     GURL url;
-    Window* window;
     base::Closure hashchange_callback;
     scoped_refptr<base::BasicClock> navigation_start_clock;
     base::Callback<void(const GURL&)> navigation_callback;
@@ -136,7 +133,8 @@
   };
 
   Document(HTMLElementContext* html_element_context,
-           const Options& options = Options());
+           const Options& options = Options(),
+           web::CspDelegate* csp_delegate = nullptr);
 
   // Web API: Node
   //
@@ -274,10 +272,11 @@
   // Returns whether the document has browsing context. Having the browsing
   // context means the document is shown on the screen.
   //   https://www.w3.org/TR/html50/browsers.html#browsing-context
-  bool HasBrowsingContext() const { return !!window_; }
+  bool HasBrowsingContext() const {
+    return window() && (window()->document().get() == this);
+  }
 
-  void set_window(Window* window) { window_ = window; }
-  const scoped_refptr<Window> window();
+  const scoped_refptr<Window> window() const;
 
   // Sets the active element of the document.
   void SetActiveElement(Element* active_element);
@@ -363,12 +362,7 @@
   }
 
   // Virtual for testing.
-  virtual web::CspDelegate* csp_delegate() const {
-    if (window_) {
-      return window_->csp_delegate();
-    }
-    return nullptr;
-  }
+  virtual web::CspDelegate* GetCSPDelegate() const;
 
   // Triggers a synchronous layout.
   scoped_refptr<render_tree::Node> DoSynchronousLayoutAndGetRenderTree();
@@ -414,7 +408,8 @@
   // Page Visibility fields.
   bool hidden() const { return visibility_state() == kVisibilityStateHidden; }
   VisibilityState visibility_state() const {
-    return application_lifecycle_state()->GetVisibilityState();
+    const ApplicationLifecycleState* state = application_lifecycle_state();
+    return state ? state->GetVisibilityState() : kVisibilityStateHidden;
   }
   const EventListenerScriptValue* onvisibilitychange() const {
     return GetAttributeEventListener(base::Tokens::visibilitychange());
@@ -548,8 +543,6 @@
   // situation more gracefully than crashing.
   base::WeakPtr<ApplicationLifecycleState> application_lifecycle_state_;
 
-  // Reference to the associated window object.
-  Window* window_;
   // Associated DOM implementation object.
   scoped_refptr<DOMImplementation> implementation_;
   // List of CSS style sheets.
diff --git a/cobalt/dom/document_test.cc b/cobalt/dom/document_test.cc
index b693173..4668a6b 100644
--- a/cobalt/dom/document_test.cc
+++ b/cobalt/dom/document_test.cc
@@ -37,6 +37,7 @@
 #include "cobalt/dom/testing/fake_document.h"
 #include "cobalt/dom/testing/html_collection_testing.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
+#include "cobalt/dom/testing/stub_window.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom/ui_event.h"
 #include "cobalt/script/testing/mock_exception_state.h"
@@ -64,25 +65,31 @@
   DocumentTest();
   ~DocumentTest() override;
 
-  base::MessageLoop message_loop_;
-  testing::StubEnvironmentSettings environment_settings_;
+  const scoped_refptr<Document>& document() {
+    return window_->window()->document();
+  }
+
+  std::unique_ptr<testing::StubWindow> window_;
+  std::unique_ptr<HTMLElementContext> html_element_context_;
   std::unique_ptr<css_parser::Parser> css_parser_;
   std::unique_ptr<DomStatTracker> dom_stat_tracker_;
-  HTMLElementContext html_element_context_;
 };
 
 DocumentTest::DocumentTest()
-    : css_parser_(css_parser::Parser::Create()),
-      dom_stat_tracker_(new DomStatTracker("DocumentTest")),
-      html_element_context_(&environment_settings_, NULL, NULL,
-                            css_parser_.get(), NULL, NULL, NULL, NULL, NULL,
-                            NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                            dom_stat_tracker_.get(), "",
-                            base::kApplicationStateStarted, NULL, NULL) {
+    : window_(new testing::StubWindow),
+      css_parser_(css_parser::Parser::Create()),
+      dom_stat_tracker_(new DomStatTracker("DocumentTest")) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+  window_->InitializeWindow();
+  html_element_context_.reset(new HTMLElementContext(
+      window_->web_context()->environment_settings(), NULL, NULL,
+      css_parser_.get(), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, dom_stat_tracker_.get(), "",
+      base::kApplicationStateStarted, NULL, NULL));
 }
 
 DocumentTest::~DocumentTest() {
+  window_.reset();
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
 }
 
@@ -92,29 +99,43 @@
 
 TEST_F(DocumentTest, Create) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   ASSERT_TRUE(document);
 
   EXPECT_EQ(Node::kDocumentNode, document->node_type());
   EXPECT_EQ("#document", document->node_name());
 
   GURL url("http://a valid url");
-  document =
-      new testing::FakeDocument(&html_element_context_, Document::Options(url));
+  document = new testing::FakeDocument(html_element_context_.get(),
+                                       Document::Options(url));
   EXPECT_EQ(url.spec(), document->url());
   EXPECT_EQ(url.spec(), document->document_uri());
   EXPECT_EQ(url, document->location()->url());
 }
 
-TEST_F(DocumentTest, IsNotXMLDocument) {
+TEST_F(DocumentTest, MainDocumentIsNotXMLDocument) {
+  EXPECT_FALSE(document()->IsXMLDocument());
+}
+
+TEST_F(DocumentTest, NewDocumentIsNotXMLDocument) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_FALSE(document->IsXMLDocument());
 }
 
+TEST_F(DocumentTest, MainDocumentHasBrowsingContext) {
+  EXPECT_TRUE(document()->HasBrowsingContext());
+}
+
+TEST_F(DocumentTest, NewDocumentHasNoBrowsingContext) {
+  scoped_refptr<Document> document =
+      new testing::FakeDocument(html_element_context_.get());
+  EXPECT_FALSE(document->HasBrowsingContext());
+}
+
 TEST_F(DocumentTest, DocumentElement) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_EQ(NULL, document->document_element().get());
 
   scoped_refptr<Text> text = new Text(document, "test_text");
@@ -127,7 +148,7 @@
 
 TEST_F(DocumentTest, CreateElement) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   scoped_refptr<Element> element = document->CreateElement("element");
 
   EXPECT_EQ(Node::kElementNode, element->node_type());
@@ -144,7 +165,7 @@
 
 TEST_F(DocumentTest, CreateTextNode) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   scoped_refptr<Text> text = document->CreateTextNode("test_text");
 
   EXPECT_EQ(Node::kTextNode, text->node_type());
@@ -155,7 +176,7 @@
 
 TEST_F(DocumentTest, CreateComment) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   scoped_refptr<Comment> comment = document->CreateComment("test_comment");
 
   EXPECT_EQ(Node::kCommentNode, comment->node_type());
@@ -168,7 +189,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Create an Event, the name is case insensitive.
   scoped_refptr<web::Event> event =
@@ -193,7 +214,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Create an Event, the name is case insensitive.
   scoped_refptr<web::Event> event =
@@ -207,7 +228,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Create an Event, the name is case insensitive.
   scoped_refptr<web::Event> event =
@@ -226,7 +247,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Create an Event, the name is case insensitive.
   scoped_refptr<web::Event> event =
@@ -245,7 +266,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Create an Event, the name is case insensitive.
   scoped_refptr<web::Event> event =
@@ -259,7 +280,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Create an Event, the name is case insensitive.
   scoped_refptr<web::Event> event =
@@ -278,7 +299,7 @@
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   EXPECT_CALL(exception_state, SetException(_))
       .WillOnce(SaveArg<0>(&exception));
@@ -294,19 +315,19 @@
 
 TEST_F(DocumentTest, GetElementsByClassName) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   testing::TestGetElementsByClassName(document);
 }
 
 TEST_F(DocumentTest, GetElementsByTagName) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   testing::TestGetElementsByTagName(document);
 }
 
 TEST_F(DocumentTest, GetElementById) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   // Construct a tree:
   // document
@@ -340,34 +361,34 @@
 
 TEST_F(DocumentTest, Implementation) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_TRUE(document->implementation());
 }
 
 TEST_F(DocumentTest, Location) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_TRUE(document->location());
 }
 
 TEST_F(DocumentTest, StyleSheets) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   scoped_refptr<HTMLElement> element1 =
-      html_element_context_.html_element_factory()->CreateHTMLElement(
+      html_element_context_->html_element_factory()->CreateHTMLElement(
           document, base::Token(HTMLStyleElement::kTagName));
   element1->set_text_content(std::string("body { background-color: #D3D3D3 }"));
   document->AppendChild(element1);
 
   scoped_refptr<HTMLElement> element2 =
-      html_element_context_.html_element_factory()->CreateHTMLElement(
+      html_element_context_->html_element_factory()->CreateHTMLElement(
           document, base::Token(HTMLStyleElement::kTagName));
   element2->set_text_content(std::string("h1 { color: #00F }"));
   document->AppendChild(element2);
 
   scoped_refptr<HTMLElement> element3 =
-      html_element_context_.html_element_factory()->CreateHTMLElement(
+      html_element_context_->html_element_factory()->CreateHTMLElement(
           document, base::Token(HTMLStyleElement::kTagName));
   element3->set_text_content(std::string("p { color: #008000 }"));
   document->AppendChild(element3);
@@ -398,22 +419,22 @@
 
 TEST_F(DocumentTest, StyleSheetsAddedToFront) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
 
   scoped_refptr<HTMLElement> element1 =
-      html_element_context_.html_element_factory()->CreateHTMLElement(
+      html_element_context_->html_element_factory()->CreateHTMLElement(
           document, base::Token(HTMLStyleElement::kTagName));
   element1->set_text_content(std::string("body { background-color: #D3D3D3 }"));
   document->AppendChild(element1);
 
   scoped_refptr<HTMLElement> element2 =
-      html_element_context_.html_element_factory()->CreateHTMLElement(
+      html_element_context_->html_element_factory()->CreateHTMLElement(
           document, base::Token(HTMLStyleElement::kTagName));
   element2->set_text_content(std::string("h1 { color: #00F }"));
   document->InsertBefore(element2, element1);
 
   scoped_refptr<HTMLElement> element3 =
-      html_element_context_.html_element_factory()->CreateHTMLElement(
+      html_element_context_->html_element_factory()->CreateHTMLElement(
           document, base::Token(HTMLStyleElement::kTagName));
   element3->set_text_content(std::string("p { color: #008000 }"));
   document->InsertBefore(element3, element2);
@@ -444,7 +465,7 @@
 
 TEST_F(DocumentTest, HtmlElement) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_FALSE(document->html());
 
   scoped_refptr<Node> div =
@@ -459,7 +480,7 @@
 
 TEST_F(DocumentTest, HeadElement) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_FALSE(document->head());
 
   scoped_refptr<Node> html =
@@ -478,7 +499,7 @@
 
 TEST_F(DocumentTest, BodyElement) {
   scoped_refptr<Document> document =
-      new testing::FakeDocument(&html_element_context_);
+      new testing::FakeDocument(html_element_context_.get());
   EXPECT_FALSE(document->body());
 
   scoped_refptr<Node> html =
diff --git a/cobalt/dom/dom_stat_tracker.cc b/cobalt/dom/dom_stat_tracker.cc
index 093889f..2846654 100644
--- a/cobalt/dom/dom_stat_tracker.cc
+++ b/cobalt/dom/dom_stat_tracker.cc
@@ -68,8 +68,9 @@
 
   // Verify that all of the elements were removed from the document and
   // destroyed.
-  DCHECK_EQ(count_html_element_, 0);
-  DCHECK_EQ(count_html_element_document_, 0);
+  DCHECK_EQ(count_html_element_, 0) << count_html_element_ << " html elements.";
+  DCHECK_EQ(count_html_element_document_, 0)
+      << count_html_element_document_ << " html elements in the document.";
 
   event_video_start_delay_stop_watch_.Stop();
 }
diff --git a/cobalt/dom/global_stats.cc b/cobalt/dom/global_stats.cc
index 66acba6..b589091 100644
--- a/cobalt/dom/global_stats.cc
+++ b/cobalt/dom/global_stats.cc
@@ -47,13 +47,13 @@
 GlobalStats::~GlobalStats() {}
 
 bool GlobalStats::CheckNoLeaks() {
-  DCHECK(num_attrs_ == 0);
-  DCHECK(num_dom_string_maps_ == 0);
-  DCHECK(num_dom_token_lists_ == 0);
-  DCHECK(num_html_collections_ == 0);
-  DCHECK(num_named_node_maps_ == 0);
-  DCHECK(num_nodes_ == 0);
-  DCHECK(num_node_lists_ == 0);
+  DCHECK(num_attrs_ == 0) << num_attrs_;
+  DCHECK(num_dom_string_maps_ == 0) << num_dom_string_maps_;
+  DCHECK(num_dom_token_lists_ == 0) << num_dom_token_lists_;
+  DCHECK(num_html_collections_ == 0) << num_html_collections_;
+  DCHECK(num_named_node_maps_ == 0) << num_named_node_maps_;
+  DCHECK(num_nodes_ == 0) << num_nodes_;
+  DCHECK(num_node_lists_ == 0) << num_node_lists_;
   return web::GlobalStats::GetInstance()->CheckNoLeaks() &&
          xhr::GlobalStats::GetInstance()->CheckNoLeaks() && num_attrs_ == 0 &&
          num_dom_string_maps_ == 0 && num_dom_token_lists_ == 0 &&
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc
index 970c56e..7b73605 100644
--- a/cobalt/dom/html_element.cc
+++ b/cobalt/dom/html_element.cc
@@ -834,7 +834,7 @@
 
 void HTMLElement::SetStyleAttribute(const std::string& value) {
   Document* document = node_document();
-  web::CspDelegate* csp_delegate = document->csp_delegate();
+  web::CspDelegate* csp_delegate = document->GetCSPDelegate();
   if (value.empty() ||
       csp_delegate->AllowInline(
           web::CspDelegate::kStyle,
@@ -1474,6 +1474,21 @@
   }
 }
 
+void HTMLElement::SetUiNavItemBounds() {
+  if (!ui_nav_item_->IsContainer()) {
+    return;
+  }
+  float scrollable_width = scroll_width() - client_width();
+  float scroll_top_lower_bound = 0.0f;
+  float scroll_left_lower_bound =
+      GetUsedDirState() == DirState::kDirRightToLeft ? -scrollable_width : 0.0f;
+  float scroll_top_upper_bound = scroll_height() - client_height();
+  float scroll_left_upper_bound =
+      GetUsedDirState() == DirState::kDirRightToLeft ? 0.0f : scrollable_width;
+  ui_nav_item_->SetBounds(scroll_top_lower_bound, scroll_left_lower_bound,
+                          scroll_top_upper_bound, scroll_left_upper_bound);
+}
+
 void HTMLElement::SetDir(const std::string& value) {
   // https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-dir-attribute
   auto previous_dir = dir_;
diff --git a/cobalt/dom/html_element.h b/cobalt/dom/html_element.h
index 0b59022..18f4258 100644
--- a/cobalt/dom/html_element.h
+++ b/cobalt/dom/html_element.h
@@ -369,6 +369,9 @@
   // for this HTML element (if any).
   void UpdateUiNavigationFocus();
 
+  // Update boundaries for all UI navigation items.
+  void SetUiNavItemBounds();
+
   // Returns true if the element is the root element as defined in
   // https://www.w3.org/TR/html50/semantics.html#the-root-element.
   bool IsRootElement();
@@ -393,7 +396,7 @@
                       const std::string& value) override;
   void OnRemoveAttribute(const std::string& name) override;
 
-    // Create Performance Resource Timing entry for background image.
+  // Create Performance Resource Timing entry for background image.
   void GetLoadTimingInfoAndCreateResourceTiming();
 
   // HTMLElement keeps a pointer to the dom stat tracker to ensure that it can
diff --git a/cobalt/dom/html_element_context.cc b/cobalt/dom/html_element_context.cc
index 7328cde..c3ca88a 100644
--- a/cobalt/dom/html_element_context.cc
+++ b/cobalt/dom/html_element_context.cc
@@ -55,7 +55,7 @@
 #endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
 HTMLElementContext::HTMLElementContext(
-    script::EnvironmentSettings* environment_settings,
+    web::EnvironmentSettings* environment_settings,
     loader::FetcherFactory* fetcher_factory,
     loader::LoaderFactory* loader_factory, cssom::CSSParser* css_parser,
     Parser* dom_parser, media::CanPlayTypeHandler* can_play_type_handler,
@@ -72,8 +72,7 @@
     loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
     const std::string& font_language_script,
     base::ApplicationState initial_application_state,
-    base::WaitableEvent* synchronous_loader_interrupt,
-    Performance* performance,
+    base::WaitableEvent* synchronous_loader_interrupt, Performance* performance,
     bool enable_inline_script_warnings, float video_playback_rate_multiplier)
     : environment_settings_(environment_settings),
       fetcher_factory_(fetcher_factory),
diff --git a/cobalt/dom/html_element_context.h b/cobalt/dom/html_element_context.h
index 4f4ff40..72e9de7 100644
--- a/cobalt/dom/html_element_context.h
+++ b/cobalt/dom/html_element_context.h
@@ -57,7 +57,7 @@
 #endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
   HTMLElementContext(
-      script::EnvironmentSettings* environment_settings,
+      web::EnvironmentSettings* environment_settings,
       loader::FetcherFactory* fetcher_factory,
       loader::LoaderFactory* loader_factory, cssom::CSSParser* css_parser,
       Parser* dom_parser, media::CanPlayTypeHandler* can_play_type_handler,
@@ -79,7 +79,7 @@
       float video_playback_rate_multiplier = 1.0);
   ~HTMLElementContext();
 
-  script::EnvironmentSettings* environment_settings() const {
+  web::EnvironmentSettings* environment_settings() const {
     return environment_settings_;
   }
 
@@ -166,10 +166,10 @@
  private:
 #if !defined(COBALT_BUILD_TYPE_GOLD)
   // StubEnvironmentSettings for no-args test constructor.
-  std::unique_ptr<script::EnvironmentSettings> stub_environment_settings_;
+  std::unique_ptr<web::EnvironmentSettings> stub_environment_settings_;
 #endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
-  script::EnvironmentSettings* environment_settings_;
+  web::EnvironmentSettings* environment_settings_;
   loader::FetcherFactory* const fetcher_factory_;
   loader::LoaderFactory* const loader_factory_;
   cssom::CSSParser* const css_parser_;
diff --git a/cobalt/dom/html_element_test.cc b/cobalt/dom/html_element_test.cc
index 9426a09..d30f4a7 100644
--- a/cobalt/dom/html_element_test.cc
+++ b/cobalt/dom/html_element_test.cc
@@ -21,12 +21,14 @@
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/cssom/css_computed_style_data.h"
 #include "cobalt/cssom/css_declared_style_data.h"
+#include "cobalt/cssom/css_parser.h"
 #include "cobalt/cssom/keyword_value.h"
 #include "cobalt/cssom/testing/mock_css_parser.h"  // nogncheck
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_rect_list.h"
 #include "cobalt/dom/dom_stat_tracker.h"
+#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_body_element.h"
 #include "cobalt/dom/html_div_element.h"
 #include "cobalt/dom/html_element_context.h"
@@ -82,13 +84,33 @@
 class HTMLElementTest : public ::testing::Test {
  protected:
   HTMLElementTest()
-      : dom_stat_tracker_(new DomStatTracker("HTMLElementTest")),
-        html_element_context_(&environment_settings_, NULL, NULL, &css_parser_,
-                              NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, dom_stat_tracker_.get(),
-                              "", base::kApplicationStateStarted, NULL, NULL),
-        document_(new testing::FakeDocument(&html_element_context_)) {}
-  ~HTMLElementTest() override {}
+      : window_(new testing::StubWindow),
+        dom_stat_tracker_(new DomStatTracker("HTMLElementTest")) {
+    EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+    window_->set_css_parser(new cssom::testing::MockCSSParser);
+    // We expect one call to parse the user agent style sheet.
+    EXPECT_CALL(css_parser(), ParseStyleSheet(_, _))
+        .WillRepeatedly(Return(scoped_refptr<cssom::CSSStyleSheet>()));
+    window_->InitializeWindow();
+    html_element_context_.reset(new HTMLElementContext(
+        window_->web_context()->environment_settings(), NULL, NULL,
+        window_->css_parser(), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+        NULL, NULL, NULL, NULL, dom_stat_tracker_.get(), "",
+        base::kApplicationStateStarted, NULL, NULL));
+  }
+  ~HTMLElementTest() override {
+    window_.reset();
+    EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+  }
+
+  cssom::testing::MockCSSParser& css_parser() {
+    return *base::polymorphic_downcast<cssom::testing::MockCSSParser*>(
+        window_->css_parser());
+  }
+
+  const scoped_refptr<Document>& document() {
+    return window_->window()->document();
+  }
 
   // This creates simple DOM tree with mock layout boxes for all elements except
   // the last child element. It returns the root html element.
@@ -98,17 +120,15 @@
   void SetElementStyle(const scoped_refptr<cssom::CSSDeclaredStyleData>& data,
                        HTMLElement* html_element);
 
-  testing::StubEnvironmentSettings environment_settings_;
-  cssom::testing::MockCSSParser css_parser_;
+  std::unique_ptr<testing::StubWindow> window_;
   std::unique_ptr<DomStatTracker> dom_stat_tracker_;
-  HTMLElementContext html_element_context_;
-  scoped_refptr<Document> document_;
+  std::unique_ptr<HTMLElementContext> html_element_context_;
 };
 
 void HTMLElementTest::SetElementStyle(
     const scoped_refptr<cssom::CSSDeclaredStyleData>& data,
     HTMLElement* html_element) {
-  EXPECT_CALL(css_parser_,
+  EXPECT_CALL(css_parser(),
               ParseStyleDeclarationList(kFooBarDeclarationString, _))
       .WillOnce(Return(data));
   html_element->SetAttribute("style", kFooBarDeclarationString);
@@ -117,12 +137,13 @@
 scoped_refptr<HTMLElement>
 HTMLElementTest::CreateHTMLElementTreeWithMockLayoutBoxes(
     const char* null_terminated_element_names[]) {
-  DCHECK(!document_->IsXMLDocument());
+  DCHECK(!document()->IsXMLDocument());
   scoped_refptr<HTMLElement> root_html_element;
   scoped_refptr<HTMLElement> parent_html_element;
   do {
     scoped_refptr<HTMLElement> child_html_element(
-        document_->CreateElement(*null_terminated_element_names)
+        document()
+            ->CreateElement(*null_terminated_element_names)
             ->AsHTMLElement());
     DCHECK(child_html_element);
     child_html_element->css_computed_style_declaration()->SetData(
@@ -163,7 +184,7 @@
 TEST_F(HTMLElementTest, Dir) {
   for (size_t i = 0; i < arraysize(kHtmlElementTagNames); ++i) {
     scoped_refptr<HTMLElement> html_element =
-        document_->CreateElement(kHtmlElementTagNames[i])->AsHTMLElement();
+        document()->CreateElement(kHtmlElementTagNames[i])->AsHTMLElement();
     EXPECT_EQ("", html_element->dir());
 
     html_element->set_dir("invalid");
@@ -189,7 +210,7 @@
 TEST_F(HTMLElementTest, TabIndex) {
   for (size_t i = 0; i < arraysize(kHtmlElementTagNames); ++i) {
     scoped_refptr<HTMLElement> html_element =
-        document_->CreateElement(kHtmlElementTagNames[i])->AsHTMLElement();
+        document()->CreateElement(kHtmlElementTagNames[i])->AsHTMLElement();
 
     EXPECT_EQ(0, html_element->tab_index());
 
@@ -205,29 +226,26 @@
 }
 
 TEST_F(HTMLElementTest, Focus) {
-  // Give the document browsing context which is needed for focus to work.
-  testing::StubWindow window;
-  document_->set_window(window.window());
   // Give the document initial computed style.
-  document_->SetViewport(kViewSize);
+  document()->SetViewport(kViewSize);
 
   scoped_refptr<HTMLElement> html_element_1 =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<HTMLElement> html_element_2 =
-      document_->CreateElement("div")->AsHTMLElement();
-  document_->AppendChild(html_element_1);
-  document_->AppendChild(html_element_2);
-  EXPECT_FALSE(document_->active_element());
+      document()->CreateElement("div")->AsHTMLElement();
+  document()->AppendChild(html_element_1);
+  document()->AppendChild(html_element_2);
+  EXPECT_FALSE(document()->active_element());
 
   html_element_1->set_tab_index(-1);
   html_element_1->Focus();
-  ASSERT_TRUE(document_->active_element());
-  EXPECT_EQ(html_element_1, document_->active_element()->AsHTMLElement());
+  ASSERT_TRUE(document()->active_element());
+  EXPECT_EQ(html_element_1, document()->active_element()->AsHTMLElement());
 
   html_element_2->set_tab_index(-1);
   html_element_2->Focus();
-  ASSERT_TRUE(document_->active_element());
-  EXPECT_EQ(html_element_2, document_->active_element()->AsHTMLElement());
+  ASSERT_TRUE(document()->active_element());
+  EXPECT_EQ(html_element_2, document()->active_element()->AsHTMLElement());
 
   // Make sure that if we try to focus an element that has display set to none,
   // it will not take the focus.
@@ -237,70 +255,64 @@
       cssom::kDisplayProperty, cssom::KeywordValue::GetNone(), false);
   SetElementStyle(display_none_style, html_element_1);
   html_element_1->Focus();
-  ASSERT_TRUE(document_->active_element());
-  EXPECT_EQ(html_element_2, document_->active_element()->AsHTMLElement());
+  ASSERT_TRUE(document()->active_element());
+  EXPECT_EQ(html_element_2, document()->active_element()->AsHTMLElement());
 
   // Make sure that if we try to focus an element whose ancestor has display
   // set to none, it will not take the focus.
   scoped_refptr<HTMLElement> html_element_3 =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<HTMLElement> html_element_4 =
-      document_->CreateElement("div")->AsHTMLElement();
-  document_->AppendChild(html_element_3);
+      document()->CreateElement("div")->AsHTMLElement();
+  document()->AppendChild(html_element_3);
   html_element_3->AppendChild(html_element_4);
 
   html_element_4->set_tab_index(-1);
   SetElementStyle(display_none_style, html_element_3);
   html_element_4->Focus();
-  ASSERT_TRUE(document_->active_element());
-  EXPECT_EQ(html_element_2, document_->active_element()->AsHTMLElement());
+  ASSERT_TRUE(document()->active_element());
+  EXPECT_EQ(html_element_2, document()->active_element()->AsHTMLElement());
 }
 
 TEST_F(HTMLElementTest, Blur) {
-  // Give the document browsing context which is needed for focus to work.
-  testing::StubWindow window;
-  document_->set_window(window.window());
   // Give the document initial computed style.
-  document_->SetViewport(kViewSize);
+  document()->SetViewport(kViewSize);
 
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
-  document_->AppendChild(html_element);
-  EXPECT_FALSE(document_->active_element());
+      document()->CreateElement("div")->AsHTMLElement();
+  document()->AppendChild(html_element);
+  EXPECT_FALSE(document()->active_element());
 
   html_element->set_tab_index(-1);
   html_element->Focus();
-  ASSERT_TRUE(document_->active_element());
-  EXPECT_EQ(html_element, document_->active_element()->AsHTMLElement());
+  ASSERT_TRUE(document()->active_element());
+  EXPECT_EQ(html_element, document()->active_element()->AsHTMLElement());
 
   html_element->Blur();
-  EXPECT_FALSE(document_->active_element());
+  EXPECT_FALSE(document()->active_element());
 }
 
 TEST_F(HTMLElementTest, RemoveActiveElementShouldRunBlur) {
-  // Give the document browsing context which is needed for focus to work.
-  testing::StubWindow window;
-  document_->set_window(window.window());
   // Give the document initial computed style.
-  document_->SetViewport(kViewSize);
+  document()->SetViewport(kViewSize);
 
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
-  document_->AppendChild(html_element);
-  EXPECT_FALSE(document_->active_element());
+      document()->CreateElement("div")->AsHTMLElement();
+  document()->AppendChild(html_element);
+  EXPECT_FALSE(document()->active_element());
 
   html_element->set_tab_index(-1);
   html_element->Focus();
-  ASSERT_TRUE(document_->active_element());
-  EXPECT_EQ(html_element, document_->active_element()->AsHTMLElement());
+  ASSERT_TRUE(document()->active_element());
+  EXPECT_EQ(html_element, document()->active_element()->AsHTMLElement());
 
-  document_->RemoveChild(html_element);
-  EXPECT_FALSE(document_->active_element());
+  document()->RemoveChild(html_element);
+  EXPECT_FALSE(document()->active_element());
 }
 
 TEST_F(HTMLElementTest, LayoutBoxesGetter) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
 
   std::unique_ptr<MockLayoutBoxes> mock_layout_boxes(new MockLayoutBoxes);
   MockLayoutBoxes* saved_mock_layout_boxes_ptr = mock_layout_boxes.get();
@@ -319,7 +331,7 @@
 
 TEST_F(HTMLElementTest, GetBoundingClientRectWithoutLayoutBox) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<DOMRect> rect = html_element->GetBoundingClientRect();
   DCHECK(rect);
   EXPECT_FLOAT_EQ(rect->x(), 0.0f);
@@ -336,7 +348,7 @@
 //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-element-clienttop
 TEST_F(HTMLElementTest, ClientTop) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
 
   // 1. If the element has no associated CSS layout box, return zero.
   EXPECT_FLOAT_EQ(html_element->client_top(), 0.0f);
@@ -371,7 +383,7 @@
 //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-element-clientleft
 TEST_F(HTMLElementTest, ClientLeft) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
 
   // 1. If the element has no associated CSS layout box, return zero.
   EXPECT_FLOAT_EQ(html_element->client_left(), 0.0f);
@@ -655,7 +667,7 @@
 //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-htmlelement-offsetwidth
 TEST_F(HTMLElementTest, OffsetWidth) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
 
   // 1. If the element does not have any associated CSS layout box return zero
   // and terminate this algorithm.
@@ -679,7 +691,7 @@
 //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-htmlelement-offsetheight
 TEST_F(HTMLElementTest, OffsetHeight) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
 
   // 1. If the element does not have any associated CSS layout box return zero
   // and terminate this algorithm.
@@ -701,7 +713,7 @@
 
 TEST_F(HTMLElementTest, SetAttributeMatchesGetAttribute) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   html_element->SetAttribute("foo", "bar");
   EXPECT_EQ(1, html_element->attributes()->length());
   EXPECT_EQ("bar", html_element->GetAttribute("foo").value());
@@ -709,10 +721,10 @@
 
 TEST_F(HTMLElementTest, SetAttributeStyleSetsElementStyle) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<cssom::CSSDeclaredStyleData> style(
       new cssom::CSSDeclaredStyleData());
-  EXPECT_CALL(css_parser_, ParseStyleDeclarationList("", _))
+  EXPECT_CALL(css_parser(), ParseStyleDeclarationList("", _))
       .WillOnce(Return(style));
   html_element->SetAttribute("style", "");
   EXPECT_EQ(style, html_element->style()->data());
@@ -720,10 +732,10 @@
 
 TEST_F(HTMLElementTest, SetAttributeStyleReplacesExistingElementStyle) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<cssom::CSSDeclaredStyleData> style(
       new cssom::CSSDeclaredStyleData());
-  EXPECT_CALL(css_parser_,
+  EXPECT_CALL(css_parser(),
               ParseStyleDeclarationList(kDisplayInlineDeclarationString, _))
       .WillOnce(Return(style));
   html_element->SetAttribute("style", kDisplayInlineDeclarationString);
@@ -735,7 +747,7 @@
 
   scoped_refptr<cssom::CSSDeclaredStyleData> new_style(
       new cssom::CSSDeclaredStyleData());
-  EXPECT_CALL(css_parser_,
+  EXPECT_CALL(css_parser(),
               ParseStyleDeclarationList(kFooBarDeclarationString, _))
       .WillOnce(Return(new_style));
   html_element->SetAttribute("style", kFooBarDeclarationString);
@@ -747,10 +759,10 @@
 
 TEST_F(HTMLElementTest, GetAttributeStyleMatchesSetAttributeStyle) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<cssom::CSSDeclaredStyleData> style(
       new cssom::CSSDeclaredStyleData());
-  EXPECT_CALL(css_parser_,
+  EXPECT_CALL(css_parser(),
               ParseStyleDeclarationList(kFooBarDeclarationString, _))
       .WillOnce(Return(style));
   html_element->SetAttribute("style", kFooBarDeclarationString);
@@ -762,16 +774,16 @@
 TEST_F(HTMLElementTest,
        GetAttributeStyleDoesNotMatchSetAttributeStyleAfterStyleMutation) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<cssom::CSSDeclaredStyleData> style(
       new cssom::CSSDeclaredStyleData());
-  EXPECT_CALL(css_parser_,
+  EXPECT_CALL(css_parser(),
               ParseStyleDeclarationList(kFooBarDeclarationString, _))
       .WillOnce(Return(style));
   html_element->SetAttribute("style", kFooBarDeclarationString);
   EXPECT_EQ(1, html_element->attributes()->length());
-  EXPECT_CALL(css_parser_, ParsePropertyIntoDeclarationData("display", "inline",
-                                                            _, style.get()))
+  EXPECT_CALL(css_parser(), ParsePropertyIntoDeclarationData(
+                                "display", "inline", _, style.get()))
       .WillOnce(InvokeCallback0(base::Bind(
           &cssom::CSSDeclaredStyleData::SetPropertyValueAndImportance,
           base::Unretained(style.get()), cssom::kDisplayProperty,
@@ -785,16 +797,16 @@
 TEST_F(HTMLElementTest,
        GetAttributeStyleMatchesSerializedStyleAfterStyleMutation) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   scoped_refptr<cssom::CSSDeclaredStyleData> style(
       new cssom::CSSDeclaredStyleData());
-  EXPECT_CALL(css_parser_,
+  EXPECT_CALL(css_parser(),
               ParseStyleDeclarationList(kFooBarDeclarationString, _))
       .WillOnce(Return(style));
   html_element->SetAttribute("style", kFooBarDeclarationString);
   EXPECT_EQ(1, html_element->attributes()->length());
-  EXPECT_CALL(css_parser_, ParsePropertyIntoDeclarationData("display", "inline",
-                                                            _, style.get()))
+  EXPECT_CALL(css_parser(), ParsePropertyIntoDeclarationData(
+                                "display", "inline", _, style.get()))
       .WillOnce(InvokeCallback0(base::Bind(
           &cssom::CSSDeclaredStyleData::SetPropertyValueAndImportance,
           base::Unretained(style.get()), cssom::kDisplayProperty,
@@ -807,7 +819,7 @@
 
 TEST_F(HTMLElementTest, Duplicate) {
   scoped_refptr<HTMLElement> html_element =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   html_element->SetAttribute("a", "1");
   html_element->SetAttribute("b", "2");
   scoped_refptr<HTMLElement> new_html_element =
diff --git a/cobalt/dom/html_link_element.cc b/cobalt/dom/html_link_element.cc
index 17a6de1..d01d0e0 100644
--- a/cobalt/dom/html_link_element.cc
+++ b/cobalt/dom/html_link_element.cc
@@ -192,7 +192,7 @@
   // attribute, the origin being the origin of the link element's Document, and
   // the default origin behaviour set to taint.
   csp::SecurityCallback csp_callback = base::Bind(
-      &web::CspDelegate::CanLoad, base::Unretained(document->csp_delegate()),
+      &web::CspDelegate::CanLoad, base::Unretained(document->GetCSPDelegate()),
       GetCspResourceTypeForRel(rel()));
 
   fetched_last_url_origin_ = loader::Origin();
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index d496b01..e4fb384b 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -835,8 +835,8 @@
   DCHECK(!media_source_);
   if (url.SchemeIs(kMediaSourceUrlProtocol)) {
     // Check whether url is allowed by security policy.
-    if (!node_document()->csp_delegate()->CanLoad(web::CspDelegate::kMedia, url,
-                                                  false)) {
+    if (!node_document()->GetCSPDelegate()->CanLoad(web::CspDelegate::kMedia,
+                                                    url, false)) {
       DLOG(INFO) << "URL " << url << " is rejected by security policy.";
       NoneSupported("URL is rejected by security policy.");
       return;
@@ -883,7 +883,7 @@
   } else {
     csp::SecurityCallback csp_callback =
         base::Bind(&web::CspDelegate::CanLoad,
-                   base::Unretained(node_document()->csp_delegate()),
+                   base::Unretained(node_document()->GetCSPDelegate()),
                    web::CspDelegate::kMedia);
     request_mode_ = GetRequestMode(GetAttribute("crossOrigin"));
     DCHECK(node_document()->location());
diff --git a/cobalt/dom/html_meta_element.cc b/cobalt/dom/html_meta_element.cc
index 3852a5a..5f58d40 100644
--- a/cobalt/dom/html_meta_element.cc
+++ b/cobalt/dom/html_meta_element.cc
@@ -42,7 +42,7 @@
                                           ? csp::kHeaderSourceMeta
                                           : csp::kHeaderSourceMetaOutsideHead;
 
-    node_document()->csp_delegate()->OnReceiveHeader(
+    node_document()->GetCSPDelegate()->OnReceiveHeader(
         csp_text, csp::kHeaderTypeEnforce, header_source);
   }
 
diff --git a/cobalt/dom/html_script_element.cc b/cobalt/dom/html_script_element.cc
index 236cd3e..3634e62 100644
--- a/cobalt/dom/html_script_element.cc
+++ b/cobalt/dom/html_script_element.cc
@@ -79,15 +79,12 @@
 }
 
 std::string HTMLScriptElement::src() const {
-  auto src = GetAttribute("src");
-  if (!src.has_value()) {
-    return "";
+  std::string src = GetAttribute("src").value_or("");
+  if (src == "" || !node_document()) {
+    return src;
   }
-  if (!node_document()) {
-    return src.value();
-  }
-  const GURL& base_url = node_document()->location()->url();
-  return base_url.Resolve(src.value()).spec();
+  GURL url = node_document()->location()->url().Resolve(src);
+  return url.is_valid() ? url.spec() : src;
 }
 
 base::Optional<std::string> HTMLScriptElement::cross_origin() const {
@@ -304,7 +301,7 @@
 
   // https://www.w3.org/TR/CSP2/#directive-script-src
 
-  web::CspDelegate* csp_delegate = document_->csp_delegate();
+  web::CspDelegate* csp_delegate = document_->GetCSPDelegate();
   // If the script element has a valid nonce, we always permit it, regardless
   // of its URL or inline nature.
   const bool bypass_csp =
@@ -347,7 +344,8 @@
               csp_callback, request_mode_,
               document_->location() ? document_->location()->GetOriginAsObject()
                                     : loader::Origin(),
-              disk_cache::kUncompiledScript),
+              disk_cache::kUncompiledScript, net::HttpRequestHeaders(),
+              /*skip_fetch_intercept=*/false),
           base::Bind(&loader::TextDecoder::Create,
                      base::Bind(&HTMLScriptElement::OnSyncContentProduced,
                                 base::Unretained(this)),
diff --git a/cobalt/dom/html_style_element.cc b/cobalt/dom/html_style_element.cc
index 1706b3f..d5d0be0 100644
--- a/cobalt/dom/html_style_element.cc
+++ b/cobalt/dom/html_style_element.cc
@@ -72,7 +72,7 @@
     return;
   }
 
-  web::CspDelegate* csp_delegate = document->csp_delegate();
+  web::CspDelegate* csp_delegate = document->GetCSPDelegate();
   // If the style element has a valid nonce, we always permit it.
   const bool bypass_csp = csp_delegate->IsValidNonce(
       web::CspDelegate::kStyle, GetAttribute("nonce").value_or(""));
diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc
index 22f6bc5..a3571f1 100644
--- a/cobalt/dom/media_source.cc
+++ b/cobalt/dom/media_source.cc
@@ -110,13 +110,13 @@
 // NOTE: This only works when IsAsynchronousReductionEnabled() returns true,
 //       and it is currently only enabled for buffer append.
 // The default value is 16 KB.  Set to 0 will disable immediate job completely.
-int GetMinSizeForImmediateJob(script::EnvironmentSettings* settings) {
-  const int kDefaultMinSize = 16 * 1024;
-  auto min_size =
-      GetMediaSettings(settings)->GetMinSizeForImmediateJob().value_or(
-          kDefaultMinSize);
-  DCHECK_GE(min_size, 0);
-  return min_size;
+int GetMaxSizeForImmediateJob(script::EnvironmentSettings* settings) {
+  const int kDefaultMaxSize = 16 * 1024;
+  auto max_size =
+      GetMediaSettings(settings)->GetMaxSizeForImmediateJob().value_or(
+          kDefaultMaxSize);
+  DCHECK_GE(max_size, 0);
+  return max_size;
 }
 
 }  // namespace
@@ -125,7 +125,7 @@
     : web::EventTarget(settings),
       algorithm_offload_enabled_(IsAlgorithmOffloadEnabled(settings)),
       asynchronous_reduction_enabled_(IsAsynchronousReductionEnabled(settings)),
-      min_size_for_immediate_job_(GetMinSizeForImmediateJob(settings)),
+      max_size_for_immediate_job_(GetMaxSizeForImmediateJob(settings)),
       default_algorithm_runner_(asynchronous_reduction_enabled_),
       chunk_demuxer_(NULL),
       ready_state_(kMediaSourceReadyStateClosed),
@@ -137,8 +137,8 @@
             << (algorithm_offload_enabled_ ? "enabled" : "disabled");
   LOG(INFO) << "Asynchronous reduction is "
             << (asynchronous_reduction_enabled_ ? "enabled" : "disabled");
-  LOG(INFO) << "Min size of immediate job is set to "
-            << min_size_for_immediate_job_;
+  LOG(INFO) << "Max size of immediate job is set to "
+            << max_size_for_immediate_job_;
 }
 
 MediaSource::~MediaSource() { SetReadyState(kMediaSourceReadyStateClosed); }
@@ -581,7 +581,7 @@
     return &default_algorithm_runner_;
   }
   if (asynchronous_reduction_enabled_ &&
-      job_size <= min_size_for_immediate_job_) {
+      job_size <= max_size_for_immediate_job_) {
     // Append without posting new tasks is only supported on the default runner.
     return &default_algorithm_runner_;
   }
diff --git a/cobalt/dom/media_source.h b/cobalt/dom/media_source.h
index 65ab2a8..1c84e8b 100644
--- a/cobalt/dom/media_source.h
+++ b/cobalt/dom/media_source.h
@@ -144,10 +144,10 @@
   // Set to true to reduce asynchronous behaviors.  For example, queued events
   // will be dispatached immediately when possible.
   const bool asynchronous_reduction_enabled_;
-  // Only used when |asynchronous_reduction_enabled_| is set true, where any
+  // Only used when |asynchronous_reduction_enabled_| is set to true, where any
   // buffer append job smaller than its value will happen immediately instead of
   // being scheduled asynchronously.
-  const int min_size_for_immediate_job_;
+  const int max_size_for_immediate_job_;
 
   // The default algorithm runner runs all steps on the web thread.
   DefaultAlgorithmRunner<SourceBufferAlgorithm> default_algorithm_runner_;
diff --git a/cobalt/dom/media_source_settings.cc b/cobalt/dom/media_source_settings.cc
index 84ac0f9..d39602a 100644
--- a/cobalt/dom/media_source_settings.cc
+++ b/cobalt/dom/media_source_settings.cc
@@ -46,9 +46,9 @@
       LOG(INFO) << name << ": set to " << value;
       return true;
     }
-  } else if (name == "MediaSource.MinSizeForImmediateJob") {
+  } else if (name == "MediaSource.MaxSizeForImmediateJob") {
     if (value >= 0) {
-      min_size_for_immediate_job_ = value;
+      max_size_for_immediate_job_ = value;
       LOG(INFO) << name << ": set to " << value;
       return true;
     }
diff --git a/cobalt/dom/media_source_settings.h b/cobalt/dom/media_source_settings.h
index 61641b1..a122834 100644
--- a/cobalt/dom/media_source_settings.h
+++ b/cobalt/dom/media_source_settings.h
@@ -35,7 +35,7 @@
   virtual base::Optional<int> GetMinimumProcessorCountToOffloadAlgorithm()
       const = 0;
   virtual base::Optional<bool> IsAsynchronousReductionEnabled() const = 0;
-  virtual base::Optional<int> GetMinSizeForImmediateJob() const = 0;
+  virtual base::Optional<int> GetMaxSizeForImmediateJob() const = 0;
   virtual base::Optional<int> GetMaxSourceBufferAppendSizeInBytes() const = 0;
 
  protected:
@@ -64,9 +64,9 @@
     base::AutoLock auto_lock(lock_);
     return is_asynchronous_reduction_enabled_;
   }
-  base::Optional<int> GetMinSizeForImmediateJob() const override {
+  base::Optional<int> GetMaxSizeForImmediateJob() const override {
     base::AutoLock auto_lock(lock_);
-    return min_size_for_immediate_job_;
+    return max_size_for_immediate_job_;
   }
   base::Optional<int> GetMaxSourceBufferAppendSizeInBytes() const override {
     base::AutoLock auto_lock(lock_);
@@ -83,7 +83,7 @@
   base::Optional<int> source_buffer_evict_extra_in_bytes_;
   base::Optional<int> minimum_processor_count_to_offload_algorithm_;
   base::Optional<bool> is_asynchronous_reduction_enabled_;
-  base::Optional<int> min_size_for_immediate_job_;
+  base::Optional<int> max_size_for_immediate_job_;
   base::Optional<int> max_source_buffer_append_size_in_bytes_;
 };
 
diff --git a/cobalt/dom/media_source_settings_test.cc b/cobalt/dom/media_source_settings_test.cc
index 25d7878..d8a27ed 100644
--- a/cobalt/dom/media_source_settings_test.cc
+++ b/cobalt/dom/media_source_settings_test.cc
@@ -26,7 +26,7 @@
   EXPECT_FALSE(impl.GetSourceBufferEvictExtraInBytes());
   EXPECT_FALSE(impl.GetMinimumProcessorCountToOffloadAlgorithm());
   EXPECT_FALSE(impl.IsAsynchronousReductionEnabled());
-  EXPECT_FALSE(impl.GetMinSizeForImmediateJob());
+  EXPECT_FALSE(impl.GetMaxSizeForImmediateJob());
   EXPECT_FALSE(impl.GetMaxSourceBufferAppendSizeInBytes());
 }
 
@@ -37,13 +37,13 @@
   ASSERT_TRUE(
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 101));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 1));
-  ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 103));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSizeForImmediateJob", 103));
   ASSERT_TRUE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 100000));
 
   EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 100);
   EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 101);
   EXPECT_TRUE(impl.IsAsynchronousReductionEnabled().value());
-  EXPECT_EQ(impl.GetMinSizeForImmediateJob().value(), 103);
+  EXPECT_EQ(impl.GetMaxSizeForImmediateJob().value(), 103);
   EXPECT_EQ(impl.GetMaxSourceBufferAppendSizeInBytes().value(), 100000);
 }
 
@@ -54,13 +54,13 @@
   ASSERT_FALSE(
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", -101));
   ASSERT_FALSE(impl.Set("MediaSource.EnableAsynchronousReduction", 2));
-  ASSERT_FALSE(impl.Set("MediaSource.MinSizeForImmediateJob", -103));
+  ASSERT_FALSE(impl.Set("MediaSource.MaxSizeForImmediateJob", -103));
   ASSERT_FALSE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 0));
 
   EXPECT_FALSE(impl.GetSourceBufferEvictExtraInBytes());
   EXPECT_FALSE(impl.GetMinimumProcessorCountToOffloadAlgorithm());
   EXPECT_FALSE(impl.IsAsynchronousReductionEnabled());
-  EXPECT_FALSE(impl.GetMinSizeForImmediateJob());
+  EXPECT_FALSE(impl.GetMaxSizeForImmediateJob());
   EXPECT_FALSE(impl.GetMaxSourceBufferAppendSizeInBytes());
 }
 
@@ -71,13 +71,13 @@
   ASSERT_TRUE(
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 0));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 0));
-  ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 0));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSizeForImmediateJob", 0));
   // O is an invalid value for "MediaSource.MaxSourceBufferAppendSizeInBytes".
 
   EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 0);
   EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 0);
   EXPECT_FALSE(impl.IsAsynchronousReductionEnabled().value());
-  EXPECT_EQ(impl.GetMinSizeForImmediateJob().value(), 0);
+  EXPECT_EQ(impl.GetMaxSizeForImmediateJob().value(), 0);
 }
 
 TEST(MediaSourceSettingsImplTest, Updatable) {
@@ -87,20 +87,20 @@
   ASSERT_TRUE(
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 0));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 0));
-  ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 0));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSizeForImmediateJob", 0));
   ASSERT_TRUE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 1));
 
   ASSERT_TRUE(impl.Set("MediaSource.SourceBufferEvictExtraInBytes", 1));
   ASSERT_TRUE(
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 1));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 1));
-  ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 1));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSizeForImmediateJob", 1));
   ASSERT_TRUE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 2));
 
   EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 1);
   EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 1);
   EXPECT_TRUE(impl.IsAsynchronousReductionEnabled().value());
-  EXPECT_EQ(impl.GetMinSizeForImmediateJob().value(), 1);
+  EXPECT_EQ(impl.GetMaxSizeForImmediateJob().value(), 1);
   EXPECT_EQ(impl.GetMaxSourceBufferAppendSizeInBytes().value(), 2);
 }
 
diff --git a/cobalt/dom/node.cc b/cobalt/dom/node.cc
index bdd6209..26c78eb 100644
--- a/cobalt/dom/node.cc
+++ b/cobalt/dom/node.cc
@@ -440,9 +440,7 @@
 Text* Node::AsText() { return NULL; }
 
 const base::DebuggerHooks& Node::debugger_hooks() const {
-  return base::polymorphic_downcast<DOMSettings*>(
-             node_document()->html_element_context()->environment_settings())
-      ->debugger_hooks();
+  return environment_settings()->debugger_hooks();
 }
 
 void Node::TraceMembers(script::Tracer* tracer) {
diff --git a/cobalt/dom/performance_resource_timing.cc b/cobalt/dom/performance_resource_timing.cc
index f3bb1e8..09e72a3 100644
--- a/cobalt/dom/performance_resource_timing.cc
+++ b/cobalt/dom/performance_resource_timing.cc
@@ -128,6 +128,14 @@
   return timing_info_.encoded_body_size;
 }
 
+DOMHighResTimeStamp PerformanceResourceTiming::worker_start() const {
+  if (timing_info_.service_worker_start_time.is_null()) {
+    PerformanceEntry::start_time();
+  }
+  return Performance::MonotonicTimeToDOMHighResTimeStamp(
+      time_origin_, timing_info_.service_worker_start_time);
+}
+
 void PerformanceResourceTiming::SetResourceTimingEntry(
     const net::LoadTimingInfo& timing_info, const std::string& initiator_type,
     const std::string& requested_url, const std::string& cache_mode) {
diff --git a/cobalt/dom/performance_resource_timing.h b/cobalt/dom/performance_resource_timing.h
index c80e45f..a7fa31d 100644
--- a/cobalt/dom/performance_resource_timing.h
+++ b/cobalt/dom/performance_resource_timing.h
@@ -57,6 +57,7 @@
   DOMHighResTimeStamp response_end() const;
   uint64_t transfer_size() const;
   uint64_t encoded_body_size() const;
+  DOMHighResTimeStamp worker_start() const;
 
   std::string entry_type() const override { return "resource"; }
   PerformanceEntryType EntryTypeEnum() const override {
diff --git a/cobalt/dom/performance_resource_timing.idl b/cobalt/dom/performance_resource_timing.idl
index fe2875f..511c43d 100644
--- a/cobalt/dom/performance_resource_timing.idl
+++ b/cobalt/dom/performance_resource_timing.idl
@@ -28,4 +28,5 @@
   readonly  attribute DOMHighResTimeStamp responseEnd;
   readonly  attribute unsigned long long  transferSize;
   readonly  attribute unsigned long long  encodedBodySize;
+  readonly  attribute DOMHighResTimeStamp workerStart;
 };
diff --git a/cobalt/dom/pointer_state.cc b/cobalt/dom/pointer_state.cc
index 5dec62c..315c890 100644
--- a/cobalt/dom/pointer_state.cc
+++ b/cobalt/dom/pointer_state.cc
@@ -261,6 +261,42 @@
   pointers_with_active_buttons_.clear();
 }
 
+void PointerState::SetClientCoordinates(int32_t pointer_id,
+                                        math::Vector2dF position) {
+  client_coordinates_[pointer_id] = position;
+}
+
+base::Optional<math::Vector2dF> PointerState::GetClientCoordinates(
+    int32_t pointer_id) {
+  auto client_coordinate = client_coordinates_.find(pointer_id);
+  if (client_coordinate != client_coordinates_.end()) {
+    return client_coordinate->second;
+  }
+  base::Optional<math::Vector2dF> ret;
+  return ret;
+}
+
+void PointerState::ClearClientCoordinates(int32_t pointer_id) {
+  client_coordinates_.erase(pointer_id);
+}
+
+void PointerState::SetClientTimeStamp(int32_t pointer_id, uint64 time_stamp) {
+  client_time_stamps_[pointer_id] = time_stamp;
+}
+
+base::Optional<uint64> PointerState::GetClientTimeStamp(int32_t pointer_id) {
+  auto time_stamp = client_time_stamps_.find(pointer_id);
+  if (time_stamp != client_time_stamps_.end()) {
+    return time_stamp->second;
+  }
+  base::Optional<uint64> ret;
+  return ret;
+}
+
+void PointerState::ClearTimeStamp(int32_t pointer_id) {
+  client_time_stamps_.erase(pointer_id);
+}
+
 // static
 bool PointerState::CanQueueEvent(const scoped_refptr<web::Event>& event) {
   return event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
diff --git a/cobalt/dom/pointer_state.h b/cobalt/dom/pointer_state.h
index 15ed507..9448d4f 100644
--- a/cobalt/dom/pointer_state.h
+++ b/cobalt/dom/pointer_state.h
@@ -77,6 +77,14 @@
   // shutdown.
   void ClearForShutdown();
 
+  void SetClientCoordinates(int32_t pointer_id, math::Vector2dF position);
+  base::Optional<math::Vector2dF> GetClientCoordinates(int32_t pointer_id);
+  void ClearClientCoordinates(int32_t pointer_id);
+
+  void SetClientTimeStamp(int32_t pointer_id, uint64 time_stamp);
+  base::Optional<uint64> GetClientTimeStamp(int32_t pointer_id);
+  void ClearTimeStamp(int32_t pointer_id);
+
   static bool CanQueueEvent(const scoped_refptr<web::Event>& event);
 
  private:
@@ -95,6 +103,9 @@
   // Store the set of pointers with active buttons.
   //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#dfn-active-buttons-state
   std::set<int32_t> pointers_with_active_buttons_;
+
+  std::map<int32_t, math::Vector2dF> client_coordinates_;
+  std::map<int32_t, uint64> client_time_stamps_;
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/rule_matching_test.cc b/cobalt/dom/rule_matching_test.cc
index e11362e..b7f4006 100644
--- a/cobalt/dom/rule_matching_test.cc
+++ b/cobalt/dom/rule_matching_test.cc
@@ -26,6 +26,7 @@
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/element.h"
+#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_collection.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/dom/html_element_context.h"
@@ -38,6 +39,8 @@
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/script/script_exception.h"
 #include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/testing/stub_web_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using cobalt::cssom::ViewportSize;
@@ -51,47 +54,74 @@
 class RuleMatchingTest : public ::testing::Test {
  protected:
   RuleMatchingTest()
-      : css_parser_(css_parser::Parser::Create()),
+      : window_(new testing::StubWindow),
         dom_parser_(new dom_parser::Parser()),
-        dom_stat_tracker_(new DomStatTracker("RuleMatchingTest")),
-        html_element_context_(&environment_settings_, NULL, NULL,
-                              css_parser_.get(), dom_parser_.get(), NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, dom_stat_tracker_.get(), "",
-                              base::kApplicationStateStarted, NULL, NULL),
-        document_(new testing::FakeDocument(&html_element_context_)),
-        root_(document_->CreateElement("html")->AsHTMLElement()),
-        head_(document_->CreateElement("head")->AsHTMLElement()),
-        body_(document_->CreateElement("body")->AsHTMLElement()) {
+        dom_stat_tracker_(new DomStatTracker("RuleMatchingTest")) {
+    EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+    window_->InitializeWindow();
+    html_element_context_.reset(new HTMLElementContext(
+        window_->web_context()->environment_settings(), NULL, NULL,
+        css_parser(), dom_parser_.get(), NULL, NULL, NULL, NULL, NULL, NULL,
+        NULL, NULL, NULL, NULL, NULL, dom_stat_tracker_.get(), "",
+        base::kApplicationStateStarted, NULL, NULL));
+    root_ = document()->CreateElement("html")->AsHTMLElement();
+    head_ = document()->CreateElement("head")->AsHTMLElement();
+    body_ = document()->CreateElement("body")->AsHTMLElement();
     root_->AppendChild(head_);
     root_->AppendChild(body_);
-    document_->AppendChild(root_);
+    document()->AppendChild(root_);
   }
 
-  ~RuleMatchingTest() override {}
+  cssom::CSSParser* css_parser() { return window_->css_parser(); }
+
+  const scoped_refptr<Document>& document() {
+    return window_->window()->document();
+  }
+
+  ~RuleMatchingTest() override {
+    body_ = nullptr;
+    head_ = nullptr;
+    root_ = nullptr;
+    window_.reset();
+    EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+  }
 
   void UpdateAllMatchingRules();
 
   scoped_refptr<cssom::CSSStyleSheet> GetDocumentStyleSheet(
       unsigned int index) {
-    return document_->style_sheets()->Item(index)->AsCSSStyleSheet();
+    return document()->style_sheets()->Item(index)->AsCSSStyleSheet();
   }
 
-  testing::StubEnvironmentSettings environment_settings_;
-  std::unique_ptr<css_parser::Parser> css_parser_;
+  void ExpectAndRemoveUserAgentStyleSheetRule(
+      cssom::RulesWithCascadePrecedence* matching_rules,
+      unsigned int index = 0) {
+    // Expecting the user agent style sheet style to be the first matching rule.
+    ASSERT_GT(matching_rules->size(), index);
+    ASSERT_EQ(
+        "address, blockquote, center, div, figure, figcaption, footer, form, "
+        "header, hr, legend, listing, p, plaintext, pre, xmp",
+        (*matching_rules)[index].first->selector_text());
+    ASSERT_EQ("display: block;",
+              (*matching_rules)[index].first->css_text(nullptr));
+    // Remove the user agent style sheet style from the matching rules.
+    matching_rules->erase(matching_rules->begin() + index);
+  }
+
+
+  std::unique_ptr<testing::StubWindow> window_;
   std::unique_ptr<dom_parser::Parser> dom_parser_;
   std::unique_ptr<DomStatTracker> dom_stat_tracker_;
-  HTMLElementContext html_element_context_;
+  std::unique_ptr<HTMLElementContext> html_element_context_;
 
-  scoped_refptr<Document> document_;
   scoped_refptr<HTMLElement> root_;
   scoped_refptr<HTMLElement> head_;
   scoped_refptr<HTMLElement> body_;
 };
 
 void RuleMatchingTest::UpdateAllMatchingRules() {
-  document_->UpdateSelectorTree();
-  NodeDescendantsIterator iterator(document_);
+  document()->UpdateSelectorTree();
+  NodeDescendantsIterator iterator(document());
   Node* child = iterator.First();
   while (child) {
     if (child->AsElement()) {
@@ -115,6 +145,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 1);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -128,6 +159,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -141,6 +173,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -154,6 +187,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -167,6 +201,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -181,6 +216,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(1),
             (*matching_rules)[0].first);
@@ -195,6 +231,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(2),
             (*matching_rules)[0].first);
@@ -208,6 +245,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -221,6 +259,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 }
 
@@ -232,6 +271,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -245,6 +285,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -258,6 +299,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -271,6 +313,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -284,17 +327,15 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
 }
 
 // div:focus should match focused div.
 TEST_F(RuleMatchingTest, FocusPseudoClassMatch) {
-  // Give the document browsing context which is needed for focus to work.
-  testing::StubWindow window;
-  document_->set_window(window.window());
   // Give the document initial computed style.
   ViewportSize view_size(320, 240);
-  document_->SetViewport(view_size);
+  document()->SetViewport(view_size);
 
   head_->set_inner_html("<style>:focus {}</style>");
   body_->set_inner_html("<div tabIndex=\"-1\"/>");
@@ -306,6 +347,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -313,12 +355,9 @@
 
 // div:focus shouldn't match unfocused div.
 TEST_F(RuleMatchingTest, FocusPseudoClassNoMatch) {
-  // Give the document browsing context which is needed for focus to work.
-  testing::StubWindow window;
-  document_->set_window(window.window());
   // Give the document initial computed style.
   ViewportSize view_size(320, 240);
-  document_->SetViewport(view_size);
+  document()->SetViewport(view_size);
 
   head_->set_inner_html("<style>:focus {}</style>");
   body_->set_inner_html("<div tabIndex=\"-1\"/>");
@@ -326,6 +365,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
 }
 
@@ -337,6 +377,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -350,6 +391,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
 }
 
@@ -364,6 +406,7 @@
   ASSERT_EQ(2, GetDocumentStyleSheet(0)->css_rules_same_origin()->length());
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 }
 
@@ -377,6 +420,7 @@
   ASSERT_EQ(2, GetDocumentStyleSheet(0)->css_rules_same_origin()->length());
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -392,6 +436,7 @@
   ASSERT_EQ(2, GetDocumentStyleSheet(0)->css_rules_same_origin()->length());
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(1),
             (*matching_rules)[0].first);
@@ -408,6 +453,7 @@
   ASSERT_EQ(2, GetDocumentStyleSheet(0)->css_rules_same_origin()->length());
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(2, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -424,6 +470,7 @@
   HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
@@ -452,6 +499,7 @@
   HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
@@ -483,6 +531,7 @@
   HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
   matching_rules =
@@ -511,6 +560,7 @@
   HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
   matching_rules =
@@ -541,6 +591,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
 }
 
@@ -552,6 +603,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -565,6 +617,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   matching_rules =
       body_->last_element_child()->AsHTMLElement()->matching_rules();
@@ -691,6 +744,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(3, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -723,6 +777,7 @@
           ->last_element_child()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -735,12 +790,11 @@
       "  <div id='div3'/>"
       "</div>");
 
-  scoped_refptr<Element> div1 =
-      QuerySelector(document_, "div", css_parser_.get());
+  scoped_refptr<Element> div1 = QuerySelector(document(), "div", css_parser());
   ASSERT_TRUE(div1);
   EXPECT_EQ("div1", div1->id());
 
-  EXPECT_FALSE(QuerySelector(document_, "span", css_parser_.get()));
+  EXPECT_FALSE(QuerySelector(document(), "span", css_parser()));
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorShouldLimitResultInSubtree) {
@@ -750,12 +804,11 @@
       "  <div id='div3'/>"
       "</div>");
 
-  scoped_refptr<Element> div1 =
-      QuerySelector(document_, "div", css_parser_.get());
+  scoped_refptr<Element> div1 = QuerySelector(document(), "div", css_parser());
   ASSERT_TRUE(div1);
   EXPECT_EQ("div1", div1->id());
 
-  scoped_refptr<Element> div2 = QuerySelector(div1, "div", css_parser_.get());
+  scoped_refptr<Element> div2 = QuerySelector(div1, "div", css_parser());
   ASSERT_TRUE(div2);
   EXPECT_EQ("div2", div2->id());
 }
@@ -769,11 +822,10 @@
       "</div>");
 
   scoped_refptr<Element> div1 =
-      QuerySelector(document_, "#div1", css_parser_.get());
+      QuerySelector(document(), "#div1", css_parser());
   ASSERT_TRUE(div1);
   EXPECT_EQ("div1", div1->id());
-  scoped_refptr<Element> span =
-      QuerySelector(div1, ".out span", css_parser_.get());
+  scoped_refptr<Element> span = QuerySelector(div1, ".out span", css_parser());
   EXPECT_TRUE(span);
 }
 
@@ -786,7 +838,7 @@
       "<span/>");
 
   scoped_refptr<Element> span =
-      QuerySelector(document_, "div + div ~ span", css_parser_.get());
+      QuerySelector(document(), "div + div ~ span", css_parser());
   EXPECT_TRUE(span);
 }
 
@@ -804,7 +856,7 @@
       "</div>");
 
   scoped_refptr<Element> div = QuerySelector(
-      document_, "div ~ span + div ~ div + div > div + div", css_parser_.get());
+      document(), "div ~ span + div ~ div + div > div + div", css_parser());
   EXPECT_TRUE(div);
 }
 
@@ -816,18 +868,18 @@
       "</div>");
 
   scoped_refptr<NodeList> node_list;
-  node_list = QuerySelectorAll(document_, "div", css_parser_.get());
+  node_list = QuerySelectorAll(document(), "div", css_parser());
   ASSERT_EQ(3, node_list->length());
   EXPECT_EQ("div1", node_list->Item(0)->AsElement()->id());
   EXPECT_EQ("div2", node_list->Item(1)->AsElement()->id());
   EXPECT_EQ("div3", node_list->Item(2)->AsElement()->id());
 
-  node_list = QuerySelectorAll(document_, "span", css_parser_.get());
+  node_list = QuerySelectorAll(document(), "span", css_parser());
   EXPECT_EQ(0, node_list->length());
 }
 
 TEST_F(RuleMatchingTest, ElementMatches) {
-  scoped_refptr<Element> root = new Element(document_, base::Token("root"));
+  scoped_refptr<Element> root = new Element(document(), base::Token("root"));
   StrictMock<MockExceptionState> exception_state;
   EXPECT_TRUE(root->Matches("root", &exception_state));
   EXPECT_FALSE(root->Matches("r", &exception_state));
@@ -842,6 +894,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 }
 
@@ -854,16 +907,17 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 1);
   ASSERT_EQ(1, matching_rules->size());
 }
 
 TEST_F(RuleMatchingTest, StyleElementReorderingOneMatching) {
   scoped_refptr<HTMLElement> div1 =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   div1->set_inner_html("<style/>");
 
   scoped_refptr<HTMLElement> div2 =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   div2->set_inner_html("<style>* {}</style>");
 
   body_->set_inner_html("<div/>");
@@ -874,6 +928,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       head_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 1);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_NE(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -887,6 +942,7 @@
 
   matching_rules =
       head_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 1);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -896,11 +952,11 @@
 
 TEST_F(RuleMatchingTest, StyleElementReorderingTwoMatching) {
   scoped_refptr<HTMLElement> div1 =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   div1->set_inner_html("<style>* {}</style>");
 
   scoped_refptr<HTMLElement> div2 =
-      document_->CreateElement("div")->AsHTMLElement();
+      document()->CreateElement("div")->AsHTMLElement();
   div2->set_inner_html("<style>* {}</style>");
 
   body_->set_inner_html("<div/>");
@@ -911,6 +967,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       head_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 2);
   ASSERT_EQ(2, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -924,6 +981,7 @@
 
   matching_rules =
       head_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 2);
   ASSERT_EQ(2, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -936,19 +994,22 @@
   head_->set_inner_html("<style>div:hover {}</style>");
   body_->set_inner_html("<div/>");
 
-  document_->SetIndicatedElement(body_->first_element_child()->AsHTMLElement());
+  document()->SetIndicatedElement(
+      body_->first_element_child()->AsHTMLElement());
 
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
 
-  document_->SetIndicatedElement(NULL);
+  document()->SetIndicatedElement(NULL);
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -962,11 +1023,14 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
-  document_->SetIndicatedElement(body_->first_element_child()->AsHTMLElement());
+  document()->SetIndicatedElement(
+      body_->first_element_child()->AsHTMLElement());
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -978,20 +1042,22 @@
   head_->set_inner_html("<style>div:hover {}</style>");
   body_->set_inner_html("<div><span/></div>");
 
-  document_->SetIndicatedElement(
+  document()->SetIndicatedElement(
       body_->first_element_child()->first_element_child()->AsHTMLElement());
 
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
 
-  document_->SetIndicatedElement(NULL);
+  document()->SetIndicatedElement(NULL);
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -1005,12 +1071,14 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
-  document_->SetIndicatedElement(
+  document()->SetIndicatedElement(
       body_->first_element_child()->first_element_child()->AsHTMLElement());
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -1026,11 +1094,13 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
   body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -1046,6 +1116,7 @@
 
   cssom::RulesWithCascadePrecedence* matching_rules =
       body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -1053,6 +1124,7 @@
   body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -1069,11 +1141,13 @@
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
   body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -1092,6 +1166,7 @@
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules, 1);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -1099,6 +1174,7 @@
   body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -1115,11 +1191,13 @@
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
   body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -1138,6 +1216,7 @@
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -1145,6 +1224,7 @@
   body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -1161,11 +1241,13 @@
           ->next_element_sibling()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
   body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -1184,6 +1266,7 @@
           ->next_element_sibling()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -1191,6 +1274,7 @@
   body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -1207,11 +1291,13 @@
           ->next_element_sibling()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(0, matching_rules->size());
 
   body_->first_element_child()->AsHTMLElement()->set_class_name("my-class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
@@ -1231,6 +1317,7 @@
           ->next_element_sibling()
           ->AsHTMLElement()
           ->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   ASSERT_EQ(1, matching_rules->size());
   EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
             (*matching_rules)[0].first);
@@ -1238,6 +1325,7 @@
   body_->first_element_child()->AsHTMLElement()->RemoveAttribute("class");
 
   UpdateAllMatchingRules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
 
   ASSERT_EQ(0, matching_rules->size());
 }
@@ -1254,6 +1342,7 @@
   HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
@@ -1267,6 +1356,7 @@
   UpdateAllMatchingRules();
 
   matching_rules = html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
@@ -1289,6 +1379,7 @@
   HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
@@ -1302,6 +1393,7 @@
   UpdateAllMatchingRules();
 
   matching_rules = html_element->matching_rules();
+  ExpectAndRemoveUserAgentStyleSheetRule(matching_rules);
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
diff --git a/cobalt/dom/source_buffer_metrics.cc b/cobalt/dom/source_buffer_metrics.cc
index f2ae570..82aed8d 100644
--- a/cobalt/dom/source_buffer_metrics.cc
+++ b/cobalt/dom/source_buffer_metrics.cc
@@ -19,6 +19,7 @@
 #include "base/logging.h"
 #include "cobalt/base/statistics.h"
 #include "starboard/common/string.h"
+#include "starboard/once.h"
 #include "starboard/types.h"
 
 namespace cobalt {
@@ -39,8 +40,15 @@
 using BandwidthStatistics =
     base::Statistics<int64_t, SbTimeMonotonic, 1024, GetBandwidthForStatistics>;
 
-BandwidthStatistics s_accumulated_wall_time_bandwidth_;
-BandwidthStatistics s_accumulated_thread_time_bandwidth_;
+class StatisticsWrapper {
+ public:
+  static StatisticsWrapper* GetInstance();
+  BandwidthStatistics accumulated_wall_time_bandwidth_{
+      "DOM.Performance.BandwidthStatsWallTime"};
+  BandwidthStatistics accumulated_thread_time_bandwidth_{
+      "DOM.Performance.BandwidthStatsThreadTime"};
+};
+
 
 double GetWallToThreadTimeRatio(int64_t wall_time, int64_t thread_time) {
   if (thread_time == 0) {
@@ -48,8 +56,8 @@
   }
   return static_cast<double>(wall_time) / thread_time;
 }
-
 }  // namespace
+SB_ONCE_INITIALIZE_FUNCTION(StatisticsWrapper, StatisticsWrapper::GetInstance);
 
 void SourceBufferMetrics::StartTracking() {
   if (!is_primary_video_) {
@@ -99,9 +107,11 @@
     return;
   }
 
-  s_accumulated_wall_time_bandwidth_.AddSample(total_size_, total_wall_time_);
-  s_accumulated_thread_time_bandwidth_.AddSample(total_size_,
-                                                 total_thread_time_);
+  StatisticsWrapper::GetInstance()->accumulated_wall_time_bandwidth_.AddSample(
+      total_size_, total_wall_time_);
+  StatisticsWrapper::GetInstance()
+      ->accumulated_thread_time_bandwidth_.AddSample(total_size_,
+                                                     total_thread_time_);
 
   LOG_IF(INFO, total_thread_time_ > total_wall_time_)
       << "Total thread time " << total_thread_time_
@@ -140,16 +150,26 @@
       "    thread bandwidth statistics (B/s):\n"
       "        min %d, median %d, average %d, max %d",
       GetWallToThreadTimeRatio(
-          s_accumulated_wall_time_bandwidth_.accumulated_divisor(),
-          s_accumulated_thread_time_bandwidth_.accumulated_divisor()),
-      static_cast<int>(s_accumulated_wall_time_bandwidth_.min()),
-      static_cast<int>(s_accumulated_wall_time_bandwidth_.GetMedian()),
-      static_cast<int>(s_accumulated_wall_time_bandwidth_.average()),
-      static_cast<int>(s_accumulated_wall_time_bandwidth_.max()),
-      static_cast<int>(s_accumulated_thread_time_bandwidth_.min()),
-      static_cast<int>(s_accumulated_thread_time_bandwidth_.GetMedian()),
-      static_cast<int>(s_accumulated_thread_time_bandwidth_.average()),
-      static_cast<int>(s_accumulated_thread_time_bandwidth_.max()));
+          StatisticsWrapper::GetInstance()
+              ->accumulated_wall_time_bandwidth_.accumulated_divisor(),
+          StatisticsWrapper::GetInstance()
+              ->accumulated_thread_time_bandwidth_.accumulated_divisor()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_wall_time_bandwidth_.min()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_wall_time_bandwidth_.GetMedian()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_wall_time_bandwidth_.average()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_wall_time_bandwidth_.max()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_thread_time_bandwidth_.min()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_thread_time_bandwidth_.GetMedian()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_thread_time_bandwidth_.average()),
+      static_cast<int>(StatisticsWrapper::GetInstance()
+                           ->accumulated_thread_time_bandwidth_.max()));
 }
 
 #endif  // !defined(COBALT_BUILD_TYPE_GOLD)
diff --git a/cobalt/dom/testing/fake_document.h b/cobalt/dom/testing/fake_document.h
index 3ab3d94..494da4e 100644
--- a/cobalt/dom/testing/fake_document.h
+++ b/cobalt/dom/testing/fake_document.h
@@ -55,7 +55,7 @@
         options.csp_insecure_allowed_token);
   }
 
-  web::CspDelegate* csp_delegate() const override {
+  web::CspDelegate* GetCSPDelegate() const override {
     return csp_delegate_.get();
   }
 
diff --git a/cobalt/dom/testing/stub_window.h b/cobalt/dom/testing/stub_window.h
index 9a67b66..34a2d94 100644
--- a/cobalt/dom/testing/stub_window.h
+++ b/cobalt/dom/testing/stub_window.h
@@ -24,6 +24,7 @@
 #include "base/optional.h"
 #include "cobalt/base/debugger_hooks.h"
 #include "cobalt/css_parser/parser.h"
+#include "cobalt/cssom/css_parser.h"
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/captions/system_caption_settings.h"
 #include "cobalt/dom/dom_settings.h"
@@ -68,7 +69,7 @@
     return web_context_.get();
   }
 
-  scoped_refptr<dom::Window> window() {
+  const scoped_refptr<dom::Window>& window() {
     if (!window_) InitializeWindow();
     return window_;
   }
@@ -78,7 +79,7 @@
     if (!window_) InitializeWindow();
     return web_context()->global_environment();
   }
-  css_parser::Parser* css_parser() { return css_parser_.get(); }
+  cssom::CSSParser* css_parser() { return css_parser_.get(); }
   web::EnvironmentSettings* environment_settings() {
     // The Window has to be set as the global object for the environment
     // settings object to be valid.
@@ -91,6 +92,8 @@
     on_screen_keyboard_bridge_ = on_screen_keyboard_bridge;
   }
 
+  void set_css_parser(cssom::CSSParser* parser) { css_parser_.reset(parser); }
+
   void InitializeWindow() {
     loader_factory_.reset(new loader::LoaderFactory(
         "Test", web_context()->fetcher_factory(), NULL,
@@ -98,7 +101,9 @@
         base::ThreadPriority::DEFAULT));
     system_caption_settings_ = new cobalt::dom::captions::SystemCaptionSettings(
         web_context()->environment_settings());
-    css_parser_.reset(css_parser::Parser::Create().release());
+    if (!css_parser_) {
+      css_parser_.reset(css_parser::Parser::Create().release());
+    }
     dom_parser_.reset(
         new dom_parser::Parser(base::Bind(&StubLoadCompleteCallback)));
     dom_stat_tracker_.reset(new dom::DomStatTracker("StubWindow"));
@@ -146,7 +151,7 @@
 
   OnScreenKeyboardBridge* on_screen_keyboard_bridge_ = nullptr;
   DOMSettings::Options options_;
-  std::unique_ptr<css_parser::Parser> css_parser_;
+  std::unique_ptr<cssom::CSSParser> css_parser_;
   std::unique_ptr<dom_parser::Parser> dom_parser_;
   std::unique_ptr<loader::LoaderFactory> loader_factory_;
   dom::LocalStorageDatabase local_storage_database_;
diff --git a/cobalt/dom/window.cc b/cobalt/dom/window.cc
index 0284726..c5d5ac0 100644
--- a/cobalt/dom/window.cc
+++ b/cobalt/dom/window.cc
@@ -84,7 +84,7 @@
 };
 
 Window::Window(
-    script::EnvironmentSettings* settings, const ViewportSize& view_size,
+    web::EnvironmentSettings* settings, const ViewportSize& view_size,
     base::ApplicationState initial_application_state,
     cssom::CSSParser* css_parser, Parser* dom_parser,
     loader::FetcherFactory* fetcher_factory,
@@ -152,14 +152,6 @@
           initial_application_state, synchronous_loader_interrupt,
           performance_.get(), enable_inline_script_warnings,
           video_playback_rate_multiplier)),
-      ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document(
-          html_element_context(),
-          Document::Options(
-              settings->creation_url(), this,
-              base::Bind(&Window::FireHashChangeEvent, base::Unretained(this)),
-              performance_->timing()->GetNavigationStartClock(),
-              navigation_callback, ParseUserAgentStyleSheet(css_parser),
-              view_size, cookie_jar, dom_max_element_depth)))),
       document_loader_(nullptr),
       history_(new History()),
       navigator_(new Navigator(environment_settings(), captions)),
@@ -191,6 +183,16 @@
       screenshot_manager_(settings, screenshot_function_callback),
       ui_nav_root_(ui_nav_root),
       enable_map_to_mesh_(enable_map_to_mesh) {
+  document_ = new Document(
+      html_element_context(),
+      Document::Options(
+          settings->creation_url(),
+          base::Bind(&Window::FireHashChangeEvent, base::Unretained(this)),
+          performance_->timing()->GetNavigationStartClock(),
+          navigation_callback, ParseUserAgentStyleSheet(css_parser), view_size,
+          cookie_jar, dom_max_element_depth),
+      csp_delegate());
+
   set_navigator_base(navigator_);
   document_->AddObserver(relay_on_load_event_.get());
   html_element_context()->application_lifecycle_state()->AddObserver(this);
diff --git a/cobalt/dom/window.h b/cobalt/dom/window.h
index 033ac82..73688a1 100644
--- a/cobalt/dom/window.h
+++ b/cobalt/dom/window.h
@@ -59,13 +59,13 @@
 #include "cobalt/network_bridge/cookie_jar.h"
 #include "cobalt/network_bridge/net_poster.h"
 #include "cobalt/script/callback_function.h"
-#include "cobalt/script/environment_settings.h"
 #include "cobalt/script/error_report.h"
 #include "cobalt/script/execution_state.h"
 #include "cobalt/script/script_runner.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/ui_navigation/nav_item.h"
 #include "cobalt/web/csp_delegate_type.h"
+#include "cobalt/web/environment_settings.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/url_registry.h"
 #include "cobalt/web/user_agent_platform_info.h"
@@ -131,8 +131,7 @@
   };
 
   Window(
-      script::EnvironmentSettings* settings,
-      const cssom::ViewportSize& view_size,
+      web::EnvironmentSettings* settings, const cssom::ViewportSize& view_size,
       base::ApplicationState initial_application_state,
       cssom::CSSParser* css_parser, Parser* dom_parser,
       loader::FetcherFactory* fetcher_factory,
diff --git a/cobalt/dom/xml_document_test.cc b/cobalt/dom/xml_document_test.cc
index 625d6fb..7adeaf1 100644
--- a/cobalt/dom/xml_document_test.cc
+++ b/cobalt/dom/xml_document_test.cc
@@ -14,19 +14,65 @@
 
 #include "cobalt/dom/xml_document.h"
 
+#include <memory>
+
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/document.h"
+#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
+#include "cobalt/dom/testing/stub_window.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
 namespace dom {
 
+class XMLDocumentTestInWindow : public ::testing::Test {
+ protected:
+  XMLDocumentTestInWindow();
+  ~XMLDocumentTestInWindow() override;
+
+  std::unique_ptr<testing::StubWindow> window_;
+  std::unique_ptr<HTMLElementContext> html_element_context_;
+};
+
+XMLDocumentTestInWindow::XMLDocumentTestInWindow()
+    : window_(new testing::StubWindow) {
+  EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+  window_->InitializeWindow();
+  html_element_context_.reset(new HTMLElementContext(
+      window_->web_context()->environment_settings(), NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+      "", base::kApplicationStateStarted, NULL, NULL));
+}
+
+XMLDocumentTestInWindow::~XMLDocumentTestInWindow() {
+  window_.reset();
+  EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
+}
+
 TEST(XMLDocumentTest, IsXMLDocument) {
   HTMLElementContext html_element_context;
   scoped_refptr<Document> document = new XMLDocument(&html_element_context);
   EXPECT_TRUE(document->IsXMLDocument());
 }
 
+TEST(XMLDocumentTest, HasNoBrowsingContext) {
+  HTMLElementContext html_element_context;
+  scoped_refptr<Document> document = new XMLDocument(&html_element_context);
+  EXPECT_FALSE(document->HasBrowsingContext());
+}
+
+TEST_F(XMLDocumentTestInWindow, IsXMLDocument) {
+  scoped_refptr<Document> document =
+      new XMLDocument(html_element_context_.get());
+  EXPECT_TRUE(document->IsXMLDocument());
+}
+
+TEST_F(XMLDocumentTestInWindow, HasNoBrowsingContext) {
+  scoped_refptr<Document> document =
+      new XMLDocument(html_element_context_.get());
+  EXPECT_FALSE(document->HasBrowsingContext());
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/cobalt/dom_parser/html_decoder.cc b/cobalt/dom_parser/html_decoder.cc
index 742d4db..01267f3 100644
--- a/cobalt/dom_parser/html_decoder.cc
+++ b/cobalt/dom_parser/html_decoder.cc
@@ -60,7 +60,7 @@
   }
 
   csp::ResponseHeaders csp_headers(headers);
-  if (document_->csp_delegate()->OnReceiveHeaders(csp_headers) ||
+  if (document_->GetCSPDelegate()->OnReceiveHeaders(csp_headers) ||
       require_csp_ == csp::kCSPOptional) {
     return loader::kLoadResponseContinue;
   } else {
diff --git a/cobalt/fetch/fetch_internal.idl b/cobalt/fetch/fetch_internal.idl
index d665bf2..5f46c77 100644
--- a/cobalt/fetch/fetch_internal.idl
+++ b/cobalt/fetch/fetch_internal.idl
@@ -16,6 +16,8 @@
 // specific to the fetch polyfill and may change as the implementation changes.
 // This is not meant to be public and should not be used outside of the fetch
 // implementation.
+
+[Exposed=(Window,Worker)]
 interface FetchInternal {
   [CallWith=EnvironmentSettings] static boolean isUrlValid(
       DOMString url, boolean allowCredentials);
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc
index d734a0e..cecdd0c 100644
--- a/cobalt/h5vcc/h5vcc_storage.cc
+++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -74,6 +74,11 @@
   backend->DoomAllEntries(base::DoNothing());
 }
 
+void ClearCacheOfTypeHelper(disk_cache::ResourceType type,
+                            disk_cache::CobaltBackendImpl* backend) {
+  backend->DoomAllEntriesOfType(type, base::DoNothing());
+}
+
 }  // namespace
 
 H5vccStorage::H5vccStorage(
@@ -229,7 +234,8 @@
     H5vccStorageResourceTypeQuotaBytesDictionary quota) {
   if (!quota.has_other() || !quota.has_html() || !quota.has_css() ||
       !quota.has_image() || !quota.has_font() || !quota.has_splash() ||
-      !quota.has_uncompiled_js() || !quota.has_compiled_js()) {
+      !quota.has_uncompiled_js() || !quota.has_compiled_js() ||
+      !quota.has_cache_api()) {
     return SetQuotaResponse(
         "H5vccStorageResourceTypeQuotaBytesDictionary input parameter missing "
         "required fields.");
@@ -237,16 +243,17 @@
 
   if (quota.other() < 0 || quota.html() < 0 || quota.css() < 0 ||
       quota.image() < 0 || quota.font() < 0 || quota.splash() < 0 ||
-      quota.uncompiled_js() < 0 || quota.compiled_js() < 0) {
+      quota.uncompiled_js() < 0 || quota.compiled_js() < 0 ||
+      quota.cache_api() < 0) {
     return SetQuotaResponse(
         "H5vccStorageResourceTypeQuotaBytesDictionary input parameter fields "
-        "cannot "
-        "have a negative value.");
+        "cannot have a negative value.");
   }
 
   auto quota_total = quota.other() + quota.html() + quota.css() +
                      quota.image() + quota.font() + quota.splash() +
-                     quota.uncompiled_js() + quota.compiled_js();
+                     quota.uncompiled_js() + quota.compiled_js() +
+                     quota.cache_api();
 
   uint32_t max_quota_size = 24 * 1024 * 1024;
 #if SB_API_VERSION >= 14
@@ -282,6 +289,8 @@
                             static_cast<uint32_t>(quota.uncompiled_js()));
   SetAndSaveQuotaForBackend(disk_cache::kCompiledScript,
                             static_cast<uint32_t>(quota.compiled_js()));
+  SetAndSaveQuotaForBackend(disk_cache::kCacheApi,
+                            static_cast<uint32_t>(quota.cache_api()));
   return SetQuotaResponse("", true);
 }
 
@@ -289,6 +298,12 @@
                                              uint32_t bytes) {
   if (cache_backend_) {
     cache_backend_->UpdateSizes(type, bytes);
+
+    if (bytes == 0) {
+      network_module_->task_runner()->PostTask(
+          FROM_HERE, base::Bind(&ClearCacheOfTypeHelper, type,
+                                base::Unretained(cache_backend_)));
+    }
   }
   cobalt::cache::Cache::GetInstance()->Resize(type, bytes);
 }
@@ -312,6 +327,7 @@
       cobalt::cache::Cache::GetInstance()
           ->GetMaxCacheStorageInBytes(disk_cache::kCompiledScript)
           .value());
+  quota.set_cache_api(cache_backend_->GetQuota(disk_cache::kCacheApi));
 
   uint32_t max_quota_size = 24 * 1024 * 1024;
 #if SB_API_VERSION >= 14
diff --git a/cobalt/h5vcc/h5vcc_storage_resource_type_quota_bytes_dictionary.idl b/cobalt/h5vcc/h5vcc_storage_resource_type_quota_bytes_dictionary.idl
index bb2677b..322f82d 100644
--- a/cobalt/h5vcc/h5vcc_storage_resource_type_quota_bytes_dictionary.idl
+++ b/cobalt/h5vcc/h5vcc_storage_resource_type_quota_bytes_dictionary.idl
@@ -21,5 +21,6 @@
   unsigned long splash;
   unsigned long uncompiled_js;
   unsigned long compiled_js;
+  unsigned long cache_api;
   unsigned long total;
 };
diff --git a/cobalt/layout/layout.cc b/cobalt/layout/layout.cc
index e446bf9..bd9ee75 100644
--- a/cobalt/layout/layout.cc
+++ b/cobalt/layout/layout.cc
@@ -174,6 +174,14 @@
   }
 }
 
+void UpdateUiNavItemBoundaries(const scoped_refptr<dom::Document>& document) {
+  TRACE_EVENT0("cobalt::layout", "UpdateUiNavItemBoundaries()");
+  const auto& ui_nav_elements = document->ui_navigation_elements();
+  for (dom::HTMLElement* html_element : ui_nav_elements) {
+    html_element->SetUiNavItemBounds();
+  }
+}
+
 scoped_refptr<render_tree::Node> GenerateRenderTreeFromBoxTree(
     UsedStyleProvider* used_style_provider,
     LayoutStatTracker* layout_stat_tracker,
diff --git a/cobalt/layout/layout.h b/cobalt/layout/layout.h
index ce4a2ca..f4174d1 100644
--- a/cobalt/layout/layout.h
+++ b/cobalt/layout/layout.h
@@ -49,6 +49,9 @@
     scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block,
     bool clear_window_with_background_color);
 
+// Update all UI navigation elements with their scroll boundaries.
+void UpdateUiNavItemBoundaries(const scoped_refptr<dom::Document>& document);
+
 // Generates the render tree (along with corresponding animations) of the box
 // tree contained within the provided containing block.
 scoped_refptr<render_tree::Node> GenerateRenderTreeFromBoxTree(
diff --git a/cobalt/layout/layout_manager.cc b/cobalt/layout/layout_manager.cc
index 2f82d74..6592f0e 100644
--- a/cobalt/layout/layout_manager.cc
+++ b/cobalt/layout/layout_manager.cc
@@ -284,6 +284,7 @@
         line_break_iterator_.get(), character_break_iterator_.get(),
         &initial_containing_block_, clear_window_with_background_color_);
     are_computed_styles_and_box_tree_dirty_ = false;
+    layout::UpdateUiNavItemBoundaries(window_->document());
   }
 }
 
diff --git a/cobalt/layout_tests/testdata/BUILD.gn b/cobalt/layout_tests/testdata/BUILD.gn
index 172a734..5741156 100644
--- a/cobalt/layout_tests/testdata/BUILD.gn
+++ b/cobalt/layout_tests/testdata/BUILD.gn
@@ -1764,6 +1764,7 @@
     "web-platform-tests/mediasession/web_platform_tests.txt",
     "web-platform-tests/performance-timeline/web_platform_tests.txt",
     "web-platform-tests/resource-timing/web_platform_tests.txt",
+    "web-platform-tests/service-workers/web_platform_tests.txt",
     "web-platform-tests/streams/web_platform_tests.txt",
     "web-platform-tests/websockets/web_platform_tests.txt",
     "web-platform-tests/workers/web_platform_tests.txt",
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
new file mode 100644
index 0000000..ade7e63
--- /dev/null
+++ b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
@@ -0,0 +1,21 @@
+# Service Worker API tests
+
+cache-storage/common.https.html, PASS
+cache-storage/serviceworker/cache-add.https.html, DISABLE
+cache-storage/serviceworker/cache-delete.https.html, DISABLE
+cache-storage/serviceworker/cache-match.https.html, DISABLE
+cache-storage/serviceworker/cache-put.https.html, DISABLE
+cache-storage/serviceworker/cache-storage-match.https.html, DISABLE
+cache-storage/serviceworker/cache-storage.https.html, DISABLE
+cache-storage/worker/cache-add.https.html, PASS
+cache-storage/worker/cache-delete.https.html, PASS
+cache-storage/worker/cache-match.https.html, PASS
+cache-storage/worker/cache-put.https.html, PASS
+cache-storage/worker/cache-storage-match.https.html, PASS
+cache-storage/worker/cache-storage.https.html, PASS
+cache-storage/window/cache-add.https.html, PASS
+cache-storage/window/cache-delete.https.html, PASS
+cache-storage/window/cache-match.https.html, PASS
+cache-storage/window/cache-put.https.html, PASS
+cache-storage/window/cache-storage-match.https.html, PASS
+cache-storage/window/cache-storage.https.html, PASS
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index c5406bf..fda4f74 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -447,6 +447,11 @@
     ::testing::ValuesIn(EnumerateWebPlatformTests("resource-timing")),
     GetTestName());
 
+INSTANTIATE_TEST_CASE_P(
+    service_workers, WebPlatformTest,
+    ::testing::ValuesIn(EnumerateWebPlatformTests("service-workers")),
+    GetTestName());
+
 INSTANTIATE_TEST_CASE_P(streams, WebPlatformTest,
                         ::testing::ValuesIn(EnumerateWebPlatformTests(
                             "streams", "'ReadableStream' in this")),
diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn
index 3b8f6e0..8d50f70 100644
--- a/cobalt/loader/BUILD.gn
+++ b/cobalt/loader/BUILD.gn
@@ -29,6 +29,8 @@
     "embedded_fetcher.h",
     "error_fetcher.cc",
     "error_fetcher.h",
+    "fetch_interceptor_coordinator.cc",
+    "fetch_interceptor_coordinator.h",
     "fetcher.cc",
     "fetcher.h",
     "fetcher_cache.cc",
diff --git a/cobalt/loader/fetch_interceptor_coordinator.cc b/cobalt/loader/fetch_interceptor_coordinator.cc
new file mode 100644
index 0000000..fc2e8e8
--- /dev/null
+++ b/cobalt/loader/fetch_interceptor_coordinator.cc
@@ -0,0 +1,66 @@
+// Copyright 2022 The Cobalt Authors. 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/loader/fetch_interceptor_coordinator.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/singleton.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace loader {
+
+// static
+FetchInterceptorCoordinator* FetchInterceptorCoordinator::GetInstance() {
+  return base::Singleton<
+      FetchInterceptorCoordinator,
+      base::LeakySingletonTraits<FetchInterceptorCoordinator>>::get();
+}
+
+FetchInterceptorCoordinator::FetchInterceptorCoordinator()
+    : fetch_interceptor_(nullptr) {}
+
+
+void FetchInterceptorCoordinator::TryIntercept(
+    const GURL& url,
+    std::unique_ptr<base::OnceCallback<void(std::unique_ptr<std::string>)>>
+        callback,
+    std::unique_ptr<base::OnceCallback<void(const net::LoadTimingInfo&)>>
+        report_load_timing_info,
+    std::unique_ptr<base::OnceClosure> fallback) {
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#handle-fetch
+  // Steps 17 to 23
+  // TODO: add should skip event handling
+  // (https://w3c.github.io/ServiceWorker/#should-skip-event).
+  // TODO: determine |shouldSoftUpdate| and if true run soft update in parallel
+  // (https://w3c.github.io/ServiceWorker/#soft-update).
+  if (!fetch_interceptor_) {
+    std::move(*fallback).Run();
+    return;
+  }
+  // TODO: test interception once registered service workers are persisted.
+  //       Consider moving the interception out of the ServiceWorkerGlobalScope
+  //       to avoid a race condition. Fetches should be able to be intercepted
+  //       by an active, registered, service worker.
+  fetch_interceptor_->StartFetch(url, std::move(callback),
+                                 std::move(report_load_timing_info),
+                                 std::move(fallback));
+}
+
+}  // namespace loader
+}  // namespace cobalt
diff --git a/cobalt/loader/fetch_interceptor_coordinator.h b/cobalt/loader/fetch_interceptor_coordinator.h
new file mode 100644
index 0000000..13eb373
--- /dev/null
+++ b/cobalt/loader/fetch_interceptor_coordinator.h
@@ -0,0 +1,75 @@
+// Copyright 2022 The Cobalt Authors. 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_LOADER_FETCH_INTERCEPTOR_COORDINATOR_H_
+#define COBALT_LOADER_FETCH_INTERCEPTOR_COORDINATOR_H_
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "net/base/load_timing_info.h"
+#include "url/gurl.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace cobalt {
+namespace loader {
+
+class FetchInterceptor {
+ public:
+  virtual void StartFetch(
+      const GURL& url,
+      std::unique_ptr<base::OnceCallback<void(std::unique_ptr<std::string>)>>
+          callback,
+      std::unique_ptr<base::OnceCallback<void(const net::LoadTimingInfo&)>>
+          report_load_timing_info,
+      std::unique_ptr<base::OnceClosure> fallback) = 0;
+};
+
+// NetFetcher is for fetching data from the network.
+class FetchInterceptorCoordinator {
+ public:
+  static FetchInterceptorCoordinator* GetInstance();
+
+  void Add(FetchInterceptor* fetch_interceptor) {
+    fetch_interceptor_ = fetch_interceptor;
+  }
+
+  void Clear() { fetch_interceptor_ = nullptr; }
+
+  void TryIntercept(
+      const GURL& url,
+      std::unique_ptr<base::OnceCallback<void(std::unique_ptr<std::string>)>>
+          callback,
+      std::unique_ptr<base::OnceCallback<void(const net::LoadTimingInfo&)>>
+          report_load_timing_info,
+      std::unique_ptr<base::OnceClosure> fallback);
+
+ private:
+  friend struct base::DefaultSingletonTraits<FetchInterceptorCoordinator>;
+  FetchInterceptorCoordinator();
+
+  FetchInterceptor* fetch_interceptor_;
+
+  DISALLOW_COPY_AND_ASSIGN(FetchInterceptorCoordinator);
+};
+
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // COBALT_LOADER_FETCH_INTERCEPTOR_COORDINATOR_H_
diff --git a/cobalt/loader/fetcher_factory.cc b/cobalt/loader/fetcher_factory.cc
index ce60177..d938046 100644
--- a/cobalt/loader/fetcher_factory.cc
+++ b/cobalt/loader/fetcher_factory.cc
@@ -17,6 +17,7 @@
 #include <memory>
 #include <sstream>
 #include <string>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
@@ -86,13 +87,15 @@
     const GURL& url, const disk_cache::ResourceType type,
     Fetcher::Handler* handler) {
   return CreateSecureFetcher(url, csp::SecurityCallback(), kNoCORSMode,
-                             Origin(), type, handler);
+                             Origin(), type, net::HttpRequestHeaders(),
+                             /*skip_fetch_intercept=*/false, handler);
 }
 
 std::unique_ptr<Fetcher> FetcherFactory::CreateSecureFetcher(
     const GURL& url, const csp::SecurityCallback& url_security_callback,
     RequestMode request_mode, const Origin& origin,
-    const disk_cache::ResourceType type, Fetcher::Handler* handler) {
+    const disk_cache::ResourceType type, net::HttpRequestHeaders headers,
+    bool skip_fetch_intercept, Fetcher::Handler* handler) {
   LOG(INFO) << "Fetching: " << ClipUrl(url, 200);
 
   if (!url.is_valid()) {
@@ -106,6 +109,8 @@
       network_module_) {
     NetFetcher::Options options;
     options.resource_type = type;
+    options.headers = std::move(headers);
+    options.skip_fetch_intercept = skip_fetch_intercept;
     return std::unique_ptr<Fetcher>(
         new NetFetcher(url, url_security_callback, handler, network_module_,
                        options, request_mode, origin));
diff --git a/cobalt/loader/fetcher_factory.h b/cobalt/loader/fetcher_factory.h
index 4638e10..cb46a6a 100644
--- a/cobalt/loader/fetcher_factory.h
+++ b/cobalt/loader/fetcher_factory.h
@@ -25,6 +25,7 @@
 #include "cobalt/loader/blob_fetcher.h"
 #include "cobalt/loader/fetcher.h"
 #include "net/disk_cache/cobalt/resource_type.h"
+#include "net/http/http_request_headers.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -56,7 +57,8 @@
   std::unique_ptr<Fetcher> CreateSecureFetcher(
       const GURL& url, const csp::SecurityCallback& url_security_callback,
       RequestMode request_mode, const Origin& origin,
-      const disk_cache::ResourceType type, Fetcher::Handler* handler);
+      const disk_cache::ResourceType type, net::HttpRequestHeaders headers,
+      bool skip_fetch_intercept, Fetcher::Handler* handler);
 
   network::NetworkModule* network_module() const { return network_module_; }
 
diff --git a/cobalt/loader/net_fetcher.cc b/cobalt/loader/net_fetcher.cc
index 5c55904..e8bcf89 100644
--- a/cobalt/loader/net_fetcher.cc
+++ b/cobalt/loader/net_fetcher.cc
@@ -18,10 +18,12 @@
 #include <string>
 #include <utility>
 
+#include "base/memory/ptr_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/loader/cors_preflight.h"
+#include "cobalt/loader/fetch_interceptor_coordinator.h"
 #include "cobalt/loader/url_fetcher_string_writer.h"
 #include "cobalt/network/network_module.h"
 #include "net/base/mime_util.h"
@@ -101,8 +103,13 @@
           base::Bind(&NetFetcher::Start, base::Unretained(this)))),
       request_cross_origin_(false),
       origin_(origin),
-      request_script_(options.resource_type == disk_cache::kUncompiledScript) {
+      request_script_(options.resource_type == disk_cache::kUncompiledScript),
+      task_runner_(base::MessageLoop::current()->task_runner()),
+      skip_fetch_intercept_(options.skip_fetch_intercept) {
   url_fetcher_ = net::URLFetcher::Create(url, options.request_method, this);
+  if (!options.headers.IsEmpty()) {
+    url_fetcher_->SetExtraRequestHeaders(options.headers.ToString());
+  }
   url_fetcher_->SetRequestContext(
       network_module->url_request_context_getter().get());
   std::unique_ptr<URLFetcherStringWriter> download_data_writer(
@@ -140,7 +147,22 @@
                original_url.spec());
   if (security_callback_.is_null() ||
       security_callback_.Run(original_url, false /* did not redirect */)) {
-    url_fetcher_->Start();
+    if (skip_fetch_intercept_) {
+      url_fetcher_->Start();
+      return;
+    }
+    FetchInterceptorCoordinator::GetInstance()->TryIntercept(
+        original_url,
+        std::make_unique<
+            base::OnceCallback<void(std::unique_ptr<std::string>)>>(
+            base::BindOnce(&NetFetcher::OnFetchIntercepted,
+                           base::Unretained(this))),
+        std::make_unique<base::OnceCallback<void(const net::LoadTimingInfo&)>>(
+            base::BindOnce(&NetFetcher::ReportLoadTimingInfo,
+                           base::Unretained(this))),
+        std::make_unique<base::OnceClosure>(base::BindOnce(
+            &net::URLFetcher::Start, base::Unretained(url_fetcher_.get()))));
+
   } else {
     std::string msg(base::StringPrintf("URL %s rejected by security policy.",
                                        original_url.spec().c_str()));
@@ -148,6 +170,22 @@
   }
 }
 
+void NetFetcher::OnFetchIntercepted(std::unique_ptr<std::string> body) {
+  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&NetFetcher::OnFetchIntercepted,
+                                  base::Unretained(this), std::move(body)));
+    return;
+  }
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (!body || body->empty()) {
+    url_fetcher_->Start();
+    return;
+  }
+  handler()->OnReceivedPassed(this, std::make_unique<std::string>(*body));
+  handler()->OnDone(this);
+}
+
 void NetFetcher::OnURLFetchResponseStarted(const net::URLFetcher* source) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   TRACE_EVENT1("cobalt::loader", "NetFetcher::OnURLFetchResponseStarted", "url",
@@ -164,14 +202,13 @@
     }
   }
 
-  if (IsResponseCodeSuccess(source->GetResponseCode())) {
-    if (handler()->OnResponseStarted(this, source->GetResponseHeaders()) ==
-        kLoadResponseAbort) {
-      std::string msg(
-          base::StringPrintf("Handler::OnResponseStarted aborted URL %s",
-                             source->GetURL().spec().c_str()));
-      return HandleError(msg).InvalidateThis();
-    }
+  if ((handler()->OnResponseStarted(this, source->GetResponseHeaders()) ==
+       kLoadResponseAbort) ||
+      (!IsResponseCodeSuccess(source->GetResponseCode()))) {
+    std::string msg(
+        base::StringPrintf("Handler::OnResponseStarted aborted URL %s",
+                           source->GetURL().spec().c_str()));
+    return HandleError(msg).InvalidateThis();
   }
 
   // net::URLFetcher can not guarantee GetResponseHeaders() always return
diff --git a/cobalt/loader/net_fetcher.h b/cobalt/loader/net_fetcher.h
index 4fd1e80..3a6d3d8 100644
--- a/cobalt/loader/net_fetcher.h
+++ b/cobalt/loader/net_fetcher.h
@@ -27,6 +27,7 @@
 #include "cobalt/loader/fetcher.h"
 #include "cobalt/network/network_module.h"
 #include "net/disk_cache/cobalt/resource_type.h"
+#include "net/http/http_request_headers.h"
 #include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_fetcher_delegate.h"
 #include "url/gurl.h"
@@ -41,9 +42,12 @@
    public:
     Options()
         : request_method(net::URLFetcher::GET),
-          resource_type(disk_cache::kOther) {}
+          resource_type(disk_cache::kOther),
+          skip_fetch_intercept(false) {}
     net::URLFetcher::RequestType request_method;
     disk_cache::ResourceType resource_type;
+    net::HttpRequestHeaders headers;
+    bool skip_fetch_intercept;
   };
 
   NetFetcher(const GURL& url, const csp::SecurityCallback& security_callback,
@@ -60,6 +64,8 @@
                                   int64_t current_network_bytes) override;
   void ReportLoadTimingInfo(const net::LoadTimingInfo& timing_info) override;
 
+  void OnFetchIntercepted(std::unique_ptr<std::string> body);
+
   net::URLFetcher* url_fetcher() const {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     return url_fetcher_.get();
@@ -114,6 +120,9 @@
   // True when the requested resource type is script.
   bool request_script_;
 
+  scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
+  bool skip_fetch_intercept_;
+
   DISALLOW_COPY_AND_ASSIGN(NetFetcher);
 };
 
diff --git a/cobalt/loader/script_loader_factory.cc b/cobalt/loader/script_loader_factory.cc
index 0e64cf0..97d096d 100644
--- a/cobalt/loader/script_loader_factory.cc
+++ b/cobalt/loader/script_loader_factory.cc
@@ -15,6 +15,7 @@
 #include "cobalt/loader/script_loader_factory.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/threading/platform_thread.h"
 #include "cobalt/loader/image/threaded_image_decoder_proxy.h"
@@ -46,23 +47,14 @@
     const GURL& url, const Origin& origin,
     const csp::SecurityCallback& url_security_callback,
     const TextDecoder::TextAvailableCallback& script_available_callback,
-    const Loader::OnCompleteFunction& load_complete_callback) {
-  return CreateScriptLoader(
-      url, origin, url_security_callback, script_available_callback,
-      TextDecoder::ResponseStartedCallback(), load_complete_callback);
-}
-
-std::unique_ptr<Loader> ScriptLoaderFactory::CreateScriptLoader(
-    const GURL& url, const Origin& origin,
-    const csp::SecurityCallback& url_security_callback,
-    const TextDecoder::TextAvailableCallback& script_available_callback,
     const TextDecoder::ResponseStartedCallback& response_started_callback,
-    const Loader::OnCompleteFunction& load_complete_callback) {
+    const Loader::OnCompleteFunction& load_complete_callback,
+    net::HttpRequestHeaders headers, bool skip_fetch_intercept) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  Loader::FetcherCreator fetcher_creator =
-      MakeFetcherCreator(url, url_security_callback, kNoCORSMode, origin,
-                         disk_cache::kUncompiledScript);
+  Loader::FetcherCreator fetcher_creator = MakeFetcherCreator(
+      url, url_security_callback, kNoCORSMode, origin,
+      disk_cache::kUncompiledScript, std::move(headers), skip_fetch_intercept);
 
   std::unique_ptr<Loader> loader(
       new Loader(fetcher_creator,
@@ -80,12 +72,14 @@
 Loader::FetcherCreator ScriptLoaderFactory::MakeFetcherCreator(
     const GURL& url, const csp::SecurityCallback& url_security_callback,
     RequestMode request_mode, const Origin& origin,
-    disk_cache::ResourceType type) {
+    disk_cache::ResourceType type, net::HttpRequestHeaders headers,
+    bool skip_fetch_intercept) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   return base::Bind(&FetcherFactory::CreateSecureFetcher,
                     base::Unretained(fetcher_factory_), url,
-                    url_security_callback, request_mode, origin, type);
+                    url_security_callback, request_mode, origin, type,
+                    std::move(headers), skip_fetch_intercept);
 }
 
 void ScriptLoaderFactory::OnLoaderCreated(Loader* loader) {
diff --git a/cobalt/loader/script_loader_factory.h b/cobalt/loader/script_loader_factory.h
index 2ae2a40..6ff287e 100644
--- a/cobalt/loader/script_loader_factory.h
+++ b/cobalt/loader/script_loader_factory.h
@@ -27,6 +27,7 @@
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader.h"
 #include "cobalt/loader/text_decoder.h"
+#include "net/http/http_request_headers.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -46,14 +47,22 @@
       const GURL& url, const Origin& origin,
       const csp::SecurityCallback& url_security_callback,
       const TextDecoder::TextAvailableCallback& script_available_callback,
-      const Loader::OnCompleteFunction& load_complete_callback);
+      const Loader::OnCompleteFunction& load_complete_callback,
+      bool skip_fetch_intercept = false) {
+    return CreateScriptLoader(
+        url, origin, url_security_callback, script_available_callback,
+        TextDecoder::ResponseStartedCallback(), load_complete_callback,
+        net::HttpRequestHeaders(), skip_fetch_intercept);
+  }
 
   std::unique_ptr<Loader> CreateScriptLoader(
       const GURL& url, const Origin& origin,
       const csp::SecurityCallback& url_security_callback,
       const TextDecoder::TextAvailableCallback& script_available_callback,
       const TextDecoder::ResponseStartedCallback& response_started_callback,
-      const Loader::OnCompleteFunction& load_complete_callback);
+      const Loader::OnCompleteFunction& load_complete_callback,
+      net::HttpRequestHeaders headers = net::HttpRequestHeaders(),
+      bool skip_fetch_intercept = false);
 
  protected:
   void OnLoaderCreated(Loader* loader);
@@ -62,7 +71,9 @@
   Loader::FetcherCreator MakeFetcherCreator(
       const GURL& url, const csp::SecurityCallback& url_security_callback,
       RequestMode request_mode, const Origin& origin,
-      disk_cache::ResourceType type = disk_cache::kOther);
+      disk_cache::ResourceType type = disk_cache::kOther,
+      net::HttpRequestHeaders headers = net::HttpRequestHeaders(),
+      bool skip_fetch_intercept = false);
 
   // Ensures that the LoaderFactory methods are only called from the same
   // thread.
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index f8499cb..d1abff9 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -41,11 +41,11 @@
     "base/interleaved_sinc_resampler.h",
     "base/playback_statistics.cc",
     "base/playback_statistics.h",
+    "base/sbplayer_bridge.cc",
+    "base/sbplayer_bridge.h",
     "base/sbplayer_pipeline.cc",
     "base/sbplayer_set_bounds_helper.cc",
     "base/sbplayer_set_bounds_helper.h",
-    "base/starboard_player.cc",
-    "base/starboard_player.h",
     "decoder_buffer_allocator.cc",
     "decoder_buffer_allocator.h",
     "decoder_buffer_memory_info.h",
diff --git a/cobalt/media/base/starboard_player.cc b/cobalt/media/base/sbplayer_bridge.cc
similarity index 78%
rename from cobalt/media/base/starboard_player.cc
rename to cobalt/media/base/sbplayer_bridge.cc
index 3c9d8cf..1135217 100644
--- a/cobalt/media/base/starboard_player.cc
+++ b/cobalt/media/base/sbplayer_bridge.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/base/starboard_player.h"
+#include "cobalt/media/base/sbplayer_bridge.h"
 
 #include <algorithm>
 #include <iomanip>
@@ -30,6 +30,7 @@
 #include "starboard/common/string.h"
 #include "starboard/configuration.h"
 #include "starboard/memory.h"
+#include "starboard/once.h"
 #include "third_party/chromium/media/base/starboard_utils.h"
 
 namespace cobalt {
@@ -37,60 +38,68 @@
 
 namespace {
 
-base::Statistics<SbTime, int, 1024> s_startup_latency;
-
+class StatisticsWrapper {
+ public:
+  static StatisticsWrapper* GetInstance();
+  base::Statistics<SbTime, int, 1024> startup_latency{
+      "Media.PlaybackStartupLatency"};
+};
 }  // namespace
+SB_ONCE_INITIALIZE_FUNCTION(StatisticsWrapper, StatisticsWrapper::GetInstance);
 
-StarboardPlayer::CallbackHelper::CallbackHelper(StarboardPlayer* player)
-    : player_(player) {}
+SbPlayerBridge::CallbackHelper::CallbackHelper(SbPlayerBridge* player_bridge)
+    : player_bridge_(player_bridge) {}
 
-void StarboardPlayer::CallbackHelper::ClearDecoderBufferCache() {
+void SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache() {
   base::AutoLock auto_lock(lock_);
-  if (player_) {
-    player_->ClearDecoderBufferCache();
+  if (player_bridge_) {
+    player_bridge_->ClearDecoderBufferCache();
   }
 }
 
-void StarboardPlayer::CallbackHelper::OnDecoderStatus(
-    SbPlayer player, SbMediaType type, SbPlayerDecoderState state, int ticket) {
-  base::AutoLock auto_lock(lock_);
-  if (player_) {
-    player_->OnDecoderStatus(player, type, state, ticket);
-  }
-}
-
-void StarboardPlayer::CallbackHelper::OnPlayerStatus(SbPlayer player,
-                                                     SbPlayerState state,
+void SbPlayerBridge::CallbackHelper::OnDecoderStatus(SbPlayer player,
+                                                     SbMediaType type,
+                                                     SbPlayerDecoderState state,
                                                      int ticket) {
   base::AutoLock auto_lock(lock_);
-  if (player_) {
-    player_->OnPlayerStatus(player, state, ticket);
+  if (player_bridge_) {
+    player_bridge_->OnDecoderStatus(player, type, state, ticket);
   }
 }
 
-void StarboardPlayer::CallbackHelper::OnPlayerError(
-    SbPlayer player, SbPlayerError error, const std::string& message) {
+void SbPlayerBridge::CallbackHelper::OnPlayerStatus(SbPlayer player,
+                                                    SbPlayerState state,
+                                                    int ticket) {
   base::AutoLock auto_lock(lock_);
-  if (player_) {
-    player_->OnPlayerError(player, error, message);
+  if (player_bridge_) {
+    player_bridge_->OnPlayerStatus(player, state, ticket);
   }
 }
 
-void StarboardPlayer::CallbackHelper::OnDeallocateSample(
+void SbPlayerBridge::CallbackHelper::OnPlayerError(SbPlayer player,
+                                                   SbPlayerError error,
+                                                   const std::string& message) {
+  base::AutoLock auto_lock(lock_);
+  if (player_bridge_) {
+    player_bridge_->OnPlayerError(player, error, message);
+  }
+}
+
+void SbPlayerBridge::CallbackHelper::OnDeallocateSample(
     const void* sample_buffer) {
   base::AutoLock auto_lock(lock_);
-  if (player_) {
-    player_->OnDeallocateSample(sample_buffer);
+  if (player_bridge_) {
+    player_bridge_->OnDeallocateSample(sample_buffer);
   }
 }
 
-void StarboardPlayer::CallbackHelper::ResetPlayer() {
+void SbPlayerBridge::CallbackHelper::ResetPlayer() {
   base::AutoLock auto_lock(lock_);
-  player_ = NULL;
+  player_bridge_ = NULL;
 }
 
 #if SB_HAS(PLAYER_WITH_URL)
-StarboardPlayer::StarboardPlayer(
+SbPlayerBridge::SbPlayerBridge(
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
     const std::string& url, SbWindow window, Host* host,
     SbPlayerSetBoundsHelper* set_bounds_helper, bool allow_resume_after_suspend,
@@ -119,12 +128,12 @@
 
   task_runner->PostTask(
       FROM_HERE,
-      base::Bind(&StarboardPlayer::CallbackHelper::ClearDecoderBufferCache,
+      base::Bind(&SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache,
                  callback_helper_));
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
-StarboardPlayer::StarboardPlayer(
+SbPlayerBridge::SbPlayerBridge(
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
     const GetDecodeTargetGraphicsContextProviderFunc&
         get_decode_target_graphics_context_provider_func,
@@ -177,16 +186,16 @@
   if (SbPlayerIsValid(player_)) {
     task_runner->PostTask(
         FROM_HERE,
-        base::Bind(&StarboardPlayer::CallbackHelper::ClearDecoderBufferCache,
+        base::Bind(&SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache,
                    callback_helper_));
   }
 }
 
-StarboardPlayer::~StarboardPlayer() {
+SbPlayerBridge::~SbPlayerBridge() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   callback_helper_->ResetPlayer();
-  set_bounds_helper_->SetPlayer(NULL);
+  set_bounds_helper_->SetPlayerBridge(NULL);
 
   decode_target_provider_->SetOutputMode(
       DecodeTargetProvider::kOutputModeInvalid);
@@ -197,8 +206,8 @@
   }
 }
 
-void StarboardPlayer::UpdateAudioConfig(const AudioDecoderConfig& audio_config,
-                                        const std::string& mime_type) {
+void SbPlayerBridge::UpdateAudioConfig(const AudioDecoderConfig& audio_config,
+                                       const std::string& mime_type) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(audio_config.IsValidConfig());
 
@@ -212,8 +221,8 @@
   LOG(INFO) << "Converted to SbMediaAudioSampleInfo -- " << audio_sample_info_;
 }
 
-void StarboardPlayer::UpdateVideoConfig(const VideoDecoderConfig& video_config,
-                                        const std::string& mime_type) {
+void SbPlayerBridge::UpdateVideoConfig(const VideoDecoderConfig& video_config,
+                                       const std::string& mime_type) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(video_config.IsValidConfig());
 
@@ -236,8 +245,8 @@
   LOG(INFO) << "Converted to SbMediaVideoSampleInfo -- " << video_sample_info_;
 }
 
-void StarboardPlayer::WriteBuffer(DemuxerStream::Type type,
-                                  const scoped_refptr<DecoderBuffer>& buffer) {
+void SbPlayerBridge::WriteBuffer(DemuxerStream::Type type,
+                                 const scoped_refptr<DecoderBuffer>& buffer) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(buffer);
 #if SB_HAS(PLAYER_WITH_URL)
@@ -256,7 +265,7 @@
   WriteBufferInternal(type, buffer);
 }
 
-void StarboardPlayer::SetBounds(int z_index, const gfx::Rect& rect) {
+void SbPlayerBridge::SetBounds(int z_index, const gfx::Rect& rect) {
   base::AutoLock auto_lock(lock_);
 
   set_bounds_z_index_ = z_index;
@@ -269,7 +278,7 @@
   UpdateBounds_Locked();
 }
 
-void StarboardPlayer::PrepareForSeek() {
+void SbPlayerBridge::PrepareForSeek() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   seek_pending_ = true;
@@ -282,7 +291,7 @@
   SbPlayerSetPlaybackRate(player_, 0.f);
 }
 
-void StarboardPlayer::Seek(base::TimeDelta time) {
+void SbPlayerBridge::Seek(base::TimeDelta time) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   decoder_buffer_cache_.ClearAll();
@@ -307,7 +316,7 @@
   SbPlayerSetPlaybackRate(player_, playback_rate_);
 }
 
-void StarboardPlayer::SetVolume(float volume) {
+void SbPlayerBridge::SetVolume(float volume) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   volume_ = volume;
@@ -320,7 +329,7 @@
   SbPlayerSetVolume(player_, volume);
 }
 
-void StarboardPlayer::SetPlaybackRate(double playback_rate) {
+void SbPlayerBridge::SetPlaybackRate(double playback_rate) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   playback_rate_ = playback_rate;
@@ -336,9 +345,9 @@
   SbPlayerSetPlaybackRate(player_, playback_rate);
 }
 
-void StarboardPlayer::GetInfo(uint32* video_frames_decoded,
-                              uint32* video_frames_dropped,
-                              base::TimeDelta* media_time) {
+void SbPlayerBridge::GetInfo(uint32* video_frames_decoded,
+                             uint32* video_frames_dropped,
+                             base::TimeDelta* media_time) {
   DCHECK(video_frames_decoded || video_frames_dropped || media_time);
 
   base::AutoLock auto_lock(lock_);
@@ -346,7 +355,7 @@
 }
 
 #if SB_HAS(PLAYER_WITH_URL)
-void StarboardPlayer::GetUrlPlayerBufferedTimeRanges(
+void SbPlayerBridge::GetUrlPlayerBufferedTimeRanges(
     base::TimeDelta* buffer_start_time, base::TimeDelta* buffer_length_time) {
   DCHECK(buffer_start_time || buffer_length_time);
   DCHECK(is_url_based_);
@@ -372,7 +381,7 @@
   }
 }
 
-void StarboardPlayer::GetVideoResolution(int* frame_width, int* frame_height) {
+void SbPlayerBridge::GetVideoResolution(int* frame_width, int* frame_height) {
   DCHECK(frame_width);
   DCHECK(frame_height);
   DCHECK(is_url_based_);
@@ -395,7 +404,7 @@
   *frame_height = video_sample_info_.frame_height;
 }
 
-base::TimeDelta StarboardPlayer::GetDuration() {
+base::TimeDelta SbPlayerBridge::GetDuration() {
   DCHECK(is_url_based_);
 
   if (state_ == kSuspended) {
@@ -413,7 +422,7 @@
   return base::TimeDelta::FromMicroseconds(info.duration);
 }
 
-base::TimeDelta StarboardPlayer::GetStartDate() {
+base::TimeDelta SbPlayerBridge::GetStartDate() {
   DCHECK(is_url_based_);
 
   if (state_ == kSuspended) {
@@ -427,7 +436,7 @@
   return base::TimeDelta::FromMicroseconds(info.start_date);
 }
 
-void StarboardPlayer::SetDrmSystem(SbDrmSystem drm_system) {
+void SbPlayerBridge::SetDrmSystem(SbDrmSystem drm_system) {
   DCHECK(is_url_based_);
 
   drm_system_ = drm_system;
@@ -435,7 +444,7 @@
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
-void StarboardPlayer::Suspend() {
+void SbPlayerBridge::Suspend() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   // Check if the player is already suspended.
@@ -447,7 +456,7 @@
 
   SbPlayerSetPlaybackRate(player_, 0.0);
 
-  set_bounds_helper_->SetPlayer(NULL);
+  set_bounds_helper_->SetPlayerBridge(NULL);
 
   base::AutoLock auto_lock(lock_);
   GetInfo_Locked(&cached_video_frames_decoded_, &cached_video_frames_dropped_,
@@ -464,7 +473,7 @@
   player_ = kSbPlayerInvalid;
 }
 
-void StarboardPlayer::Resume(SbWindow window) {
+void SbPlayerBridge::Resume(SbWindow window) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   window_ = window;
@@ -517,18 +526,18 @@
 
 #if SB_HAS(PLAYER_WITH_URL)
 // static
-void StarboardPlayer::EncryptedMediaInitDataEncounteredCB(
+void SbPlayerBridge::EncryptedMediaInitDataEncounteredCB(
     SbPlayer player, void* context, const char* init_data_type,
     const unsigned char* init_data, unsigned int init_data_length) {
-  StarboardPlayer* helper = static_cast<StarboardPlayer*>(context);
+  SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context);
   DCHECK(!helper->on_encrypted_media_init_data_encountered_cb_.is_null());
   // TODO: Use callback_helper here.
   helper->on_encrypted_media_init_data_encountered_cb_.Run(
       init_data_type, init_data, init_data_length);
 }
 
-void StarboardPlayer::CreateUrlPlayer(const std::string& url) {
-  TRACE_EVENT0("cobalt::media", "StarboardPlayer::CreateUrlPlayer");
+void SbPlayerBridge::CreateUrlPlayer(const std::string& url) {
+  TRACE_EVENT0("cobalt::media", "SbPlayerBridge::CreateUrlPlayer");
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   DCHECK(!on_encrypted_media_init_data_encountered_cb_.is_null());
@@ -541,28 +550,28 @@
   player_creation_time_ = SbTimeGetMonotonicNow();
 
   player_ =
-      SbUrlPlayerCreate(url.c_str(), window_, &StarboardPlayer::PlayerStatusCB,
-                        &StarboardPlayer::EncryptedMediaInitDataEncounteredCB,
-                        &StarboardPlayer::PlayerErrorCB, this);
+      SbUrlPlayerCreate(url.c_str(), window_, &SbPlayerBridge::PlayerStatusCB,
+                        &SbPlayerBridge::EncryptedMediaInitDataEncounteredCB,
+                        &SbPlayerBridge::PlayerErrorCB, this);
   DCHECK(SbPlayerIsValid(player_));
 
   if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
     // If the player is setup to decode to texture, then provide Cobalt with
     // a method of querying that texture.
     decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
-        &StarboardPlayer::GetCurrentSbDecodeTarget, base::Unretained(this)));
+        &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this)));
   }
   decode_target_provider_->SetOutputMode(
       ToVideoFrameProviderOutputMode(output_mode_));
 
-  set_bounds_helper_->SetPlayer(this);
+  set_bounds_helper_->SetPlayerBridge(this);
 
   base::AutoLock auto_lock(lock_);
   UpdateBounds_Locked();
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
-void StarboardPlayer::CreatePlayer() {
-  TRACE_EVENT0("cobalt::media", "StarboardPlayer::CreatePlayer");
+void SbPlayerBridge::CreatePlayer() {
+  TRACE_EVENT0("cobalt::media", "SbPlayerBridge::CreatePlayer");
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   bool is_visible = SbWindowIsValid(window_);
@@ -590,9 +599,9 @@
   creation_param.output_mode = output_mode_;
   DCHECK_EQ(SbPlayerGetPreferredOutputMode(&creation_param), output_mode_);
   player_ = SbPlayerCreate(
-      window_, &creation_param, &StarboardPlayer::DeallocateSampleCB,
-      &StarboardPlayer::DecoderStatusCB, &StarboardPlayer::PlayerStatusCB,
-      &StarboardPlayer::PlayerErrorCB, this,
+      window_, &creation_param, &SbPlayerBridge::DeallocateSampleCB,
+      &SbPlayerBridge::DecoderStatusCB, &SbPlayerBridge::PlayerStatusCB,
+      &SbPlayerBridge::PlayerErrorCB, this,
       get_decode_target_graphics_context_provider_func_.Run());
 
   is_creating_player_ = false;
@@ -605,17 +614,17 @@
     // If the player is setup to decode to texture, then provide Cobalt with
     // a method of querying that texture.
     decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
-        &StarboardPlayer::GetCurrentSbDecodeTarget, base::Unretained(this)));
+        &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this)));
   }
   decode_target_provider_->SetOutputMode(
       ToVideoFrameProviderOutputMode(output_mode_));
-  set_bounds_helper_->SetPlayer(this);
+  set_bounds_helper_->SetPlayerBridge(this);
 
   base::AutoLock auto_lock(lock_);
   UpdateBounds_Locked();
 }
 
-void StarboardPlayer::WriteNextBufferFromCache(DemuxerStream::Type type) {
+void SbPlayerBridge::WriteNextBufferFromCache(DemuxerStream::Type type) {
   DCHECK(state_ != kSuspended);
 #if SB_HAS(PLAYER_WITH_URL)
   DCHECK(!is_url_based_);
@@ -631,7 +640,7 @@
   WriteBufferInternal(type, buffer);
 }
 
-void StarboardPlayer::WriteBufferInternal(
+void SbPlayerBridge::WriteBufferInternal(
     DemuxerStream::Type type, const scoped_refptr<DecoderBuffer>& buffer) {
 #if SB_HAS(PLAYER_WITH_URL)
   DCHECK(!is_url_based_);
@@ -697,17 +706,17 @@
   SbPlayerWriteSample2(player_, sample_type, &sample_info, 1);
 }
 
-SbDecodeTarget StarboardPlayer::GetCurrentSbDecodeTarget() {
+SbDecodeTarget SbPlayerBridge::GetCurrentSbDecodeTarget() {
   return SbPlayerGetCurrentFrame(player_);
 }
 
-SbPlayerOutputMode StarboardPlayer::GetSbPlayerOutputMode() {
+SbPlayerOutputMode SbPlayerBridge::GetSbPlayerOutputMode() {
   return output_mode_;
 }
 
-void StarboardPlayer::GetInfo_Locked(uint32* video_frames_decoded,
-                                     uint32* video_frames_dropped,
-                                     base::TimeDelta* media_time) {
+void SbPlayerBridge::GetInfo_Locked(uint32* video_frames_decoded,
+                                    uint32* video_frames_dropped,
+                                    base::TimeDelta* media_time) {
   lock_.AssertAcquired();
   if (state_ == kSuspended) {
     if (video_frames_decoded) {
@@ -739,7 +748,7 @@
   }
 }
 
-void StarboardPlayer::UpdateBounds_Locked() {
+void SbPlayerBridge::UpdateBounds_Locked() {
   lock_.AssertAcquired();
   DCHECK(SbPlayerIsValid(player_));
 
@@ -752,7 +761,7 @@
                     rect.width(), rect.height());
 }
 
-void StarboardPlayer::ClearDecoderBufferCache() {
+void SbPlayerBridge::ClearDecoderBufferCache() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   if (state_ != kResuming) {
@@ -763,14 +772,14 @@
 
   task_runner_->PostDelayedTask(
       FROM_HERE,
-      base::Bind(&StarboardPlayer::CallbackHelper::ClearDecoderBufferCache,
+      base::Bind(&SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache,
                  callback_helper_),
       base::TimeDelta::FromMilliseconds(
           kClearDecoderCacheIntervalInMilliseconds));
 }
 
-void StarboardPlayer::OnDecoderStatus(SbPlayer player, SbMediaType type,
-                                      SbPlayerDecoderState state, int ticket) {
+void SbPlayerBridge::OnDecoderStatus(SbPlayer player, SbMediaType type,
+                                     SbPlayerDecoderState state, int ticket) {
 #if SB_HAS(PLAYER_WITH_URL)
   DCHECK(!is_url_based_);
 #endif  // SB_HAS(PLAYER_WITH_URL)
@@ -803,9 +812,9 @@
   host_->OnNeedData(::media::SbMediaTypeToDemuxerStreamType(type));
 }
 
-void StarboardPlayer::OnPlayerStatus(SbPlayer player, SbPlayerState state,
-                                     int ticket) {
-  TRACE_EVENT1("cobalt::media", "StarboardPlayer::OnPlayerStatus", "state",
+void SbPlayerBridge::OnPlayerStatus(SbPlayer player, SbPlayerState state,
+                                    int ticket) {
+  TRACE_EVENT1("cobalt::media", "SbPlayerBridge::OnPlayerStatus", "state",
                state);
   DCHECK(task_runner_->BelongsToCurrentThread());
 
@@ -844,8 +853,8 @@
   host_->OnPlayerStatus(state);
 }
 
-void StarboardPlayer::OnPlayerError(SbPlayer player, SbPlayerError error,
-                                    const std::string& message) {
+void SbPlayerBridge::OnPlayerError(SbPlayer player, SbPlayerError error,
+                                   const std::string& message) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   if (player_ != player) {
@@ -854,7 +863,7 @@
   host_->OnPlayerError(error, message);
 }
 
-void StarboardPlayer::OnDeallocateSample(const void* sample_buffer) {
+void SbPlayerBridge::OnDeallocateSample(const void* sample_buffer) {
 #if SB_HAS(PLAYER_WITH_URL)
   DCHECK(!is_url_based_);
 #endif  // SB_HAS(PLAYER_WITH_URL)
@@ -863,7 +872,7 @@
   DecodingBuffers::iterator iter = decoding_buffers_.find(sample_buffer);
   DCHECK(iter != decoding_buffers_.end());
   if (iter == decoding_buffers_.end()) {
-    LOG(ERROR) << "StarboardPlayer::OnDeallocateSample encounters unknown "
+    LOG(ERROR) << "SbPlayerBridge::OnDeallocateSample encounters unknown "
                << "sample_buffer " << sample_buffer;
     return;
   }
@@ -873,7 +882,7 @@
   }
 }
 
-bool StarboardPlayer::TryToSetPlayerCreationErrorMessage(
+bool SbPlayerBridge::TryToSetPlayerCreationErrorMessage(
     const std::string& message) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   if (is_creating_player_) {
@@ -887,29 +896,29 @@
 }
 
 // static
-void StarboardPlayer::DecoderStatusCB(SbPlayer player, void* context,
-                                      SbMediaType type,
-                                      SbPlayerDecoderState state, int ticket) {
-  StarboardPlayer* helper = static_cast<StarboardPlayer*>(context);
+void SbPlayerBridge::DecoderStatusCB(SbPlayer player, void* context,
+                                     SbMediaType type,
+                                     SbPlayerDecoderState state, int ticket) {
+  SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context);
   helper->task_runner_->PostTask(
       FROM_HERE,
-      base::Bind(&StarboardPlayer::CallbackHelper::OnDecoderStatus,
+      base::Bind(&SbPlayerBridge::CallbackHelper::OnDecoderStatus,
                  helper->callback_helper_, player, type, state, ticket));
 }
 
 // static
-void StarboardPlayer::PlayerStatusCB(SbPlayer player, void* context,
-                                     SbPlayerState state, int ticket) {
-  StarboardPlayer* helper = static_cast<StarboardPlayer*>(context);
+void SbPlayerBridge::PlayerStatusCB(SbPlayer player, void* context,
+                                    SbPlayerState state, int ticket) {
+  SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context);
   helper->task_runner_->PostTask(
-      FROM_HERE, base::Bind(&StarboardPlayer::CallbackHelper::OnPlayerStatus,
+      FROM_HERE, base::Bind(&SbPlayerBridge::CallbackHelper::OnPlayerStatus,
                             helper->callback_helper_, player, state, ticket));
 }
 
 // static
-void StarboardPlayer::PlayerErrorCB(SbPlayer player, void* context,
-                                    SbPlayerError error, const char* message) {
-  StarboardPlayer* helper = static_cast<StarboardPlayer*>(context);
+void SbPlayerBridge::PlayerErrorCB(SbPlayer player, void* context,
+                                   SbPlayerError error, const char* message) {
+  SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context);
   if (player == kSbPlayerInvalid) {
     // TODO: Simplify by combining the functionality of
     // TryToSetPlayerCreationErrorMessage() with OnPlayerError().
@@ -918,24 +927,23 @@
     }
   }
   helper->task_runner_->PostTask(
-      FROM_HERE, base::Bind(&StarboardPlayer::CallbackHelper::OnPlayerError,
+      FROM_HERE, base::Bind(&SbPlayerBridge::CallbackHelper::OnPlayerError,
                             helper->callback_helper_, player, error,
                             message ? std::string(message) : ""));
 }
 
 // static
-void StarboardPlayer::DeallocateSampleCB(SbPlayer player, void* context,
-                                         const void* sample_buffer) {
-  StarboardPlayer* helper = static_cast<StarboardPlayer*>(context);
+void SbPlayerBridge::DeallocateSampleCB(SbPlayer player, void* context,
+                                        const void* sample_buffer) {
+  SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context);
   helper->task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&StarboardPlayer::CallbackHelper::OnDeallocateSample,
-                 helper->callback_helper_, sample_buffer));
+      FROM_HERE, base::Bind(&SbPlayerBridge::CallbackHelper::OnDeallocateSample,
+                            helper->callback_helper_, sample_buffer));
 }
 
 #if SB_HAS(PLAYER_WITH_URL)
 // static
-SbPlayerOutputMode StarboardPlayer::ComputeSbUrlPlayerOutputMode(
+SbPlayerOutputMode SbPlayerBridge::ComputeSbUrlPlayerOutputMode(
     bool prefer_decode_to_texture) {
   // Try to choose the output mode according to the passed in value of
   // |prefer_decode_to_texture|.  If the preferred output mode is unavailable
@@ -955,7 +963,7 @@
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
 // static
-SbPlayerOutputMode StarboardPlayer::ComputeSbPlayerOutputMode(
+SbPlayerOutputMode SbPlayerBridge::ComputeSbPlayerOutputMode(
     bool prefer_decode_to_texture) const {
   SbPlayerCreationParam creation_param = {};
   creation_param.drm_system = drm_system_;
@@ -974,7 +982,7 @@
   return output_mode;
 }
 
-void StarboardPlayer::LogStartupLatency() const {
+void SbPlayerBridge::LogStartupLatency() const {
   std::string first_events_str;
   if (set_drm_system_ready_cb_time_ == -1) {
     first_events_str =
@@ -1006,7 +1014,8 @@
       std::max(first_audio_sample_time_, first_video_sample_time_);
   SbTime startup_latency = sb_player_state_presenting_time_ - first_event_time;
 
-  s_startup_latency.AddSample(startup_latency, 1);
+  StatisticsWrapper::GetInstance()->startup_latency.AddSample(startup_latency,
+                                                              1);
 
   // clang-format off
   LOG(INFO) << starboard::FormatString(
@@ -1024,9 +1033,10 @@
       first_events_str.c_str(), player_initialization_time_delta,
       player_preroll_time_delta, first_audio_sample_time_delta,
       first_video_sample_time_delta, player_presenting_time_delta,
-      s_startup_latency.min(),
-      s_startup_latency.GetMedian(),
-      s_startup_latency.average(), s_startup_latency.max());
+      StatisticsWrapper::GetInstance()->startup_latency.min(),
+      StatisticsWrapper::GetInstance()->startup_latency.GetMedian(),
+      StatisticsWrapper::GetInstance()->startup_latency.average(),
+      StatisticsWrapper::GetInstance()->startup_latency.max());
   // clang-format on
 }
 
diff --git a/cobalt/media/base/starboard_player.h b/cobalt/media/base/sbplayer_bridge.h
similarity index 85%
rename from cobalt/media/base/starboard_player.h
rename to cobalt/media/base/sbplayer_bridge.h
index 1e75888..e3768b8 100644
--- a/cobalt/media/base/starboard_player.h
+++ b/cobalt/media/base/sbplayer_bridge.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_BASE_STARBOARD_PLAYER_H_
-#define COBALT_MEDIA_BASE_STARBOARD_PLAYER_H_
+#ifndef COBALT_MEDIA_BASE_SBPLAYER_BRIDGE_H_
+#define COBALT_MEDIA_BASE_SBPLAYER_BRIDGE_H_
 
 #include <map>
 #include <string>
@@ -42,7 +42,7 @@
 namespace media {
 
 // TODO: Add switch to disable caching
-class StarboardPlayer {
+class SbPlayerBridge {
   typedef ::media::AudioDecoderConfig AudioDecoderConfig;
   typedef ::media::DecoderBuffer DecoderBuffer;
   typedef ::media::DemuxerStream DemuxerStream;
@@ -67,32 +67,30 @@
 #if SB_HAS(PLAYER_WITH_URL)
   typedef base::Callback<void(const char*, const unsigned char*, unsigned)>
       OnEncryptedMediaInitDataEncounteredCB;
-  // Create a StarboardPlayer with url-based player.
-  StarboardPlayer(
-      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
-      const std::string& url, SbWindow window, Host* host,
-      SbPlayerSetBoundsHelper* set_bounds_helper,
-      bool allow_resume_after_suspend, bool prefer_decode_to_texture,
-      const OnEncryptedMediaInitDataEncounteredCB&
-          encrypted_media_init_data_encountered_cb,
-      DecodeTargetProvider* const decode_target_provider);
+  // Create an SbPlayerBridge with url-based player.
+  SbPlayerBridge(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+                 const std::string& url, SbWindow window, Host* host,
+                 SbPlayerSetBoundsHelper* set_bounds_helper,
+                 bool allow_resume_after_suspend, bool prefer_decode_to_texture,
+                 const OnEncryptedMediaInitDataEncounteredCB&
+                     encrypted_media_init_data_encountered_cb,
+                 DecodeTargetProvider* const decode_target_provider);
 #endif  // SB_HAS(PLAYER_WITH_URL)
-  // Create a StarboardPlayer with normal player
-  StarboardPlayer(
-      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
-      const GetDecodeTargetGraphicsContextProviderFunc&
-          get_decode_target_graphics_context_provider_func,
-      const AudioDecoderConfig& audio_config,
-      const std::string& audio_mime_type,
-      const VideoDecoderConfig& video_config,
-      const std::string& video_mime_type, SbWindow window,
-      SbDrmSystem drm_system, Host* host,
-      SbPlayerSetBoundsHelper* set_bounds_helper,
-      bool allow_resume_after_suspend, bool prefer_decode_to_texture,
-      DecodeTargetProvider* const decode_target_provider,
-      const std::string& max_video_capabilities);
+  // Create a SbPlayerBridge with normal player
+  SbPlayerBridge(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+                 const GetDecodeTargetGraphicsContextProviderFunc&
+                     get_decode_target_graphics_context_provider_func,
+                 const AudioDecoderConfig& audio_config,
+                 const std::string& audio_mime_type,
+                 const VideoDecoderConfig& video_config,
+                 const std::string& video_mime_type, SbWindow window,
+                 SbDrmSystem drm_system, Host* host,
+                 SbPlayerSetBoundsHelper* set_bounds_helper,
+                 bool allow_resume_after_suspend, bool prefer_decode_to_texture,
+                 DecodeTargetProvider* const decode_target_provider,
+                 const std::string& max_video_capabilities);
 
-  ~StarboardPlayer();
+  ~SbPlayerBridge();
 
   bool IsValid() const { return SbPlayerIsValid(player_); }
 
@@ -152,10 +150,10 @@
   };
 
   // This class ensures that the callbacks posted to |task_runner_| are ignored
-  // automatically once StarboardPlayer is destroyed.
+  // automatically once SbPlayerBridge is destroyed.
   class CallbackHelper : public base::RefCountedThreadSafe<CallbackHelper> {
    public:
-    explicit CallbackHelper(StarboardPlayer* player);
+    explicit CallbackHelper(SbPlayerBridge* player_bridge);
 
     void ClearDecoderBufferCache();
 
@@ -170,7 +168,7 @@
 
    private:
     base::Lock lock_;
-    StarboardPlayer* player_;
+    SbPlayerBridge* player_bridge_;
   };
 
   static const int64 kClearDecoderCacheIntervalInMilliseconds = 1000;
@@ -306,4 +304,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_BASE_STARBOARD_PLAYER_H_
+#endif  // COBALT_MEDIA_BASE_SBPLAYER_BRIDGE_H_
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index 9b9d538..6c158bc 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -34,8 +34,8 @@
 #include "cobalt/media/base/media_export.h"
 #include "cobalt/media/base/pipeline.h"
 #include "cobalt/media/base/playback_statistics.h"
+#include "cobalt/media/base/sbplayer_bridge.h"
 #include "cobalt/media/base/sbplayer_set_bounds_helper.h"
-#include "cobalt/media/base/starboard_player.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/time.h"
@@ -63,9 +63,9 @@
 using ::media::Demuxer;
 using ::media::DemuxerHost;
 using ::media::DemuxerStream;
-using ::media::VideoDecoderConfig;
 using ::media::PipelineStatistics;
 using ::media::PipelineStatusCallback;
+using ::media::VideoDecoderConfig;
 
 static const int kRetryDelayAtSuspendInMilliseconds = 100;
 
@@ -94,7 +94,7 @@
 // interface internally.
 class MEDIA_EXPORT SbPlayerPipeline : public Pipeline,
                                       public DemuxerHost,
-                                      public StarboardPlayer::Host {
+                                      public SbPlayerBridge::Host {
  public:
   // Constructs a media pipeline that will execute on |task_runner|.
   SbPlayerPipeline(
@@ -178,7 +178,7 @@
   void OnDemuxerStreamRead(DemuxerStream::Type type,
                            DemuxerStream::Status status,
                            scoped_refptr<DecoderBuffer> buffer);
-  // StarboardPlayer::Host implementation.
+  // SbPlayerBridge::Host implementation.
   void OnNeedData(DemuxerStream::Type type) override;
   void OnPlayerStatus(SbPlayerState state) override;
   void OnPlayerError(SbPlayerError error, const std::string& message) override;
@@ -206,7 +206,7 @@
   // playback resume.
   std::string GetTimeInformation() const;
 
-  void RunSetDrmSystemReadyCB();
+  void RunSetDrmSystemReadyCB(DrmSystemReadyCB drm_system_ready_cb);
 
   // An identifier string for the pipeline, used in CVal to identify multiple
   // pipelines.
@@ -273,7 +273,7 @@
   base::Closure content_size_change_cb_;
   base::Optional<bool> decode_to_texture_output_mode_;
 #if SB_HAS(PLAYER_WITH_URL)
-  StarboardPlayer::OnEncryptedMediaInitDataEncounteredCB
+  SbPlayerBridge::OnEncryptedMediaInitDataEncounteredCB
       on_encrypted_media_init_data_encountered_cb_;
 #endif  //  SB_HAS(PLAYER_WITH_URL)
 
@@ -300,7 +300,7 @@
   // Temporary callback used for Start() and Seek().
   SeekCB seek_cb_;
   TimeDelta seek_time_;
-  std::unique_ptr<StarboardPlayer> player_;
+  std::unique_ptr<SbPlayerBridge> player_bridge_;
   bool is_initial_preroll_ = true;
   base::CVal<bool> started_;
   base::CVal<bool> suspended_;
@@ -395,7 +395,7 @@
   SbMediaSetAudioWriteDuration(kAudioLimit);
 }
 
-SbPlayerPipeline::~SbPlayerPipeline() { DCHECK(!player_); }
+SbPlayerPipeline::~SbPlayerPipeline() { DCHECK(!player_bridge_); }
 
 void SbPlayerPipeline::Suspend() {
   DCHECK(!task_runner_->BelongsToCurrentThread());
@@ -518,7 +518,7 @@
                  on_encrypted_media_init_data_encountered_cb);
   set_drm_system_ready_cb_ = parameters.set_drm_system_ready_cb;
   DCHECK(!set_drm_system_ready_cb_.is_null());
-  RunSetDrmSystemReadyCB();
+  RunSetDrmSystemReadyCB(base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
 
   task_runner_->PostTask(FROM_HERE,
                          base::Bind(&SbPlayerPipeline::StartTask, this,
@@ -540,15 +540,15 @@
 
   stopped_ = true;
 
-  if (player_) {
-    std::unique_ptr<StarboardPlayer> player;
+  if (player_bridge_) {
+    std::unique_ptr<SbPlayerBridge> player_bridge;
     {
       base::AutoLock auto_lock(lock_);
-      player = std::move(player_);
+      player_bridge = std::move(player_bridge_);
     }
 
     LOG(INFO) << "Destroying SbPlayer.";
-    player.reset();
+    player_bridge.reset();
     LOG(INFO) << "SbPlayer destroyed.";
   }
 
@@ -572,14 +572,14 @@
 
   playback_statistics_.OnSeek(time);
 
-  if (!player_) {
+  if (!player_bridge_) {
     seek_cb.Run(::media::PIPELINE_ERROR_INVALID_STATE, false,
                 AppendStatisticsString("SbPlayerPipeline::Seek failed: "
-                                       "player_ is nullptr."));
+                                       "player_bridge_ is nullptr."));
     return;
   }
 
-  player_->PrepareForSeek();
+  player_bridge_->PrepareForSeek();
   ended_ = false;
 
   DCHECK(seek_cb_.is_null());
@@ -607,7 +607,7 @@
 
 #if SB_HAS(PLAYER_WITH_URL)
   if (is_url_based_) {
-    player_->Seek(seek_time_);
+    player_bridge_->Seek(seek_time_);
     return;
   }
 #endif  // SB_HAS(PLAYER_WITH_URL)
@@ -664,7 +664,7 @@
     StoreMediaTime(seek_time_);
     return seek_time_;
   }
-  if (!player_) {
+  if (!player_bridge_) {
     StoreMediaTime(TimeDelta());
     return TimeDelta();
   }
@@ -677,7 +677,7 @@
   if (is_url_based_) {
     int frame_width;
     int frame_height;
-    player_->GetVideoResolution(&frame_width, &frame_height);
+    player_bridge_->GetVideoResolution(&frame_width, &frame_height);
     if (frame_width != natural_size_.width() ||
         frame_height != natural_size_.height()) {
       natural_size_ = gfx::Size(frame_width, frame_height);
@@ -685,8 +685,8 @@
     }
   }
 #endif  // SB_HAS(PLAYER_WITH_URL)
-  player_->GetInfo(&statistics_.video_frames_decoded,
-                   &statistics_.video_frames_dropped, &media_time);
+  player_bridge_->GetInfo(&statistics_.video_frames_decoded,
+                          &statistics_.video_frames_dropped, &media_time);
 
   // Guarantee that we report monotonically increasing media time
   if (media_time.ToSbTime() < last_media_time_) {
@@ -705,7 +705,7 @@
 #if SB_HAS(PLAYER_WITH_URL)
   ::media::Ranges<TimeDelta> time_ranges;
 
-  if (!player_) {
+  if (!player_bridge_) {
     return time_ranges;
   }
 
@@ -713,10 +713,10 @@
     base::TimeDelta media_time;
     base::TimeDelta buffer_start_time;
     base::TimeDelta buffer_length_time;
-    player_->GetInfo(&statistics_.video_frames_decoded,
-                     &statistics_.video_frames_dropped, &media_time);
-    player_->GetUrlPlayerBufferedTimeRanges(&buffer_start_time,
-                                            &buffer_length_time);
+    player_bridge_->GetInfo(&statistics_.video_frames_decoded,
+                            &statistics_.video_frames_dropped, &media_time);
+    player_bridge_->GetUrlPlayerBufferedTimeRanges(&buffer_start_time,
+                                                   &buffer_length_time);
 
     if (buffer_length_time.InSeconds() == 0) {
       buffered_time_ranges_ = time_ranges;
@@ -796,7 +796,7 @@
 
   // The player can't be created yet, if it is, then we're updating the output
   // mode too late.
-  DCHECK(!player_);
+  DCHECK(!player_bridge_);
 
   decode_to_texture_output_mode_ = enabled;
 }
@@ -838,16 +838,16 @@
 void SbPlayerPipeline::SetVolumeTask(float volume) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
-  if (player_) {
-    player_->SetVolume(volume_);
+  if (player_bridge_) {
+    player_bridge_->SetVolume(volume_);
   }
 }
 
 void SbPlayerPipeline::SetPlaybackRateTask(float volume) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
-  if (player_) {
-    player_->SetPlaybackRate(playback_rate_);
+  if (player_bridge_) {
+    player_bridge_->SetPlaybackRate(playback_rate_);
   }
 }
 
@@ -908,22 +908,22 @@
   std::string error_message;
   {
     base::AutoLock auto_lock(lock_);
-    LOG(INFO) << "Creating StarboardPlayer with url: " << source_url;
-    player_.reset(new StarboardPlayer(
+    LOG(INFO) << "Creating SbPlayerBridge with url: " << source_url;
+    player_bridge_.reset(new SbPlayerBridge(
         task_runner_, source_url, window_, this, set_bounds_helper_.get(),
         allow_resume_after_suspend_, *decode_to_texture_output_mode_,
         on_encrypted_media_init_data_encountered_cb_, decode_target_provider_));
-    if (player_->IsValid()) {
+    if (player_bridge_->IsValid()) {
       SetPlaybackRateTask(playback_rate_);
       SetVolumeTask(volume_);
     } else {
-      error_message = player_->GetPlayerCreationErrorMessage();
-      player_.reset();
-      LOG(INFO) << "Failed to create a valid StarboardPlayer.";
+      error_message = player_bridge_->GetPlayerCreationErrorMessage();
+      player_bridge_.reset();
+      LOG(INFO) << "Failed to create a valid SbPlayerBridge.";
     }
   }
 
-  if (player_ && player_->IsValid()) {
+  if (player_bridge_ && player_bridge_->IsValid()) {
     base::Closure output_mode_change_cb;
     {
       base::AutoLock auto_lock(lock_);
@@ -936,12 +936,12 @@
 
   std::string time_information = GetTimeInformation();
   LOG(INFO) << "SbPlayerPipeline::CreateUrlPlayer failed to create a valid "
-               "StarboardPlayer - "
+               "SbPlayerBridge - "
             << time_information << " \'" << error_message << "\'";
 
   CallSeekCB(::media::DECODER_ERROR_NOT_SUPPORTED,
              "SbPlayerPipeline::CreateUrlPlayer failed to create a valid "
-             "StarboardPlayer - " +
+             "SbPlayerBridge - " +
                  time_information + " \'" + error_message + "\'");
 }
 
@@ -949,14 +949,14 @@
   TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::SetDrmSystem");
 
   base::AutoLock auto_lock(lock_);
-  if (!player_) {
+  if (!player_bridge_) {
     LOG(INFO) << "Player not set before calling SbPlayerPipeline::SetDrmSystem";
     return;
   }
 
-  if (player_->IsValid()) {
-    player_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
-    player_->SetDrmSystem(drm_system);
+  if (player_bridge_->IsValid()) {
+    player_bridge_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
+    player_bridge_->SetDrmSystem(drm_system);
   }
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
@@ -1005,30 +1005,30 @@
   std::string error_message;
   {
     base::AutoLock auto_lock(lock_);
-    SB_DCHECK(!player_);
-    // In the extreme case that CreatePlayer() is called when a |player_| is
-    // available, reset the existing player first to reduce the number of active
-    // players.
-    player_.reset();
-    LOG(INFO) << "Creating StarboardPlayer.";
-    player_.reset(new StarboardPlayer(
+    SB_DCHECK(!player_bridge_);
+    // In the extreme case that CreatePlayer() is called when a |player_bridge_|
+    // is available, reset the existing player first to reduce the number of
+    // active players.
+    player_bridge_.reset();
+    LOG(INFO) << "Creating SbPlayerBridge.";
+    player_bridge_.reset(new SbPlayerBridge(
         task_runner_, get_decode_target_graphics_context_provider_func_,
         audio_config, audio_mime_type, video_config, video_mime_type, window_,
         drm_system, this, set_bounds_helper_.get(), allow_resume_after_suspend_,
         *decode_to_texture_output_mode_, decode_target_provider_,
         max_video_capabilities_));
-    if (player_->IsValid()) {
+    if (player_bridge_->IsValid()) {
       SetPlaybackRateTask(playback_rate_);
       SetVolumeTask(volume_);
     } else {
-      error_message = player_->GetPlayerCreationErrorMessage();
-      player_.reset();
-      LOG(INFO) << "Failed to create a valid StarboardPlayer.";
+      error_message = player_bridge_->GetPlayerCreationErrorMessage();
+      player_bridge_.reset();
+      LOG(INFO) << "Failed to create a valid SbPlayerBridge.";
     }
   }
 
-  if (player_ && player_->IsValid()) {
-    player_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
+  if (player_bridge_ && player_bridge_->IsValid()) {
+    player_bridge_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
     base::Closure output_mode_change_cb;
     {
       base::AutoLock auto_lock(lock_);
@@ -1048,12 +1048,12 @@
 
   std::string time_information = GetTimeInformation();
   LOG(INFO) << "SbPlayerPipeline::CreatePlayer failed to create a valid "
-               "StarboardPlayer - "
+               "SbPlayerBridge - "
             << time_information << " \'" << error_message << "\'";
 
   CallSeekCB(::media::DECODER_ERROR_NOT_SUPPORTED,
              "SbPlayerPipeline::CreatePlayer failed to create a valid "
-             "StarboardPlayer - " +
+             "SbPlayerBridge - " +
                  time_information + " \'" + error_message + "\'");
 }
 
@@ -1130,7 +1130,8 @@
       content_size_change_cb_.Run();
     }
     if (is_encrypted) {
-      RunSetDrmSystemReadyCB();
+      RunSetDrmSystemReadyCB(::media::BindToCurrentLoop(
+          base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
       return;
     }
   }
@@ -1141,8 +1142,8 @@
 void SbPlayerPipeline::OnDemuxerSeeked(PipelineStatus status) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
-  if (status == ::media::PIPELINE_OK && player_) {
-    player_->Seek(seek_time_);
+  if (status == ::media::PIPELINE_OK && player_bridge_) {
+    player_bridge_->Seek(seek_time_);
   }
 }
 
@@ -1179,7 +1180,7 @@
   DCHECK(stream);
 
   // In case if Stop() has been called.
-  if (!player_) {
+  if (!player_bridge_) {
     return;
   }
 
@@ -1215,7 +1216,7 @@
     playback_statistics_.OnVideoAU(buffer);
   }
 
-  player_->WriteBuffer(type, buffer);
+  player_bridge_->WriteBuffer(type, buffer);
 }
 
 void SbPlayerPipeline::OnNeedData(DemuxerStream::Type type) {
@@ -1225,7 +1226,7 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   // In case if Stop() has been called.
-  if (!player_) {
+  if (!player_bridge_) {
     return;
   }
 
@@ -1283,7 +1284,7 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   // In case if Stop() has been called.
-  if (!player_) {
+  if (!player_bridge_) {
     return;
   }
   player_state_ = state;
@@ -1302,12 +1303,12 @@
     case kSbPlayerStatePresenting: {
 #if SB_HAS(PLAYER_WITH_URL)
       if (is_url_based_) {
-        duration_ = player_->GetDuration();
-        start_date_ = player_->GetStartDate();
+        duration_ = player_bridge_->GetDuration();
+        start_date_ = player_bridge_->GetStartDate();
         buffering_state_cb_.Run(kHaveMetadata);
         int frame_width;
         int frame_height;
-        player_->GetVideoResolution(&frame_width, &frame_height);
+        player_bridge_->GetVideoResolution(&frame_width, &frame_height);
         bool natural_size_changed = (frame_width != natural_size_.width() ||
                                      frame_height != natural_size_.height());
         natural_size_ = gfx::Size(frame_width, frame_height);
@@ -1340,7 +1341,7 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   // In case if Stop() has been called.
-  if (!player_) {
+  if (!player_bridge_) {
     return;
   }
 
@@ -1384,7 +1385,7 @@
 
   if (stream->type() == DemuxerStream::AUDIO) {
     const AudioDecoderConfig& decoder_config = stream->audio_decoder_config();
-    player_->UpdateAudioConfig(decoder_config, stream->mime_type());
+    player_bridge_->UpdateAudioConfig(decoder_config, stream->mime_type());
   } else {
     DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
     const VideoDecoderConfig& decoder_config = stream->video_decoder_config();
@@ -1393,7 +1394,7 @@
         (decoder_config.natural_size().width() != natural_size_.width() ||
          decoder_config.natural_size().height() != natural_size_.height());
     natural_size_ = decoder_config.natural_size();
-    player_->UpdateVideoConfig(decoder_config, stream->mime_type());
+    player_bridge_->UpdateVideoConfig(decoder_config, stream->mime_type());
     if (natural_size_changed) {
       content_size_change_cb_.Run();
     }
@@ -1443,11 +1444,11 @@
     return;
   }
 
-  if (player_) {
-    // Cancel pending delayed calls to OnNeedData. After player_->Resume(),
-    // |player_| will call OnNeedData again.
+  if (player_bridge_) {
+    // Cancel pending delayed calls to OnNeedData. After
+    // player_bridge_->Resume(), |player_bridge_| will call OnNeedData again.
     audio_read_delayed_ = false;
-    player_->Suspend();
+    player_bridge_->Suspend();
   }
 
   suspended_ = true;
@@ -1469,22 +1470,22 @@
 
   window_ = window;
 
-  if (player_) {
-    player_->Resume(window);
-    if (!player_->IsValid()) {
+  if (player_bridge_) {
+    player_bridge_->Resume(window);
+    if (!player_bridge_->IsValid()) {
       std::string error_message;
       {
         base::AutoLock auto_lock(lock_);
-        error_message = player_->GetPlayerCreationErrorMessage();
-        player_.reset();
+        error_message = player_bridge_->GetPlayerCreationErrorMessage();
+        player_bridge_.reset();
       }
       std::string time_information = GetTimeInformation();
       LOG(INFO) << "SbPlayerPipeline::ResumeTask failed to create a valid "
-                   "StarboardPlayer - "
+                   "SbPlayerBridge - "
                 << time_information << " \'" << error_message << "\'";
       CallErrorCB(::media::DECODER_ERROR_NOT_SUPPORTED,
                   "SbPlayerPipeline::ResumeTask failed to create a valid "
-                  "StarboardPlayer - " +
+                  "SbPlayerBridge - " +
                       time_information + " \'" + error_message + "\'");
       done_event->Signal();
       return;
@@ -1537,16 +1538,11 @@
          ", time since last resume: " + time_since_resume;
 }
 
-void SbPlayerPipeline::RunSetDrmSystemReadyCB() {
+void SbPlayerPipeline::RunSetDrmSystemReadyCB(
+    DrmSystemReadyCB drm_system_ready_cb) {
   TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::RunSetDrmSystemReadyCB");
   set_drm_system_ready_cb_time_ = SbTimeGetMonotonicNow();
-#if SB_HAS(PLAYER_WITH_URL)
-  set_drm_system_ready_cb_.Run(
-      base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
-#else   // SB_HAS(PLAYER_WITH_URL)
-  set_drm_system_ready_cb_.Run(::media::BindToCurrentLoop(
-      base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
-#endif  // SB_HAS(PLAYER_WITH_URL)
+  set_drm_system_ready_cb_.Run(drm_system_ready_cb);
 }
 
 }  // namespace
diff --git a/cobalt/media/base/sbplayer_set_bounds_helper.cc b/cobalt/media/base/sbplayer_set_bounds_helper.cc
index d29e042..9b02952 100644
--- a/cobalt/media/base/sbplayer_set_bounds_helper.cc
+++ b/cobalt/media/base/sbplayer_set_bounds_helper.cc
@@ -15,7 +15,7 @@
 #include "cobalt/media/base/sbplayer_set_bounds_helper.h"
 
 #include "base/atomic_sequence_num.h"
-#include "cobalt/media/base/starboard_player.h"
+#include "cobalt/media/base/sbplayer_bridge.h"
 
 namespace cobalt {
 namespace media {
@@ -28,22 +28,22 @@
 base::AtomicSequenceNumber s_z_index;
 }  // namespace
 
-void SbPlayerSetBoundsHelper::SetPlayer(StarboardPlayer* player) {
+void SbPlayerSetBoundsHelper::SetPlayerBridge(SbPlayerBridge* player_bridge) {
   base::AutoLock auto_lock(lock_);
-  player_ = player;
-  if (player_ && rect_.has_value()) {
-    player_->SetBounds(s_z_index.GetNext(), rect_.value());
+  player_bridge_ = player_bridge;
+  if (player_bridge_ && rect_.has_value()) {
+    player_bridge_->SetBounds(s_z_index.GetNext(), rect_.value());
   }
 }
 
 bool SbPlayerSetBoundsHelper::SetBounds(int x, int y, int width, int height) {
   base::AutoLock auto_lock(lock_);
   rect_ = gfx::Rect(x, y, width, height);
-  if (player_) {
-    player_->SetBounds(s_z_index.GetNext(), rect_.value());
+  if (player_bridge_) {
+    player_bridge_->SetBounds(s_z_index.GetNext(), rect_.value());
   }
 
-  return player_ != nullptr;
+  return player_bridge_ != nullptr;
 }
 
 }  // namespace media
diff --git a/cobalt/media/base/sbplayer_set_bounds_helper.h b/cobalt/media/base/sbplayer_set_bounds_helper.h
index 4c09e3c..c5f4b95 100644
--- a/cobalt/media/base/sbplayer_set_bounds_helper.h
+++ b/cobalt/media/base/sbplayer_set_bounds_helper.h
@@ -23,19 +23,19 @@
 namespace cobalt {
 namespace media {
 
-class StarboardPlayer;
+class SbPlayerBridge;
 
 class SbPlayerSetBoundsHelper
     : public base::RefCountedThreadSafe<SbPlayerSetBoundsHelper> {
  public:
   SbPlayerSetBoundsHelper() {}
 
-  void SetPlayer(StarboardPlayer* player);
+  void SetPlayerBridge(SbPlayerBridge* player_bridge);
   bool SetBounds(int x, int y, int width, int height);
 
  private:
   base::Lock lock_;
-  StarboardPlayer* player_ = nullptr;
+  SbPlayerBridge* player_bridge_ = nullptr;
   base::Optional<gfx::Rect> rect_;
 
   DISALLOW_COPY_AND_ASSIGN(SbPlayerSetBoundsHelper);
diff --git a/cobalt/media/progressive/progressive_demuxer.cc b/cobalt/media/progressive/progressive_demuxer.cc
index 8599a31..9470096 100644
--- a/cobalt/media/progressive/progressive_demuxer.cc
+++ b/cobalt/media/progressive/progressive_demuxer.cc
@@ -540,19 +540,6 @@
   reader_->Stop();
 }
 
-void ProgressiveDemuxer::DataSourceStopped(const base::Closure& callback) {
-  TRACE_EVENT0("media_stack", "ProgressiveDemuxer::DataSourceStopped()");
-  DCHECK(MessageLoopBelongsToCurrentThread());
-  // stop the download thread
-  blocking_thread_.Stop();
-
-  // tell downstream we've stopped
-  if (audio_demuxer_stream_) audio_demuxer_stream_->Stop();
-  if (video_demuxer_stream_) video_demuxer_stream_->Stop();
-
-  callback.Run();
-}
-
 bool ProgressiveDemuxer::HasStopCalled() {
   base::AutoLock auto_lock(lock_for_stopped_);
   return stopped_;
diff --git a/cobalt/media/progressive/progressive_demuxer.h b/cobalt/media/progressive/progressive_demuxer.h
index f4ef49a..73f3026 100644
--- a/cobalt/media/progressive/progressive_demuxer.h
+++ b/cobalt/media/progressive/progressive_demuxer.h
@@ -163,7 +163,6 @@
 
  private:
   void ParseConfigDone(PipelineStatusCallback status_cb, PipelineStatus status);
-  void DataSourceStopped(const base::Closure& callback);
   bool HasStopCalled();
 
   // methods that perform blocking I/O, and are therefore run on the
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc
index ffebc2a..822a5d3 100644
--- a/cobalt/persistent_storage/persistent_settings.cc
+++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -17,7 +17,9 @@
 #include <utility>
 #include <vector>
 
-#include "base/values.h"
+#include "base/bind.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/json_read_only_pref_store.h"
 #include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration_constants.h"
@@ -31,140 +33,253 @@
 // settings file has been validated.
 const char kValidated[] = "validated";
 
+// Signals the given WaitableEvent.
+void SignalWaitableEvent(base::WaitableEvent* event) { event->Signal(); }
+
 }  // namespace
 
-PersistentSettings::PersistentSettings(
-    const std::string& file_name,
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+void PersistentSettings::WillDestroyCurrentMessageLoop() {
+  // Clear all member variables allocated from the thread.
+  pref_store_.reset();
+}
+
+PersistentSettings::PersistentSettings(const std::string& file_name)
+    : thread_("PersistentSettings") {
+  if (!thread_.Start()) return;
+  DCHECK(message_loop());
+
   std::vector<char> storage_dir(kSbFileMaxPath + 1, 0);
   SbSystemGetPath(kSbSystemPathStorageDirectory, storage_dir.data(),
                   kSbFileMaxPath);
 
   persistent_settings_file_ =
       std::string(storage_dir.data()) + kSbFileSepString + file_name;
-  SB_LOG(INFO) << "Persistent settings file path: "
-               << persistent_settings_file_;
+  LOG(INFO) << "Persistent settings file path: " << persistent_settings_file_;
 
-  task_runner_ = task_runner;
+  // Initialize pref_store_ with a JSONReadOnlyPrefStore, Used for
+  // synchronous PersistentSettings::Get calls made before the asynchronous
+  // InitializeWriteablePrefStore initializes pref_store_ with a writable
+  // instance.
+  {
+    base::AutoLock auto_lock(pref_store_lock_);
+    pref_store_ = base::MakeRefCounted<JsonReadOnlyPrefStore>(
+        base::FilePath(persistent_settings_file_));
+    pref_store_->ReadPrefs();
+  }
 
-  pref_store_ = base::MakeRefCounted<JsonPrefStore>(
-      base::FilePath(persistent_settings_file_));
-  pref_store_->ReadPrefs();
+  message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::Bind(&PersistentSettings::InitializeWriteablePrefStore,
+                            base::Unretained(this)));
 
+  // Register as a destruction observer to shut down the Web Agent once all
+  // pending tasks have been executed and the message loop is about to be
+  // destroyed. This allows us to safely stop the thread, drain the task queue,
+  // then destroy the internal components before the message loop is reset.
+  // No posted tasks will be executed once the thread is stopped.
+  message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::Bind(&base::MessageLoop::AddDestructionObserver,
+                 base::Unretained(message_loop()), base::Unretained(this)));
+
+  // This works almost like a PostBlockingTask, except that any blocking that
+  // may be necessary happens when Stop() is called instead of right now.
+  message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::Bind(&SignalWaitableEvent,
+                            base::Unretained(&destruction_observer_added_)));
+}
+
+PersistentSettings::~PersistentSettings() {
+  DCHECK(message_loop());
+  DCHECK(thread_.IsRunning());
+
+  // Ensure that the destruction observer got added and the pref store was
+  // initialized before stopping the thread. Stop the thread. This will cause
+  // the destruction observer to be notified.
+  writeable_pref_store_initialized_.Wait();
+  destruction_observer_added_.Wait();
+  thread_.Stop();
+}
+
+void PersistentSettings::InitializeWriteablePrefStore() {
+  DCHECK_EQ(base::MessageLoop::current(), message_loop());
+  {
+    base::AutoLock auto_lock(pref_store_lock_);
+    pref_store_ = base::MakeRefCounted<JsonPrefStore>(
+        base::FilePath(persistent_settings_file_));
+    pref_store_->ReadPrefs();
+    // PersistentSettings Set and Remove Helper methods will now be able to
+    // access the pref_store_ initialized from the dedicated thread_.
+    writeable_pref_store_initialized_.Signal();
+  }
   validated_initial_settings_ = GetPersistentSettingAsBool(kValidated, false);
-  if (!validated_initial_settings_)
+  if (!validated_initial_settings_) {
     starboard::SbFileDeleteRecursive(persistent_settings_file_.c_str(), true);
+  }
 }
 
 void PersistentSettings::ValidatePersistentSettings() {
-  task_runner_->PostTask(
+  message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&PersistentSettings::ValidatePersistentSettingsHelper,
                      base::Unretained(this)));
 }
 
 void PersistentSettings::ValidatePersistentSettingsHelper() {
+  DCHECK_EQ(base::MessageLoop::current(), message_loop());
   if (!validated_initial_settings_) {
-    pref_store_->SetValue(kValidated, std::make_unique<base::Value>(true),
-                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-    pref_store_->CommitPendingWrite();
+    base::AutoLock auto_lock(pref_store_lock_);
+    writeable_pref_store()->SetValue(
+        kValidated, std::make_unique<base::Value>(true),
+        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    writeable_pref_store()->CommitPendingWrite();
     validated_initial_settings_ = true;
   }
 }
 
 bool PersistentSettings::GetPersistentSettingAsBool(const std::string& key,
                                                     bool default_setting) {
+  base::AutoLock auto_lock(pref_store_lock_);
   auto persistent_settings = pref_store_->GetValues();
   const base::Value* result = persistent_settings->FindKey(key);
   if (result && result->is_bool()) return result->GetBool();
-  SB_LOG(INFO) << "Persistent setting does not exist: " << key;
+  LOG(INFO) << "Persistent setting does not exist: " << key;
   return default_setting;
 }
 
 int PersistentSettings::GetPersistentSettingAsInt(const std::string& key,
                                                   int default_setting) {
+  base::AutoLock auto_lock(pref_store_lock_);
   auto persistent_settings = pref_store_->GetValues();
   const base::Value* result = persistent_settings->FindKey(key);
   if (result && result->is_int()) return result->GetInt();
-  SB_LOG(INFO) << "Persistent setting does not exist: " << key;
+  LOG(INFO) << "Persistent setting does not exist: " << key;
   return default_setting;
 }
 
 double PersistentSettings::GetPersistentSettingAsDouble(
     const std::string& key, double default_setting) {
+  base::AutoLock auto_lock(pref_store_lock_);
   auto persistent_settings = pref_store_->GetValues();
   const base::Value* result = persistent_settings->FindKey(key);
   if (result && result->is_double()) return result->GetDouble();
-  SB_LOG(INFO) << "Persistent setting does not exist: " << key;
+  LOG(INFO) << "Persistent setting does not exist: " << key;
   return default_setting;
 }
 
 std::string PersistentSettings::GetPersistentSettingAsString(
     const std::string& key, const std::string& default_setting) {
+  base::AutoLock auto_lock(pref_store_lock_);
   auto persistent_settings = pref_store_->GetValues();
   const base::Value* result = persistent_settings->FindKey(key);
   if (result && result->is_string()) return result->GetString();
-  SB_LOG(INFO) << "Persistent setting does not exist: " << key;
+  LOG(INFO) << "Persistent setting does not exist: " << key;
   return default_setting;
 }
 
+std::vector<base::Value> PersistentSettings::GetPersistentSettingAsList(
+    const std::string& key) {
+  base::AutoLock auto_lock(pref_store_lock_);
+  auto persistent_settings = pref_store_->GetValues();
+  base::Value* result = persistent_settings->FindKey(key);
+  if (result && result->is_list()) {
+    return std::move(result->TakeList());
+  }
+  LOG(INFO) << "Persistent setting does not exist: " << key;
+  return std::vector<base::Value>();
+}
+
+base::flat_map<std::string, std::unique_ptr<base::Value>>
+PersistentSettings::GetPersistentSettingAsDictionary(const std::string& key) {
+  base::AutoLock auto_lock(pref_store_lock_);
+  auto persistent_settings = pref_store_->GetValues();
+  base::Value* result = persistent_settings->FindKey(key);
+  base::flat_map<std::string, std::unique_ptr<base::Value>> dict;
+  if (result && result->is_dict()) {
+    for (const auto& key_value : result->DictItems()) {
+      dict.insert(std::make_pair(
+          key_value.first,
+          std::make_unique<base::Value>(std::move(key_value.second))));
+    }
+    return dict;
+  }
+  LOG(INFO) << "Persistent setting does not exist: " << key;
+  return dict;
+}
+
 void PersistentSettings::SetPersistentSetting(
-    const std::string& key, std::unique_ptr<base::Value> value) {
+    const std::string& key, std::unique_ptr<base::Value> value,
+    base::OnceClosure closure) {
   if (key == kValidated) {
-    SB_LOG(ERROR) << "Cannot set protected persistent setting: " << key;
+    LOG(ERROR) << "Cannot set protected persistent setting: " << key;
     return;
   }
-  task_runner_->PostTask(
+  message_loop()->task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&PersistentSettings::SetPersistentSettingHelper,
-                                base::Unretained(this), key, std::move(value)));
+                                base::Unretained(this), key, std::move(value),
+                                std::move(closure)));
 }
 
 void PersistentSettings::SetPersistentSettingHelper(
-    const std::string& key, std::unique_ptr<base::Value> value) {
+    const std::string& key, std::unique_ptr<base::Value> value,
+    base::OnceClosure closure) {
+  DCHECK_EQ(base::MessageLoop::current(), message_loop());
   if (validated_initial_settings_) {
-    pref_store_->SetValue(kValidated, std::make_unique<base::Value>(false),
-                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-    pref_store_->SetValue(key, std::move(value),
-                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-    pref_store_->CommitPendingWrite();
+    base::AutoLock auto_lock(pref_store_lock_);
+    writeable_pref_store()->SetValue(
+        kValidated, std::make_unique<base::Value>(false),
+        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    writeable_pref_store()->SetValue(
+        key, std::move(value), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    writeable_pref_store()->CommitPendingWrite();
   } else {
-    SB_LOG(ERROR) << "Cannot set persistent setting while unvalidated: " << key;
+    LOG(ERROR) << "Cannot set persistent setting while unvalidated: " << key;
   }
+  std::move(closure).Run();
 }
 
-void PersistentSettings::RemovePersistentSetting(const std::string& key) {
-  task_runner_->PostTask(
+void PersistentSettings::RemovePersistentSetting(const std::string& key,
+                                                 base::OnceClosure closure) {
+  message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&PersistentSettings::RemovePersistentSettingHelper,
-                     base::Unretained(this), key));
+                     base::Unretained(this), key, std::move(closure)));
 }
 
-void PersistentSettings::RemovePersistentSettingHelper(const std::string& key) {
+void PersistentSettings::RemovePersistentSettingHelper(
+    const std::string& key, base::OnceClosure closure) {
+  DCHECK_EQ(base::MessageLoop::current(), message_loop());
   if (validated_initial_settings_) {
-    pref_store_->SetValue(kValidated, std::make_unique<base::Value>(false),
-                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-    pref_store_->RemoveValue(key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-    pref_store_->CommitPendingWrite();
+    base::AutoLock auto_lock(pref_store_lock_);
+    writeable_pref_store()->SetValue(
+        kValidated, std::make_unique<base::Value>(false),
+        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    writeable_pref_store()->RemoveValue(
+        key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    writeable_pref_store()->CommitPendingWrite();
   } else {
-    SB_LOG(ERROR) << "Cannot remove persistent setting while unvalidated: "
-                  << key;
+    LOG(ERROR) << "Cannot remove persistent setting while unvalidated: " << key;
   }
+  std::move(closure).Run();
 }
 
-void PersistentSettings::DeletePersistentSettings() {
-  task_runner_->PostTask(
+void PersistentSettings::DeletePersistentSettings(base::OnceClosure closure) {
+  message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&PersistentSettings::DeletePersistentSettingsHelper,
-                     base::Unretained(this)));
+                     base::Unretained(this), std::move(closure)));
 }
 
-void PersistentSettings::DeletePersistentSettingsHelper() {
+void PersistentSettings::DeletePersistentSettingsHelper(
+    base::OnceClosure closure) {
+  DCHECK_EQ(base::MessageLoop::current(), message_loop());
   if (validated_initial_settings_) {
     starboard::SbFileDeleteRecursive(persistent_settings_file_.c_str(), true);
-    pref_store_->ReadPrefs();
+    base::AutoLock auto_lock(pref_store_lock_);
+    writeable_pref_store()->ReadPrefs();
   } else {
-    SB_LOG(ERROR) << "Cannot delete persistent setting while unvalidated.";
+    LOG(ERROR) << "Cannot delete persistent setting while unvalidated.";
   }
+  std::move(closure).Run();
 }
 
 }  // namespace persistent_storage
diff --git a/cobalt/persistent_storage/persistent_settings.h b/cobalt/persistent_storage/persistent_settings.h
index eaadd7b..47cd446 100644
--- a/cobalt/persistent_storage/persistent_settings.h
+++ b/cobalt/persistent_storage/persistent_settings.h
@@ -17,8 +17,18 @@
 
 #include <memory>
 #include <string>
+#include <utility>
+#include <vector>
 
-#include "components/prefs/json_pref_store.h"
+#include "base/bind_helpers.h"
+#include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store.h"
 
 namespace cobalt {
 namespace persistent_storage {
@@ -27,51 +37,97 @@
 // lifecycle to another as a JSON file in kSbSystemPathStorageDirectory.
 // PersistentSettings uses JsonPrefStore for most of its functionality.
 // JsonPrefStore maintains thread safety by requiring that all access occurs on
-// the Sequence that created it. This is why some functions rely on a PostTask
-// to the SingleThreadTaskRunner that calls and is subsequently passed into
-// PersistentSettings' constructor.
-class PersistentSettings {
+// the Sequence that created it.
+class PersistentSettings : public base::MessageLoop::DestructionObserver {
  public:
-  explicit PersistentSettings(
-      const std::string& file_name,
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+  explicit PersistentSettings(const std::string& file_name);
+  ~PersistentSettings();
+
+  // The message loop this object is running on.
+  base::MessageLoop* message_loop() const { return thread_.message_loop(); }
+
+  // From base::MessageLoop::DestructionObserver.
+  void WillDestroyCurrentMessageLoop() override;
 
   // Validates persistent settings by restoring the file on successful start up.
   void ValidatePersistentSettings();
 
   // Getters and Setters for persistent settings.
   bool GetPersistentSettingAsBool(const std::string& key, bool default_setting);
+
   int GetPersistentSettingAsInt(const std::string& key, int default_setting);
+
   double GetPersistentSettingAsDouble(const std::string& key,
                                       double default_setting);
+
   std::string GetPersistentSettingAsString(const std::string& key,
                                            const std::string& default_setting);
-  void SetPersistentSetting(const std::string& key,
-                            std::unique_ptr<base::Value> value);
-  void RemovePersistentSetting(const std::string& key);
 
-  void DeletePersistentSettings();
+  std::vector<base::Value> GetPersistentSettingAsList(const std::string& key);
+
+  base::flat_map<std::string, std::unique_ptr<base::Value>>
+  GetPersistentSettingAsDictionary(const std::string& key);
+
+  void SetPersistentSetting(
+      const std::string& key, std::unique_ptr<base::Value> value,
+      base::OnceClosure closure = std::move(base::DoNothing()));
+
+  void RemovePersistentSetting(
+      const std::string& key,
+      base::OnceClosure closure = std::move(base::DoNothing()));
+
+  void DeletePersistentSettings(
+      base::OnceClosure closure = std::move(base::DoNothing()));
 
  private:
-  // Persistent settings file path.
-  std::string persistent_settings_file_;
-  // TaskRunner of the thread that initialized PersistentSettings and
-  // PrefStore.
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  // PrefStore used for persistent settings.
-  scoped_refptr<JsonPrefStore> pref_store_;
-  // Flag indicating whether or not initial persistent settings have been
-  // validated.
-  bool validated_initial_settings_;
+  // Called by the constructor to initialize pref_store_ from
+  // the dedicated thread_ as a writeable JSONPrefStore.
+  void InitializeWriteablePrefStore();
 
   void ValidatePersistentSettingsHelper();
 
   void SetPersistentSettingHelper(const std::string& key,
-                                  std::unique_ptr<base::Value> value);
+                                  std::unique_ptr<base::Value> value,
+                                  base::OnceClosure closure);
 
-  void RemovePersistentSettingHelper(const std::string& key);
+  void RemovePersistentSettingHelper(const std::string& key,
+                                     base::OnceClosure closure);
 
-  void DeletePersistentSettingsHelper();
+  void DeletePersistentSettingsHelper(base::OnceClosure closure);
+
+  scoped_refptr<PersistentPrefStore> writeable_pref_store() {
+    writeable_pref_store_initialized_.Wait();
+    return pref_store_;
+  }
+
+  // Persistent settings file path.
+  std::string persistent_settings_file_;
+
+  // PrefStore used for persistent settings.
+  scoped_refptr<PersistentPrefStore> pref_store_;
+
+  // Flag indicating whether or not initial persistent settings have been
+  // validated.
+  bool validated_initial_settings_;
+
+  // The thread created and owned by PersistentSettings. All pref_store_
+  // methods must be called from this thread.
+  base::Thread thread_;
+
+  // This event is used to signal when Initialize has been called and
+  // pref_store_ mutations can now occur.
+  base::WaitableEvent writeable_pref_store_initialized_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+  // This event is used to signal when the destruction observers have been
+  // added to the message loop. This is then used in Stop() to ensure that
+  // processing doesn't continue until the thread is cleanly shutdown.
+  base::WaitableEvent destruction_observer_added_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+  base::Lock pref_store_lock_;
 };
 
 }  // namespace persistent_storage
diff --git a/cobalt/persistent_storage/persistent_settings_test.cc b/cobalt/persistent_storage/persistent_settings_test.cc
index a4632e9..4a3f3a7 100644
--- a/cobalt/persistent_storage/persistent_settings_test.cc
+++ b/cobalt/persistent_storage/persistent_settings_test.cc
@@ -12,9 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <utility>
 #include <vector>
 
+#include "base/bind.h"
+#include "base/bind_internal.h"
+#include "base/callback_forward.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/test/scoped_task_environment.h"
+#include "base/threading/platform_thread.h"
 #include "base/values.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "starboard/common/file.h"
@@ -43,26 +49,27 @@
 
     persistent_settings_file_ = std::string(storage_dir.data()) +
                                 kSbFileSepString + kPersistentSettingsJson;
-    SB_LOG(INFO) << "Persistent settings test file path: "
-                 << persistent_settings_file_;
   }
 
   void SetUp() final {
+    test_done_.Reset();
     starboard::SbFileDeleteRecursive(persistent_settings_file_.c_str(), true);
   }
 
   void TearDown() final {
+    test_done_.Reset();
     starboard::SbFileDeleteRecursive(persistent_settings_file_.c_str(), true);
   }
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::string persistent_settings_file_;
+  base::WaitableEvent test_done_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED};
 };
 
 TEST_F(PersistentSettingTest, GetDefaultBool) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   // does not exist
@@ -70,35 +77,60 @@
   ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
 
   // exists but invalid
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(4.2));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsInt("key", true));
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsInt("key", true));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(4.2), std::move(closure));
+  test_done_.Wait();
+  delete persistent_settings;
 }
 
 TEST_F(PersistentSettingTest, GetSetBool) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
-  persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>(true));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", false));
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
 
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>(false));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
+      "key", std::make_unique<base::Value>(true), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
+
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_FALSE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_FALSE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(false), std::move(closure));
+
+  test_done_.Wait();
+  delete persistent_settings;
 }
 
 TEST_F(PersistentSettingTest, GetDefaultInt) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   // does not exist
@@ -107,38 +139,61 @@
   ASSERT_EQ(42, persistent_settings->GetPersistentSettingAsInt("key", 42));
 
   // exists but invalid
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(4.2));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ(8, persistent_settings->GetPersistentSettingAsInt("key", 8));
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ(8, persistent_settings->GetPersistentSettingAsInt("key", 8));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(4.2), std::move(closure));
+  test_done_.Wait();
 }
 
 TEST_F(PersistentSettingTest, GetSetInt) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(-1));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ(-1, persistent_settings->GetPersistentSettingAsInt("key", 8));
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ(-1, persistent_settings->GetPersistentSettingAsInt("key", 8));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(-1), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(0));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ(0, persistent_settings->GetPersistentSettingAsInt("key", 8));
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ(0, persistent_settings->GetPersistentSettingAsInt("key", 8));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(0), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(42));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ(42, persistent_settings->GetPersistentSettingAsInt("key", 8));
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ(42, persistent_settings->GetPersistentSettingAsInt("key", 8));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(42), std::move(closure));
+  test_done_.Wait();
 }
 
 TEST_F(PersistentSettingTest, GetDefaultString) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   // does not exist
@@ -153,127 +208,225 @@
             persistent_settings->GetPersistentSettingAsString("key", "\\n"));
 
   // exists but invalid
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(4.2));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ("hello",
-            persistent_settings->GetPersistentSettingAsString("key", "hello"));
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ("hello", persistent_settings->GetPersistentSettingAsString(
+                               "key", "hello"));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>(4.2), std::move(closure));
+  test_done_.Wait();
+  delete persistent_settings;
 }
 
 TEST_F(PersistentSettingTest, GetSetString) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
-  persistent_settings->SetPersistentSetting("key",
-                                            std::make_unique<base::Value>(""));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ("",
-            persistent_settings->GetPersistentSettingAsString("key", "hello"));
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ("", persistent_settings->GetPersistentSettingAsString(
+                          "key", "hello"));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
 
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>("hello there"));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ("hello there",
+      "key", std::make_unique<base::Value>(""), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
+
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ(
+            "hello there",
             persistent_settings->GetPersistentSettingAsString("key", "hello"));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
 
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>("42"));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ("42",
-            persistent_settings->GetPersistentSettingAsString("key", "hello"));
+      "key", std::make_unique<base::Value>("hello there"), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ("42", persistent_settings->GetPersistentSettingAsString(
+                            "key", "hello"));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>("\n"));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ("\n",
-            persistent_settings->GetPersistentSettingAsString("key", "hello"));
+      "key", std::make_unique<base::Value>("42"), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ("\n", persistent_settings->GetPersistentSettingAsString(
+                            "key", "hello"));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>("\\n"));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_EQ("\\n",
-            persistent_settings->GetPersistentSettingAsString("key", "hello"));
+      "key", std::make_unique<base::Value>("\n"), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
+
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_EQ("\\n", persistent_settings->GetPersistentSettingAsString(
+                             "key", "hello"));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->SetPersistentSetting(
+      "key", std::make_unique<base::Value>("\\n"), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 }
 
 TEST_F(PersistentSettingTest, RemoveSetting) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
   ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
 
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>(true));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", false));
+      "key", std::make_unique<base::Value>(true), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
-  persistent_settings->RemovePersistentSetting("key");
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_FALSE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->RemovePersistentSetting("key", std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
+
+  delete persistent_settings;
 }
 
 TEST_F(PersistentSettingTest, DeleteSettings) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
   ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
 
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>(true));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", false));
+      "key", std::make_unique<base::Value>(true), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
-  persistent_settings->DeletePersistentSettings();
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_FALSE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
+  persistent_settings->DeletePersistentSettings(std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
+
+  delete persistent_settings;
 }
 
 TEST_F(PersistentSettingTest, InvalidSettings) {
-  auto persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  auto persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
   ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
 
+  base::OnceClosure closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>(true));
-  scoped_task_environment_.RunUntilIdle();
+      "key", std::make_unique<base::Value>(true), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
+
+  delete persistent_settings;
+  // Sleep for one second to allow for the previous persistent_setting's
+  // JsonPrefStore instance time to write to disk before creating a new
+  // persistent_settings and JsonPrefStore instance.
+  base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
+  persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
   ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", false));
 
-  persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
-
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", false));
-
+  closure = base::BindOnce(
+      [](PersistentSettings* persistent_settings,
+         base::WaitableEvent* test_done) {
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", true));
+        ASSERT_TRUE(
+            persistent_settings->GetPersistentSettingAsBool("key", false));
+        test_done->Signal();
+      },
+      persistent_settings, &test_done_);
   persistent_settings->SetPersistentSetting(
-      "key", std::make_unique<base::Value>(false));
-  scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
-  ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", false));
+      "key", std::make_unique<base::Value>(false), std::move(closure));
+  test_done_.Wait();
+  test_done_.Reset();
 
-  persistent_settings = std::make_unique<PersistentSettings>(
-      kPersistentSettingsJson,
-      scoped_task_environment_.GetMainThreadTaskRunner());
+  delete persistent_settings;
+  persistent_settings = new PersistentSettings(kPersistentSettingsJson);
   persistent_settings->ValidatePersistentSettings();
 
   ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
   ASSERT_FALSE(persistent_settings->GetPersistentSettingAsBool("key", false));
+
+  delete persistent_settings;
 }
 
 }  // namespace persistent_storage
diff --git a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index 0393771..40bfa2f 100644
--- a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -164,6 +164,13 @@
                               GL_TEXTURE_WRAP_S, GL_REPEAT));
     }
 
+    if (image.type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+      GL_CALL(
+          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+      GL_CALL(
+          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+    }
+
     GL_CALL(glUniform1i(blit_program.texture_uniforms[i], i));
 
     if (blit_program.texture_size_uniforms[i] != -1) {
@@ -190,16 +197,26 @@
 
     SB_DCHECK(image.num_textures() >= 1);
     math::Size texture_size = image.textures[0].texture->GetSize();
-    GL_CALL(glUniform2f(
-        blit_program.viewport_to_texture_size_ratio_uniform,
-        viewport_size.width() / static_cast<float>(texture_size.width()),
-        viewport_size.height() / static_cast<float>(texture_size.height())));
-
     // The pixel shader requires the actual frame size of the first plane
     // for calculations.
     size_t subtexture_width = (texture_size.width() + 2) / 3;
     GL_CALL(glUniform2f(blit_program.subtexture_size_uniform, subtexture_width,
                         texture_size.height()));
+
+    const int kCompactTextureFilteringThreshold = 1920;
+    int enable_filtering = (abs(image.textures[0].content_region.width()) <
+                            kCompactTextureFilteringThreshold)
+                               ? 1
+                               : 0;
+    GL_CALL(
+        glUniform1i(blit_program.enable_filtering_uniform, enable_filtering));
+
+    GL_CALL(glUniform2f(blit_program.content_region_size_uniform,
+                        abs(image.textures[0].content_region.width()),
+                        abs(image.textures[0].content_region.height())));
+
+    GL_CALL(glUniform2f(blit_program.texture_size_as_unpacked_uniform,
+                        texture_size.width(), texture_size.height()));
   }
 
   GL_CALL(glDrawArrays(mode, 0, num_vertices));
@@ -514,7 +531,9 @@
       "uniform sampler2D texture_u;"
       "uniform sampler2D texture_v;"
       "uniform vec2 viewport_size;"
-      "uniform vec2 viewport_to_texture_ratio;"
+      "uniform vec2 texture_size_as_unpacked;"
+      "uniform vec2 content_region_size;"
+      "uniform int enable_filtering;"
       "uniform vec2 texture_size;"
       "uniform mat4 to_rgb_color_matrix;"
       // In a compacted YUV image each channel, Y, U and V, is stored as a
@@ -526,37 +545,95 @@
       // decompacted_position = compacted_position / 3;
       // pixel = CompactedTexture.Sample(Sampler, decompacted_position)[index];
       "void main() {"
-      // Take into account display size to texture size ratio for Y component.
-      "vec2 DTid = vec2(floor(v_tex_coord_y * viewport_size));"
-      "vec2 compact_pos_Y = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
-      // Calculate the position of 10-bit pixel for Y.
-      "vec2 decompact_pos_Y;"
-      "decompact_pos_Y.x = (floor(compact_pos_Y.x / 3.0) + 0.5) / "
-      "texture_size.x;"
-      "decompact_pos_Y.y = compact_pos_Y.y / texture_size.y;"
-      // Calculate the index of 10-bit pixel for Y.
-      "int index_Y = int(mod(floor(compact_pos_Y.x), 3.0));"
-      // Extract Y component.
-      "float Y_component = texture2D(texture_y, decompact_pos_Y)[index_Y];"
-      // For yuv420 U and V textures have dimensions twice less than Y.
-      "DTid = vec2(DTid / 2.0);"
-      "vec2 texture_size_UV = vec2(texture_size / 2.0);"
-      "vec2 compact_pos_UV = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
-      // Calculate the position of 10-bit pixels for U and V.
-      "vec2 decompact_pos_UV;"
-      "decompact_pos_UV.x = (floor(compact_pos_UV.x / 3.0) + 0.5) / "
-      "texture_size_UV.x;"
-      "decompact_pos_UV.y = (floor(compact_pos_UV.y) + 0.5) / "
-      "texture_size_UV.y;"
-      // Calculate the index of 10-bit pixels for U and V.
-      "int index_UV = int(mod(floor(compact_pos_UV), 3.0));"
-      // Extract U and V components.
-      "float U_component = texture2D(texture_u, decompact_pos_UV)[index_UV];"
-      "float V_component =  texture2D(texture_v, decompact_pos_UV)[index_UV];"
-      // Perform the YUV->RGB transform and output the color value.
-      "vec4 untransformed_color = vec4(Y_component, U_component, V_component, "
-      "1.0);"
-      "gl_FragColor = untransformed_color * to_rgb_color_matrix;"
+      // texture size as unpacked, f.i. 2562x1440
+      "     float frame_w = texture_size_as_unpacked.x;   \n"
+      "     float frame_h = texture_size_as_unpacked.y;   \n"
+      // texture size in textels, f.i. 854x1440
+      "    float ptex_w = texture_size.x;\n"
+      "     float ptex_h = texture_size.y;\n"
+      "    float y_step = 1.0 / ptex_h;\n"
+      "     float w_mult = 1.0 / ptex_w;\n"
+      "     float y = (enable_filtering != 0) ?\n"
+      "          floor(v_tex_coord_y.y * frame_h) * y_step :\n"
+      "          v_tex_coord_y.y;\n"
+      "     float x = (frame_w - 1.0) * v_tex_coord_y.x;\n"
+      "     float xi = floor(x);\n"
+      "     float xcoord = floor(xi * (1.0 / 3.0));\n"
+      "     int xcoord_fr = int(floor(xi - xcoord * 3.0));\n"
+      "     xcoord *= w_mult;\n"
+      "    float Y_component = texture2D(texture_y, vec2(xcoord, "
+      "y))[xcoord_fr];\n"
+      "     if (enable_filtering != 0)\n"
+      "     {\n"
+      "          float cx1 = x - xi;\n"
+      "          float cy1 = (v_tex_coord_y.y - y) * frame_h;\n"
+      "          float cx0 = 1.0 - cx1;\n"
+      "          float cy0 = 1.0 - cy1;\n"
+      "          Y_component *= cx0 * cy0;\n"
+      "          float y2 = min(y + y_step, y_step * (content_region_size.y - "
+      "1.0));\n"
+      "       Y_component += cx0 * cy1 * texture2D(texture_y, "
+      "vec2(xcoord, y2))[xcoord_fr];\n"
+      "          xi = min(xi + 1.0, float(content_region_size.x - 1.0));\n"
+      "          xcoord = floor(xi  * (1.0 / 3.0));\n"
+      "          xcoord_fr = int(floor(xi - xcoord * 3.0));\n"
+      "          xcoord *= w_mult;\n"
+      "          Y_component += cx1 * cy0 * texture2D(texture_y, vec2(xcoord, "
+      "y))[xcoord_fr];\n"
+      "          Y_component += cx1 * cy1 * texture2D(texture_y, vec2(xcoord, "
+      "y2))[xcoord_fr];\n"
+      "     }\n"
+      "      //chroma:\n"
+      "      float f_w = frame_w / 2.0;\n"
+      "      float f_h = frame_h / 2.0;\n"
+      "      y_step = 1.0 / (ptex_h / 2.0);\n"
+      "      w_mult = 1.0 / (ptex_w / 2.0);\n"
+      "      y = (enable_filtering != 0) ?\n"
+      "          floor(v_tex_coord_y.y * f_h) * y_step :\n"
+      "          v_tex_coord_y.y;\n"
+      "      x = (f_w - 1.0) * v_tex_coord_y.x;\n"
+      "      xi = floor(x);\n"
+      "      xcoord = floor(xi * (1.0 / 3.0));\n"
+      "      xcoord_fr = int(floor(xi - xcoord * 3.0));\n"
+      "      xcoord *= w_mult;\n"
+      "      float U_component = texture2D(texture_u, vec2(xcoord, "
+      "y))[xcoord_fr];\n"
+      "      float V_component = texture2D(texture_v, vec2(xcoord, "
+      "y))[xcoord_fr];\n"
+      "      if (enable_filtering != 0)\n"
+      "      {\n"
+      "          float cx1 = x - xi;\n"
+      "          float cy1 = (v_tex_coord_y.y - y) * f_h;\n"
+      "          float coef0 = (1.0 - cx1) * (1.0 - cy1);\n"
+      "          float coef2 = cx1 * (1.0 - cy1);\n"
+      "          float coef1 = (1.0 - cx1) * cy1;\n"
+      "          float coef3 = cx1 * cy1;\n"
+      "          U_component *= coef0;\n"
+      "          V_component *= coef0;\n"
+      "          float y2 = min(y + y_step, y_step * (content_region_size.y / "
+      "2.0 - "
+      "1.0));\n"
+      "          U_component += coef1 * texture2D(texture_u, vec2(xcoord, "
+      "y2))[xcoord_fr];\n"
+      "          V_component += coef1 * texture2D(texture_v, vec2(xcoord, "
+      "y2))[xcoord_fr];\n"
+      "          xi = min(xi + 1.0, float(content_region_size.x / 2.0 - "
+      "1.0));\n"
+      "          xcoord = floor(xi  * (1.0 / 3.0));\n"
+      "          xcoord_fr = int(floor(xi - xcoord * 3.0));\n"
+      "          xcoord *= w_mult;\n"
+      "          U_component += coef2 * texture2D(texture_u, vec2(xcoord, "
+      "y))[xcoord_fr];\n"
+      "          V_component += coef2 * texture2D(texture_v, vec2(xcoord, "
+      "y))[xcoord_fr];\n"
+      "          U_component += coef3 * texture2D(texture_u, vec2(xcoord, "
+      "y2))[xcoord_fr];\n"
+      "          V_component += coef3 * texture2D(texture_v, vec2(xcoord, "
+      "y2))[xcoord_fr];\n"
+      "      }\n"
+      "    vec4 untransformed_color = vec4(Y_component, U_component, "
+      "V_component, 1.0);\n"
+      "     gl_FragColor = untransformed_color * to_rgb_color_matrix;\n"
       "}";
 
   return CompileShader(blit_fragment_shader_source);
@@ -624,13 +701,19 @@
       result.viewport_size_uniform = GL_CALL_SIMPLE(
           glGetUniformLocation(result.gl_program_id, "viewport_size"));
       DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
-      result.viewport_to_texture_size_ratio_uniform =
-          GL_CALL_SIMPLE(glGetUniformLocation(result.gl_program_id,
-                                              "viewport_to_texture_ratio"));
-      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
       result.subtexture_size_uniform = GL_CALL_SIMPLE(
           glGetUniformLocation(result.gl_program_id, "texture_size"));
       DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+      result.texture_size_as_unpacked_uniform =
+          GL_CALL_SIMPLE(glGetUniformLocation(result.gl_program_id,
+                                              "texture_size_as_unpacked"));
+      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+      result.enable_filtering_uniform = GL_CALL_SIMPLE(
+          glGetUniformLocation(result.gl_program_id, "enable_filtering"));
+      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+      result.content_region_size_uniform = GL_CALL_SIMPLE(
+          glGetUniformLocation(result.gl_program_id, "content_region_size"));
+      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
     }
   }
 
diff --git a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
index 2faac78..8d70738 100644
--- a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
+++ b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
@@ -127,7 +127,9 @@
     int32 texture_size_uniforms[3];
     uint32 gl_program_id;
     int32 viewport_size_uniform;
-    int32 viewport_to_texture_size_ratio_uniform;
+    int32 enable_filtering_uniform;
+    int32 content_region_size_uniform;
+    int32 texture_size_as_unpacked_uniform;
     int32 subtexture_size_uniform;
   };
   // We key each program off of their GL texture type and image type.
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
index 67fe50a..e37e258 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.h"
 
+#include <ft2build.h>
+#include FT_TYPES_H
 #include <libxml/parser.h>
 #include <limits>
 #include <memory>
@@ -339,8 +341,12 @@
   DCHECK(file != NULL);
 
   // A <font> may have following attributes:
-  // weight (non-negative integer), style (normal, italic), font_name (string),
-  // postscript_name (string), and index (non-negative integer)
+  //   - weight (non-negative integer)
+  //   - style (normal, italic)
+  //   - font_name (string),
+  //   - postscript_name (string)
+  //   - index (non-negative integer)
+  //   - tags (non-negative integer) [if using font variations]
   // The element should contain a filename.
 
   for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL;
@@ -378,6 +384,23 @@
           continue;
         }
         break;
+      case 8:
+        if (strncmp("tag_", name, 4) == 0) {
+          // The remaining 4 characters define the tag.
+          FontFileInfo::VariationCoordinate coord;
+          coord.tag = FT_MAKE_TAG(name[4], name[5], name[6], name[7]);
+          int axis_value;
+          if (ParseNonNegativeInteger(value, &axis_value)) {
+            // Convert axis_value to Fixed16.
+            coord.value = axis_value << 16;
+            file->variation_position.push_back(coord);
+          } else {
+            LOG(ERROR) << "---- Invalid tag (" << name << ") value [" << value
+                       << "]";
+            NOTREACHED();
+          }
+        }
+        break;
       case 9:
         if (strncmp("font_name", name, 9) == 0) {
           SkAutoAsciiToLC to_lowercase(value);
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
index 7d694af..ee32ab1 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
@@ -282,7 +282,7 @@
 
   // To pre-fetch glyphs for remote fonts, we could pass character_map here.
   if (!sk_freetype_cobalt::ScanFont(stream.get(), face_index, &name, &style,
-                                    &is_fixed_pitch, nullptr)) {
+                                    &is_fixed_pitch, nullptr, nullptr)) {
     return NULL;
   }
   scoped_refptr<font_character_map::CharacterMap> character_map =
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc
index 00ffc9c..136b2e3 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc
@@ -70,6 +70,49 @@
   return score;
 }
 
+
+// Calculate the variation design coordinates for use with
+// FT_Set_Var_Design_Coordinates or passed to SkFontData.
+bool ComputeVariationPosition(
+    const sk_freetype_cobalt::AxisDefinitions& axis_definitions,
+    const FontFileInfo::VariationPosition& variation_position,
+    SkFontStyleSet_Cobalt::ComputedVariationPosition* out_position) {
+  if (variation_position.count() > axis_definitions.count()) {
+    // More variation axes were specified than actually supported.
+    return false;
+  }
+
+  int positions_used = 0;
+  out_position->resize(axis_definitions.count());
+  for (int axis = 0; axis < axis_definitions.count(); ++axis) {
+    // See if this axis has a specified position. If not, then use the default
+    // value from the axis definitions.
+    Fixed16 value = axis_definitions[axis].def;
+    for (int pos = 0; pos < variation_position.count(); ++pos) {
+      if (variation_position[pos].tag == axis_definitions[axis].tag) {
+        value = variation_position[pos].value;
+        DCHECK(value >= axis_definitions[axis].minimum);
+        DCHECK(value <= axis_definitions[axis].maximum);
+        if (value < axis_definitions[axis].minimum ||
+            value > axis_definitions[axis].maximum) {
+          return false;
+        }
+        ++positions_used;
+        break;
+      }
+    }
+    (*out_position)[axis] = value;
+  }
+
+  if (positions_used != variation_position.count()) {
+    // Some axes were specified which aren't supported.
+    LOG(ERROR) << "Mismatched font variation tags specified";
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace
 
 SkFontStyleSet_Cobalt::SkFontStyleSet_Cobalt(
@@ -94,16 +137,91 @@
   family_name_ = family_info.names[0];
   SkTHashMap<SkString, int> styles_index_map;
 
+  // These structures support validation of entries for font variations.
+  SkTHashMap<SkString, sk_freetype_cobalt::AxisDefinitions>
+      variation_definitions;
+  ComputedVariationPosition computed_variation_position;
+  sk_freetype_cobalt::AxisDefinitions axis_definitions;
+
+  // Avoid expensive alloc / dealloc calls with SkTArray by reserving size and
+  // using resize(0) instead of reset(). Adobe multiple master fonts are limited
+  // to 4 axes, and although OpenType font variations may have more, they tend
+  // not to -- it's usually just weight, width, and slant. But just in case,
+  // use a relatively high reservation.
+  computed_variation_position.reserve(16);
+  axis_definitions.reserve(16);
+
   for (int i = 0; i < family_info.fonts.count(); ++i) {
     const FontFileInfo& font_file = family_info.fonts[i];
 
     SkString file_path(SkOSPath::Join(base_path, font_file.file_name.c_str()));
 
-    // Validate that the file exists at this location. If it does not, then skip
-    // over it; it isn't being added to the set.
-    if (!sk_exists(file_path.c_str(), kRead_SkFILE_Flag)) {
-      DLOG(INFO) << "Failed to find font file: " << file_path.c_str();
-      continue;
+    // Validate the font file entry.
+    if (font_file.variation_position.empty()) {
+      // Static font files only need to check for file existence.
+      if (!sk_exists(file_path.c_str(), kRead_SkFILE_Flag)) {
+        DLOG(INFO) << "Failed to find static font file: " << file_path.c_str();
+        continue;
+      }
+      computed_variation_position.resize(0);
+    } else {
+      // Need to scan the font file to verify variation parameters. To improve
+      // performance, cache the scan results.
+
+      // In case axis definition changes based on font index, include font
+      // index in the key.
+      SkString cache_key(font_file.file_name);
+      char cache_key_suffix[3] = {':', static_cast<char>(font_file.index + 'A'),
+                                  0};
+      cache_key.append(cache_key_suffix);
+
+      sk_freetype_cobalt::AxisDefinitions* cached_axis_definitions =
+          variation_definitions.find(cache_key);
+
+      if (cached_axis_definitions == nullptr) {
+        // Scan the font file to get the variation information (if any).
+        if (!sk_exists(file_path.c_str(), kRead_SkFILE_Flag)) {
+          // Add an empty axis definition list as placeholder. This will
+          // be detected as an incompatible font file.
+          axis_definitions.resize(0);
+        } else {
+          DLOG(INFO) << "Scanning variable font file: " << file_path.c_str();
+
+          // Create a stream to scan the font. Since these fonts may not ever
+          // be used at runtime, purge stream's chunks after scanning to avoid
+          // memory bloat.
+          SkFileMemoryChunkStreamProvider* stream_provider =
+              local_typeface_stream_manager_->GetStreamProvider(
+                  file_path.c_str());
+          std::unique_ptr<const SkFileMemoryChunks> memory_chunks_snapshot(
+              stream_provider->CreateMemoryChunksSnapshot());
+          std::unique_ptr<SkFileMemoryChunkStream> stream(
+              stream_provider->OpenStream());
+
+          SkString unused_face_name;
+          SkFontStyle unused_font_style;
+          bool unused_is_fixed_pitch;
+          if (!sk_freetype_cobalt::ScanFont(
+                  stream.get(), font_file.index, &unused_face_name,
+                  &unused_font_style, &unused_is_fixed_pitch, &axis_definitions,
+                  nullptr)) {
+            axis_definitions.resize(0);
+          }
+
+          stream.reset(nullptr);
+          stream_provider->PurgeUnusedMemoryChunks();
+        }
+
+        cached_axis_definitions =
+            variation_definitions.set(cache_key, axis_definitions);
+      }
+
+      if (!ComputeVariationPosition(*cached_axis_definitions,
+                                    font_file.variation_position,
+                                    &computed_variation_position)) {
+        DLOG(INFO) << "Incompatible variable font file: " << file_path.c_str();
+        continue;
+      }
     }
 
     auto file_name = font_file.file_name.c_str();
@@ -149,10 +267,10 @@
       font_name = SkString(full_font_name.c_str());
     }
     auto font = new SkFontStyleSetEntry_Cobalt(
-        file_path, font_file.index, style, full_font_name, postscript_name,
-        font_file.disable_synthetic_bolding);
+        file_path, font_file.index, computed_variation_position, style,
+        full_font_name, postscript_name, font_file.disable_synthetic_bolding);
     int* index = styles_index_map.find(font_name);
-    if (index != NULL) {
+    if (index != nullptr) {
       // If style with name already exists in family, replace it.
       if (font_format_setting == kTtfPreferred &&
           SbStringCompareNoCase("ttf", extension) == 0) {
@@ -357,12 +475,19 @@
     character_map = character_maps_[style_index].get();
   }
 
+  SkFontStyle old_style = style->font_style;
   if (!sk_freetype_cobalt::ScanFont(
           stream, style->face_index, &style->face_name, &style->font_style,
-          &style->face_is_fixed_pitch, character_map)) {
+          &style->face_is_fixed_pitch, nullptr, character_map)) {
     return false;
   }
 
+  if (style->computed_variation_position.count() > 0) {
+    // For font variations, use the font style parsed from fonts.xml rather than
+    // the style returned by ScanFont since that's just the default.
+    style->font_style = old_style;
+  }
+
   style->is_face_info_generated = true;
   return true;
 }
@@ -372,6 +497,10 @@
   int max_score = std::numeric_limits<int>::min();
   for (int i = 0; i < styles_.count(); ++i) {
     int score = MatchScore(pattern, styles_[i]->font_style);
+    if (styles_[i]->computed_variation_position.count() > 0) {
+      // Slightly prefer static fonts over font variations to maintain old look.
+      score -= 1;
+    }
     if (score > max_score) {
       closest_index = i;
       max_score = score;
@@ -403,7 +532,8 @@
     style_entry->typeface.reset(new SkTypeface_CobaltStreamProvider(
         stream_provider, style_entry->face_index, style_entry->font_style,
         style_entry->face_is_fixed_pitch, family_name_,
-        style_entry->disable_synthetic_bolding, map));
+        style_entry->disable_synthetic_bolding,
+        style_entry->computed_variation_position, map));
   } else {
     LOG(ERROR) << "Failed to scan font: "
                << style_entry->font_file_path.c_str();
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.h b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.h
index fb5d6d0..11b0241 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.h
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.h
@@ -40,19 +40,23 @@
 // entry are lazily loaded the first time that they are needed.
 class SkFontStyleSet_Cobalt : public SkFontStyleSet {
  public:
+  typedef SkTArray<Fixed16, true> ComputedVariationPosition;
+
   struct SkFontStyleSetEntry_Cobalt : public SkRefCnt {
     // NOTE: SkFontStyleSetEntry_Cobalt objects are not guaranteed to last for
     // the lifetime of SkFontMgr_Cobalt and can be removed by their owning
     // SkFontStyleSet_Cobalt if their typeface fails to load properly. As a
     // result, it is not safe to store their pointers outside of
     // SkFontStyleSet_Cobalt.
-    SkFontStyleSetEntry_Cobalt(const SkString& file_path, const int face_index,
-                               const SkFontStyle& style,
-                               const std::string& full_name,
-                               const std::string& postscript_name,
-                               const bool disable_synthetic_bolding)
+    SkFontStyleSetEntry_Cobalt(
+        const SkString& file_path, const int face_index,
+        const ComputedVariationPosition& computed_variation_position,
+        const SkFontStyle& style, const std::string& full_name,
+        const std::string& postscript_name,
+        const bool disable_synthetic_bolding)
         : font_file_path(file_path),
           face_index(face_index),
+          computed_variation_position(computed_variation_position),
           font_style(style),
           full_font_name(full_name),
           font_postscript_name(postscript_name),
@@ -62,6 +66,7 @@
 
     const SkString font_file_path;
     const int face_index;
+    const ComputedVariationPosition computed_variation_position;
     SkFontStyle font_style;
     const std::string full_font_name;
     const std::string font_postscript_name;
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
index 6314e5a..95b7d56 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
@@ -25,6 +25,9 @@
 #include "base/basictypes.h"
 #include "base/memory/ref_counted.h"
 
+// This mimics FT_Fixed which is a 16.16 fixed point format.
+typedef int32_t Fixed16;
+
 // The font_character_map namespace contains all of the constants, typedefs, and
 // utility functions used with determining whether or not a font supports a
 // character.
@@ -133,6 +136,12 @@
   };
   typedef uint32_t FontStyle;
 
+  struct VariationCoordinate {
+    uint32_t tag;
+    Fixed16 value;
+  };
+  typedef SkTArray<VariationCoordinate, true> VariationPosition;
+
   FontFileInfo()
       : index(0),
         weight(400),
@@ -144,6 +153,7 @@
   int weight;
   FontStyle style;
   bool disable_synthetic_bolding;
+  VariationPosition variation_position;
 
   SkString full_font_name;
   SkString postscript_name;
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.cc b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.cc
index cbcc362..75cd24c 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.cc
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.cc
@@ -16,6 +16,7 @@
 
 #include <ft2build.h>
 #include FT_FREETYPE_H
+#include FT_MULTIPLE_MASTERS_H
 #include FT_TRUETYPE_TABLES_H
 #include FT_TYPE1_TABLES_H
 
@@ -118,10 +119,11 @@
 // These functions are used by FreeType during FT_Open_Face.
 extern "C" {
 
-static unsigned long sk_freetype_cobalt_stream_io(FT_Stream ftStream,
-                                                  unsigned long offset,
-                                                  unsigned char* buffer,
-                                                  unsigned long count) {
+static unsigned long sk_freetype_cobalt_stream_io(  // NOLINT(runtime/int)
+    FT_Stream ftStream,
+    unsigned long offset,   // NOLINT(runtime/int)
+    unsigned char* buffer,  // NOLINT(runtime/int)
+    unsigned long count) {  // NOLINT(runtime/int)
   SkStreamAsset* stream =
       static_cast<SkStreamAsset*>(ftStream->descriptor.pointer);
   stream->seek(offset);
@@ -134,7 +136,7 @@
 namespace sk_freetype_cobalt {
 
 bool ScanFont(SkStreamAsset* stream, int face_index, SkString* name,
-              SkFontStyle* style, bool* is_fixed_pitch,
+              SkFontStyle* style, bool* is_fixed_pitch, AxisDefinitions* axes,
               font_character_map::CharacterMap* maybe_character_map /*=NULL*/) {
   TRACE_EVENT0("cobalt::renderer", "SkFreeTypeUtil::ScanFont()");
 
@@ -166,6 +168,27 @@
   *style = GenerateSkFontStyleFromFace(face);
   *is_fixed_pitch = FT_IS_FIXED_WIDTH(face);
 
+  if (axes && FT_HAS_MULTIPLE_MASTERS(face)) {
+    FT_MM_Var* variations = nullptr;
+    err = FT_Get_MM_Var(face, &variations);
+    if (err) {
+      LOG(INFO) << "Unable to get variations for " << name;
+    } else {
+      axes->resize(variations->num_axis);
+      for (int i = 0; i < variations->num_axis; ++i) {
+        const FT_Var_Axis& ft_axis = variations->axis[i];
+        AxisDefinition& cobalt_axis = (*axes)[i];
+        cobalt_axis.tag = ft_axis.tag;
+        // The following values are already in 16.16 format, so no need to
+        // convert to Fixed16.
+        cobalt_axis.minimum = ft_axis.minimum;
+        cobalt_axis.def = ft_axis.def;
+        cobalt_axis.maximum = ft_axis.maximum;
+      }
+      FT_Done_MM_Var(freetype_lib, variations);
+    }
+  }
+
   if (maybe_character_map) {
     GenerateCharacterMapFromFace(face, maybe_character_map);
   }
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.h b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.h
index 0f4c3a5..8eb43c7 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.h
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFreeType_cobalt.h
@@ -21,11 +21,20 @@
 
 namespace sk_freetype_cobalt {
 
+// This contains information from FT_Var_Axis.
+struct AxisDefinition {
+  uint32_t tag;
+  Fixed16 minimum;
+  Fixed16 def;
+  Fixed16 maximum;
+};
+typedef SkTArray<AxisDefinition, true> AxisDefinitions;
+
 // Scans the font stream using FreeType, setting its name, style and whether or
 // not it has a fixed pitch. It also generates the font's character map if that
 // optional parameter is provided.
 bool ScanFont(SkStreamAsset* stream, int face_index, SkString* name,
-              SkFontStyle* style, bool* is_fixed_pitch,
+              SkFontStyle* style, bool* is_fixed_pitch, AxisDefinitions* axes,
               font_character_map::CharacterMap* maybe_character_map = NULL);
 
 }  // namespace sk_freetype_cobalt
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
index 837ce75..eb6f55e 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
@@ -64,6 +64,17 @@
   *family_name = family_name_;
 }
 
+std::unique_ptr<SkFontData> SkTypeface_Cobalt::onMakeFontData() const {
+  int index;
+  std::unique_ptr<SkStreamAsset> stream(this->onOpenStream(&index));
+  if (!stream) {
+    return nullptr;
+  }
+  return std::make_unique<SkFontData>(std::move(stream), index,
+                                      computed_variation_position_.data(),
+                                      computed_variation_position_.count());
+}
+
 SkTypeface_CobaltStream::SkTypeface_CobaltStream(
     std::unique_ptr<SkStreamAsset> stream, int face_index, SkFontStyle style,
     bool is_fixed_pitch, const SkString& family_name,
@@ -93,31 +104,23 @@
   return stream_->getLength();
 }
 
-#ifdef USE_SKIA_NEXT
-std::unique_ptr<SkFontData> SkTypeface_CobaltStream::onMakeFontData() const {
-  int index;
-  std::unique_ptr<SkStreamAsset> stream(this->onOpenStream(&index));
-  if (!stream) {
-    return nullptr;
-  }
-  return std::make_unique<SkFontData>(std::move(stream), index, nullptr, 0);
-}
-#endif
-
 SkTypeface_CobaltStreamProvider::SkTypeface_CobaltStreamProvider(
     SkFileMemoryChunkStreamProvider* stream_provider, int face_index,
     SkFontStyle style, bool is_fixed_pitch, const SkString& family_name,
     bool disable_synthetic_bolding,
+    const ComputedVariationPosition& computed_variation_position,
     scoped_refptr<font_character_map::CharacterMap> character_map)
     : INHERITED(face_index, style, is_fixed_pitch, family_name, character_map),
       stream_provider_(stream_provider) {
   if (disable_synthetic_bolding) {
     synthesizes_bold_ = false;
   }
-  LOG(INFO) << "Created SkTypeface_CobaltStreamProvider: "
-            << family_name.c_str() << "(" << style.weight() << ", "
-            << style.width() << ", " << style.slant() << "); File: \""
-            << stream_provider->file_path() << "\"";
+  computed_variation_position_ = computed_variation_position;
+  LOG(INFO) << "Created " << font_type_string()
+            << " SkTypeface_CobaltStreamProvider: " << family_name.c_str()
+            << "(" << style.weight() << ", " << style.width() << ", "
+            << style.slant() << "); File: \"" << stream_provider->file_path()
+            << "\"";
 }
 
 void SkTypeface_CobaltStreamProvider::onGetFontDescriptor(
@@ -143,15 +146,3 @@
       stream_provider_->OpenStream());
   return stream->getLength();
 }
-
-#ifdef USE_SKIA_NEXT
-std::unique_ptr<SkFontData> SkTypeface_CobaltStreamProvider::onMakeFontData()
-    const {
-  int index;
-  std::unique_ptr<SkStreamAsset> stream(this->onOpenStream(&index));
-  if (!stream) {
-    return nullptr;
-  }
-  return std::make_unique<SkFontData>(std::move(stream), index, nullptr, 0);
-}
-#endif
diff --git a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
index f5ede9c..3b1cfcd 100644
--- a/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
+++ b/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
@@ -28,6 +28,9 @@
 
 class SkTypeface_Cobalt : public SkTypeface_FreeType {
  public:
+  typedef SkFontStyleSet_Cobalt::ComputedVariationPosition
+      ComputedVariationPosition;
+
   SkTypeface_Cobalt(
       int face_index, SkFontStyle style, bool is_fixed_pitch,
       const SkString& family_name,
@@ -45,10 +48,20 @@
 
   void onGetFamilyName(SkString* family_name) const override;
 
+  std::unique_ptr<SkFontData> onMakeFontData() const override;
+
+  const char* font_type_string() const {
+    return computed_variation_position_.empty() ? "static" : "variable";
+  }
+
   int face_index_;
   SkString family_name_;
   bool synthesizes_bold_;
 
+  // Support font variations. Axis data should already be sorted to match
+  // the order defined in the font file.
+  ComputedVariationPosition computed_variation_position_;
+
  private:
   typedef SkTypeface_FreeType INHERITED;
   SkGlyphID characterMapGetGlyphIdForCharacter(SkUnichar character) const;
@@ -66,9 +79,6 @@
                            bool* serialize) const override;
 
   std::unique_ptr<SkStreamAsset> onOpenStream(int* face_index) const override;
-#ifdef USE_SKIA_NEXT
-  std::unique_ptr<SkFontData> onMakeFontData() const override;
-#endif
 
   size_t GetStreamLength() const override;
 
@@ -84,15 +94,13 @@
       SkFileMemoryChunkStreamProvider* stream_provider, int face_index,
       SkFontStyle style, bool is_fixed_pitch, const SkString& family_name,
       bool disable_synthetic_bolding,
+      const ComputedVariationPosition& computed_variation_position,
       scoped_refptr<font_character_map::CharacterMap> character_map);
 
   void onGetFontDescriptor(SkFontDescriptor* descriptor,
                            bool* serialize) const override;
 
   std::unique_ptr<SkStreamAsset> onOpenStream(int* face_index) const override;
-#ifdef USE_SKIA_NEXT
-  std::unique_ptr<SkFontData> onMakeFontData() const override;
-#endif
 
   size_t GetStreamLength() const override;
 
diff --git a/cobalt/script/fake_global_environment.h b/cobalt/script/fake_global_environment.h
index 51760fa..c7376b8 100644
--- a/cobalt/script/fake_global_environment.h
+++ b/cobalt/script/fake_global_environment.h
@@ -28,6 +28,10 @@
   void CreateGlobalObject() override {}
   Wrappable* global_wrappable() const override { return nullptr; };
 
+  v8::MaybeLocal<v8::Script> Compile(
+      const scoped_refptr<SourceCode>& source_code) {
+    return {};
+  }
   bool EvaluateScript(const scoped_refptr<SourceCode>& script_utf8,
                       std::string* out_result) override {
     return false;
@@ -51,6 +55,7 @@
   void DisableEval(const std::string& message) override {}
   void EnableEval() override {}
   void DisableJit() override {}
+  bool IsEvalEnabled() override { return true; }
   void SetReportEvalCallback(const base::Closure& report_eval) override {}
   void SetReportErrorCallback(const ReportErrorCallback& report_eval) override {
   }
diff --git a/cobalt/script/global_environment.h b/cobalt/script/global_environment.h
index 4e569f3..3b6c506 100644
--- a/cobalt/script/global_environment.h
+++ b/cobalt/script/global_environment.h
@@ -60,6 +60,10 @@
   // Returns the global object if it is a wrappable.
   virtual Wrappable* global_wrappable() const = 0;
 
+  // Compiles the code but does not execute.
+  virtual v8::MaybeLocal<v8::Script> Compile(
+      const scoped_refptr<SourceCode>& source_code) = 0;
+
   // Evaluate the JavaScript source code. Returns true on success,
   // false if there is an exception.
   // If out_result is non-NULL, it will be set to hold the result of the script
@@ -100,6 +104,8 @@
   // Allow eval().
   virtual void EnableEval() = 0;
 
+  virtual bool IsEvalEnabled() = 0;
+
   // Disable just-in-time compilation of JavaScript source code to native
   // code.  Calling this is a no-op if JIT was not enabled in the first place,
   // or if the engine does not support disabling JIT.
diff --git a/cobalt/script/promise.h b/cobalt/script/promise.h
index d9e1c17..4500466 100644
--- a/cobalt/script/promise.h
+++ b/cobalt/script/promise.h
@@ -22,6 +22,7 @@
 #include "cobalt/script/exception_message.h"
 #include "cobalt/script/script_exception.h"
 #include "cobalt/script/script_value.h"
+#include "v8/include/v8.h"
 
 namespace cobalt {
 namespace script {
@@ -59,6 +60,9 @@
   virtual void AddStateChangeCallback(
       std::unique_ptr<base::OnceCallback<void()>> callback) = 0;
 
+  virtual v8::Local<v8::Promise> promise() const = 0;
+  virtual v8::Local<v8::Promise::Resolver> resolver() const = 0;
+
   virtual ~Promise() {}
 };
 
diff --git a/cobalt/script/script_value_factory.h b/cobalt/script/script_value_factory.h
index 393f60b..c3abbf5 100644
--- a/cobalt/script/script_value_factory.h
+++ b/cobalt/script/script_value_factory.h
@@ -21,10 +21,16 @@
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
+#include "cobalt/script/value_handle.h"
 
 namespace cobalt {
 namespace script {
 
+using Any = script::Handle<script::ValueHandle>;
+using PromiseAny = script::Promise<script::Handle<script::ValueHandle>>;
+using HandlePromiseAny = script::Handle<PromiseAny>;
+using ValuePromiseAny = ScriptValue<PromiseAny>;
+
 using SequenceWrappable = Sequence<scoped_refptr<Wrappable>>;
 using PromiseSequenceWrappable = Promise<SequenceWrappable>;
 using HandlePromiseSequenceWrappable = Handle<PromiseSequenceWrappable>;
diff --git a/cobalt/script/script_value_factory_instantiations.h b/cobalt/script/script_value_factory_instantiations.h
index ad37696..a7cc56d 100644
--- a/cobalt/script/script_value_factory_instantiations.h
+++ b/cobalt/script/script_value_factory_instantiations.h
@@ -49,6 +49,7 @@
 PROMISE_TEMPLATE_INSTANTIATION(scoped_refptr<Wrappable>);
 PROMISE_TEMPLATE_INSTANTIATION(Sequence<scoped_refptr<Wrappable>>);
 PROMISE_TEMPLATE_INSTANTIATION(BufferSource);
+PROMISE_TEMPLATE_INSTANTIATION(Handle<ValueHandle>);
 
 #undef PROMISE_TEMPLATE_INSTANTIATION
 
diff --git a/cobalt/script/source_code.h b/cobalt/script/source_code.h
index 7b51fe9..28773d7 100644
--- a/cobalt/script/source_code.h
+++ b/cobalt/script/source_code.h
@@ -33,6 +33,12 @@
       const std::string& script_utf8,
       const base::SourceLocation& script_location, bool is_muted = false);
 
+  // Same as |CreateSourceCode()| with a flag to indicate that the compiled
+  // JavaScript should not be cached.
+  static scoped_refptr<SourceCode> CreateSourceCodeWithoutCaching(
+      const std::string& script_utf8,
+      const base::SourceLocation& script_location, bool is_muted = false);
+
  protected:
   SourceCode() {}
   virtual ~SourceCode() {}
diff --git a/cobalt/script/v8c/script_promise.h b/cobalt/script/v8c/script_promise.h
index 93c4561..7ae2c44 100644
--- a/cobalt/script/v8c/script_promise.h
+++ b/cobalt/script/v8c/script_promise.h
@@ -111,17 +111,17 @@
     }
   }
 
-  v8::Local<v8::Promise> promise() const {
+  v8::Local<v8::Promise> promise() const override {
     DCHECK(!this->IsEmpty());
     return resolver()->GetPromise();
   }
 
- protected:
-  v8::Local<v8::Promise::Resolver> resolver() const {
+  v8::Local<v8::Promise::Resolver> resolver() const override {
     DCHECK(!this->IsEmpty());
     return this->Get().Get(isolate_).template As<v8::Promise::Resolver>();
   }
 
+ protected:
   v8::Isolate* isolate() const { return isolate_; }
 
  private:
diff --git a/cobalt/script/v8c/v8c_global_environment.cc b/cobalt/script/v8c/v8c_global_environment.cc
index 8c382db..0fd5937 100644
--- a/cobalt/script/v8c/v8c_global_environment.cc
+++ b/cobalt/script/v8c/v8c_global_environment.cc
@@ -279,6 +279,14 @@
   context->AllowCodeGenerationFromStrings(true);
 }
 
+bool V8cGlobalEnvironment::IsEvalEnabled() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  EntryScope entry_scope(isolate_);
+  v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+  return context->IsCodeGenerationFromStringsAllowed();
+}
+
 void V8cGlobalEnvironment::DisableJit() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   LOG(INFO) << "V8 version " << V8_MAJOR_VERSION << '.' << V8_MINOR_VERSION
@@ -424,7 +432,7 @@
   // up by our caller.
 
   v8::Local<v8::Script> script;
-  if (!CompileWithCaching(source_code).ToLocal(&script)) {
+  if (!Compile(source_code).ToLocal(&script)) {
     return {};
   }
 
@@ -432,9 +440,9 @@
   return script->Run(isolate_->GetCurrentContext());
 }
 
-v8::MaybeLocal<v8::Script> V8cGlobalEnvironment::CompileWithCaching(
+v8::MaybeLocal<v8::Script> V8cGlobalEnvironment::Compile(
     const scoped_refptr<SourceCode>& source_code) {
-  TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::CompileWithCaching()");
+  TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::Compile()");
   v8::Local<v8::Context> context = isolate_->GetCurrentContext();
   V8cSourceCode* v8c_source_code =
       base::polymorphic_downcast<V8cSourceCode*>(source_code.get());
@@ -471,6 +479,18 @@
       /*resource_is_shared_cross_origin=*/
       v8::Boolean::New(isolate_, v8c_source_code->is_muted()));
 
+  if (!v8c_source_code->should_cache_compiled_javascript()) {
+    v8::Local<v8::Script> script;
+    {
+      TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
+      if (!v8::Script::Compile(context, source, &script_origin)
+               .ToLocal(&script)) {
+        return {};
+      }
+    }
+    return script;
+  }
+
   std::string javascript_engine_version =
       script::GetJavaScriptEngineNameAndVersion();
   uint32_t javascript_cache_key = CreateJavaScriptCacheKey(
@@ -478,19 +498,22 @@
       v8c_source_code->source_utf8(), source_location.file_path);
   auto retrieved_cached_data = cobalt::cache::Cache::GetInstance()->Retrieve(
       disk_cache::ResourceType::kCompiledScript, javascript_cache_key,
-      [&]() -> std::unique_ptr<std::vector<uint8_t>> {
+      [&]() -> std::pair<std::unique_ptr<std::vector<uint8_t>>,
+                         base::Optional<base::Value>> {
         v8::Local<v8::Script> script;
         {
           TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
           if (!v8::Script::Compile(context, source, &script_origin)
                    .ToLocal(&script)) {
-            return nullptr;
+            return std::make_pair(/*data=*/nullptr, /*metadata=*/base::nullopt);
           }
         }
         std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data(
             v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
-        return std::make_unique<std::vector<uint8_t>>(
-            cached_data->data, cached_data->data + cached_data->length);
+        return std::make_pair(
+            std::make_unique<std::vector<uint8_t>>(
+                cached_data->data, cached_data->data + cached_data->length),
+            /*metadata=*/base::nullopt);
       });
   if (!retrieved_cached_data) {
     return {};
diff --git a/cobalt/script/v8c/v8c_global_environment.h b/cobalt/script/v8c/v8c_global_environment.h
index a422397..ba9e063 100644
--- a/cobalt/script/v8c/v8c_global_environment.h
+++ b/cobalt/script/v8c/v8c_global_environment.h
@@ -62,6 +62,9 @@
       const scoped_refptr<GlobalInterface>& global_interface,
       EnvironmentSettings* environment_settings);
 
+  v8::MaybeLocal<v8::Script> Compile(
+      const scoped_refptr<SourceCode>& source_code) override;
+
   bool EvaluateScript(const scoped_refptr<SourceCode>& script,
                       std::string* out_result_utf8) override;
 
@@ -86,6 +89,8 @@
 
   void EnableEval() override;
 
+  bool IsEvalEnabled() override;
+
   void DisableJit() override;
 
   void SetReportEvalCallback(const base::Closure& report_eval) override;
@@ -152,9 +157,6 @@
   v8::MaybeLocal<v8::Value> EvaluateScriptInternal(
       const scoped_refptr<SourceCode>& source_code);
 
-  v8::MaybeLocal<v8::Script> CompileWithCaching(
-      const scoped_refptr<SourceCode>& source_code);
-
   void EvaluateEmbeddedScript(const unsigned char* data, size_t size,
                               const char* filename);
 
diff --git a/cobalt/script/v8c/v8c_source_code.cc b/cobalt/script/v8c/v8c_source_code.cc
index 477d901..9109a43 100644
--- a/cobalt/script/v8c/v8c_source_code.cc
+++ b/cobalt/script/v8c/v8c_source_code.cc
@@ -21,7 +21,16 @@
 scoped_refptr<SourceCode> SourceCode::CreateSourceCode(
     const std::string& script_utf8, const base::SourceLocation& script_location,
     bool is_muted) {
-  return new v8c::V8cSourceCode(script_utf8, script_location, is_muted);
+  return new v8c::V8cSourceCode(script_utf8, script_location, is_muted,
+                                /*should_cache_compiled_javascript=*/true);
+}
+
+// static
+scoped_refptr<SourceCode> SourceCode::CreateSourceCodeWithoutCaching(
+    const std::string& script_utf8, const base::SourceLocation& script_location,
+    bool is_muted) {
+  return new v8c::V8cSourceCode(script_utf8, script_location, is_muted,
+                                /*should_cache_compiled_javascript=*/false);
 }
 
 }  // namespace script
diff --git a/cobalt/script/v8c/v8c_source_code.h b/cobalt/script/v8c/v8c_source_code.h
index bba30b4..b6fce5e 100644
--- a/cobalt/script/v8c/v8c_source_code.h
+++ b/cobalt/script/v8c/v8c_source_code.h
@@ -31,19 +31,25 @@
  public:
   V8cSourceCode(const std::string& source_utf8,
                 const base::SourceLocation& source_location,
-                bool is_muted = false)
+                bool is_muted = false,
+                bool should_cache_compiled_javascript = true)
       : source_utf8_(source_utf8),
         location_(source_location),
-        is_muted_(is_muted) {}
+        is_muted_(is_muted),
+        should_cache_compiled_javascript_(should_cache_compiled_javascript) {}
 
   const std::string& source_utf8() const { return source_utf8_; }
   const base::SourceLocation& location() const { return location_; }
   bool is_muted() const { return is_muted_; }
+  bool should_cache_compiled_javascript() const {
+    return should_cache_compiled_javascript_;
+  }
 
  private:
   std::string source_utf8_;
   base::SourceLocation location_;
   bool is_muted_;
+  bool should_cache_compiled_javascript_;
 };
 
 }  // namespace v8c
diff --git a/cobalt/tools/automated_testing/cobalt_runner.py b/cobalt/tools/automated_testing/cobalt_runner.py
index efdcc06..d35afbe 100644
--- a/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/cobalt/tools/automated_testing/cobalt_runner.py
@@ -132,14 +132,16 @@
       logging.basicConfig(stream=self.log_file, level=logging.INFO)
     else:
       self.log_file = sys.stdout
-    self.url = url
+    if url:
+      self.url = url
     self.target_params = target_params
     self.success_message = success_message
-    url_string = '--url=' + self.url
-    if not self.target_params:
-      self.target_params = [url_string]
-    else:
-      self.target_params.append(url_string)
+    if hasattr(self, 'url'):
+      url_string = '--url=' + self.url
+      if not self.target_params:
+        self.target_params = [url_string]
+      else:
+        self.target_params.append(url_string)
     if self.launcher_params.target_params:
       self.target_params.extend(self.launcher_params.target_params)
 
diff --git a/cobalt/ui_navigation/interface.cc b/cobalt/ui_navigation/interface.cc
index d833d7d..da1f51f 100644
--- a/cobalt/ui_navigation/interface.cc
+++ b/cobalt/ui_navigation/interface.cc
@@ -22,25 +22,68 @@
 namespace {
 
 struct ItemImpl {
-  explicit ItemImpl(NativeItemType type) : type(type) {}
+  explicit ItemImpl(NativeItemType type, const NativeCallbacks* callbacks,
+                    void* callback_context)
+      : type(type), callbacks(callbacks), callback_context(callback_context) {}
 
   starboard::SpinLock lock;
 
   const NativeItemType type;
+  const NativeCallbacks* callbacks;
+  void* callback_context;
+  bool is_enabled = true;
+
   float content_offset_x = 0.0f;
   float content_offset_y = 0.0f;
+
+  float scroll_top_lower_bound = 0.0f;
+  float scroll_left_lower_bound = 0.0f;
+  float scroll_top_upper_bound = 0.0f;
+  float scroll_left_upper_bound = 0.0f;
 };
 
 NativeItem CreateItem(NativeItemType type, const NativeCallbacks* callbacks,
                       void* callback_context) {
-  return reinterpret_cast<NativeItem>(new ItemImpl(type));
+  return reinterpret_cast<NativeItem>(
+      new ItemImpl(type, callbacks, callback_context));
 }
 
 void DestroyItem(NativeItem item) { delete reinterpret_cast<ItemImpl*>(item); }
 
+void SetItemBounds(NativeItem item, float scroll_top_lower_bound,
+                   float scroll_left_lower_bound, float scroll_top_upper_bound,
+                   float scroll_left_upper_bound) {
+  ItemImpl* stub_item = reinterpret_cast<ItemImpl*>(item);
+  if (stub_item->type == kNativeItemTypeContainer) {
+    starboard::ScopedSpinLock scoped_lock(stub_item->lock);
+    stub_item->scroll_top_lower_bound = scroll_top_lower_bound;
+    stub_item->scroll_left_lower_bound = scroll_left_lower_bound;
+    stub_item->scroll_top_upper_bound = scroll_top_upper_bound;
+    stub_item->scroll_left_upper_bound = scroll_left_upper_bound;
+  }
+}
+
+void GetItemBounds(NativeItem item, float* out_scroll_top_lower_bound,
+                   float* out_scroll_left_lower_bound,
+                   float* out_scroll_top_upper_bound,
+                   float* out_scroll_left_upper_bound) {
+  ItemImpl* stub_item = reinterpret_cast<ItemImpl*>(item);
+  if (stub_item->type == kNativeItemTypeContainer) {
+    starboard::ScopedSpinLock scoped_lock(stub_item->lock);
+    *out_scroll_top_lower_bound = stub_item->scroll_top_lower_bound;
+    *out_scroll_left_lower_bound = stub_item->scroll_left_lower_bound;
+    *out_scroll_top_upper_bound = stub_item->scroll_top_upper_bound;
+    *out_scroll_left_upper_bound = stub_item->scroll_left_upper_bound;
+  }
+}
+
 void SetFocus(NativeItem item) {}
 
-void SetItemEnabled(NativeItem item, bool enabled) {}
+void SetItemEnabled(NativeItem item, bool enabled) {
+  ItemImpl* stub_item = reinterpret_cast<ItemImpl*>(item);
+  starboard::ScopedSpinLock scoped_lock(stub_item->lock);
+  stub_item->is_enabled = enabled;
+}
 
 void SetItemDir(NativeItem item, NativeItemDir dir) {}
 
@@ -65,10 +108,19 @@
 void SetItemContentOffset(NativeItem item, float content_offset_x,
                           float content_offset_y) {
   ItemImpl* stub_item = reinterpret_cast<ItemImpl*>(item);
-  if (stub_item->type == kNativeItemTypeContainer) {
-    starboard::ScopedSpinLock scoped_lock(stub_item->lock);
-    stub_item->content_offset_x = content_offset_x;
-    stub_item->content_offset_y = content_offset_y;
+  if (stub_item->type != kNativeItemTypeContainer) {
+    return;
+  }
+  starboard::ScopedSpinLock scoped_lock(stub_item->lock);
+  const bool scroll_changed = stub_item->content_offset_x != content_offset_x ||
+                              stub_item->content_offset_y != content_offset_y;
+  if (!scroll_changed) {
+    return;
+  }
+  stub_item->content_offset_x = content_offset_x;
+  stub_item->content_offset_y = content_offset_y;
+  if (stub_item->is_enabled) {
+    stub_item->callbacks->onscroll(item, stub_item->callback_context);
   }
 }
 
@@ -90,6 +142,8 @@
   if (SbUiNavGetInterface(&sb_ui_interface)) {
     interface.create_item = sb_ui_interface.create_item;
     interface.destroy_item = sb_ui_interface.destroy_item;
+    interface.set_item_bounds = nullptr;
+    interface.get_item_bounds = nullptr;
     interface.set_focus = sb_ui_interface.set_focus;
     interface.set_item_enabled = sb_ui_interface.set_item_enabled;
     interface.set_item_dir = sb_ui_interface.set_item_dir;
@@ -115,6 +169,8 @@
 
   interface.create_item = &CreateItem;
   interface.destroy_item = &DestroyItem;
+  interface.set_item_bounds = &SetItemBounds;
+  interface.get_item_bounds = &GetItemBounds;
   interface.set_focus = &SetFocus;
   interface.set_item_enabled = &SetItemEnabled;
   interface.set_item_dir = &SetItemDir;
diff --git a/cobalt/ui_navigation/interface.h b/cobalt/ui_navigation/interface.h
index ec8b71b..2d42301 100644
--- a/cobalt/ui_navigation/interface.h
+++ b/cobalt/ui_navigation/interface.h
@@ -37,6 +37,14 @@
                             const NativeCallbacks* callbacks,
                             void* callback_context);
   void (*destroy_item)(NativeItem item);
+  void (*set_item_bounds)(NativeItem item, float scroll_top_lower_bound,
+                          float scroll_left_lower_bound,
+                          float scroll_top_upper_bound,
+                          float scroll_left_upper_bound);
+  void (*get_item_bounds)(NativeItem item, float* out_scroll_top_lower_bound,
+                          float* out_scroll_left_lower_bound,
+                          float* out_scroll_top_upper_bound,
+                          float* out_scroll_left_upper_bound);
   void (*set_focus)(NativeItem item);
   void (*set_item_enabled)(NativeItem item, bool enabled);
   void (*set_item_dir)(NativeItem item, NativeItemDir dir);
diff --git a/cobalt/ui_navigation/nav_item.cc b/cobalt/ui_navigation/nav_item.cc
index ff5eb6c..8795d59 100644
--- a/cobalt/ui_navigation/nav_item.cc
+++ b/cobalt/ui_navigation/nav_item.cc
@@ -146,6 +146,29 @@
   GetInterface().do_batch_update(&ProcessPendingChanges, &updates_snapshot);
 }
 
+void NavItem::SetBounds(float scroll_top_lower_bound,
+                        float scroll_left_lower_bound,
+                        float scroll_top_upper_bound,
+                        float scroll_left_upper_bound) {
+  starboard::ScopedSpinLock lock(&g_pending_updates_lock);
+  if (GetInterface().set_item_bounds) {
+    GetInterface().set_item_bounds(
+        nav_item_, scroll_top_lower_bound, scroll_left_lower_bound,
+        scroll_top_upper_bound, scroll_left_upper_bound);
+  }
+}
+
+void NavItem::GetBounds(float* out_scroll_top_lower_bound,
+                        float* out_scroll_left_lower_bound,
+                        float* out_scroll_top_upper_bound,
+                        float* out_scroll_left_upper_bound) {
+  if (GetInterface().get_item_bounds) {
+    GetInterface().get_item_bounds(
+        nav_item_, out_scroll_top_lower_bound, out_scroll_left_lower_bound,
+        out_scroll_top_upper_bound, out_scroll_left_upper_bound);
+  }
+}
+
 void NavItem::Focus() {
   starboard::ScopedSpinLock lock(&g_pending_updates_lock);
   if (state_ == kStateEnabled) {
diff --git a/cobalt/ui_navigation/nav_item.h b/cobalt/ui_navigation/nav_item.h
index fbba34f..eebbfa4 100644
--- a/cobalt/ui_navigation/nav_item.h
+++ b/cobalt/ui_navigation/nav_item.h
@@ -41,6 +41,13 @@
   // periodically to ensure all UI updates are executed.
   void PerformQueuedUpdates();
 
+  void SetBounds(float scroll_top_lower_bound, float scroll_left_lower_bound,
+                 float scroll_top_upper_bound, float scroll_left_upper_bound);
+  void GetBounds(float* out_scroll_top_lower_bound,
+                 float* out_scroll_left_lower_bound,
+                 float* out_scroll_top_upper_bound,
+                 float* out_scroll_left_upper_bound);
+
   void Focus();
   void UnfocusAll();
   void SetEnabled(bool enabled);
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index 8ad8474..5105785 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -79,7 +79,6 @@
 
   watchdog_file_name_ = watchdog_file_name;
   watchdog_monitor_frequency_ = watchdog_monitor_frequency;
-  SB_CHECK(SbMutexCreate(&mutex_));
   pending_write_ = false;
   write_wait_time_microseconds_ = kWatchdogWriteWaitTime;
 
@@ -109,7 +108,7 @@
 #endif  // defined(_DEBUG)
 
   // Starts monitor thread.
-  is_monitoring_ = true;
+  is_monitoring_.store(true);
   SB_DCHECK(!SbThreadIsValid(watchdog_thread_));
   watchdog_thread_ = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
                                     true, "Watchdog", &Watchdog::Monitor, this);
@@ -125,10 +124,11 @@
 void Watchdog::Uninitialize() {
   if (is_disabled_) return;
 
-  SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
+  mutex_.Acquire();
   if (pending_write_) WriteWatchdogViolations();
-  is_monitoring_ = false;
-  SB_CHECK(SbMutexRelease(&mutex_));
+  is_monitoring_.store(false);
+  monitor_wait_.Signal();
+  mutex_.Release();
   SbThreadJoin(watchdog_thread_, nullptr);
 }
 
@@ -162,22 +162,13 @@
 void Watchdog::UpdateState(base::ApplicationState state) {
   if (is_disabled_) return;
 
-  SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
+  starboard::ScopedLock scoped_lock(mutex_);
   state_ = state;
-  SB_CHECK(SbMutexRelease(&mutex_));
 }
 
 void* Watchdog::Monitor(void* context) {
+  starboard::ScopedLock scoped_lock(static_cast<Watchdog*>(context)->mutex_);
   while (1) {
-    SB_CHECK(SbMutexAcquire(&(static_cast<Watchdog*>(context))->mutex_) ==
-             kSbMutexAcquired);
-
-    // Shutdown
-    if (!((static_cast<Watchdog*>(context))->is_monitoring_)) {
-      SB_CHECK(SbMutexRelease(&(static_cast<Watchdog*>(context))->mutex_));
-      break;
-    }
-
     SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
 
     // Iterates through client map to monitor all registered clients.
@@ -216,8 +207,12 @@
       MaybeWriteWatchdogViolations(context);
     if (watchdog_violation) MaybeTriggerCrash(context);
 
-    SB_CHECK(SbMutexRelease(&(static_cast<Watchdog*>(context))->mutex_));
-    SbThreadSleep(static_cast<Watchdog*>(context)->watchdog_monitor_frequency_);
+    // Wait
+    static_cast<Watchdog*>(context)->monitor_wait_.WaitTimed(
+        static_cast<Watchdog*>(context)->watchdog_monitor_frequency_);
+
+    // Shutdown
+    if (!(static_cast<Watchdog*>(context)->is_monitoring_.load())) break;
   }
   return nullptr;
 }
@@ -416,7 +411,7 @@
     return false;
   }
 
-  SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
+  starboard::ScopedLock scoped_lock(mutex_);
 
   int64_t current_time = SbTimeToPosix(SbTimeGetNow());
   SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
@@ -431,7 +426,6 @@
         it->second->time_last_pinged_microseconds = current_time;
         it->second->time_last_updated_monotonic_microseconds =
             current_monotonic_time;
-        SB_CHECK(SbMutexRelease(&mutex_));
         return true;
       }
       if (replace == ALL) Unregister(name, false);
@@ -454,8 +448,6 @@
   // Registers.
   auto result = client_map_.emplace(name, std::move(client));
 
-  SB_CHECK(SbMutexRelease(&mutex_));
-
   if (result.second) {
     SB_DLOG(INFO) << "[Watchdog] Registered: " << name;
   } else {
@@ -468,9 +460,9 @@
   if (is_disabled_) return true;
 
   // Unregisters.
-  if (lock) SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
+  if (lock) mutex_.Acquire();
   auto result = client_map_.erase(name);
-  if (lock) SB_CHECK(SbMutexRelease(&mutex_));
+  if (lock) mutex_.Release();
 
   if (result) {
     SB_DLOG(INFO) << "[Watchdog] Unregistered: " << name;
@@ -493,7 +485,7 @@
     return false;
   }
 
-  SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
+  starboard::ScopedLock scoped_lock(mutex_);
 
   auto it = client_map_.find(name);
   bool client_exists = it != client_map_.end();
@@ -522,7 +514,6 @@
   } else {
     SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name;
   }
-  SB_CHECK(SbMutexRelease(&mutex_));
   return client_exists;
 }
 
@@ -534,7 +525,7 @@
 
   std::string watchdog_json = "";
 
-  SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
+  starboard::ScopedLock scoped_lock(mutex_);
 
   if (pending_write_) WriteWatchdogViolations();
 
@@ -558,7 +549,6 @@
   } else {
     SB_LOG(INFO) << "[Watchdog] No violations.";
   }
-  SB_CHECK(SbMutexRelease(&mutex_));
   return watchdog_json;
 }
 
@@ -605,7 +595,7 @@
 void Watchdog::MaybeInjectDebugDelay(const std::string& name) {
   if (is_disabled_) return;
 
-  starboard::ScopedLock scoped_lock(delay_lock_);
+  starboard::ScopedLock scoped_lock(delay_mutex_);
 
   if (name != delay_name_) return;
 
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index 04ba81d..ff2e486 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -23,8 +23,9 @@
 #include "cobalt/base/application_state.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/watchdog/singleton.h"
+#include "starboard/atomic.h"
+#include "starboard/common/condition_variable.h"
 #include "starboard/common/mutex.h"
-#include "starboard/mutex.h"
 #include "starboard/thread.h"
 #include "starboard/time.h"
 
@@ -124,7 +125,7 @@
   // only occur in between loops of monitor. API functions like Register(),
   // Unregister(), Ping(), and GetWatchdogViolations() will be called by
   // various threads and interact with these class variables.
-  SbMutex mutex_;
+  starboard::Mutex mutex_;
   // Tracks application state.
   base::ApplicationState state_ = base::kApplicationStateStarted;
   // Flag to trigger Watchdog violations writes to persistent storage.
@@ -143,12 +144,15 @@
   // Monitor thread.
   SbThread watchdog_thread_;
   // Flag to stop monitor thread.
-  bool is_monitoring_;
+  starboard::atomic_bool is_monitoring_;
+  // Conditional Variable to wait and shutdown monitor thread.
+  starboard::ConditionVariable monitor_wait_ =
+      starboard::ConditionVariable(mutex_);
   // The frequency in microseconds of monitor loops.
   int64_t watchdog_monitor_frequency_;
 
 #if defined(_DEBUG)
-  starboard::Mutex delay_lock_;
+  starboard::Mutex delay_mutex_;
   // Name of the client to inject a delay for.
   std::string delay_name_ = "";
   // Monotonically increasing timestamp when a delay was last injected. 0
diff --git a/cobalt/web/BUILD.gn b/cobalt/web/BUILD.gn
index 4d53f0a..cda07f8 100644
--- a/cobalt/web/BUILD.gn
+++ b/cobalt/web/BUILD.gn
@@ -60,6 +60,13 @@
     "blob.h",
     "buffer_source.cc",
     "buffer_source.h",
+    "cache.cc",
+    "cache.h",
+    "cache_request.h",
+    "cache_storage.cc",
+    "cache_storage.h",
+    "cache_utils.cc",
+    "cache_utils.h",
     "cobalt_ua_data_values_interface.cc",
     "cobalt_ua_data_values_interface.h",
     "context.h",
@@ -71,6 +78,8 @@
     "csp_delegate_factory.h",
     "csp_violation_reporter.cc",
     "csp_violation_reporter.h",
+    "environment_settings_helper.cc",
+    "environment_settings_helper.h",
     "location_base.h",
     "message_event.cc",
     "message_event.h",
@@ -96,12 +105,14 @@
     ":web_events",
     "//cobalt/base",
     "//cobalt/browser:generated_bindings",
+    "//cobalt/cache",
     "//cobalt/csp",
     "//cobalt/network",
     "//cobalt/network_bridge",
     "//cobalt/script",
     "//cobalt/script:engine",
     "//cobalt/script/v8c:engine",
+    "//net",
     "//url",
   ]
 
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index f44a345..e66ef8e 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -110,14 +110,14 @@
   scoped_refptr<worker::ServiceWorkerRegistration>
   LookupServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) final;
-  // https://w3c.github.io/ServiceWorker/#service-worker-registration-creation
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-registration-creation
   scoped_refptr<worker::ServiceWorkerRegistration> GetServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) final;
 
   void RemoveServiceWorker(worker::ServiceWorkerObject* worker) final;
   scoped_refptr<worker::ServiceWorker> LookupServiceWorker(
       worker::ServiceWorkerObject* worker) final;
-  // https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-the-service-worker-object
   scoped_refptr<worker::ServiceWorker> GetServiceWorker(
       worker::ServiceWorkerObject* worker) final;
 
@@ -133,7 +133,7 @@
     return network_module()->preferred_language();
   }
 
-  // https://w3c.github.io/ServiceWorker/#dfn-control
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
   bool is_controlled_by(worker::ServiceWorkerObject* worker) const final {
     // When a service worker client has a non-null active service worker, it is
     // said to be controlled by that active service worker.
@@ -199,13 +199,13 @@
   std::unique_ptr<EnvironmentSettings> environment_settings_;
 
   // The service worker registration object map.
-  //   https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-registration-object-map
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#environment-settings-object-service-worker-registration-object-map
   std::map<worker::ServiceWorkerRegistrationObject*,
            scoped_refptr<worker::ServiceWorkerRegistration>>
       service_worker_registration_object_map_;
 
   // The service worker object map.
-  //   https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#environment-settings-object-service-worker-object-map
   std::map<worker::ServiceWorkerObject*, scoped_refptr<worker::ServiceWorker>>
       service_worker_object_map_;
 
@@ -337,7 +337,7 @@
 Impl::GetServiceWorkerRegistration(
     worker::ServiceWorkerRegistrationObject* registration) {
   // Algorithm for 'get the service worker registration object':
-  //   https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-the-service-worker-registration-object
   scoped_refptr<worker::ServiceWorkerRegistration> worker_registration;
   if (!registration) {
     // Return undefined when registration is null.
@@ -404,7 +404,7 @@
 scoped_refptr<worker::ServiceWorker> Impl::LookupServiceWorker(
     worker::ServiceWorkerObject* worker) {
   // Algorithm for 'get the service worker object':
-  //   https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-the-service-worker-object
   scoped_refptr<worker::ServiceWorker> service_worker;
 
   if (!worker) {
@@ -424,7 +424,7 @@
 scoped_refptr<worker::ServiceWorker> Impl::GetServiceWorker(
     worker::ServiceWorkerObject* worker) {
   // Algorithm for 'get the service worker object':
-  //   https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-the-service-worker-object
   scoped_refptr<worker::ServiceWorker> service_worker;
 
   if (!worker) {
diff --git a/cobalt/web/cache.cc b/cobalt/web/cache.cc
new file mode 100644
index 0000000..bbef89f
--- /dev/null
+++ b/cobalt/web/cache.cc
@@ -0,0 +1,507 @@
+// Copyright 2022 The Cobalt Authors. 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/web/cache.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/json/string_escape.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "cobalt/base/source_location.h"
+#include "cobalt/cache/cache.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/script/v8c/conversion_helpers.h"
+#include "cobalt/script/v8c/v8c_user_object_holder.h"
+#include "cobalt/script/v8c/v8c_value_handle.h"
+#include "cobalt/web/cache_utils.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings_helper.h"
+#include "net/url_request/url_request_context.h"
+#include "v8/include/v8.h"
+
+namespace cobalt {
+namespace web {
+
+namespace {
+
+const disk_cache::ResourceType kResourceType =
+    disk_cache::ResourceType::kCacheApi;
+
+}  // namespace
+
+Cache::Fetcher::Fetcher(network::NetworkModule* network_module, const GURL& url,
+                        base::OnceCallback<void(bool)> callback)
+    : network_module_(network_module),
+      url_(url),
+      callback_(std::move(callback)),
+      buffer_(new net::GrowableIOBuffer()) {
+  network_module_->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&Cache::Fetcher::Start, base::Unretained(this)));
+}
+
+std::vector<uint8_t> Cache::Fetcher::BufferToVector() const {
+  auto* buffer_begin = reinterpret_cast<const uint8_t*>(buffer_->data());
+  return std::vector<uint8_t>(buffer_begin, buffer_begin + buffer_size_);
+}
+
+std::string Cache::Fetcher::BufferToString() const {
+  return std::string(buffer_->data(), buffer_size_);
+}
+
+void Cache::Fetcher::Start() {
+  request_ = network_module_->url_request_context()->CreateRequest(
+      url_, net::RequestPriority::DEFAULT_PRIORITY, this);
+  request_->Start();
+}
+
+void Cache::Fetcher::Notify(bool success) {
+  // Need to delete |URLRequest| instance on network thread.
+  request_.reset();
+  std::move(callback_).Run(success);
+}
+
+void Cache::Fetcher::OnDone(bool success) {
+  buffer_size_ = buffer_->offset();
+  buffer_->set_offset(0);
+  network_module_->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&Cache::Fetcher::Notify, base::Unretained(this), success));
+}
+
+void Cache::Fetcher::ReadResponse(net::URLRequest* request) {
+  int bytes_read = request->Read(buffer_, buffer_->RemainingCapacity());
+  bool read_running_async = bytes_read == net::ERR_IO_PENDING;
+  bool response_complete = !read_running_async && bytes_read <= 0;
+  // If read is still running, the buffer will be written to and then
+  // |OnReadCompleted()| will be called.
+  if (read_running_async) {
+    return;
+  }
+  if (response_complete) {
+    OnDone(/*success=*/bytes_read == net::OK);
+    return;
+  }
+
+  // Read completed synchronously. Call |OnReadCompleted()| asynchronously to
+  // avoid blocking IO.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&Cache::Fetcher::OnReadCompleted,
+                                base::Unretained(this), request, bytes_read));
+}
+
+void Cache::Fetcher::OnResponseStarted(net::URLRequest* request,
+                                       int net_error) {
+  DCHECK_NE(net::ERR_IO_PENDING, net_error);
+  request->GetMimeType(&mime_type_);
+  if (net_error != net::OK) {
+    OnDone(/*success=*/false);
+    return;
+  }
+  int initial_capacity = request->response_headers()->HasHeader(
+                             net::HttpRequestHeaders::kContentLength)
+                             ? request->response_headers()->GetContentLength()
+                             : 64 * 1024;
+  buffer_->SetCapacity(initial_capacity);
+  ReadResponse(request);
+}
+
+void Cache::Fetcher::OnReadCompleted(net::URLRequest* request, int bytes_read) {
+  DCHECK_NE(net::ERR_IO_PENDING, bytes_read);
+  if (bytes_read <= 0) {
+    OnDone(/*success=*/bytes_read == net::OK);
+    return;
+  }
+  // The offset is how much of the buffer is used.
+  buffer_->set_offset(buffer_->offset() + bytes_read);
+  // Grow the buffer, if needed.
+  if (buffer_->RemainingCapacity() == 0) {
+    buffer_->SetCapacity(buffer_->capacity() + 16 * 1024);
+  }
+  ReadResponse(request);
+}
+
+script::HandlePromiseAny Cache::Match(
+    script::EnvironmentSettings* environment_settings,
+    const script::ValueHandleHolder& request) {
+  script::HandlePromiseAny promise =
+      get_script_value_factory(environment_settings)
+          ->CreateBasicPromise<script::Any>();
+  auto promise_reference =
+      std::make_unique<script::ValuePromiseAny::Reference>(this, promise);
+  auto context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](script::EnvironmentSettings* environment_settings, uint32_t key,
+             std::unique_ptr<script::ValuePromiseAny::Reference>
+                 promise_reference) {
+            auto global_environment =
+                get_global_environment(environment_settings);
+            auto* isolate = global_environment->isolate();
+            auto cached =
+                cache::Cache::GetInstance()->Retrieve(kResourceType, key);
+            if (!cached) {
+              promise_reference->value().Resolve(
+                  cache_utils::GetUndefined(environment_settings));
+              return;
+            }
+            script::v8c::EntryScope entry_scope(isolate);
+            auto response = cache_utils::CreateResponse(environment_settings,
+                                                        std::move(cached));
+            if (!response) {
+              promise_reference->value().Reject();
+            } else {
+              promise_reference->value().Resolve(script::Any(response.value()));
+            }
+          },
+          environment_settings,
+          cache_utils::GetKey(environment_settings, request),
+          std::move(promise_reference)));
+  return promise;
+}
+
+void Cache::PerformAdd(
+    script::EnvironmentSettings* environment_settings,
+    std::unique_ptr<script::ValueHandleHolder::Reference> request_reference,
+    std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
+  auto* global_environment = get_global_environment(environment_settings);
+  auto* isolate = global_environment->isolate();
+  script::v8c::EntryScope entry_scope(isolate);
+  uint32_t key = cache_utils::GetKey(environment_settings,
+                                     request_reference->referenced_value());
+  if (fetchers_.find(key) != fetchers_.end()) {
+    base::AutoLock auto_lock(*(fetchers_[key]->lock()));
+    auto* promises = &(fetch_contexts_[key].first);
+    promises->push_back(std::move(promise_reference));
+    return;
+  }
+  auto promises =
+      std::vector<std::unique_ptr<script::ValuePromiseVoid::Reference>>();
+  promises.push_back(std::move(promise_reference));
+  fetch_contexts_[key] =
+      std::make_pair(std::move(promises), environment_settings);
+  auto* context = get_context(environment_settings);
+  fetchers_[key] = std::make_unique<Cache::Fetcher>(
+      context->network_module(),
+      GURL(cache_utils::GetUrl(environment_settings,
+                               request_reference->referenced_value())),
+      base::BindOnce(&Cache::OnFetchCompleted, base::Unretained(this), key));
+}
+
+script::HandlePromiseVoid Cache::Add(
+    script::EnvironmentSettings* environment_settings,
+    const script::ValueHandleHolder& request) {
+  auto request_reference =
+      std::make_unique<script::ValueHandleHolder::Reference>(this, request);
+  script::HandlePromiseVoid promise =
+      get_script_value_factory(environment_settings)
+          ->CreateBasicPromise<void>();
+  auto promise_reference =
+      std::make_unique<script::ValuePromiseVoid::Reference>(this, promise);
+  auto context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&Cache::PerformAdd, base::Unretained(this),
+                     environment_settings, std::move(request_reference),
+                     std::move(promise_reference)));
+  return promise;
+}
+
+script::HandlePromiseVoid Cache::Put(
+    script::EnvironmentSettings* environment_settings,
+    const script::ValueHandleHolder& request,
+    const script::ValueHandleHolder& response) {
+  auto request_reference =
+      std::make_unique<script::ValueHandleHolder::Reference>(this, request);
+  auto response_reference =
+      std::make_unique<script::ValueHandleHolder::Reference>(this, response);
+  script::HandlePromiseVoid promise =
+      get_script_value_factory(environment_settings)
+          ->CreateBasicPromise<void>();
+  auto promise_reference =
+      std::make_unique<script::ValuePromiseVoid::Reference>(this, promise);
+
+  auto context = get_context(environment_settings);
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](script::EnvironmentSettings* environment_settings,
+             std::unique_ptr<script::ValueHandleHolder::Reference>
+                 request_reference,
+             std::unique_ptr<script::ValueHandleHolder::Reference>
+                 response_reference,
+             std::unique_ptr<script::ValuePromiseVoid::Reference>
+                 promise_reference) {
+
+            auto* global_environment =
+                get_global_environment(environment_settings);
+            auto* isolate = global_environment->isolate();
+            script::v8c::EntryScope entry_scope(isolate);
+            auto context = global_environment->context();
+            auto maybe_body_used = cache_utils::TryGet(
+                context, GetV8Value(response_reference->referenced_value()),
+                "bodyUsed");
+            if (maybe_body_used.IsEmpty() ||
+                maybe_body_used.ToLocalChecked().As<v8::Boolean>()->Value()) {
+              promise_reference->value().Reject(script::kTypeError);
+              return;
+            }
+            auto maybe_text_function = cache_utils::TryGet(
+                context, GetV8Value(response_reference->referenced_value()),
+                "text");
+            if (maybe_text_function.IsEmpty()) {
+              promise_reference->value().Reject();
+              return;
+            }
+            auto text_function = maybe_text_function.ToLocalChecked();
+            v8::Local<v8::Value> text_result;
+            auto response_context =
+                script::GetIsolate(response_reference->referenced_value())
+                    ->GetCurrentContext();
+            if (text_function.IsEmpty() || !text_function->IsFunction() ||
+                !(text_function.As<v8::Function>()
+                      ->Call(response_context,
+                             GetV8Value(response_reference->referenced_value()),
+                             /*argc=*/0,
+                             /*argv=*/nullptr)
+                      .ToLocal(&text_result))) {
+              promise_reference->value().Reject();
+              return;
+            }
+            std::string url = cache_utils::GetUrl(
+                environment_settings, request_reference->referenced_value());
+            auto data = v8::Object::New(isolate);
+            cache_utils::Set(context, data, "environment_settings",
+                             v8::External::New(isolate, environment_settings));
+            cache_utils::Set(
+                context, data, "promise_reference",
+                v8::External::New(isolate, promise_reference.release()));
+            cache_utils::Set(
+                context, data, "request_reference",
+                v8::External::New(isolate, request_reference.release()));
+            auto then_callback =
+                v8::Function::New(
+                    context,
+                    [](const v8::FunctionCallbackInfo<v8::Value>& info) {
+                      auto* isolate = info.GetIsolate();
+                      auto context = info.GetIsolate()->GetCurrentContext();
+                      auto* environment_settings =
+                          static_cast<script::EnvironmentSettings*>(
+                              cache_utils::Get(context, info.Data(),
+                                               "environment_settings")
+                                  .As<v8::External>()
+                                  ->Value());
+                      std::unique_ptr<script::ValueHandleHolder::Reference>
+                      request_reference(
+                          static_cast<script::ValueHandleHolder::Reference*>(
+                              cache_utils::Get(context, info.Data(),
+                                               "request_reference")
+                                  .As<v8::External>()
+                                  ->Value()));
+                      std::unique_ptr<script::ValuePromiseVoid::Reference>
+                      promise_reference(
+                          static_cast<script::ValuePromiseVoid::Reference*>(
+                              cache_utils::Get(context, info.Data(),
+                                               "promise_reference")
+                                  .As<v8::External>()
+                                  ->Value()));
+                      uint32_t key = cache_utils::GetKey(
+                          environment_settings,
+                          request_reference->referenced_value());
+                      std::string url = cache_utils::GetUrl(
+                          environment_settings,
+                          request_reference->referenced_value());
+                      std::string body;
+                      FromJSValue(info.GetIsolate(), info[0],
+                                  script::v8c::kNoConversionFlags, nullptr,
+                                  &body);
+                      auto* begin =
+                          reinterpret_cast<const uint8_t*>(body.data());
+                      auto data = std::make_unique<std::vector<uint8_t>>(
+                          begin, begin + body.size());
+                      cache::Cache::GetInstance()->Store(
+                          kResourceType, key, *data, base::Value(url));
+                      promise_reference->value().Resolve();
+                    },
+                    data)
+                    .ToLocalChecked();
+            if (text_result.As<v8::Promise>()
+                    ->Then(context, then_callback)
+                    .IsEmpty()) {
+              promise_reference->value().Reject();
+              return;
+            }
+            auto catch_callback =
+                v8::Function::New(
+                    context,
+                    [](const v8::FunctionCallbackInfo<v8::Value>& info) {
+                      auto* isolate = info.GetIsolate();
+                      auto context = info.GetIsolate()->GetCurrentContext();
+                      std::unique_ptr<script::ValuePromiseVoid::Reference>
+                      promise_reference(
+                          static_cast<script::ValuePromiseVoid::Reference*>(
+                              cache_utils::Get(context, info.Data(),
+                                               "promise_reference")
+                                  .As<v8::External>()
+                                  ->Value()));
+                      promise_reference->value().Reject();
+                    },
+                    data)
+                    .ToLocalChecked();
+            if (text_result.As<v8::Promise>()
+                    ->Catch(context, catch_callback)
+                    .IsEmpty()) {
+              promise_reference->value().Reject();
+              return;
+            }
+            // Run |response.text()| promise.
+            isolate->PerformMicrotaskCheckpoint();
+          },
+          environment_settings, std::move(request_reference),
+          std::move(response_reference), std::move(promise_reference)));
+  return promise;
+}
+
+script::HandlePromiseBool Cache::Delete(
+    script::EnvironmentSettings* environment_settings,
+    const script::ValueHandleHolder& request) {
+  script::HandlePromiseBool promise =
+      get_script_value_factory(environment_settings)
+          ->CreateBasicPromise<bool>();
+  auto request_reference =
+      std::make_unique<script::ValueHandleHolder::Reference>(this, request);
+  auto promise_reference =
+      std::make_unique<script::ValuePromiseBool::Reference>(this, promise);
+  auto context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](script::EnvironmentSettings* environment_settings,
+             std::unique_ptr<script::ValueHandleHolder::Reference>
+                 request_reference,
+             std::unique_ptr<script::ValuePromiseBool::Reference>
+                 promise_reference) {
+            auto* global_environment =
+                get_global_environment(environment_settings);
+            auto* isolate = global_environment->isolate();
+            script::v8c::EntryScope entry_scope(isolate);
+            promise_reference->value().Resolve(
+                cache::Cache::GetInstance()->Delete(
+                    kResourceType, cache_utils::GetKey(
+                                       environment_settings,
+                                       request_reference->referenced_value())));
+          },
+          environment_settings, std::move(request_reference),
+          std::move(promise_reference)));
+  return promise;
+}
+
+script::HandlePromiseAny Cache::Keys(
+    script::EnvironmentSettings* environment_settings) {
+  script::HandlePromiseAny promise =
+      get_script_value_factory(environment_settings)
+          ->CreateBasicPromise<script::Any>();
+  auto promise_reference =
+      std::make_unique<script::ValuePromiseAny::Reference>(this, promise);
+  auto context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](script::EnvironmentSettings* environment_settings,
+             std::unique_ptr<script::ValuePromiseAny::Reference>
+                 promise_reference) {
+            auto* global_environment =
+                get_global_environment(environment_settings);
+            auto* isolate = global_environment->isolate();
+            script::v8c::EntryScope entry_scope(isolate);
+            auto keys =
+                cache::Cache::GetInstance()->KeysWithMetadata(kResourceType);
+            std::vector<v8::Local<v8::Value>> requests;
+            for (uint8_t key :
+                 cache::Cache::GetInstance()->KeysWithMetadata(kResourceType)) {
+              std::unique_ptr<base::Value> url =
+                  cache::Cache::GetInstance()->Metadata(kResourceType, key);
+              if (url && url->is_string()) {
+                base::Optional<script::Any> request =
+                    cache_utils::CreateRequest(environment_settings,
+                                               url->GetString());
+                if (request) {
+                  requests.push_back(GetV8Value(*(request->GetScriptValue())));
+                }
+              }
+            }
+            promise_reference->value().Resolve(
+                script::Any(new script::v8c::V8cValueHandleHolder(
+                    isolate, v8::Array::New(isolate, requests.data(),
+                                            requests.size()))));
+
+          },
+          environment_settings, std::move(promise_reference)));
+  return promise;
+}
+
+void Cache::OnFetchCompleted(uint32_t key, bool success) {
+  auto* environment_settings = fetch_contexts_[key].second;
+  auto* context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&Cache::OnFetchCompletedMainThread,
+                                base::Unretained(this), key, success));
+}
+
+void Cache::OnFetchCompletedMainThread(uint32_t key, bool success) {
+  auto* fetcher = fetchers_[key].get();
+  auto* promises = &(fetch_contexts_[key].first);
+  if (!success) {
+    {
+      base::AutoLock auto_lock(*fetcher->lock());
+      while (promises->size() > 0) {
+        promises->back()->value().Reject();
+        promises->pop_back();
+      }
+    }
+    fetchers_.erase(key);
+    fetch_contexts_.erase(key);
+    return;
+  }
+  {
+    cache::Cache::GetInstance()->Store(kResourceType, key,
+                                       fetcher->BufferToVector(),
+                                       base::Value(fetcher->url().spec()));
+    if (fetcher->mime_type() == "text/javascript") {
+      auto* environment_settings = fetch_contexts_[key].second;
+      auto* global_environment = get_global_environment(environment_settings);
+      auto* isolate = global_environment->isolate();
+      script::v8c::EntryScope entry_scope(isolate);
+      global_environment->Compile(script::SourceCode::CreateSourceCode(
+          fetcher->BufferToString(), base::SourceLocation(__FILE__, 1, 1)));
+    }
+    base::AutoLock auto_lock(*fetcher->lock());
+    while (promises->size() > 0) {
+      promises->back()->value().Resolve();
+      promises->pop_back();
+    }
+  }
+  fetchers_.erase(key);
+  fetch_contexts_.erase(key);
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/cache.h b/cobalt/web/cache.h
new file mode 100644
index 0000000..ee09fb7
--- /dev/null
+++ b/cobalt/web/cache.h
@@ -0,0 +1,112 @@
+// Copyright 2022 The Cobalt Authors. 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_WEB_CACHE_H_
+#define COBALT_WEB_CACHE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/synchronization/lock.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/sequence.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace web {
+
+class Cache : public script::Wrappable {
+ public:
+  Cache() = default;
+
+  // Web API: Cache
+  //
+  script::Handle<script::Promise<script::Handle<script::ValueHandle>>> Match(
+      script::EnvironmentSettings* environment_settings,
+      const script::ValueHandleHolder& request);
+  script::HandlePromiseVoid Add(
+      script::EnvironmentSettings* environment_settings,
+      const script::ValueHandleHolder& request);
+  script::HandlePromiseVoid Put(
+      script::EnvironmentSettings* environment_settings,
+      const script::ValueHandleHolder& request,
+      const script::ValueHandleHolder& response);
+  script::HandlePromiseBool Delete(
+      script::EnvironmentSettings* environment_settings,
+      const script::ValueHandleHolder& request);
+  script::Handle<script::Promise<script::Handle<script::ValueHandle>>> Keys(
+      script::EnvironmentSettings* environment_settings);
+
+  DEFINE_WRAPPABLE_TYPE(Cache);
+
+ private:
+  class Fetcher : public net::URLRequest::Delegate {
+   public:
+    Fetcher(network::NetworkModule* network_module, const GURL& url,
+            base::OnceCallback<void(bool)> callback);
+
+    // net::URLRequest::Delegate implementations.
+    void OnResponseStarted(net::URLRequest* request, int net_error) override;
+    void OnReadCompleted(net::URLRequest* request, int bytes_read) override;
+
+    const std::string& mime_type() const { return mime_type_; }
+    GURL url() const { return url_; }
+    base::Lock* lock() const { return &lock_; }
+    std::vector<uint8_t> BufferToVector() const;
+    std::string BufferToString() const;
+
+   private:
+    void Notify(bool success);
+    void OnDone(bool success);
+    void ReadResponse(net::URLRequest* request);
+    void Start();
+
+    network::NetworkModule* network_module_;
+    GURL url_;
+    base::OnceCallback<void(bool)> callback_;
+    std::unique_ptr<net::URLRequest> request_;
+    std::string mime_type_;
+    scoped_refptr<net::GrowableIOBuffer> buffer_;
+    int buffer_size_;
+    mutable base::Lock lock_;
+  };
+
+  void PerformAdd(
+      script::EnvironmentSettings* environment_settings,
+      std::unique_ptr<script::ValueHandleHolder::Reference> request_reference,
+      std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
+  void OnFetchCompleted(uint32_t key, bool success);
+  void OnFetchCompletedMainThread(uint32_t key, bool success);
+
+  std::map<uint32_t, std::unique_ptr<Fetcher>> fetchers_;
+  std::map<uint32_t, std::pair<std::vector<std::unique_ptr<
+                                   script::ValuePromiseVoid::Reference>>,
+                               script::EnvironmentSettings*>>
+      fetch_contexts_;
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_CACHE_H_
diff --git a/cobalt/web/cache.idl b/cobalt/web/cache.idl
new file mode 100644
index 0000000..87ea59a
--- /dev/null
+++ b/cobalt/web/cache.idl
@@ -0,0 +1,27 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#cache-interface
+
+[Exposed=(Window,Worker)]
+interface Cache {
+  // |match| returns a new |Response| object. |Response| is polyfilled, so
+  // return object of type |any|. When promise is resovled with |undefined|,
+  // the cache does not have an entry for the given |request|.
+  [CallWith=EnvironmentSettings, NewObject] Promise<any> match(any request);
+  [CallWith=EnvironmentSettings, NewObject] Promise<void> add(any request);
+  [CallWith=EnvironmentSettings, NewObject] Promise<void> put(any request, any response);
+  [CallWith=EnvironmentSettings, NewObject] Promise<boolean> delete(any request);
+  [CallWith=EnvironmentSettings, NewObject] Promise<any> keys();
+};
diff --git a/cobalt/web/cache_request.h b/cobalt/web/cache_request.h
new file mode 100644
index 0000000..b5fb6b8
--- /dev/null
+++ b/cobalt/web/cache_request.h
@@ -0,0 +1,38 @@
+// Copyright 2022 The Cobalt Authors. 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_WEB_CACHE_REQUEST_H_
+#define COBALT_WEB_CACHE_REQUEST_H_
+
+#include <string>
+
+namespace cobalt {
+namespace web {
+
+class CacheRequest : public script::Wrappable {
+ public:
+  explicit CacheRequest(const std::string& input) : url_(input) {}
+
+  const std::string& url() const { return url_; }
+
+  DEFINE_WRAPPABLE_TYPE(CacheRequest);
+
+ private:
+  const std::string url_;
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_CACHE_REQUEST_H_
diff --git a/cobalt/web/cache_request.idl b/cobalt/web/cache_request.idl
new file mode 100644
index 0000000..7f9ccdb
--- /dev/null
+++ b/cobalt/web/cache_request.idl
@@ -0,0 +1,23 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#cache-interface
+
+[
+  Exposed=(Window,Worker),
+  Constructor(USVString input),
+]
+interface CacheRequest {
+  readonly attribute USVString url;
+};
diff --git a/cobalt/web/cache_storage.cc b/cobalt/web/cache_storage.cc
new file mode 100644
index 0000000..db96ded
--- /dev/null
+++ b/cobalt/web/cache_storage.cc
@@ -0,0 +1,94 @@
+// Copyright 2022 The Cobalt Authors. 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/web/cache_storage.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "cobalt/base/source_location.h"
+#include "cobalt/cache/cache.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings_helper.h"
+
+namespace cobalt {
+namespace web {
+
+script::Handle<script::Promise<script::Handle<script::ValueHandle>>>
+CacheStorage::Match(script::EnvironmentSettings* environment_settings,
+                    const script::ValueHandleHolder& request) {
+  return GetOrCreateCache()->Match(environment_settings, request);
+}
+
+script::Handle<script::Promise<script::Handle<script::ValueHandle>>>
+CacheStorage::Match(script::EnvironmentSettings* environment_settings,
+                    const script::ValueHandleHolder& request,
+                    const MultiCacheQueryOptions& options) {
+  return Match(environment_settings, request);
+}
+
+void CacheStorage::PerformOpen(
+    std::unique_ptr<script::ValuePromiseWrappable::Reference>
+        promise_reference) {
+  promise_reference->value().Resolve(GetOrCreateCache());
+}
+
+script::HandlePromiseWrappable CacheStorage::Open(
+    script::EnvironmentSettings* environment_settings,
+    const std::string& cache_name) {
+  script::HandlePromiseWrappable promise =
+      get_script_value_factory(environment_settings)
+          ->CreateInterfacePromise<scoped_refptr<Cache>>();
+  auto* context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&CacheStorage::PerformOpen, base::Unretained(this),
+                     std::make_unique<script::ValuePromiseWrappable::Reference>(
+                         this, promise)));
+  return promise;
+}
+
+script::HandlePromiseBool CacheStorage::Delete(
+    script::EnvironmentSettings* environment_settings,
+    const std::string& cache_name) {
+  script::HandlePromiseBool promise =
+      get_script_value_factory(environment_settings)
+          ->CreateBasicPromise<bool>();
+  auto promise_reference =
+      std::make_unique<script::ValuePromiseBool::Reference>(this, promise);
+  auto* context = get_context(environment_settings);
+  context->message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](std::unique_ptr<script::ValuePromiseBool::Reference>
+                            promise_reference) {
+                       cache::Cache::GetInstance()->Delete(
+                           disk_cache::ResourceType::kCacheApi);
+                       promise_reference->value().Resolve(true);
+                     },
+                     std::move(promise_reference)));
+  return promise;
+}
+
+scoped_refptr<Cache> CacheStorage::GetOrCreateCache() {
+  if (!cache_) {
+    cache_ = new Cache();
+  }
+  return cache_;
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/cache_storage.h b/cobalt/web/cache_storage.h
new file mode 100644
index 0000000..ec25b1b
--- /dev/null
+++ b/cobalt/web/cache_storage.h
@@ -0,0 +1,66 @@
+// Copyright 2022 The Cobalt Authors. 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_WEB_CACHE_STORAGE_H_
+#define COBALT_WEB_CACHE_STORAGE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/cache.h"
+#include "cobalt/web/multi_cache_query_options.h"
+
+namespace cobalt {
+namespace web {
+
+class CacheStorage : public script::Wrappable {
+ public:
+  CacheStorage() = default;
+
+  // Web API: CacheStorage
+  //
+  script::Handle<script::Promise<script::Handle<script::ValueHandle>>> Match(
+      script::EnvironmentSettings* environment_settings,
+      const script::ValueHandleHolder& request);
+  script::Handle<script::Promise<script::Handle<script::ValueHandle>>> Match(
+      script::EnvironmentSettings* environment_settings,
+      const script::ValueHandleHolder& request,
+      const MultiCacheQueryOptions& options);
+  script::HandlePromiseWrappable Open(
+      script::EnvironmentSettings* environment_settings,
+      const std::string& cache_name);
+  script::HandlePromiseBool Delete(
+      script::EnvironmentSettings* environment_settings,
+      const std::string& cache_name);
+
+  DEFINE_WRAPPABLE_TYPE(CacheStorage);
+
+ private:
+  scoped_refptr<Cache> GetOrCreateCache();
+  void PerformOpen(std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                       promise_reference);
+
+  scoped_refptr<Cache> cache_;
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_CACHE_STORAGE_H_
diff --git a/cobalt/web/cache_storage.idl b/cobalt/web/cache_storage.idl
new file mode 100644
index 0000000..a9744a6
--- /dev/null
+++ b/cobalt/web/cache_storage.idl
@@ -0,0 +1,25 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#cachestorage
+
+// TODO(b/250611661): implement complete Cache API and adhere to web spec.
+
+[Exposed=(Window,Worker)]
+interface CacheStorage {
+  [CallWith=EnvironmentSettings, NewObject] Promise<any> match(any request, optional MultiCacheQueryOptions options);
+  [CallWith=EnvironmentSettings, NewObject] Promise<Cache> open(DOMString cacheName);
+  // Ignores |cacheName| and deletes all Cache API data.
+  [CallWith=EnvironmentSettings, NewObject] Promise<boolean> delete(DOMString cacheName);
+};
diff --git a/cobalt/web/cache_utils.cc b/cobalt/web/cache_utils.cc
new file mode 100644
index 0000000..92fc5a2
--- /dev/null
+++ b/cobalt/web/cache_utils.cc
@@ -0,0 +1,171 @@
+// Copyright 2022 The Cobalt Authors. 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/web/cache_utils.h"
+
+#include "cobalt/cache/cache.h"
+#include "cobalt/script/v8c/conversion_helpers.h"
+#include "cobalt/web/environment_settings_helper.h"
+
+namespace cobalt {
+namespace web {
+namespace cache_utils {
+
+v8::Local<v8::String> V8String(v8::Isolate* isolate, const std::string& s) {
+  return v8::String::NewFromUtf8(isolate, s.c_str()).ToLocalChecked();
+}
+
+v8::MaybeLocal<v8::Value> TryGet(v8::Local<v8::Context> context,
+                                 v8::Local<v8::Value> object,
+                                 const std::string& key) {
+  if (!object->IsObject()) {
+    return v8::MaybeLocal<v8::Value>();
+  }
+  auto* isolate = context->GetIsolate();
+  return object.As<v8::Object>()->Get(context, V8String(isolate, key));
+}
+
+v8::Local<v8::Value> Get(v8::Local<v8::Context> context,
+                         v8::Local<v8::Value> object, const std::string& key) {
+  return TryGet(context, object, key).ToLocalChecked();
+}
+
+bool Set(v8::Local<v8::Context> context, v8::Local<v8::Value> object,
+         const std::string& key, v8::Local<v8::Value> value) {
+  if (!object->IsObject()) {
+    return false;
+  }
+  auto* isolate = context->GetIsolate();
+  auto result =
+      object.As<v8::Object>()->Set(context, V8String(isolate, key), value);
+  return !result.IsNothing();
+}
+
+v8::MaybeLocal<v8::Value> TryCall(v8::Local<v8::Context> context,
+                                  v8::Local<v8::Value> object,
+                                  const std::string& key, int argc,
+                                  v8::Local<v8::Value> argv[]) {
+  v8::Local<v8::Value> function;
+  if (!cache_utils::TryGet(context, object, key).ToLocal(&function) ||
+      function.IsEmpty() || !function->IsFunction()) {
+    return v8::MaybeLocal<v8::Value>();
+  }
+  auto object_context =
+      object.As<v8::Object>()->GetIsolate()->GetCurrentContext();
+  return function.As<v8::Function>()->Call(object_context, object, argc, argv);
+}
+
+script::Any GetUndefined(script::EnvironmentSettings* environment_settings) {
+  auto* global_environment = get_global_environment(environment_settings);
+  auto* isolate = global_environment->isolate();
+  return script::Any(
+      new script::v8c::V8cValueHandleHolder(isolate, v8::Undefined(isolate)));
+}
+
+script::Any EvaluateString(script::EnvironmentSettings* environment_settings,
+                           const std::string& js_code) {
+  auto* global_environment = get_global_environment(environment_settings);
+  auto* wrappable = get_global_wrappable(environment_settings);
+  base::Optional<script::ValueHandleHolder::Reference> reference;
+  scoped_refptr<script::SourceCode> source_code =
+      script::SourceCode::CreateSourceCodeWithoutCaching(
+          js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+  bool eval_enabled = global_environment->IsEvalEnabled();
+  if (!eval_enabled) {
+    global_environment->EnableEval();
+  }
+  bool success =
+      global_environment->EvaluateScript(source_code, wrappable, &reference);
+  if (!eval_enabled) {
+    global_environment->DisableEval("");
+  }
+  if (success && reference) {
+    return script::Any(reference.value());
+  } else {
+    return GetUndefined(environment_settings);
+  }
+}
+
+base::Optional<script::Any> CreateInstance(
+    script::EnvironmentSettings* environment_settings,
+    const std::string& class_name, int argc, v8::Local<v8::Value> argv[]) {
+  auto* global_environment = get_global_environment(environment_settings);
+  auto* isolate = global_environment->isolate();
+  auto reponse_function =
+      cache_utils::EvaluateString(environment_settings, class_name);
+  auto v8_function =
+      script::GetV8Value(*reponse_function.GetScriptValue()).As<v8::Function>();
+  auto context = isolate->GetCurrentContext();
+  auto maybe_instance = v8_function->NewInstance(context, argc, argv);
+  if (maybe_instance.IsEmpty()) {
+    return base::nullopt;
+  }
+  return script::Any(new script::v8c::V8cValueHandleHolder(
+      isolate, maybe_instance.ToLocalChecked()));
+}
+
+base::Optional<script::Any> CreateRequest(
+    script::EnvironmentSettings* environment_settings, const std::string& url) {
+  auto* global_environment = get_global_environment(environment_settings);
+  auto* isolate = global_environment->isolate();
+  v8::Local<v8::Value> argv[] = {V8String(isolate, url)};
+  return CreateInstance(environment_settings, "Request", /*argc=*/1, argv);
+}
+
+base::Optional<script::Any> CreateResponse(
+    script::EnvironmentSettings* environment_settings,
+    std::unique_ptr<std::vector<uint8_t>> data) {
+  auto* global_environment = get_global_environment(environment_settings);
+  auto* isolate = global_environment->isolate();
+  auto array_buffer = v8::ArrayBuffer::New(isolate, data->size());
+  memcpy(array_buffer->GetBackingStore()->Data(), data->data(), data->size());
+  v8::Local<v8::Value> argv[] = {array_buffer};
+  return CreateInstance(environment_settings, "Response", /*argc=*/1, argv);
+}
+
+uint32_t GetKey(const std::string& url) { return cache::Cache::CreateKey(url); }
+
+uint32_t GetKey(script::EnvironmentSettings* environment_settings,
+                const script::ValueHandleHolder& request_info) {
+  return GetKey(GetUrl(environment_settings, request_info));
+}
+
+std::string GetUrl(script::EnvironmentSettings* environment_settings,
+                   const script::ValueHandleHolder& request_info) {
+  auto v8_value = GetV8Value(request_info);
+  auto* isolate = GetIsolate(request_info);
+  v8::Local<v8::String> v8_string;
+  if (v8_value->IsString()) {
+    v8_string = v8_value.As<v8::String>();
+  } else {
+    auto context = isolate->GetCurrentContext();
+    // Treat like |Request| and get "url" property.
+    v8_string = Get(context, v8_value, "url").As<v8::String>();
+  }
+  std::string url;
+  FromJSValue(isolate, v8_string, script::v8c::kNoConversionFlags, nullptr,
+              &url);
+  GURL::Replacements replacements;
+  replacements.ClearUsername();
+  replacements.ClearPassword();
+  replacements.ClearRef();
+  return environment_settings->base_url()
+      .Resolve(url)
+      .ReplaceComponents(replacements)
+      .spec();
+}
+
+}  // namespace cache_utils
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/cache_utils.h b/cobalt/web/cache_utils.h
new file mode 100644
index 0000000..0ed4c1b
--- /dev/null
+++ b/cobalt/web/cache_utils.h
@@ -0,0 +1,105 @@
+// Copyright 2022 The Cobalt Authors. 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_WEB_CACHE_UTILS_H_
+#define COBALT_WEB_CACHE_UTILS_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/value_handle.h"
+#include "v8/include/v8.h"
+
+namespace cobalt {
+namespace web {
+namespace cache_utils {
+
+v8::Local<v8::String> V8String(v8::Isolate* isolate, const std::string& s);
+
+v8::MaybeLocal<v8::Value> TryGet(v8::Local<v8::Context> context,
+                                 v8::Local<v8::Value> object,
+                                 const std::string& key);
+
+v8::Local<v8::Value> Get(v8::Local<v8::Context> context,
+                         v8::Local<v8::Value> object, const std::string& key);
+
+template <typename T>
+inline T* GetExternal(v8::Local<v8::Context> context,
+                      v8::Local<v8::Value> object, const std::string& key) {
+  return static_cast<T*>(
+      web::cache_utils::Get(context, object, key).As<v8::External>()->Value());
+}
+
+template <typename T>
+inline std::unique_ptr<T> GetOwnedExternal(v8::Local<v8::Context> context,
+                                           v8::Local<v8::Value> object,
+                                           const std::string& key) {
+  return std::unique_ptr<T>(
+      web::cache_utils::GetExternal<T>(context, object, key));
+}
+
+bool Set(v8::Local<v8::Context> context, v8::Local<v8::Value> object,
+         const std::string& key, v8::Local<v8::Value> value);
+
+
+template <typename T>
+inline bool SetExternal(v8::Local<v8::Context> context,
+                        v8::Local<v8::Value> object, const std::string& key,
+                        T* value) {
+  auto* isolate = context->GetIsolate();
+  return Set(context, object, key, v8::External::New(isolate, value));
+}
+
+template <typename T>
+inline bool SetOwnedExternal(v8::Local<v8::Context> context,
+                             v8::Local<v8::Value> object,
+                             const std::string& key, std::unique_ptr<T> value) {
+  return SetExternal<T>(context, object, key, value.release());
+}
+
+v8::MaybeLocal<v8::Value> TryCall(v8::Local<v8::Context> context,
+                                  v8::Local<v8::Value> object,
+                                  const std::string& key, int argc = 0,
+                                  v8::Local<v8::Value> argv[] = nullptr);
+
+script::Any GetUndefined(script::EnvironmentSettings* environment_settings);
+
+script::Any EvaluateString(script::EnvironmentSettings* environment_settings,
+                           const std::string& js_code);
+
+base::Optional<script::Any> CreateInstance(
+    script::EnvironmentSettings* environment_settings,
+    const std::string& class_name, int argc, v8::Local<v8::Value> argv[]);
+base::Optional<script::Any> CreateRequest(
+    script::EnvironmentSettings* environment_settings, const std::string& url);
+base::Optional<script::Any> CreateResponse(
+    script::EnvironmentSettings* environment_settings,
+    std::unique_ptr<std::vector<uint8_t>> data);
+
+uint32_t GetKey(const std::string& url);
+
+uint32_t GetKey(script::EnvironmentSettings* environment_settings,
+                const script::ValueHandleHolder& request_info);
+
+std::string GetUrl(script::EnvironmentSettings* environment_settings,
+                   const script::ValueHandleHolder& request_info);
+
+}  // namespace cache_utils
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_CACHE_UTILS_H_
diff --git a/cobalt/web/context.h b/cobalt/web/context.h
index 360dbe2..19f6caf 100644
--- a/cobalt/web/context.h
+++ b/cobalt/web/context.h
@@ -72,7 +72,7 @@
   virtual scoped_refptr<worker::ServiceWorkerRegistration>
   LookupServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) = 0;
-  // https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-the-service-worker-registration-object
   virtual scoped_refptr<worker::ServiceWorkerRegistration>
   GetServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) = 0;
@@ -80,7 +80,7 @@
   virtual void RemoveServiceWorker(worker::ServiceWorkerObject* worker) = 0;
   virtual scoped_refptr<worker::ServiceWorker> LookupServiceWorker(
       worker::ServiceWorkerObject* worker) = 0;
-  // https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-the-service-worker-object
   virtual scoped_refptr<worker::ServiceWorker> GetServiceWorker(
       worker::ServiceWorkerObject* worker) = 0;
 
@@ -96,7 +96,7 @@
   virtual void RemoveEnvironmentSettingsChangeObserver(
       EnvironmentSettingsChangeObserver* observer) = 0;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-control
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
   virtual bool is_controlled_by(worker::ServiceWorkerObject* worker) const = 0;
 
   // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-active-service-worker
diff --git a/cobalt/web/environment_settings.h b/cobalt/web/environment_settings.h
index 6301971..6fa5d89 100644
--- a/cobalt/web/environment_settings.h
+++ b/cobalt/web/environment_settings.h
@@ -20,6 +20,9 @@
 
 #include "cobalt/base/debugger_hooks.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/wrappable.h"
 #include "url/origin.h"
 
 namespace cobalt {
@@ -37,10 +40,7 @@
   ~EnvironmentSettings() override {}
 
   // https://html.spec.whatwg.org/multipage/webappapis.html#realm-execution-context
-  Context* context() const {
-    DCHECK(context_);
-    return context_;
-  }
+  Context* context() const { return context_; }
   void set_context(Context* context) { context_ = context; }
 
   // https://storage.spec.whatwg.org/#obtain-a-storage-key
diff --git a/cobalt/web/environment_settings_helper.cc b/cobalt/web/environment_settings_helper.cc
new file mode 100644
index 0000000..4a87c2e
--- /dev/null
+++ b/cobalt/web/environment_settings_helper.cc
@@ -0,0 +1,46 @@
+// Copyright 2022 The Cobalt Authors. 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/web/environment_settings_helper.h"
+
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings.h"
+
+namespace cobalt {
+namespace web {
+
+Context* get_context(script::EnvironmentSettings* environment_settings) {
+  auto* settings =
+      base::polymorphic_downcast<EnvironmentSettings*>(environment_settings);
+  return settings->context();
+}
+
+script::GlobalEnvironment* get_global_environment(
+    script::EnvironmentSettings* environment_settings) {
+  return get_context(environment_settings)->global_environment();
+}
+
+script::Wrappable* get_global_wrappable(
+    script::EnvironmentSettings* environment_settings) {
+  return get_global_environment(environment_settings)->global_wrappable();
+}
+
+script::ScriptValueFactory* get_script_value_factory(
+    script::EnvironmentSettings* environment_settings) {
+  return get_global_environment(environment_settings)->script_value_factory();
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/environment_settings_helper.h b/cobalt/web/environment_settings_helper.h
new file mode 100644
index 0000000..ca1b83e
--- /dev/null
+++ b/cobalt/web/environment_settings_helper.h
@@ -0,0 +1,37 @@
+// Copyright 2022 The Cobalt Authors. 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_WEB_ENVIRONMENT_SETTINGS_HELPER_H_
+#define COBALT_WEB_ENVIRONMENT_SETTINGS_HELPER_H_
+
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/context.h"
+
+namespace cobalt {
+namespace web {
+
+Context* get_context(script::EnvironmentSettings* environment_settings);
+script::GlobalEnvironment* get_global_environment(
+    script::EnvironmentSettings* environment_settings);
+script::Wrappable* get_global_wrappable(
+    script::EnvironmentSettings* environment_settings);
+script::ScriptValueFactory* get_script_value_factory(
+    script::EnvironmentSettings* environment_settings);
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_ENVIRONMENT_SETTINGS_HELPER_H_
diff --git a/cobalt/web/event.cc b/cobalt/web/event.cc
index f791fef..46dece3 100644
--- a/cobalt/web/event.cc
+++ b/cobalt/web/event.cc
@@ -26,27 +26,13 @@
   InitEventInternal(base::Token(), false, false);
 }
 
-Event::Event(const std::string& type)
-    : event_phase_(kNone), time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
-  InitEventInternal(base::Token(type), false, false);
-}
-
-Event::Event(const std::string& type, const EventInit& init_dict)
-    : event_phase_(kNone), time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
-  SB_DCHECK(init_dict.has_bubbles());
-  SB_DCHECK(init_dict.has_cancelable());
-  if (init_dict.time_stamp() != 0) {
-    time_stamp_ = init_dict.time_stamp();
-  }
-  InitEventInternal(base::Token(type), init_dict.bubbles(),
-                    init_dict.cancelable());
-}
-
+Event::Event(const std::string& type) : Event(base::Token(type)) {}
 Event::Event(base::Token type)
     : event_phase_(kNone), time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   InitEventInternal(type, false, false);
 }
-
+Event::Event(const std::string& type, const EventInit& init_dict)
+    : Event(base::Token(type), init_dict) {}
 Event::Event(base::Token type, Bubbles bubbles, Cancelable cancelable)
     : event_phase_(kNone), time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
   InitEventInternal(type, bubbles == kBubbles, cancelable == kCancelable);
diff --git a/cobalt/web/global_cache_storage.idl b/cobalt/web/global_cache_storage.idl
new file mode 100644
index 0000000..702faa5
--- /dev/null
+++ b/cobalt/web/global_cache_storage.idl
@@ -0,0 +1,22 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#self-caches
+
+[NoInterfaceObject]
+interface GlobalCacheStorage {
+  readonly attribute CacheStorage caches;
+};
+Window implements GlobalCacheStorage;
+WorkerGlobalScope implements GlobalCacheStorage;
diff --git a/cobalt/web/message_port.h b/cobalt/web/message_port.h
index 9d53f49..68ae01e 100644
--- a/cobalt/web/message_port.h
+++ b/cobalt/web/message_port.h
@@ -90,6 +90,8 @@
                                              event_listener);
   }
 
+  web::EventTarget* event_target() { return event_target_; }
+
   DEFINE_WRAPPABLE_TYPE(MessagePort);
 
  private:
diff --git a/cobalt/web/message_port.idl b/cobalt/web/message_port.idl
index 24bab52..39a4f6e 100644
--- a/cobalt/web/message_port.idl
+++ b/cobalt/web/message_port.idl
@@ -14,7 +14,7 @@
 
 // https://html.spec.whatwg.org/multipage/web-messaging.html#messageport
 // https://html.spec.whatwg.org/dev/web-messaging.html#message-ports
-interface MessagePort /* : EventTarget */ {
+[Exposed = (Window,Worker)] interface MessagePort /* : EventTarget */ {
   // [RaisesException]
   void postMessage(any message);
   // TODO: Support sequence<object>: b/218501774
@@ -28,4 +28,4 @@
   // event handlers
   attribute EventHandler onmessage;
   attribute EventHandler onmessageerror;
-  };
+};
diff --git a/cobalt/web/multi_cache_query_options.idl b/cobalt/web/multi_cache_query_options.idl
new file mode 100644
index 0000000..5ad9e71
--- /dev/null
+++ b/cobalt/web/multi_cache_query_options.idl
@@ -0,0 +1,19 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#dictdef-multicachequeryoptions
+
+dictionary MultiCacheQueryOptions {
+  DOMString cacheName;
+};
diff --git a/cobalt/web/testing/stub_web_context.h b/cobalt/web/testing/stub_web_context.h
index dabc4b6..dd97ea3 100644
--- a/cobalt/web/testing/stub_web_context.h
+++ b/cobalt/web/testing/stub_web_context.h
@@ -66,8 +66,7 @@
   // WebInstance
   //
   base::MessageLoop* message_loop() const final {
-    NOTREACHED();
-    return nullptr;
+    return base::MessageLoop::current();
   }
   void ShutDownJavaScriptEngine() final { NOTREACHED(); }
   loader::FetcherFactory* fetcher_factory() const final {
diff --git a/cobalt/web/window_or_worker_global_scope.cc b/cobalt/web/window_or_worker_global_scope.cc
index f5a3fc3..769cd77 100644
--- a/cobalt/web/window_or_worker_global_scope.cc
+++ b/cobalt/web/window_or_worker_global_scope.cc
@@ -35,6 +35,7 @@
     // Global scope object EventTargets require special handling for onerror
     // events, see EventTarget constructor for more details.
     : EventTarget(settings, kUnpackOnErrorEvents),
+      caches_(new CacheStorage()),
       crypto_(new Crypto()),
       window_timers_(this, stat_tracker, debugger_hooks(),
                      options.initial_state),
@@ -87,6 +88,10 @@
   window_timers_.DisableCallbacks();
 }
 
+scoped_refptr<CacheStorage> WindowOrWorkerGlobalScope::caches() const {
+  return caches_;
+}
+
 scoped_refptr<Crypto> WindowOrWorkerGlobalScope::crypto() const {
   return crypto_;
 }
diff --git a/cobalt/web/window_or_worker_global_scope.h b/cobalt/web/window_or_worker_global_scope.h
index b2c8ea4..73d8c5c 100644
--- a/cobalt/web/window_or_worker_global_scope.h
+++ b/cobalt/web/window_or_worker_global_scope.h
@@ -23,6 +23,7 @@
 #include "base/memory/ref_counted.h"
 #include "cobalt/loader/cors_preflight_cache.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/cache_storage.h"
 #include "cobalt/web/crypto.h"
 #include "cobalt/web/csp_delegate.h"
 #include "cobalt/web/csp_delegate_type.h"
@@ -98,7 +99,10 @@
     return nullptr;
   }
 
-  web::CspDelegate* csp_delegate() const { return csp_delegate_.get(); }
+  web::CspDelegate* csp_delegate() const {
+    DCHECK(csp_delegate_);
+    return csp_delegate_.get();
+  }
 
   const scoped_refptr<loader::CORSPreflightCache> get_preflight_cache() {
     return preflight_cache_;
@@ -129,6 +133,10 @@
 
   void DestroyTimers();
 
+  // Web API: GlobalCacheStorage (implements)
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#cachestorage
+  scoped_refptr<CacheStorage> caches() const;
+
   // Web API: GlobalCrypto (implements)
   //   https://www.w3.org/TR/WebCryptoAPI/#crypto-interface
   scoped_refptr<Crypto> crypto() const;
@@ -136,6 +144,7 @@
  protected:
   virtual ~WindowOrWorkerGlobalScope() {}
 
+  scoped_refptr<CacheStorage> caches_;
   scoped_refptr<Crypto> crypto_;
   web::WindowTimers window_timers_;
 
diff --git a/cobalt/webdriver/screenshot.cc b/cobalt/webdriver/screenshot.cc
index 9c7373b..184e9c0 100644
--- a/cobalt/webdriver/screenshot.cc
+++ b/cobalt/webdriver/screenshot.cc
@@ -1,3 +1,4 @@
+
 // Copyright 2019 The Cobalt Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/cobalt/websocket/web_socket.cc b/cobalt/websocket/web_socket.cc
index 82db7a7..e842657 100644
--- a/cobalt/websocket/web_socket.cc
+++ b/cobalt/websocket/web_socket.cc
@@ -562,8 +562,6 @@
 }
 
 web::CspDelegate* WebSocket::csp_delegate() const {
-  DCHECK(environment_settings());
-  DCHECK(environment_settings()->context());
   DCHECK(environment_settings()->context()->GetWindowOrWorkerGlobalScope());
   return environment_settings()
       ->context()
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
index cf05426..5e6df51 100644
--- a/cobalt/worker/BUILD.gn
+++ b/cobalt/worker/BUILD.gn
@@ -30,6 +30,8 @@
     "extendable_event.h",
     "extendable_message_event.cc",
     "extendable_message_event.h",
+    "fetch_event.cc",
+    "fetch_event.h",
     "navigation_preload_manager.cc",
     "navigation_preload_manager.h",
     "service_worker.cc",
@@ -63,6 +65,7 @@
 
   deps = [
     "//cobalt/browser:generated_bindings",
+    "//cobalt/loader",
     "//cobalt/web",
     "//url",
   ]
diff --git a/cobalt/worker/client.cc b/cobalt/worker/client.cc
index 20349f9..7ca45be 100644
--- a/cobalt/worker/client.cc
+++ b/cobalt/worker/client.cc
@@ -29,7 +29,7 @@
                       ->service_worker()) {
   DCHECK(client);
   // Algorithm for Create Client:
-  //   https://w3c.github.io/ServiceWorker/#create-client
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-client
   // 1. Let clientObject be a new Client object.
   // 2. Set clientObject’s service worker client to client.
   service_worker_client_ = client;
@@ -39,7 +39,7 @@
 
 ClientType Client::type() {
   // Algorithm for the type getter:
-  //   https://w3c.github.io/ServiceWorker/#client-type
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#client-type
 
   // 1. Let client be this's service worker client.
   web::WindowOrWorkerGlobalScope* client_global_scope =
diff --git a/cobalt/worker/client.h b/cobalt/worker/client.h
index c40ca72..70179aa 100644
--- a/cobalt/worker/client.h
+++ b/cobalt/worker/client.h
@@ -28,22 +28,22 @@
 
 class Client : public web::MessagePort {
  public:
-  // https://w3c.github.io/ServiceWorker/#create-client-algorithm
-  static Client* Create(web::EnvironmentSettings* client) {
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-client-algorithm
+  static scoped_refptr<Client> Create(web::EnvironmentSettings* client) {
     return new Client(client);
   }
   ~Client() { service_worker_client_ = nullptr; }
 
   std::string url() {
-    // https://w3c.github.io/ServiceWorker/#client-url
+    // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#client-url
     return service_worker_client_->creation_url().spec();
   }
 
-  // https://w3c.github.io/ServiceWorker/#client-frametype
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#client-frametype
   void set_frame_type(FrameType frame_type) { frame_type_ = frame_type; }
   FrameType frame_type() { return frame_type_; }
 
-  // https://w3c.github.io/ServiceWorker/#dom-client-id
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-client-id
   std::string id() { return service_worker_client_->id(); }
   ClientType type();
 
diff --git a/cobalt/worker/clients.cc b/cobalt/worker/clients.cc
index b1ce469..0a81e08 100644
--- a/cobalt/worker/clients.cc
+++ b/cobalt/worker/clients.cc
@@ -59,7 +59,7 @@
   TRACE_EVENT0("cobalt::worker", "Clients::Get()");
   DCHECK_EQ(base::MessageLoop::current(), settings_->context()->message_loop());
   // Algorithm for get(id):
-  //   https://w3c.github.io/ServiceWorker/#clients-get
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
   // 1. Let promise be a new promise.
   script::HandlePromiseWrappable promise =
       settings_->context()
@@ -89,7 +89,7 @@
   TRACE_EVENT0("cobalt::worker", "Clients::MatchAll()");
   DCHECK_EQ(base::MessageLoop::current(), settings_->context()->message_loop());
   // Algorithm for matchAll():
-  //   https://w3c.github.io/ServiceWorker/#clients-matchall
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
   // 1. Let promise be a new promise.
   auto promise = settings_->context()
                      ->global_environment()
@@ -117,7 +117,7 @@
   TRACE_EVENT0("cobalt::worker", "Clients::Claim()");
   DCHECK_EQ(base::MessageLoop::current(), settings_->context()->message_loop());
   // Algorithm for claim():
-  //   https://w3c.github.io/ServiceWorker/#clients-claim
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-claim
   // 2. Let promise be a new promise.
   // Note: Done first because it's needed for rejecting in step 1.
   auto promise = settings_->context()
diff --git a/cobalt/worker/extendable_event.cc b/cobalt/worker/extendable_event.cc
index 6b49b47..5aaa923 100644
--- a/cobalt/worker/extendable_event.cc
+++ b/cobalt/worker/extendable_event.cc
@@ -22,7 +22,7 @@
     std::unique_ptr<script::Promise<script::ValueHandle*>>& promise,
     script::ExceptionState* exception_state) {
   // Algorithm for waitUntil(), to add lifetime promise to event.
-  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
 
   // 1. If event’s isTrusted attribute is false, throw an "InvalidStateError"
   //    DOMException.
@@ -50,7 +50,7 @@
     const script::Promise<script::ValueHandle*>* promise) {
   // Implement the microtask called upon fulfillment or rejection of a
   // promise, as part of the algorithm for waitUntil().
-  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
   DCHECK(promise);
   has_rejected_promise_ |= promise->State() == script::PromiseState::kRejected;
   // 5.1. Decrement event’s pending promises count by one.
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h
index 051e034..f915189 100644
--- a/cobalt/worker/extendable_event.h
+++ b/cobalt/worker/extendable_event.h
@@ -44,11 +44,13 @@
 class ExtendableEvent : public web::Event {
  public:
   explicit ExtendableEvent(const std::string& type) : Event(type) {}
+  ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict)
+      : ExtendableEvent(base::Token(type), init_dict) {}
   ExtendableEvent(base::Token type,
                   base::OnceCallback<void(bool)> done_callback =
                       base::OnceCallback<void(bool)>())
       : Event(type), done_callback_(std::move(done_callback)) {}
-  ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict)
+  ExtendableEvent(base::Token type, const ExtendableEventInit& init_dict)
       : Event(type, init_dict) {}
 
   void WaitUntil(
@@ -63,7 +65,7 @@
     // An ExtendableEvent object is said to be active when its timed out flag
     // is unset and either its pending promises count is greater than zero or
     // its dispatch flag is set.
-    //   https://w3c.github.io/ServiceWorker/#extendableevent-active
+    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#extendableevent-active
     return !timed_out_flag_ &&
            ((pending_promise_count_ > 0) || IsBeingDispatched());
   }
@@ -76,11 +78,11 @@
   ~ExtendableEvent() override {}
 
  private:
-  // https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#extendableevent-extend-lifetime-promises
   // std::list<script::Promise<script::ValueHandle*>> extend_lifetime_promises_;
   int pending_promise_count_ = 0;
   bool has_rejected_promise_ = false;
-  // https://w3c.github.io/ServiceWorker/#extendableevent-timed-out-flag
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#extendableevent-timed-out-flag
   bool timed_out_flag_ = false;
 
   base::OnceCallback<void(bool)> done_callback_;
diff --git a/cobalt/worker/extendable_message_event.cc b/cobalt/worker/extendable_message_event.cc
index 200e2a6..aca29cb 100644
--- a/cobalt/worker/extendable_message_event.cc
+++ b/cobalt/worker/extendable_message_event.cc
@@ -28,7 +28,7 @@
 namespace worker {
 
 ExtendableMessageEvent::ExtendableMessageEvent(
-    const std::string& type, const ExtendableMessageEventInit& init_dict)
+    base::Token type, const ExtendableMessageEventInit& init_dict)
     : ExtendableEvent(type, init_dict) {
   if (init_dict.has_data() && init_dict.data()) {
     DCHECK(init_dict.data());
@@ -40,8 +40,8 @@
   if (init_dict.has_last_event_id()) {
     last_event_id_ = init_dict.last_event_id();
   }
-  if (init_dict.has_source()) {
-    source_ = init_dict.source();
+  if (init_dict.has_source() && init_dict.source().has_value()) {
+    source_ = init_dict.source().value();
   }
   if (init_dict.has_ports()) {
     ports_ = init_dict.ports();
diff --git a/cobalt/worker/extendable_message_event.h b/cobalt/worker/extendable_message_event.h
index 475be64..965f79e 100644
--- a/cobalt/worker/extendable_message_event.h
+++ b/cobalt/worker/extendable_message_event.h
@@ -45,18 +45,24 @@
   explicit ExtendableMessageEvent(const std::string& type)
       : ExtendableEvent(type) {}
   explicit ExtendableMessageEvent(base::Token type) : ExtendableEvent(type) {}
-  ExtendableMessageEvent(base::Token type,
-                         std::unique_ptr<script::DataBuffer> data)
-      : ExtendableEvent(type), data_(std::move(data)) {}
   ExtendableMessageEvent(const std::string& type,
+                         const ExtendableMessageEventInit& init_dict)
+      : ExtendableMessageEvent(base::Token(type), init_dict) {}
+  ExtendableMessageEvent(base::Token type,
                          const ExtendableMessageEventInit& init_dict);
+  ExtendableMessageEvent(base::Token type,
+                         const ExtendableMessageEventInit& init_dict,
+                         std::unique_ptr<script::DataBuffer> data)
+      : ExtendableMessageEvent(type, init_dict) {
+    data_ = std::move(data);
+  }
 
   script::Handle<script::ValueHandle> data(
       script::EnvironmentSettings* settings = nullptr) const;
 
   const std::string& origin() const { return origin_; }
   const std::string& last_event_id() const { return last_event_id_; }
-  const scoped_refptr<web::EventTarget>& source() const { return source_; }
+  SourceType source() const { return source_; }
   script::Sequence<scoped_refptr<MessagePort>> ports() const { return ports_; }
 
   // These helper functions are custom, and not in any spec.
@@ -64,9 +70,7 @@
   void set_last_event_id(const std::string& last_event_id) {
     last_event_id_ = last_event_id;
   }
-  void set_source(const scoped_refptr<web::EventTarget>& source) {
-    source_ = source;
-  }
+  void set_source(const SourceType& source) { source_ = source; }
   void set_ports(script::Sequence<scoped_refptr<MessagePort>> ports) {
     ports_ = ports;
   }
@@ -79,7 +83,7 @@
  private:
   std::string origin_;
   std::string last_event_id_;
-  scoped_refptr<web::EventTarget> source_;
+  SourceType source_;
   script::Sequence<scoped_refptr<MessagePort>> ports_;
   std::unique_ptr<script::DataBuffer> data_;
 };
diff --git a/cobalt/worker/extendable_message_event.idl b/cobalt/worker/extendable_message_event.idl
index 600fa71..30a2001 100644
--- a/cobalt/worker/extendable_message_event.idl
+++ b/cobalt/worker/extendable_message_event.idl
@@ -21,7 +21,7 @@
   [CallWith = EnvironmentSettings] readonly attribute any data;
   readonly attribute USVString origin;
   readonly attribute DOMString lastEventId;
-  readonly attribute EventTarget                             ? source;
+  readonly attribute (Client or ServiceWorker or MessagePort)? source;
   // TODO(b/236750294): Make this be FrozenArray<MessagePort> when available.
   readonly attribute sequence<MessagePort> ports;
 };
diff --git a/cobalt/worker/extendable_message_event_init.idl b/cobalt/worker/extendable_message_event_init.idl
index 82c4e09..7b6cc75 100644
--- a/cobalt/worker/extendable_message_event_init.idl
+++ b/cobalt/worker/extendable_message_event_init.idl
@@ -18,7 +18,6 @@
   any data;
   USVString origin = "";
   DOMString lastEventId = "";
-  EventTarget? source;
-  //(Client or ServiceWorker or MessagePort) ? source = null;
+  (Client or ServiceWorker or MessagePort) ? source;
   sequence<MessagePort> ports;
 };
diff --git a/cobalt/worker/extendable_message_event_test.cc b/cobalt/worker/extendable_message_event_test.cc
index b5162b7..2e8e64f 100644
--- a/cobalt/worker/extendable_message_event_test.cc
+++ b/cobalt/worker/extendable_message_event_test.cc
@@ -34,6 +34,7 @@
 namespace worker {
 
 using script::testing::FakeScriptValue;
+using ::testing::IsSubstring;
 using web::testing::MockEventListener;
 
 namespace {
@@ -78,8 +79,9 @@
   EXPECT_NE(nullptr, data.get());
   EXPECT_NE(nullptr, data->ptr);
   EXPECT_GT(data->size, 0U);
-  scoped_refptr<ExtendableMessageEvent> event =
-      new ExtendableMessageEvent(base::Tokens::message(), std::move(data));
+  const ExtendableMessageEventInit init;
+  scoped_refptr<ExtendableMessageEvent> event = new ExtendableMessageEvent(
+      base::Tokens::message(), init, std::move(data));
 
   EXPECT_EQ("message", event->type());
   EXPECT_EQ(NULL, event->target().get());
@@ -125,7 +127,9 @@
   EXPECT_TRUE(event_data.IsEmpty());
   EXPECT_TRUE(event->origin().empty());
   EXPECT_TRUE(event->last_event_id().empty());
-  EXPECT_EQ(nullptr, event->source().get());
+  EXPECT_FALSE(event->source().IsType<scoped_refptr<Client>>());
+  EXPECT_FALSE(event->source().IsType<scoped_refptr<ServiceWorker>>());
+  EXPECT_FALSE(event->source().IsType<scoped_refptr<web::MessagePort>>());
   EXPECT_TRUE(event->ports().empty());
 }
 
@@ -137,7 +141,9 @@
   init.set_data(&(reference->referenced_value()));
   init.set_origin("OriginString");
   init.set_last_event_id("lastEventIdString");
-  init.set_source(web_context()->GetWindowOrWorkerGlobalScope());
+  base::Optional<ExtendableMessageEvent::SourceType> client(
+      Client::Create(web_context()->environment_settings()));
+  init.set_source(client);
   scoped_refptr<ExtendableMessageEvent> event =
       new ExtendableMessageEvent("mytestevent", init);
 
@@ -164,20 +170,39 @@
 
   EXPECT_EQ("OriginString", event->origin());
   EXPECT_EQ("lastEventIdString", event->last_event_id());
-  EXPECT_EQ(web_context()->GetWindowOrWorkerGlobalScope(),
-            event->source().get());
+  EXPECT_TRUE(event->source().IsType<scoped_refptr<Client>>());
+  EXPECT_EQ(client.value().AsType<scoped_refptr<Client>>().get(),
+            event->source().AsType<scoped_refptr<Client>>().get());
   EXPECT_TRUE(event->ports().empty());
 }
 
 TEST_F(ExtendableMessageEventTestWithJavaScript,
+       ConstructorWithEventTypeAndInvalidSource) {
+  std::string result;
+  EXPECT_FALSE(
+      EvaluateScript("var event = new ExtendableMessageEvent('dog', "
+                     "    {source: this});"
+                     "if (event.type == 'dog') event.source;",
+                     &result))
+      << "Failed to evaluate script.";
+  EXPECT_PRED_FORMAT2(IsSubstring,
+                      "TypeError: Value is not a member of the union type.",
+                      result);
+  // EXPECT_FALSE(IsSubstring(
+  //    "", "", "TypeError: Value is not a member of the union type.", result));
+}
+
+
+TEST_F(ExtendableMessageEventTestWithJavaScript,
        ConstructorWithEventTypeAndExtendableMessageEventInitDict) {
   std::string result;
+  // Note: None of the types for 'source' are constructible, so that can
+  // not easily be tested here.
   EXPECT_TRUE(
       EvaluateScript("var event = new ExtendableMessageEvent('dog', "
                      "    {cancelable: true, "
                      "     origin: 'OriginValue',"
                      "     lastEventId: 'LastEventIdValue',"
-                     "     source: this,"
                      "     data: {value: 'data_value'},"
                      "    }"
                      ");"
@@ -186,7 +211,6 @@
                      "    event.cancelable == true &&"
                      "    event.origin == 'OriginValue' &&"
                      "    event.lastEventId == 'LastEventIdValue' &&"
-                     "    event.source == this &&"
                      "    event.ports.length == 0 &&"
                      "    event.data.value == 'data_value') "
                      "    event.data.value;",
diff --git a/cobalt/worker/fetch_event.cc b/cobalt/worker/fetch_event.cc
new file mode 100644
index 0000000..4480643
--- /dev/null
+++ b/cobalt/worker/fetch_event.cc
@@ -0,0 +1,169 @@
+// Copyright 2022 The Cobalt Authors. 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/worker/fetch_event.h"
+
+#include <memory>
+#include <utility>
+
+#include "cobalt/script/v8c/conversion_helpers.h"
+#include "cobalt/script/v8c/v8c_value_handle.h"
+#include "cobalt/web/cache_utils.h"
+#include "cobalt/web/environment_settings_helper.h"
+
+namespace cobalt {
+namespace worker {
+
+FetchEvent::FetchEvent(script::EnvironmentSettings* environment_settings,
+                       const std::string& type,
+                       const FetchEventInit& event_init_dict)
+    : FetchEvent(
+          environment_settings, base::Token(type), event_init_dict,
+          /*respond_with_callback=*/std::make_unique<RespondWithCallback>(),
+          /*report_load_timing_info=*/
+          std::make_unique<ReportLoadTimingInfo>()) {}
+
+FetchEvent::FetchEvent(
+    script::EnvironmentSettings* environment_settings, base::Token type,
+    const FetchEventInit& event_init_dict,
+    std::unique_ptr<RespondWithCallback> respond_with_callback,
+    std::unique_ptr<ReportLoadTimingInfo> report_load_timing_info)
+    : ExtendableEvent(type, event_init_dict),
+      respond_with_callback_(std::move(respond_with_callback)),
+      report_load_timing_info_(std::move(report_load_timing_info)) {
+  auto script_value_factory =
+      web::get_script_value_factory(environment_settings);
+  handled_property_ = std::make_unique<script::ValuePromiseVoid::Reference>(
+      this, script_value_factory->CreateBasicPromise<void>());
+  request_ = std::make_unique<script::ValueHandleHolder::Reference>(
+      this, event_init_dict.request());
+
+  load_timing_info_.request_start = base::TimeTicks::Now();
+  load_timing_info_.request_start_time = base::Time::Now();
+  load_timing_info_.send_start = base::TimeTicks::Now();
+  load_timing_info_.send_end = base::TimeTicks::Now();
+  load_timing_info_.service_worker_start_time = base::TimeTicks::Now();
+}
+
+void FetchEvent::RespondWith(
+    script::EnvironmentSettings* environment_settings,
+    std::unique_ptr<script::Promise<script::ValueHandle*>>& response) {
+  respond_with_called_ = true;
+
+  // TODO: call |WaitUntil()|.
+  v8::Local<v8::Promise> v8_response = response->promise();
+  auto* global_environment = web::get_global_environment(environment_settings);
+  auto* isolate = global_environment->isolate();
+  auto context = isolate->GetCurrentContext();
+  auto data = v8::Object::New(isolate);
+  web::cache_utils::SetExternal(context, data, "environment_settings",
+                                environment_settings);
+  web::cache_utils::SetOwnedExternal(context, data, "respond_with_callback",
+                                     std::move(respond_with_callback_));
+  web::cache_utils::SetOwnedExternal(context, data, "report_load_timing_info",
+                                     std::move(report_load_timing_info_));
+  web::cache_utils::SetExternal(context, data, "load_timing_info",
+                                &load_timing_info_);
+  web::cache_utils::SetExternal(context, data, "handled",
+                                handled_property_.get());
+  auto result = v8_response->Then(
+      context,
+      v8::Function::New(
+          context,
+          [](const v8::FunctionCallbackInfo<v8::Value>& info) {
+            auto* isolate = info.GetIsolate();
+            auto context = info.GetIsolate()->GetCurrentContext();
+            v8::Local<v8::Value> text_result;
+            if (!web::cache_utils::TryCall(context, /*object=*/info[0], "text")
+                     .ToLocal(&text_result)) {
+              auto respond_with_callback =
+                  web::cache_utils::GetOwnedExternal<RespondWithCallback>(
+                      context, info.Data(), "respond_with_callback");
+              std::move(*respond_with_callback)
+                  .Run(std::make_unique<std::string>());
+              return;
+            }
+            auto* load_timing_info =
+                web::cache_utils::GetExternal<net::LoadTimingInfo>(
+                    context, info.Data(), "load_timing_info");
+            load_timing_info->receive_headers_end = base::TimeTicks::Now();
+            auto result = text_result.As<v8::Promise>()->Then(
+                context,
+                v8::Function::New(
+                    context,
+                    [](const v8::FunctionCallbackInfo<v8::Value>& info) {
+                      auto* isolate = info.GetIsolate();
+                      auto context = info.GetIsolate()->GetCurrentContext();
+                      auto* handled = web::cache_utils::GetExternal<
+                          script::ValuePromiseVoid::Reference>(
+                          context, info.Data(), "handled");
+                      handled->value().Resolve();
+                      auto respond_with_callback =
+                          web::cache_utils::GetOwnedExternal<
+                              RespondWithCallback>(context, info.Data(),
+                                                   "respond_with_callback");
+                      auto body = std::make_unique<std::string>();
+                      FromJSValue(isolate, info[0],
+                                  script::v8c::kNoConversionFlags, nullptr,
+                                  body.get());
+                      auto* load_timing_info =
+                          web::cache_utils::GetExternal<net::LoadTimingInfo>(
+                              context, info.Data(), "load_timing_info");
+                      auto report_load_timing_info =
+                          web::cache_utils::GetOwnedExternal<
+                              ReportLoadTimingInfo>(context, info.Data(),
+                                                    "report_load_timing_info");
+                      std::move(*report_load_timing_info)
+                          .Run(*load_timing_info);
+                      auto* environment_settings =
+                          web::cache_utils::GetExternal<
+                              script::EnvironmentSettings>(
+                              context, info.Data(), "environment_settings");
+                      web::get_context(environment_settings)
+                          ->network_module()
+                          ->task_runner()
+                          ->PostTask(
+                              FROM_HERE,
+                              base::BindOnce(
+                                  [](std::unique_ptr<base::OnceCallback<void(
+                                         std::unique_ptr<std::string>)>>
+                                         respond_with_callback,
+                                     std::unique_ptr<std::string> body) {
+                                    std::move(*respond_with_callback)
+                                        .Run(std::move(body));
+                                  },
+                                  std::move(respond_with_callback),
+                                  std::move(body)));
+                    },
+                    info.Data())
+                    .ToLocalChecked());
+            if (result.IsEmpty()) {
+              LOG(WARNING) << "Failure during FetchEvent respondWith handling. "
+                              "Retrieving Response text failed.";
+            }
+          },
+          data)
+          .ToLocalChecked());
+  if (result.IsEmpty()) {
+    LOG(WARNING) << "Failure during FetchEvent respondWith handling.";
+  }
+}
+
+script::HandlePromiseVoid FetchEvent::handled(
+    script::EnvironmentSettings* environment_settings) {
+  return script::HandlePromiseVoid(handled_property_->referenced_value());
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/fetch_event.h b/cobalt/worker/fetch_event.h
new file mode 100644
index 0000000..ac3ff69
--- /dev/null
+++ b/cobalt/worker/fetch_event.h
@@ -0,0 +1,73 @@
+// Copyright 2022 The Cobalt Authors. 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_WORKER_FETCH_EVENT_H_
+#define COBALT_WORKER_FETCH_EVENT_H_
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "cobalt/base/token.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/exception_state.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/worker/extendable_event.h"
+#include "cobalt/worker/fetch_event_init.h"
+#include "net/base/load_timing_info.h"
+
+namespace cobalt {
+namespace worker {
+
+class FetchEvent : public ::cobalt::worker::ExtendableEvent {
+ public:
+  using RespondWithCallback =
+      base::OnceCallback<void(std::unique_ptr<std::string>)>;
+  using ReportLoadTimingInfo =
+      base::OnceCallback<void(const net::LoadTimingInfo&)>;
+
+  FetchEvent(script::EnvironmentSettings*, const std::string& type,
+             const FetchEventInit& event_init_dict);
+  FetchEvent(script::EnvironmentSettings*, base::Token type,
+             const FetchEventInit& event_init_dict,
+             std::unique_ptr<RespondWithCallback> respond_with_callback,
+             std::unique_ptr<ReportLoadTimingInfo> report_load_timing_info);
+  ~FetchEvent() override = default;
+
+  void RespondWith(
+      script::EnvironmentSettings*,
+      std::unique_ptr<script::Promise<script::ValueHandle*>>& response);
+  script::HandlePromiseVoid handled(script::EnvironmentSettings*);
+
+  const script::ValueHandleHolder* request() {
+    return &(request_->referenced_value());
+  }
+
+  bool respond_with_called() const { return respond_with_called_; }
+
+  DEFINE_WRAPPABLE_TYPE(FetchEvent);
+
+ private:
+  std::unique_ptr<RespondWithCallback> respond_with_callback_;
+  std::unique_ptr<ReportLoadTimingInfo> report_load_timing_info_;
+  std::unique_ptr<script::ValueHandleHolder::Reference> request_;
+  std::unique_ptr<script::ValuePromiseVoid::Reference> handled_property_;
+  bool respond_with_called_ = false;
+  net::LoadTimingInfo load_timing_info_;
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_FETCH_EVENT_H_
diff --git a/cobalt/worker/fetch_event.idl b/cobalt/worker/fetch_event.idl
new file mode 100644
index 0000000..04253c3
--- /dev/null
+++ b/cobalt/worker/fetch_event.idl
@@ -0,0 +1,29 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#fetchevent-interface
+
+[
+  Exposed=ServiceWorker,
+  ConstructorCallWith=EnvironmentSettings,
+  Constructor(DOMString type, FetchEventInit eventInitDict)
+] interface FetchEvent : ExtendableEvent {
+  // Returns |any| type because |Request| is polyfilled.
+  [SameObject] readonly attribute any request;
+
+
+  // Takes a |Promise<any>| because |Response| is polyfilled.
+  [CallWith=EnvironmentSettings] void respondWith(Promise<any> response);
+  [CallWith=EnvironmentSettings] readonly attribute Promise<void> handled;
+};
diff --git a/cobalt/worker/fetch_event_init.idl b/cobalt/worker/fetch_event_init.idl
new file mode 100644
index 0000000..ace8fb1
--- /dev/null
+++ b/cobalt/worker/fetch_event_init.idl
@@ -0,0 +1,23 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#dictdef-fetcheventinit
+
+dictionary FetchEventInit : ExtendableEventInit {
+  // |any| type because |Request| is polyfilled.
+  any request = null;
+  DOMString clientId = "";
+  DOMString resultingClientId = "";
+  boolean isReload = false;
+};
diff --git a/cobalt/worker/navigation_preload_manager.idl b/cobalt/worker/navigation_preload_manager.idl
index 5a05303..92b8175 100644
--- a/cobalt/worker/navigation_preload_manager.idl
+++ b/cobalt/worker/navigation_preload_manager.idl
@@ -14,7 +14,7 @@
 
 // https://w3c.github.io/ServiceWorker/#navigation-preload-manager
 
-[Exposed = (Window, ServiceWorker)] interface NavigationPreloadManager {
+[Exposed = (Window,ServiceWorker)] interface NavigationPreloadManager {
   Promise<void> enable();
   Promise<void> disable();
   Promise<void> setHeaderValue(ByteString value);
diff --git a/cobalt/worker/service_worker.cc b/cobalt/worker/service_worker.cc
index 0756da2..6eb5597 100644
--- a/cobalt/worker/service_worker.cc
+++ b/cobalt/worker/service_worker.cc
@@ -18,12 +18,13 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "cobalt/base/tokens.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/message_port.h"
-#include "cobalt/worker/extendable_message_event.h"
 #include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_state.h"
 
@@ -36,30 +37,37 @@
       worker_(worker),
       state_(kServiceWorkerStateParsed) {}
 
-
 void ServiceWorker::PostMessage(const script::ValueHandleHolder& message) {
-  // https://w3c.github.io/ServiceWorker/#service-worker-postmessage-options
-  web::EventTarget* event_target = worker_->worker_global_scope();
-  if (!event_target) return;
+  // Algorithm for ServiceWorker.postMessage():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage
 
-  base::MessageLoop* message_loop =
-      event_target->environment_settings()->context()->message_loop();
-  if (!message_loop) {
-    return;
-  }
-  std::unique_ptr<script::DataBuffer> data_buffer(
+  // 1. Let serviceWorker be the service worker represented by this.
+  ServiceWorkerObject* service_worker = service_worker_object();
+  // 2. Let incumbentSettings be the incumbent settings object.
+  web::EnvironmentSettings* incumbent_settings = environment_settings();
+  // 3. Let incumbentGlobal be incumbentSettings’s global object.
+  // 4. Let serializeWithTransferResult be
+  //    StructuredSerializeWithTransfer(message, options["transfer"]).
+  //    Rethrow any exceptions.
+  std::unique_ptr<script::DataBuffer> serialize_result(
       script::SerializeScriptValue(message));
-  if (!data_buffer) {
+  if (!serialize_result) {
     return;
   }
-  message_loop->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](web::EventTarget* event_target,
-                        std::unique_ptr<script::DataBuffer> data_buffer) {
-                       event_target->DispatchEvent(new ExtendableMessageEvent(
-                           base::Tokens::message(), std::move(data_buffer)));
-                     },
-                     base::Unretained(event_target), std::move(data_buffer)));
+  // 5. If the result of running the Should Skip Event algorithm with
+  // "message"
+  //    and serviceWorker is true, then return.
+  if (service_worker->ShouldSkipEvent(base::Tokens::message())) return;
+  // 6. Run these substeps in parallel:
+  ServiceWorkerJobs* jobs =
+      incumbent_settings->context()->service_worker_jobs();
+  DCHECK(jobs);
+  jobs->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ServiceWorkerJobs::ServiceWorkerPostMessageSubSteps,
+                     base::Unretained(jobs), base::Unretained(service_worker),
+                     base::Unretained(incumbent_settings),
+                     std::move(serialize_result)));
 }
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker.h b/cobalt/worker/service_worker.h
index 193a6a4..07d4df5 100644
--- a/cobalt/worker/service_worker.h
+++ b/cobalt/worker/service_worker.h
@@ -34,7 +34,7 @@
 
 // The ServiceWorker interface represents a service worker within a service
 // worker client realm.
-//   https://w3c.github.io/ServiceWorker/#serviceworker-interface
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#serviceworker-interface
 class ServiceWorker : public AbstractWorker, public web::EventTarget {
  public:
   ServiceWorker(script::EnvironmentSettings* settings,
@@ -50,7 +50,7 @@
   // service worker's serialized script url.
   std::string script_url() const { return worker_->script_url().spec(); }
 
-  // https://w3c.github.io/ServiceWorker/#dom-serviceworker-state
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworker-state
   void set_state(ServiceWorkerState state) { state_ = state; }
   ServiceWorkerState state() const { return state_; }
 
@@ -70,7 +70,9 @@
     SetAttributeEventListener(base::Tokens::error(), event_listener);
   }
 
-  ServiceWorkerObject* service_worker_object() { return worker_; }
+  const scoped_refptr<ServiceWorkerObject>& service_worker_object() {
+    return worker_;
+  }
 
   DEFINE_WRAPPABLE_TYPE(ServiceWorker);
 
diff --git a/cobalt/worker/service_worker.idl b/cobalt/worker/service_worker.idl
index b1b9602..f322feb 100644
--- a/cobalt/worker/service_worker.idl
+++ b/cobalt/worker/service_worker.idl
@@ -14,7 +14,7 @@
 
 // https://w3c.github.io/ServiceWorker/#serviceworker-interface
 
-[Exposed = (Window, ServiceWorker)] interface ServiceWorker : EventTarget {
+[Exposed = (Window,ServiceWorker)] interface ServiceWorker : EventTarget {
 
   // Todo: Implement postMessage b/219986442
   void postMessage(any message);
diff --git a/cobalt/worker/service_worker_container.cc b/cobalt/worker/service_worker_container.cc
index 43fb13a..97677b2 100644
--- a/cobalt/worker/service_worker_container.cc
+++ b/cobalt/worker/service_worker_container.cc
@@ -42,7 +42,7 @@
 
 scoped_refptr<ServiceWorker> ServiceWorkerContainer::controller() {
   // Algorithm for controller:
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-controller
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-controller
   // 1. Let client be this's service worker client.
   web::EnvironmentSettings* client = environment_settings();
 
@@ -59,7 +59,7 @@
 
 script::HandlePromiseWrappable ServiceWorkerContainer::ready() {
   // Algorithm for ready attribute:
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-ready
   // 1. If this's ready promise is null, then set this's ready promise to a new
   //    promise.
   if (!promise_reference_) {
@@ -92,8 +92,8 @@
     ServiceWorkerRegistrationObject* registration) {
   // This implements resolving of the ready promise for the Activate algorithm
   // (steps 7.1-7.3) as well as for the ready attribute (step 3.3).
-  //   https://w3c.github.io/ServiceWorker/#activation-algorithm
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-ready
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerContainer::MaybeResolveReadyPromise()");
   DCHECK_EQ(base::MessageLoop::current(),
@@ -123,7 +123,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerContainer::Register()");
   DCHECK_EQ(base::MessageLoop::current(),
             environment_settings()->context()->message_loop());
-  // https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-register
   // 1. Let p be a promise.
   script::HandlePromiseWrappable promise =
       environment_settings()
@@ -165,7 +165,7 @@
   DCHECK_EQ(base::MessageLoop::current(),
             environment_settings()->context()->message_loop());
   // Algorithm for 'ServiceWorkerContainer.getRegistration()':
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
   // Let promise be a new promise.
   // Perform the rest of the steps in a task, because the promise has to be
   // returned before we can safely reject or resolve it.
@@ -195,7 +195,7 @@
   DCHECK_EQ(base::MessageLoop::current(),
             environment_settings()->context()->message_loop());
   // Algorithm for 'ServiceWorkerContainer.getRegistration()':
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
   // 1. Let client be this's service worker client.
   web::EnvironmentSettings* client = environment_settings();
 
@@ -242,36 +242,35 @@
 
 script::HandlePromiseSequenceWrappable
 ServiceWorkerContainer::GetRegistrations() {
-  // https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistrations
-  // 1. Let client be this's service worker client.
-  // 2. Let promise be a new promise.
-  // 3. Run the following steps in parallel:
-  //    1. Let registrations be a new list.
-  //    2. For each scope → registration of scope to registration map:
-  //       1. If the origin of the result of parsing scope is the same as
-  //          client’s origin, then append registration to registrations.
-  //    3. Queue a task on promise’s relevant settings object's responsible
-  //       event loop, using the DOM manipulation task source, to run the
-  //       following steps:
-  //       1. Let registrationObjects be a new list.
-  //       2. For each registration of registrations:
-  //          1. Let registrationObj be the result of getting the service worker
-  //             registration object that represents registration in promise’s
-  //             relevant settings object.
-  //          2. Append registrationObj to registrationObjects.
-  //       3. Resolve promise with a new frozen array of registrationObjects in
-  //          promise’s relevant Realm.
-  // 4. Return promise.
-  // TODO(b/235531652): Implement getRegistrations().
   auto promise = environment_settings()
                      ->context()
                      ->global_environment()
                      ->script_value_factory()
                      ->CreateBasicPromise<script::SequenceWrappable>();
-
+  std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+      promise_reference(
+          new script::ValuePromiseSequenceWrappable::Reference(this, promise));
+  base::MessageLoop::current()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ServiceWorkerContainer::GetRegistrationsTask,
+                     base::Unretained(this), std::move(promise_reference)));
   return promise;
 }
 
+void ServiceWorkerContainer::GetRegistrationsTask(
+    std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+        promise_reference) {
+  auto* client = environment_settings();
+  // https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistrations
+  worker::ServiceWorkerJobs* jobs =
+      environment_settings()->context()->service_worker_jobs();
+  url::Origin storage_key = environment_settings()->ObtainStorageKey();
+  jobs->message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::GetRegistrationsSubSteps,
+                                base::Unretained(jobs), storage_key, client,
+                                std::move(promise_reference)));
+}
+
 void ServiceWorkerContainer::StartMessages() {}
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker_container.h b/cobalt/worker/service_worker_container.h
index c28718f..aca45b6 100644
--- a/cobalt/worker/service_worker_container.h
+++ b/cobalt/worker/service_worker_container.h
@@ -34,12 +34,12 @@
 
 // The ServiceWorkerContainer interface represents the interface to register and
 // access service workers from a service worker client realm.
-//   https://w3c.github.io/ServiceWorker/#serviceworkercontainer-interface
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#serviceworkercontainer-interface
 class ServiceWorkerContainer : public web::EventTarget {
  public:
   explicit ServiceWorkerContainer(script::EnvironmentSettings* settings);
 
-  // https://w3c.github.io/ServiceWorker/#navigator-service-worker-controller
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-controller
   scoped_refptr<ServiceWorker> controller();
   script::HandlePromiseWrappable ready();
 
@@ -72,6 +72,10 @@
       std::unique_ptr<script::ValuePromiseWrappable::Reference>
           promise_reference);
 
+  void GetRegistrationsTask(
+      std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+          promise_reference);
+
   scoped_refptr<ServiceWorker> ready_;
 
   script::HandlePromiseWrappable ready_promise_;
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc
index cf7f3b6..a1fd89c 100644
--- a/cobalt/worker/service_worker_global_scope.cc
+++ b/cobalt/worker/service_worker_global_scope.cc
@@ -24,7 +24,11 @@
 #include "base/trace_event/trace_event.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
+#include "cobalt/script/v8c/entry_scope.h"
+#include "cobalt/web/environment_settings_helper.h"
 #include "cobalt/worker/clients.h"
+#include "cobalt/worker/fetch_event.h"
+#include "cobalt/worker/fetch_event_init.h"
 #include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/worker_settings.h"
 
@@ -34,7 +38,13 @@
     script::EnvironmentSettings* settings, ServiceWorkerObject* service_worker)
     : WorkerGlobalScope(settings),
       clients_(new Clients(settings)),
-      service_worker_object_(base::AsWeakPtr(service_worker)) {}
+      service_worker_object_(base::AsWeakPtr(service_worker)) {
+  loader::FetchInterceptorCoordinator::GetInstance()->Add(this);
+}
+
+ServiceWorkerGlobalScope::~ServiceWorkerGlobalScope() {
+  loader::FetchInterceptorCoordinator::GetInstance()->Clear();
+}
 
 void ServiceWorkerGlobalScope::Initialize() {}
 
@@ -42,7 +52,7 @@
     const std::vector<std::string>& urls,
     script::ExceptionState* exception_state) {
   // Algorithm for importScripts():
-  //   https://w3c.github.io/ServiceWorker/#importscripts
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#importscripts
 
   // When the importScripts(urls) method is called, the user agent must import
   // scripts into worker global scope, with the following steps to perform the
@@ -122,7 +132,7 @@
 scoped_refptr<ServiceWorkerRegistration>
 ServiceWorkerGlobalScope::registration() const {
   // Algorithm for registration():
-  //   https://w3c.github.io/ServiceWorker/#service-worker-global-scope-registration
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-global-scope-registration
 
   // The registration getter steps are to return the result of getting the
   // service worker registration object representing this's service worker's
@@ -136,7 +146,7 @@
 
 scoped_refptr<ServiceWorker> ServiceWorkerGlobalScope::service_worker() const {
   // Algorithm for service_worker():
-  //   https://w3c.github.io/ServiceWorker/#service-worker-global-scope-serviceworker
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-global-scope-serviceworker
 
   // The serviceWorker getter steps are to return the result of getting the
   // service worker object that represents this's service worker in this's
@@ -152,7 +162,7 @@
   DCHECK_EQ(base::MessageLoop::current(),
             environment_settings()->context()->message_loop());
   // Algorithm for skipWaiting():
-  //   https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-skipwaiting
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
   // 1. Let promise be a new promise.
   auto promise = environment_settings()
                      ->context()
@@ -170,10 +180,61 @@
       base::BindOnce(&ServiceWorkerJobs::SkipWaitingSubSteps,
                      base::Unretained(jobs),
                      base::Unretained(environment_settings()->context()),
-                     service_worker_object_, std::move(promise_reference)));
+                     base::Unretained(service_worker_object_.get()),
+                     std::move(promise_reference)));
   // 3. Return promise.
   return promise;
 }
 
+void ServiceWorkerGlobalScope::StartFetch(
+    const GURL& url,
+    std::unique_ptr<base::OnceCallback<void(std::unique_ptr<std::string>)>>
+        callback,
+    std::unique_ptr<base::OnceCallback<void(const net::LoadTimingInfo&)>>
+        report_load_timing_info,
+    std::unique_ptr<base::OnceClosure> fallback) {
+  if (base::MessageLoop::current() !=
+      environment_settings()->context()->message_loop()) {
+    environment_settings()->context()->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ServiceWorkerGlobalScope::StartFetch,
+                       base::Unretained(this), url, std::move(callback),
+                       std::move(report_load_timing_info),
+                       std::move(fallback)));
+    return;
+  }
+  if (!service_worker()) {
+    std::move(*fallback).Run();
+    return;
+  }
+  // TODO: handle the following steps in
+  //       https://w3c.github.io/ServiceWorker/#handle-fetch.
+  // 22. If activeWorker’s state is "activating", wait for activeWorker’s state
+  //     to become "activated".
+  // 23. If the result of running the Run Service Worker algorithm with
+  //     activeWorker is failure, then set handleFetchFailed to true.
+
+  auto* global_environment = get_global_environment(environment_settings());
+  auto* isolate = global_environment->isolate();
+  script::v8c::EntryScope entry_scope(isolate);
+  auto request =
+      web::cache_utils::CreateRequest(environment_settings(), url.spec());
+  if (!request) {
+    std::move(*fallback).Run();
+    return;
+  }
+  FetchEventInit event_init;
+  event_init.set_request(request.value().GetScriptValue());
+  scoped_refptr<FetchEvent> fetch_event =
+      new FetchEvent(environment_settings(), base::Tokens::fetch(), event_init,
+                     std::move(callback), std::move(report_load_timing_info));
+  // 24. Create and dispatch event.
+  DispatchEvent(fetch_event);
+  // TODO: implement steps 25 and 26.
+  if (!fetch_event->respond_with_called()) {
+    std::move(*fallback).Run();
+  }
+}
+
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker_global_scope.h b/cobalt/worker/service_worker_global_scope.h
index a908d86..f89606c 100644
--- a/cobalt/worker/service_worker_global_scope.h
+++ b/cobalt/worker/service_worker_global_scope.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_WORKER_SERVICE_WORKER_GLOBAL_SCOPE_H_
 #define COBALT_WORKER_SERVICE_WORKER_GLOBAL_SCOPE_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -22,11 +23,13 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/loader/fetch_interceptor_coordinator.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/cache_utils.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/event_target_listener_info.h"
 #include "cobalt/worker/clients.h"
@@ -34,14 +37,16 @@
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_registration.h"
 #include "cobalt/worker/worker_global_scope.h"
+#include "net/base/load_timing_info.h"
 
 namespace cobalt {
 namespace worker {
 
 // Implementation of Service Worker Global Scope.
-//   https://w3c.github.io/ServiceWorker/#serviceworkerglobalscope-interface
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#serviceworkerglobalscope-interface
 
-class ServiceWorkerGlobalScope : public WorkerGlobalScope {
+class ServiceWorkerGlobalScope : public WorkerGlobalScope,
+                                 public loader::FetchInterceptor {
  public:
   explicit ServiceWorkerGlobalScope(script::EnvironmentSettings* settings,
                                     ServiceWorkerObject* service_worker);
@@ -66,6 +71,14 @@
   scoped_refptr<ServiceWorker> service_worker() const;
   script::HandlePromiseVoid SkipWaiting();
 
+  void StartFetch(
+      const GURL& url,
+      std::unique_ptr<base::OnceCallback<void(std::unique_ptr<std::string>)>>
+          callback,
+      std::unique_ptr<base::OnceCallback<void(const net::LoadTimingInfo&)>>
+          report_load_timing_info,
+      std::unique_ptr<base::OnceClosure> fallback) override;
+
   const web::EventTargetListenerInfo::EventListenerScriptValue* oninstall() {
     return GetAttributeEventListener(base::Tokens::install());
   }
@@ -119,7 +132,7 @@
   DEFINE_WRAPPABLE_TYPE(ServiceWorkerGlobalScope);
 
  protected:
-  ~ServiceWorkerGlobalScope() override {}
+  ~ServiceWorkerGlobalScope() override;
 
  private:
   scoped_refptr<Clients> clients_;
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index e30f364..b6da4d4 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -27,12 +27,13 @@
 #include "base/message_loop/message_loop.h"
 #include "base/message_loop/message_loop_current.h"
 #include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
 #include "base/synchronization/lock.h"
 #include "base/task_runner.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/visibility_state.h"
+#include "cobalt/base/type_id.h"
 #include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/promise.h"
@@ -47,6 +48,7 @@
 #include "cobalt/worker/client_query_options.h"
 #include "cobalt/worker/client_type.h"
 #include "cobalt/worker/extendable_event.h"
+#include "cobalt/worker/extendable_message_event.h"
 #include "cobalt/worker/frame_type.h"
 #include "cobalt/worker/service_worker.h"
 #include "cobalt/worker/service_worker_container.h"
@@ -56,6 +58,7 @@
 #include "cobalt/worker/service_worker_update_via_cache.h"
 #include "cobalt/worker/window_client.h"
 #include "cobalt/worker/worker_type.h"
+#include "net/base/mime_util.h"
 #include "net/base/url_util.h"
 #include "starboard/atomic.h"
 #include "url/gurl.h"
@@ -131,11 +134,18 @@
 
 ServiceWorkerJobs::~ServiceWorkerJobs() {
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  scope_to_registration_map_.HandleUserAgentShutdown(this);
+  scope_to_registration_map_.AbortAllActive();
   while (!web_context_registrations_.empty()) {
     // Wait for web context registrations to be cleared.
     web_context_registrations_cleared_.Wait();
   }
-  scope_to_registration_map_.HandleUserAgentShutdown(this);
+}
+
+void ServiceWorkerJobs::Stop() {
+  if (!done_event_.IsSignaled()) {
+    done_event_.Signal();
+  }
 }
 
 void ServiceWorkerJobs::StartRegister(
@@ -150,7 +160,7 @@
   DCHECK_NE(message_loop(), base::MessageLoop::current());
   DCHECK_EQ(client->context()->message_loop(), base::MessageLoop::current());
   // Algorithm for Start Register:
-  //   https://w3c.github.io/ServiceWorker/#start-register-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm
   // 1. If scriptURL is failure, reject promise with a TypeError and abort these
   // steps.
   if (script_url_with_fragment.is_empty()) {
@@ -240,6 +250,7 @@
 
 void ServiceWorkerJobs::PromiseErrorData::Reject(
     std::unique_ptr<JobPromiseType> promise) const {
+  DCHECK(promise);
   if (message_type_ != script::kNoError) {
     promise->Reject(GetSimpleExceptionType(message_type_));
   } else {
@@ -254,7 +265,7 @@
   TRACE_EVENT2("cobalt::worker", "ServiceWorkerJobs::CreateJob()", "type", type,
                "script_url", script_url.spec());
   // Algorithm for Create Job:
-  //   https://w3c.github.io/ServiceWorker/#create-job
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-job
   // 1. Let job be a new job.
   // 2. Set job’s job type to jobType.
   // 3. Set job’s storage key to storage key.
@@ -277,7 +288,7 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
   // Algorithm for Schedule Job:
-  //   https://w3c.github.io/ServiceWorker/#schedule-job
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#schedule-job
   // 1. Let jobQueue be null.
 
   // 2. Let jobScope be job’s scope url, serialized.
@@ -316,7 +327,7 @@
       DCHECK(last_job);
       base::AutoLock lock(last_job->equivalent_jobs_promise_mutex);
       if (EquivalentJobs(job.get(), last_job) && last_job->promise &&
-          last_job->promise->State() == script::PromiseState::kPending) {
+          last_job->promise->is_pending()) {
         last_job->equivalent_jobs.push_back(std::move(job));
         return;
       }
@@ -332,7 +343,7 @@
 
 bool ServiceWorkerJobs::EquivalentJobs(Job* one, Job* two) {
   // Algorithm for Two jobs are equivalent:
-  //   https://w3c.github.io/ServiceWorker/#dfn-job-equivalent
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-equivalent
   DCHECK(one);
   DCHECK(two);
   if (!one || !two) {
@@ -361,7 +372,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunJob()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Run Job:
-  //   https://w3c.github.io/ServiceWorker/#run-job-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-job-algorithm
 
   // 1. Assert: jobQueue is not empty.
   DCHECK(job_queue && !job_queue->empty());
@@ -379,7 +390,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunJobTask()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Task for "Run Job" to run in the service worker thread.
-  //   https://w3c.github.io/ServiceWorker/#run-job-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-job-algorithm
   DCHECK(job_queue);
   if (!job_queue) return;
   DCHECK(!job_queue->empty());
@@ -414,7 +425,7 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
   // Algorithm for Register:
-  //   https://w3c.github.io/ServiceWorker/#register-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#register-algorithm
 
   // 1. If the result of running potentially trustworthy origin with the origin
   // of job’s script url as the argument is Not Trusted, then:
@@ -503,7 +514,7 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
   // Algorithm for Update:
-  //   https://w3c.github.io/ServiceWorker/#update-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-algorithm
 
   // 1. Let registration be the result of running Get Registration given job’s
   //    storage key and job’s scope url.
@@ -558,6 +569,8 @@
   //        environment settings object for this service worker.
   // To perform the fetch given request, run the following steps:
   //   8.1.  Append `Service-Worker`/`script` to request’s header list.
+  net::HttpRequestHeaders headers;
+  headers.SetHeader("Service-Worker", "script");
   //   8.2.  Set request’s cache mode to "no-cache" if any of the following are
   //         true:
   //          - registration’s update via cache mode is not "all".
@@ -569,41 +582,6 @@
   //   8.5.  Set request’s redirect mode to "error".
   //   8.6.  Fetch request, and asynchronously wait to run the remaining steps
   //         as part of fetch’s process response for the response response.
-  //   8.7.  Extract a MIME type from the response’s header list. If this MIME
-  //         type (ignoring parameters) is not a JavaScript MIME type, then:
-  //   8.7.1. Invoke Reject Job Promise with job and "SecurityError"
-  //          DOMException.
-  //   8.7.2. Asynchronously complete these steps with a network error.
-  // TODO(b/235393876): Implement Service-Worker-Allowed.
-  //   8.8.  Let serviceWorkerAllowed be the result of extracting header list
-  //         values given `Service-Worker-Allowed` and response’s header list.
-  //   8.9.  Set policyContainer to the result of creating a policy container
-  //         from a fetch response given response.
-  //   8.10. If serviceWorkerAllowed is failure, then:
-  //   8.10.1  Asynchronously complete these steps with a network error.
-  //   8.11. Let scopeURL be registration’s scope url.
-  //   8.12. Let maxScopeString be null.
-  //   8.13. If serviceWorkerAllowed is null, then:
-  //   8.13.1. Let resolvedScope be the result of parsing "./" using job’s
-  //           script url as the base URL.
-  //   8.13.2. Set maxScopeString to "/", followed by the strings in
-  //           resolvedScope’s path (including empty strings), separated from
-  //           each other by "/".
-  //   8.14. Else:
-  //   8.14.1. Let maxScope be the result of parsing serviceWorkerAllowed using
-  //           job’s script url as the base URL.
-  //   8.14.2. If maxScope’s origin is job’s script url's origin, then:
-  //   8.14.2.1. Set maxScopeString to "/", followed by the strings in
-  //             maxScope’s path (including empty strings), separated from each
-  //             other by "/".
-  //   8.15. Let scopeString be "/", followed by the strings in scopeURL’s path
-  //         (including empty strings), separated from each other by "/".
-  //   8.16. If maxScopeString is null or scopeString does not start with
-  //         maxScopeString, then:
-  //   8.16.1. Invoke Reject Job Promise with job and "SecurityError"
-  //           DOMException.
-  //   8.16.2. Asynchronously complete these steps with a network error.
-
   // TODO(b/225037465): Implement CSP check.
   csp::SecurityCallback csp_callback = base::Bind(&PermitAnyURL);
   loader::Origin origin = loader::Origin(job->script_url.GetOrigin());
@@ -611,8 +589,112 @@
       job->script_url, origin, csp_callback,
       base::Bind(&ServiceWorkerJobs::UpdateOnContentProduced,
                  base::Unretained(this), state),
+      base::Bind(&ServiceWorkerJobs::UpdateOnResponseStarted,
+                 base::Unretained(this), state),
       base::Bind(&ServiceWorkerJobs::UpdateOnLoadingComplete,
-                 base::Unretained(this), state));
+                 base::Unretained(this), state),
+      std::move(headers),
+      /*skip_fetch_intercept=*/true);
+}
+
+namespace {
+// Array of JavaScript mime types, according to the MIME Sniffinc spec:
+//   https://mimesniff.spec.whatwg.org/#javascript-mime-type
+static const char* const kJavaScriptMimeTypes[] = {"application/ecmascript",
+                                                   "application/javascript",
+                                                   "application/x-ecmascript",
+                                                   "application/x-javascript",
+                                                   "text/ecmascript",
+                                                   "text/javascript",
+                                                   "text/javascript1.0",
+                                                   "text/javascript1.1",
+                                                   "text/javascript1.2",
+                                                   "text/javascript1.3",
+                                                   "text/javascript1.4",
+                                                   "text/javascript1.5",
+                                                   "text/jscript",
+                                                   "text/livescript",
+                                                   "text/x-ecmascript",
+                                                   "text/x-javascript"};
+
+}  // namespace
+
+bool ServiceWorkerJobs::UpdateOnResponseStarted(
+    scoped_refptr<UpdateJobState> state, loader::Fetcher* fetcher,
+    const scoped_refptr<net::HttpResponseHeaders>& headers) {
+  std::string content_type;
+  bool mime_type_is_javascript = false;
+  if (headers->GetNormalizedHeader("Content-type", &content_type)) {
+    //   8.7.  Extract a MIME type from the response’s header list. If this MIME
+    //         type (ignoring parameters) is not a JavaScript MIME type, then:
+    for (auto mime_type : kJavaScriptMimeTypes) {
+      if (net::MatchesMimeType(mime_type, content_type)) {
+        mime_type_is_javascript = true;
+        break;
+      }
+    }
+  }
+  if (!mime_type_is_javascript) {
+    //   8.7.1. Invoke Reject Job Promise with job and "SecurityError"
+    //          DOMException.
+    //   8.7.2. Asynchronously complete these steps with a network error.
+    RejectJobPromise(state->job,
+                     PromiseErrorData(web::DOMException::kSecurityErr,
+                                      "Service Worker Script is not "
+                                      "JavaScript MIME type."));
+    return true;
+  }
+  //   8.8.  Let serviceWorkerAllowed be the resulf extracting header list
+  //         values given `Service-Worker-Allowed` and response’s header list.
+  std::string service_worker_allowed;
+  bool service_worker_allowed_exists = headers->GetNormalizedHeader(
+      "Service-Worker-Allowed", &service_worker_allowed);
+  //   8.9.  Set policyContainer to the result of creating a policy container
+  //         from a fetch response given response.
+  //   8.10. If serviceWorkerAllowed is failure, then:
+  //   8.10.1  Asynchronously complete these steps with a network error.
+  //   8.11. Let scopeURL be registration’s scope url.
+  GURL scope_url = state->registration->scope_url();
+  //   8.12. Let maxScopeString be null.
+  base::Optional<std::string> max_scope_string;
+  //   8.13. If serviceWorkerAllowed is null, then:
+  if (!service_worker_allowed_exists || service_worker_allowed.empty()) {
+    //   8.13.1. Let resolvedScope be the result of parsing "./" using job’s
+    //           script url as the base URL.
+    //   8.13.2. Set maxScopeString to "/", followed by the strings in
+    //           resolvedScope’s path (including empty strings), separated
+    //           from each other by "/".
+    max_scope_string = state->job->script_url.GetWithoutFilename().path();
+  } else {
+    //   8.14. Else:
+    //   8.14.1. Let maxScope be the result of parsing serviceWorkerAllowed
+    //           using job’s script url as the base URL.
+    GURL max_scope = state->job->script_url.Resolve(service_worker_allowed);
+    //   8.14.2. If maxScope’s origin is job’s script url's origin, then:
+    if (loader::Origin(state->job->script_url) == loader::Origin(max_scope)) {
+      //   8.14.2.1. Set maxScopeString to "/", followed by the strings in
+      //             maxScope’s path (including empty strings), separated from
+      //             each other by "/".
+      max_scope_string = max_scope.path();
+    }
+  }
+  //   8.15. Let scopeString be "/", followed by the strings in scopeURL’s
+  //         path (including empty strings), separated from each other by "/".
+  std::string scope_string = scope_url.path();
+  //   8.16. If maxScopeString is null or scopeString does not start with
+  //         maxScopeString, then:
+  if (!max_scope_string.has_value() ||
+      !base::StartsWith(scope_string, max_scope_string.value(),
+                        base::CompareCase::SENSITIVE)) {
+    //   8.16.1. Invoke Reject Job Promise with job and "SecurityError"
+    //           DOMException.
+    //   8.16.2. Asynchronously complete these steps with a network error.
+    RejectJobPromise(state->job,
+                     PromiseErrorData(web::DOMException::kSecurityErr,
+                                      "Scope not allowed."));
+    return true;
+  }
+  return true;
 }
 
 void ServiceWorkerJobs::UpdateOnContentProduced(
@@ -658,9 +740,28 @@
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerJobs::UpdateOnLoadingComplete()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  if (state->job->promise.get() == nullptr) {
+    // The job is already rejected, which means there was an error, so finish
+    // the job and skip the remaining steps.
+    FinishJob(state->job);
+    return;
+  }
+
+  if (error) {
+    RejectJobPromise(
+        state->job,
+        PromiseErrorData(web::DOMException::kNetworkErr, error.value()));
+    if (state->newest_worker == nullptr) {
+      scope_to_registration_map_.RemoveRegistration(state->job->storage_key,
+                                                    state->job->scope_url);
+    }
+    FinishJob(state->job);
+    return;
+  }
+
   //   8.21. If hasUpdatedResources is false and newestWorker’s classic
   //         scripts imported flag is set, then:
-  if (!state->has_updated_resources &&
+  if (!state->has_updated_resources && state->newest_worker &&
       state->newest_worker->classic_scripts_imported()) {
     // This checks if there are any updates to already stored importScripts
     // resources.
@@ -774,7 +875,7 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(worker);
   // Algorithm for "Run Service Worker"
-  //   https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
 
   // 1. Let unsafeCreationTime be the unsafe shared current time.
   auto unsafe_creation_time = base::TimeTicks::Now();
@@ -811,7 +912,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Install()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Install:
-  //   https://w3c.github.io/ServiceWorker/#installation-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm
 
   // 1. Let installFailed be false.
   // Using a shared pointer because this flag is explicitly defined in the spec
@@ -883,7 +984,7 @@
   ServiceWorkerObject* installing_worker = registration->installing_worker();
   // 11. If the result of running the Should Skip Event algorithm with
   //     installingWorker and "install" is false, then:
-  if (!ShouldSkipEvent(base::Tokens::install(), installing_worker)) {
+  if (!installing_worker->ShouldSkipEvent(base::Tokens::install())) {
     // 11.1. Let forceBypassCache be true if job’s force bypass cache flag is
     //       set, and false otherwise.
     bool force_bypass_cache = job->force_bypass_cache_flag;
@@ -899,9 +1000,8 @@
       //         DOM manipulation task source to run the following steps:
       // Using a shared pointer to ensure that it still exists when it is
       // signaled from the callback after the timeout.
-      std::shared_ptr<base::WaitableEvent> done_event(new base::WaitableEvent(
-          base::WaitableEvent::ResetPolicy::MANUAL,
-          base::WaitableEvent::InitialState::NOT_SIGNALED));
+      DCHECK(done_event_.IsSignaled());
+      done_event_.Reset();
       installing_worker->web_agent()
           ->context()
           ->message_loop()
@@ -910,7 +1010,7 @@
               FROM_HERE,
               base::Bind(
                   [](ServiceWorkerObject* installing_worker,
-                     std::shared_ptr<base::WaitableEvent> done_event,
+                     base::WaitableEvent* done_event,
                      std::shared_ptr<starboard::atomic_bool> install_failed) {
                     // 11.3.1.1. Let e be the result of creating an event with
                     //           ExtendableEvent.
@@ -927,7 +1027,7 @@
                     //             true.
                     //         If task is discarded, set installFailed to true.
                     auto done_callback = base::BindOnce(
-                        [](std::shared_ptr<base::WaitableEvent> done_event,
+                        [](base::WaitableEvent* done_event,
                            std::shared_ptr<starboard::atomic_bool>
                                install_failed,
                            bool was_rejected) {
@@ -946,7 +1046,7 @@
                       done_event->Signal();
                     }
                   },
-                  base::Unretained(installing_worker), done_event,
+                  base::Unretained(installing_worker), &done_event_,
                   install_failed));
       // 11.3.2. Wait for task to have executed or been discarded.
       // This waiting is done inside PostBlockingTask above.
@@ -955,18 +1055,20 @@
       // TODO(b/240164388): Investigate a better approach for combining waiting
       // for the ExtendableEvent while also allowing use of algorithms that run
       // on the same thread from the event handler.
-      while (!done_event->TimedWait(base::TimeDelta::FromMilliseconds(100))) {
+      while (!done_event_.TimedWait(base::TimeDelta::FromMilliseconds(100))) {
         base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
         base::RunLoop().RunUntilIdle();
       }
     }
   }
   // 12. If installFailed is true, then:
-  if (install_failed->load()) {
+  if (install_failed->load() || !registration->installing_worker()) {
     // 12.1. Run the Update Worker State algorithm passing registration’s
     //       installing worker and "redundant" as the arguments.
-    UpdateWorkerState(registration->installing_worker(),
-                      kServiceWorkerStateRedundant);
+    if (registration->installing_worker()) {
+      UpdateWorkerState(registration->installing_worker(),
+                        kServiceWorkerStateRedundant);
+    }
     // 12.2. Run the Update Registration State algorithm passing registration,
     //       "installing" and null as the arguments.
     UpdateRegistrationState(registration, kInstalling, nullptr);
@@ -1023,7 +1125,7 @@
     // When a service worker client is controlled by a service worker, it is
     // said that the service worker client is using the service worker’s
     // containing service worker registration.
-    //   https://w3c.github.io/ServiceWorker/#dfn-control
+    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
     if (context->is_controlled_by(registration->active_worker())) {
       any_client_is_using = true;
       break;
@@ -1037,7 +1139,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TryActivate()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Try Activate:
-  //   https://w3c.github.io/ServiceWorker/#try-activate-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm
 
   // 1. If registration’s waiting worker is null, return.
   if (!registration) return;
@@ -1076,7 +1178,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Activate()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Activate:
-  //   https://w3c.github.io/ServiceWorker/#activation-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
 
   // 1. If registration’s waiting worker is null, abort these steps.
   if (registration->waiting_worker() == nullptr) return;
@@ -1148,7 +1250,7 @@
     // When a service worker client is controlled by a service worker, it is
     // said that the service worker client is using the service worker’s
     // containing service worker registration.
-    //   https://w3c.github.io/ServiceWorker/#dfn-control
+    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
     if (context->is_controlled_by(registration->active_worker())) {
       // 9.1. Set client’s active worker to registration’s active worker.
       client->context()->set_active_service_worker(
@@ -1160,9 +1262,11 @@
   }
   // 10. Let activeWorker be registration’s active worker.
   ServiceWorkerObject* active_worker = registration->active_worker();
+  bool activated = true;
   // 11. If the result of running the Should Skip Event algorithm with
   //     activeWorker and "activate" is false, then:
-  if (!ShouldSkipEvent(base::Tokens::activate(), active_worker)) {
+  DCHECK(active_worker);
+  if (!active_worker->ShouldSkipEvent(base::Tokens::activate())) {
     // 11.1. If the result of running the Run Service Worker algorithm with
     //       activeWorker is not failure, then:
     auto* run_result = RunServiceWorker(active_worker);
@@ -1173,9 +1277,8 @@
                 active_worker->worker_global_scope()
                     ->environment_settings()
                     ->context());
-      std::shared_ptr<base::WaitableEvent> done_event(new base::WaitableEvent(
-          base::WaitableEvent::ResetPolicy::MANUAL,
-          base::WaitableEvent::InitialState::NOT_SIGNALED));
+      DCHECK(done_event_.IsSignaled());
+      done_event_.Reset();
       active_worker->web_agent()
           ->context()
           ->message_loop()
@@ -1184,11 +1287,11 @@
               FROM_HERE,
               base::Bind(
                   [](ServiceWorkerObject* active_worker,
-                     std::shared_ptr<base::WaitableEvent> done_event) {
-                    auto done_callback = base::BindOnce(
-                        [](std::shared_ptr<base::WaitableEvent> done_event,
-                           bool) { done_event->Signal(); },
-                        done_event);
+                     base::WaitableEvent* done_event) {
+                    auto done_callback =
+                        base::BindOnce([](base::WaitableEvent* done_event,
+                                          bool) { done_event->Signal(); },
+                                       done_event);
                     scoped_refptr<ExtendableEvent> event(new ExtendableEvent(
                         base::Tokens::activate(), std::move(done_callback)));
                     // 11.1.1.1. Let e be the result of creating an event with
@@ -1205,7 +1308,7 @@
                       done_event->Signal();
                     }
                   },
-                  base::Unretained(active_worker), done_event));
+                  base::Unretained(active_worker), &done_event_));
       // 11.1.2. Wait for task to have executed or been discarded.
       // This waiting is done inside PostBlockingTask above.
       // 11.1.3. Wait for the step labeled WaitForAsynchronousExtensions to
@@ -1213,7 +1316,7 @@
       // TODO(b/240164388): Investigate a better approach for combining waiting
       // for the ExtendableEvent while also allowing use of algorithms that run
       // on the same thread from the event handler.
-      while (!done_event->TimedWait(base::TimeDelta::FromMilliseconds(100))) {
+      while (!done_event_.TimedWait(base::TimeDelta::FromMilliseconds(100))) {
         base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
         base::RunLoop().RunUntilIdle();
       }
@@ -1221,14 +1324,16 @@
   }
   // 12. Run the Update Worker State algorithm passing registration’s active
   //     worker and "activated" as the arguments.
-  UpdateWorkerState(registration->active_worker(),
-                    kServiceWorkerStateActivated);
+  if (activated && registration->active_worker()) {
+    UpdateWorkerState(registration->active_worker(),
+                      kServiceWorkerStateActivated);
+  }
 }
 
 void ServiceWorkerJobs::NotifyControllerChange(
     web::EnvironmentSettings* client) {
   // Algorithm for Notify Controller Change:
-  // https://w3c.github.io/ServiceWorker/#notify-controller-change-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm
   // 1. Assert: client is not null.
   DCHECK(client);
 
@@ -1251,7 +1356,7 @@
 bool ServiceWorkerJobs::ServiceWorkerHasNoPendingEvents(
     ServiceWorkerObject* worker) {
   // Algorithm for Service Worker Has No Pending Events
-  //   https://w3c.github.io/ServiceWorker/#service-worker-has-no-pending-events
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events
   // TODO(b/240174245): Implement this using the 'set of extended events'.
   NOTIMPLEMENTED();
 
@@ -1265,7 +1370,7 @@
     scoped_refptr<ServiceWorkerRegistrationObject> registration) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClearRegistration()");
   // Algorithm for Clear Registration:
-  //   https://w3c.github.io/ServiceWorker/#clear-registration-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm
   // 1. Run the following steps atomically.
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
 
@@ -1314,7 +1419,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TryClearRegistration()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Try Clear Registration:
-  //   https://w3c.github.io/ServiceWorker/#try-clear-registration-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm
 
   // 1. Invoke Clear Registration with registration if no service worker client
   // is using registration and all of the following conditions are true:
@@ -1352,7 +1457,7 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(registration);
   // Algorithm for Update Registration State:
-  //   https://w3c.github.io/ServiceWorker/#update-registration-state-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm
 
   // 1. Let registrationObjects be an array containing all the
   //    ServiceWorkerRegistration objects associated with registration.
@@ -1464,7 +1569,7 @@
     return;
   }
   // Algorithm for Update Worker State:
-  //   https://w3c.github.io/ServiceWorker/#update-state-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm
   // 1. Assert: state is not "parsed".
   DCHECK_NE(kServiceWorkerStateParsed, state);
   // 2. Set worker's state to state.
@@ -1510,21 +1615,12 @@
   }
 }
 
-bool ServiceWorkerJobs::ShouldSkipEvent(base::Token event_name,
-                                        ServiceWorkerObject* worker) {
-  // Algorithm for Should Skip Event:
-  //   https://w3c.github.io/ServiceWorker/#should-skip-event-algorithm
-  // TODO(b/229622132): Implementing this algorithm will improve performance.
-  NOTIMPLEMENTED();
-  return false;
-}
-
 void ServiceWorkerJobs::HandleServiceWorkerClientUnload(
     web::EnvironmentSettings* client) {
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerJobs::HandleServiceWorkerClientUnload()");
   // Algorithm for Handle Servicer Worker Client Unload:
-  //   https://w3c.github.io/ServiceWorker/#on-user-agent-shutdown-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-user-agent-shutdown-algorithm
   DCHECK(client);
   // 1. Run the following steps atomically.
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
@@ -1559,7 +1655,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TerminateServiceWorker()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Terminate Service Worker:
-  //   https://w3c.github.io/ServiceWorker/#terminate-service-worker
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker
   // 1. Run the following steps in parallel with serviceWorker’s main loop:
   // This runs in the ServiceWorkerRegistry thread.
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
@@ -1613,7 +1709,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Unregister()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Unregister:
-  //   https://w3c.github.io/ServiceWorker/#unregister-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#unregister-algorithm
   // 1. If the origin of job’s scope url is not job’s client's origin, then:
   if (!url::Origin::Create(GURL(job->client->GetOrigin().SerializedOrigin()))
            .IsSameOriginWith(url::Origin::Create(job->scope_url))) {
@@ -1665,7 +1761,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RejectJobPromise()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Reject Job Promise:
-  //   https://w3c.github.io/ServiceWorker/#reject-job-promise
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#reject-job-promise
   // 1. If job’s client is not null, queue a task, on job’s client's responsible
   //    event loop using the DOM manipulation task source, to reject job’s job
   //    promise with a new exception with errorData and a user agent-defined
@@ -1717,7 +1813,7 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
   // Algorithm for Resolve Job Promise:
-  //   https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-job-promise-algorithm
 
   // 1. If job’s client is not null, queue a task, on job’s client's responsible
   //    event loop using the DOM manipulation task source, to run the following
@@ -1785,7 +1881,7 @@
   job->equivalent_jobs.clear();
 }
 
-// https://w3c.github.io/ServiceWorker/#finish-job-algorithm
+// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#finish-job-algorithm
 void ServiceWorkerJobs::FinishJob(Job* job) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::FinishJob()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
@@ -1808,7 +1904,7 @@
     web::EnvironmentSettings* client) {
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Sub steps of ServiceWorkerContainer.ready():
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-ready
 
   //    3.1. Let client by this's service worker client.
   //    3.2. Let storage key be the result of running obtain a storage
@@ -1853,7 +1949,7 @@
                "ServiceWorkerJobs::GetRegistrationSubSteps()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Algorithm for Sub steps of ServiceWorkerContainer.getRegistration():
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
 
   // 8.1. Let registration be the result of running Match Service Worker
   //      Registration algorithm with clientURL as its argument.
@@ -1881,9 +1977,40 @@
           client, std::move(promise_reference), registration));
 }
 
+void ServiceWorkerJobs::GetRegistrationsSubSteps(
+    const url::Origin& storage_key, web::EnvironmentSettings* client,
+    std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+        promise_reference) {
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
+      registration_objects =
+          scope_to_registration_map_.GetRegistrations(storage_key);
+  client->context()->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](web::EnvironmentSettings* settings,
+             std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+                 promise,
+             std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
+                 registration_objects) {
+            TRACE_EVENT0(
+                "cobalt::worker",
+                "ServiceWorkerJobs::GetRegistrationSubSteps() Resolve");
+            script::Sequence<scoped_refptr<script::Wrappable>> registrations;
+            for (auto registration_object : registration_objects) {
+              registrations.push_back(scoped_refptr<script::Wrappable>(
+                  settings->context()
+                      ->GetServiceWorkerRegistration(registration_object)
+                      .get()));
+            }
+            promise->value().Resolve(std::move(registrations));
+          },
+          client, std::move(promise_reference),
+          std::move(registration_objects)));
+}
+
 void ServiceWorkerJobs::SkipWaitingSubSteps(
-    web::Context* client_context,
-    const base::WeakPtr<ServiceWorkerObject>& service_worker,
+    web::Context* client_context, ServiceWorkerObject* service_worker,
     std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::SkipWaitingSubSteps()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
@@ -1895,7 +2022,7 @@
   }
 
   // Algorithm for Sub steps of ServiceWorkerGlobalScope.skipWaiting():
-  //   https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-skipwaiting
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
 
   // 2.1. Set service worker's skip waiting flag.
   service_worker->set_skip_waiting();
@@ -1917,9 +2044,9 @@
 void ServiceWorkerJobs::WaitUntilSubSteps(
     ServiceWorkerRegistrationObject* registration) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::WaitUntilSubSteps()");
-  DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Sub steps for WaitUntil.
-  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
   // 5.2.2. If registration is unregistered, invoke Try Clear Registration
   //        with registration.
   if (scope_to_registration_map_.IsUnregistered(registration)) {
@@ -1938,7 +2065,7 @@
     std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
     const std::string& id) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClientsGetSubSteps()");
-  DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Check if the client web context is still active. This may trigger if
   // Clients.get() was called and service worker installation fails.
   if (!IsWebContextRegistered(promise_context)) {
@@ -1946,7 +2073,7 @@
     return;
   }
   // Parallel sub steps (2) for algorithm for Clients.get(id):
-  //   https://w3c.github.io/ServiceWorker/#clients-get
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
   // 2.1. For each service worker client client where the result of running
   //      obtain a storage key given client equals the associated service
   //      worker's containing service worker registration's storage key:
@@ -1992,7 +2119,7 @@
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerJobs::ResolveGetClientPromise()");
   // Algorithm for Resolve Get Client Promise:
-  //   https://w3c.github.io/ServiceWorker/#resolve-get-client-promise
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-get-client-promise
 
   // 1. If client is an environment settings object, then:
   // 1.1. If client is not a secure context, queue a task to reject promise with
@@ -2050,22 +2177,16 @@
           [](web::EnvironmentSettings* client, web::Context* promise_context,
              std::unique_ptr<script::ValuePromiseWrappable::Reference>
                  promise_reference) {
-            std::unique_ptr<WindowData> window_data(new WindowData);
-            window_data->client = client;
             // 4.4.1. Let frameType be the result of running Get Frame Type with
             //        browsingContext.
-            // Cobalt does not support nested or auxiliary
-            // browsing contexts.
-            window_data->frame_type = kFrameTypeTopLevel;
-
+            // Cobalt does not support nested or auxiliary browsing contexts.
             // 4.4.2. Let visibilityState be browsingContext’s active document's
             //        visibilityState attribute value.
-            // TODO(b/235838698): Implement WindowClient.visibilityState.
-
             // 4.4.3. Let focusState be the result of running the has focus
             //        steps with browsingContext’s active document as the
             //        argument.
-            // TODO(b/235838698): Implement WindowClient.focused.
+            // Handled in the WindowData constructor.
+            std::unique_ptr<WindowData> window_data(new WindowData(client));
 
             // 4.4.4. Let ancestorOriginsList be the empty list.
             // 4.4.5. If client is a window client, set ancestorOriginsList to
@@ -2090,7 +2211,7 @@
                       //          Create Window Client with client,
                       //          frameType, visibilityState, focusState,
                       //          and ancestorOriginsList.
-                      scoped_refptr<WindowClient> window_client =
+                      scoped_refptr<Client> window_client =
                           WindowClient::Create(*window_data);
                       // 4.4.6.3. Resolve promise with windowClient.
                       promise_reference->value().Resolve(window_client);
@@ -2109,7 +2230,7 @@
     bool include_uncontrolled, ClientType type) {
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerJobs::ClientsMatchAllSubSteps()");
-  DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
   // Check if the client web context is still active. This may trigger if
   // Clients.matchAll() was called and service worker installation fails.
   if (!IsWebContextRegistered(client_context)) {
@@ -2118,7 +2239,7 @@
   }
 
   // Parallel sub steps (2) for algorithm for Clients.matchAll():
-  //   https://w3c.github.io/ServiceWorker/#clients-matchall
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
   // 2.1. Let targetClients be a new list.
   std::list<web::EnvironmentSettings*> target_clients;
 
@@ -2174,8 +2295,7 @@
 
       // 2.5.1.1. Let windowData be [ "client" -> client, "ancestorOriginsList"
       //          -> a new list ].
-      WindowData window_data;
-      window_data.client = client;
+      WindowData window_data(client);
 
       // 2.5.1.2. Let browsingContext be null.
 
@@ -2217,19 +2337,13 @@
                            //            browsingContext.
                            // Cobalt does not support nested or auxiliary
                            // browsing contexts.
-                           window_data->frame_type = kFrameTypeTopLevel;
-
                            // 2.5.1.6.4. Set windowData["visibilityState"] to
                            //            browsingContext’s active document's
                            //            visibilityState attribute value.
-                           // TODO(b/235838698): Implement
-                           // WindowClient.visibilityState.
-
                            // 2.5.1.6.5. Set windowData["focusState"] to the
                            //            result of running the has focus steps
                            //            with browsingContext’s active document
                            //            as the argument.
-                           // TODO(b/235838698): Implement WindowClient.focused.
 
                            // 2.5.1.6.6. If client is a window client, then set
                            //            windowData["ancestorOriginsList"] to
@@ -2288,9 +2402,8 @@
               //          windowData["focusState"], and
               //          windowData["ancestorOriginsList"] as the
               //          arguments.
-              // TODO(b/235838698): Implement WindowCLient properties
-              // and methods.
-              scoped_refptr<WindowClient> window_client =
+              // TODO(b/235838698): Implement WindowClient methods.
+              scoped_refptr<Client> window_client =
                   WindowClient::Create(window_data);
 
               // 2.6.2.2. Append WindowClient to clientObjects.
@@ -2332,7 +2445,7 @@
     ServiceWorkerObject* associated_service_worker,
     std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClaimSubSteps()");
-  DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
 
   // Check if the client web context is still active. This may trigger if
   // Clients.claim() was called and service worker installation fails.
@@ -2342,7 +2455,7 @@
   }
 
   // Parallel sub steps (3) for algorithm for Clients.claim():
-  //   https://w3c.github.io/ServiceWorker/#dom-clients-claim
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim
   std::list<web::EnvironmentSettings*> target_clients;
 
   // 3.1. For each service worker client client where the result of running
@@ -2412,6 +2525,90 @@
           std::move(promise_reference)));
 }
 
+void ServiceWorkerJobs::ServiceWorkerPostMessageSubSteps(
+    ServiceWorkerObject* service_worker,
+    web::EnvironmentSettings* incumbent_settings,
+    std::unique_ptr<script::DataBuffer> serialize_result) {
+  // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options
+  // 3. Let incumbentGlobal be incumbentSettings’s global object.
+  // 6.1 If the result of running the Run Service Worker algorithm with
+  //     serviceWorker is failure, then return.
+  auto* run_result = RunServiceWorker(service_worker);
+  if (!run_result) return;
+
+  // 6.2 Queue a task on the DOM manipulation task source to run the following
+  //     steps:
+  incumbent_settings->context()->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](ServiceWorkerObject* service_worker,
+             web::EnvironmentSettings* incumbent_settings,
+             std::unique_ptr<script::DataBuffer> serialize_result) {
+            web::WindowOrWorkerGlobalScope* incumbent_global =
+                incumbent_settings->context()->GetWindowOrWorkerGlobalScope();
+
+            web::EventTarget* event_target =
+                service_worker->worker_global_scope();
+            if (!event_target) return;
+
+            ExtendableMessageEventInit init_dict;
+            if (incumbent_global->GetWrappableType() ==
+                base::GetTypeId<ServiceWorkerGlobalScope>()) {
+              // 6.2.1. Let source be determined by switching on the
+              //        type of incumbentGlobal:
+              //        . ServiceWorkerGlobalScope
+              //          The result of getting the service worker
+              //          object that represents incumbentGlobal’s
+              //          service worker in the relevant settings
+              //          object of serviceWorker’s global object.
+              init_dict.set_source(ExtendableMessageEvent::SourceType(
+                  event_target->environment_settings()
+                      ->context()
+                      ->GetServiceWorker(incumbent_global->AsServiceWorker()
+                                             ->service_worker_object())));
+            } else if (incumbent_global->GetWrappableType() ==
+                       base::GetTypeId<dom::Window>()) {
+              //        . Window
+              //          a new WindowClient object that represents
+              //          incumbentGlobal’s relevant settings object.
+              init_dict.set_source(
+                  ExtendableMessageEvent::SourceType(WindowClient::Create(
+                      WindowData(incumbent_global->environment_settings()))));
+            } else {
+              //        . Otherwise
+              //          a new Client object that represents
+              //          incumbentGlobal’s associated worker
+              init_dict.set_source(ExtendableMessageEvent::SourceType(
+                  Client::Create(incumbent_global->environment_settings())));
+            }
+
+            base::MessageLoop* message_loop =
+                event_target->environment_settings()->context()->message_loop();
+            if (!message_loop) {
+              return;
+            }
+            if (!serialize_result) {
+              return;
+            }
+            message_loop->task_runner()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](const ExtendableMessageEventInit& init_dict,
+                       web::EventTarget* event_target,
+                       std::unique_ptr<script::DataBuffer> serialize_result) {
+                      event_target->DispatchEvent(
+                          new worker::ExtendableMessageEvent(
+                              base::Tokens::message(), init_dict,
+                              std::move(serialize_result)));
+                    },
+                    init_dict, base::Unretained(event_target),
+                    std::move(serialize_result)));
+          },
+          base::Unretained(service_worker),
+          base::Unretained(incumbent_settings), std::move(serialize_result)));
+}
+
 void ServiceWorkerJobs::RegisterWebContext(web::Context* context) {
   DCHECK_NE(nullptr, context);
   web_context_registrations_cleared_.Reset();
@@ -2455,22 +2652,26 @@
 
 void ServiceWorkerJobs::JobPromiseType::Resolve(const bool result) {
   DCHECK(promise_bool_reference_);
+  is_pending_.store(false);
   promise_bool_reference_->value().Resolve(result);
 }
 
 void ServiceWorkerJobs::JobPromiseType::Resolve(
     const scoped_refptr<cobalt::script::Wrappable>& result) {
   DCHECK(promise_wrappable_reference_);
+  is_pending_.store(false);
   promise_wrappable_reference_->value().Resolve(result);
 }
 
 void ServiceWorkerJobs::JobPromiseType::Reject(
     script::SimpleExceptionType exception) {
   if (promise_bool_reference_) {
+    is_pending_.store(false);
     promise_bool_reference_->value().Reject(exception);
     return;
   }
   if (promise_wrappable_reference_) {
+    is_pending_.store(false);
     promise_wrappable_reference_->value().Reject(exception);
     return;
   }
@@ -2480,26 +2681,17 @@
 void ServiceWorkerJobs::JobPromiseType::Reject(
     const scoped_refptr<script::ScriptException>& result) {
   if (promise_bool_reference_) {
+    is_pending_.store(false);
     promise_bool_reference_->value().Reject(result);
     return;
   }
   if (promise_wrappable_reference_) {
+    is_pending_.store(false);
     promise_wrappable_reference_->value().Reject(result);
     return;
   }
   NOTREACHED();
 }
 
-script::PromiseState ServiceWorkerJobs::JobPromiseType::State() {
-  if (promise_bool_reference_) {
-    return promise_bool_reference_->value().State();
-  }
-  if (promise_wrappable_reference_) {
-    return promise_wrappable_reference_->value().State();
-  }
-  NOTREACHED();
-  return script::PromiseState::kPending;
-}
-
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h
index 11b037b..7eb3370 100644
--- a/cobalt/worker/service_worker_jobs.h
+++ b/cobalt/worker/service_worker_jobs.h
@@ -48,6 +48,7 @@
 #include "cobalt/worker/service_worker_registration_object.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
 #include "cobalt/worker/worker_type.h"
+#include "starboard/atomic.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -55,10 +56,10 @@
 namespace worker {
 
 // Algorithms for Service Worker Jobs.
-//   https://w3c.github.io/ServiceWorker/#algorithms
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#algorithms
 class ServiceWorkerJobs {
  public:
-  // https://w3c.github.io/ServiceWorker/#dfn-job-type
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-type
   enum JobType { kRegister, kUpdate, kUnregister };
 
   class JobQueue;
@@ -83,18 +84,21 @@
     void Resolve(const bool result);
     void Resolve(const scoped_refptr<cobalt::script::Wrappable>& result);
     void Reject(script::SimpleExceptionType exception);
+    void Reject(web::DOMException::ExceptionCode code,
+                const std::string& message);
     void Reject(const scoped_refptr<script::ScriptException>& result);
 
-    script::PromiseState State();
+    bool is_pending() const { return is_pending_.load(); }
 
    private:
+    starboard::atomic_bool is_pending_{true};
     std::unique_ptr<script::ValuePromiseBool::Reference>
         promise_bool_reference_;
     std::unique_ptr<script::ValuePromiseWrappable::Reference>
         promise_wrappable_reference_;
   };
 
-  // https://w3c.github.io/ServiceWorker/#dfn-job
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job
   struct Job {
     Job(JobType type, const url::Origin& storage_key, const GURL& scope_url,
         const GURL& script_url, web::EnvironmentSettings* client,
@@ -103,6 +107,8 @@
           storage_key(storage_key),
           scope_url(scope_url),
           script_url(script_url),
+          update_via_cache(
+              ServiceWorkerUpdateViaCache::kServiceWorkerUpdateViaCacheImports),
           client(client),
           promise(std::move(promise)) {}
     ~Job() {
@@ -135,7 +141,7 @@
     std::unique_ptr<loader::Loader> loader;
   };
 
-  // https://w3c.github.io/ServiceWorker/#dfn-job-queue
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-queue
   class JobQueue {
    public:
     bool empty() {
@@ -176,10 +182,12 @@
                     base::MessageLoop* message_loop);
   ~ServiceWorkerJobs();
 
+  void Stop();
+
   base::MessageLoop* message_loop() { return message_loop_; }
   network::NetworkModule* network_module() { return network_module_; }
 
-  // https://w3c.github.io/ServiceWorker/#start-register-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm
   void StartRegister(const base::Optional<GURL>& scope_url,
                      const GURL& script_url,
                      std::unique_ptr<script::ValuePromiseWrappable::Reference>
@@ -190,26 +198,30 @@
   void MaybeResolveReadyPromiseSubSteps(web::EnvironmentSettings* client);
 
   // Sub steps (8) of ServiceWorkerContainer.getRegistration().
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
   void GetRegistrationSubSteps(
       const url::Origin& storage_key, const GURL& client_url,
       web::EnvironmentSettings* client,
       std::unique_ptr<script::ValuePromiseWrappable::Reference>
           promise_reference);
 
+  void GetRegistrationsSubSteps(
+      const url::Origin& storage_key, web::EnvironmentSettings* client,
+      std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+          promise_reference);
+
   // Sub steps (2) of ServiceWorkerGlobalScope.skipWaiting().
-  //   https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-skipwaiting
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
   void SkipWaitingSubSteps(
-      web::Context* client_context,
-      const base::WeakPtr<ServiceWorkerObject>& service_worker,
+      web::Context* client_context, ServiceWorkerObject* service_worker,
       std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
 
-  // Sub steps for WaitUntil.
-  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  // Sub steps for ExtendableEvent.WaitUntil().
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
   void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration);
 
   // Parallel sub steps (2) for algorithm for Clients.get(id):
-  //   https://w3c.github.io/ServiceWorker/#clients-get
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
   void ClientsGetSubSteps(
       web::Context* client_context,
       ServiceWorkerObject* associated_service_worker,
@@ -218,14 +230,14 @@
       const std::string& id);
 
   // Algorithm for Resolve Get Client Promise:
-  //   https://w3c.github.io/ServiceWorker/#resolve-get-client-promise
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-get-client-promise
   void ResolveGetClientPromise(
       web::EnvironmentSettings* client, web::Context* promise_context,
       std::unique_ptr<script::ValuePromiseWrappable::Reference>
           promise_reference);
 
   // Parallel sub steps (2) for algorithm for Clients.matchAll():
-  //   https://w3c.github.io/ServiceWorker/#clients-matchall
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
   void ClientsMatchAllSubSteps(
       web::Context* client_context,
       ServiceWorkerObject* associated_service_worker,
@@ -234,12 +246,19 @@
       bool include_uncontrolled, ClientType type);
 
   // Parallel sub steps (3) for algorithm for Clients.claim():
-  //   https://w3c.github.io/ServiceWorker/#dom-clients-claim
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim
   void ClaimSubSteps(
       web::Context* client_context,
       ServiceWorkerObject* associated_service_worker,
       std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
 
+  // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options
+  void ServiceWorkerPostMessageSubSteps(
+      ServiceWorkerObject* service_worker,
+      web::EnvironmentSettings* incumbent_settings,
+      std::unique_ptr<script::DataBuffer> serialize_result);
+
   // Registration of web contexts that may have service workers.
   void RegisterWebContext(web::Context* context);
   void UnregisterWebContext(web::Context* context);
@@ -248,7 +267,7 @@
            web_context_registrations_.find(context);
   }
 
-  // https://w3c.github.io/ServiceWorker/#create-job
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-job
   std::unique_ptr<Job> CreateJob(
       JobType type, const url::Origin& storage_key, const GURL& scope_url,
       const GURL& script_url,
@@ -270,13 +289,13 @@
                                  std::unique_ptr<JobPromiseType> promise,
                                  web::EnvironmentSettings* client);
 
-  // https://w3c.github.io/ServiceWorker/#schedule-job
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#schedule-job
   void ScheduleJob(std::unique_ptr<Job> job);
 
-  // https://w3c.github.io/ServiceWorker/#activation-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
   void Activate(scoped_refptr<ServiceWorkerRegistrationObject> registration);
 
-  // https://w3c.github.io/ServiceWorker/#clear-registration-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm
   void ClearRegistration(
       scoped_refptr<ServiceWorkerRegistrationObject> registration);
 
@@ -299,7 +318,7 @@
     bool has_updated_resources = false;
   };
 
-  // https://w3c.github.io/ServiceWorker/#dfn-scope-to-job-queue-map
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-scope-to-job-queue-map
   using JobQueueMap = std::map<std::string, std::unique_ptr<JobQueue>>;
 
   // Type to hold the errorData for rejection of promises.
@@ -326,24 +345,27 @@
 
   enum RegistrationState { kInstalling, kWaiting, kActive };
 
-  // https://w3c.github.io/ServiceWorker/#dfn-job-equivalent
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-equivalent
   bool EquivalentJobs(Job* one, Job* two);
 
-  // https://w3c.github.io/ServiceWorker/#run-job-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-job-algorithm
   void RunJob(JobQueue* job_queue);
 
   // Task for "Run Job" to run in the service worker thread.
   void RunJobTask(JobQueue* job_queue);
 
-  // https://w3c.github.io/ServiceWorker/#register-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#register-algorithm
   void Register(Job* job);
 
-  // https://w3c.github.io/ServiceWorker/#update-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-algorithm
   void Update(Job* job);
 
   void UpdateOnContentProduced(scoped_refptr<UpdateJobState> state,
                                const loader::Origin& last_url_origin,
                                std::unique_ptr<std::string> content);
+  bool UpdateOnResponseStarted(
+      scoped_refptr<UpdateJobState> state, loader::Fetcher* fetcher,
+      const scoped_refptr<net::HttpResponseHeaders>& headers);
   void UpdateOnLoadingComplete(scoped_refptr<UpdateJobState> state,
                                const base::Optional<std::string>& error);
 
@@ -352,13 +374,13 @@
                                 bool run_result);
 
 
-  // https://w3c.github.io/ServiceWorker/#unregister-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#unregister-algorithm
   void Unregister(Job* job);
 
-  // https://w3c.github.io/ServiceWorker/#reject-job-promise
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#reject-job-promise
   void RejectJobPromise(Job* job, const PromiseErrorData& error_data);
 
-  // https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-job-promise-algorithm
   void ResolveJobPromise(Job* job,
                          scoped_refptr<ServiceWorkerRegistrationObject> value) {
     ResolveJobPromise(job, false, value);
@@ -367,14 +389,14 @@
       Job* job, bool value,
       scoped_refptr<ServiceWorkerRegistrationObject> registration = nullptr);
 
-  // https://w3c.github.io/ServiceWorker/#finish-job-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#finish-job-algorithm
   void FinishJob(Job* job);
 
-  // https://w3c.github.io/ServiceWorker/#get-newest-worker
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-newest-worker
   ServiceWorker* GetNewestWorker(
       scoped_refptr<ServiceWorkerRegistrationObject> registration);
 
-  // https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
   // The return value is a 'Completion or failure'.
   // A failure is signaled by returning nullptr. Otherwise, the returned string
   // points to the value of the Completion returned by the script runner
@@ -382,37 +404,34 @@
   std::string* RunServiceWorker(ServiceWorkerObject* worker,
                                 bool force_bypass_cache = false);
 
-  // https://w3c.github.io/ServiceWorker/#installation-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm
   void Install(Job* job, scoped_refptr<ServiceWorkerObject> worker,
                scoped_refptr<ServiceWorkerRegistrationObject> registration);
 
-  // https://w3c.github.io/ServiceWorker/#try-activate-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm
   void TryActivate(scoped_refptr<ServiceWorkerRegistrationObject> registration);
 
-  // https://w3c.github.io/ServiceWorker/#service-worker-has-no-pending-events
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events
   bool ServiceWorkerHasNoPendingEvents(ServiceWorkerObject* worker);
 
-  // https://w3c.github.io/ServiceWorker/#update-registration-state-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm
   void UpdateRegistrationState(
       scoped_refptr<ServiceWorkerRegistrationObject> registration,
       RegistrationState target, scoped_refptr<ServiceWorkerObject> source);
 
-  // https://w3c.github.io/ServiceWorker/#update-state-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm
   void UpdateWorkerState(ServiceWorkerObject* worker, ServiceWorkerState state);
 
-  // https://w3c.github.io/ServiceWorker/#should-skip-event-algorithm
-  bool ShouldSkipEvent(base::Token event_name, ServiceWorkerObject* worker);
-
-  // https://w3c.github.io/ServiceWorker/#on-client-unload-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm
   void HandleServiceWorkerClientUnload(web::EnvironmentSettings* client);
 
-  // https://w3c.github.io/ServiceWorker/#terminate-service-worker
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker
   void TerminateServiceWorker(ServiceWorkerObject* worker);
 
-  // https://w3c.github.io/ServiceWorker/#notify-controller-change-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm
   void NotifyControllerChange(web::EnvironmentSettings* client);
 
-  // https://w3c.github.io/ServiceWorker/#try-clear-registration-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm
   void TryClearRegistration(
       scoped_refptr<ServiceWorkerRegistrationObject> registration);
 
@@ -434,6 +453,10 @@
   base::WaitableEvent web_context_registrations_cleared_ = {
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+  base::WaitableEvent done_event_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::SIGNALED};
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc
index e63c421..4e85305 100644
--- a/cobalt/worker/service_worker_object.cc
+++ b/cobalt/worker/service_worker_object.cc
@@ -94,7 +94,7 @@
 
 void ServiceWorkerObject::PurgeScriptResourceMap() {
   // Steps 13-15 of Algorithm for Install:
-  //   https://w3c.github.io/ServiceWorker/#installation-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm
   // 13. Let map be registration’s installing worker's script resource map.
   // 14. Let usedSet be registration’s installing worker's set of used scripts.
   // 15. For each url of map:
@@ -132,10 +132,18 @@
   web_agent_->WaitUntilDone();
 }
 
+bool ServiceWorkerObject::ShouldSkipEvent(base::Token event_name) {
+  // Algorithm for Should Skip Event:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#should-skip-event-algorithm
+  // TODO(b/229622132): Implementing this algorithm will improve performance.
+  NOTIMPLEMENTED();
+  return false;
+}
+
 void ServiceWorkerObject::Initialize(web::Context* context) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerObject::Initialize()");
   // Algorithm for "Run Service Worker"
-  // https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
 
   // 8.1. Let realmExecutionContext be the result of creating a new JavaScript
   //      realm given agent and the following customizations:
diff --git a/cobalt/worker/service_worker_object.h b/cobalt/worker/service_worker_object.h
index a3cf5cb..e1f14e4 100644
--- a/cobalt/worker/service_worker_object.h
+++ b/cobalt/worker/service_worker_object.h
@@ -41,14 +41,14 @@
 class ServiceWorkerRegistrationObject;
 
 // This class represents the 'service worker'.
-//   https://w3c.github.io/ServiceWorker/#dfn-service-worker
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-service-worker
 // Not to be confused with the ServiceWorker JavaScript object,  this represents
 // the service worker in the browser, independent from the JavaScript realm.
 // The lifetime of a service worker is tied to the execution lifetime of events
 // and not references held by service worker clients to the ServiceWorker
 // object. A user agent may terminate service workers at any time it has no
 // event to handle, or detects abnormal operation.
-//   https://w3c.github.io/ServiceWorker/#service-worker-lifetime
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-lifetime
 class ServiceWorkerObject
     : public base::RefCountedThreadSafe<ServiceWorkerObject>,
       public base::SupportsWeakPtr<ServiceWorkerObject>,
@@ -75,18 +75,18 @@
   ServiceWorkerObject(const ServiceWorkerObject&) = delete;
   ServiceWorkerObject& operator=(const ServiceWorkerObject&) = delete;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-state
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-state
   void set_state(ServiceWorkerState state) { state_ = state; }
   ServiceWorkerState state() const { return state_; }
-  // https://w3c.github.io/ServiceWorker/#dfn-script-url
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-script-url
   void set_script_url(const GURL& script_url) { script_url_ = script_url; }
   const GURL& script_url() const { return script_url_; }
 
-  // https://w3c.github.io/ServiceWorker/#dfn-containing-service-worker-registration
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-containing-service-worker-registration
   ServiceWorkerRegistrationObject* containing_service_worker_registration() {
     return options_.containing_service_worker_registration;
   }
-  // https://w3c.github.io/ServiceWorker/#dfn-skip-waiting-flag
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-skip-waiting-flag
   void set_skip_waiting() { skip_waiting_ = true; }
   bool skip_waiting() const { return skip_waiting_; }
 
@@ -94,12 +94,12 @@
   void set_classic_scripts_imported() { classic_scripts_imported_ = true; }
   bool classic_scripts_imported() { return classic_scripts_imported_; }
 
-  // https://w3c.github.io/ServiceWorker/#dfn-set-of-used-scripts
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-set-of-used-scripts
   void AppendToSetOfUsedScripts(const GURL& url) {
     set_of_used_scripts_.insert(url);
   }
 
-  // https://w3c.github.io/ServiceWorker/#dfn-script-resource-map
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-script-resource-map
   void set_script_resource_map(ScriptResourceMap&& resource_map) {
     script_resource_map_ = std::move(resource_map);
   }
@@ -108,13 +108,13 @@
   std::string* LookupScriptResource(const GURL& url) const;
 
   // Steps 13-15 of Algorithm for Install.
-  //   https://w3c.github.io/ServiceWorker/#installation-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm
   void PurgeScriptResourceMap();
   const ScriptResourceMap& script_resource_map() {
     return script_resource_map_;
   }
 
-  // https://w3c.github.io/ServiceWorker/#service-worker-start-status
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-start-status
   void set_start_status(std::string* start_status) {
     start_status_.reset(start_status);
   }
@@ -136,10 +136,14 @@
 
   void ObtainWebAgentAndWaitUntilDone();
 
+  // Algorithm for Should Skip Event:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#should-skip-event-algorithm
+  bool ShouldSkipEvent(base::Token event_name);
+
  private:
   // Called by ObtainWebAgentAndWaitUntilDone to perform initialization required
   // on the dedicated thread.
-  //   https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
   void Initialize(web::Context* context);
 
   // The message loop this object is running on.
@@ -159,25 +163,25 @@
 
   Options options_;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-state
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-state
   ServiceWorkerState state_;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-script-url
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-script-url
   GURL script_url_;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-script-resource-map
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-script-resource-map
   ScriptResourceMap script_resource_map_;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-set-of-used-scripts
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-set-of-used-scripts
   std::set<GURL> set_of_used_scripts_;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-skip-waiting-flag
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-skip-waiting-flag
   bool skip_waiting_ = false;
 
-  // https://w3c.github.io/ServiceWorker/#dfn-classic-scripts-imported-flag
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-classic-scripts-imported-flag
   bool classic_scripts_imported_ = false;
 
-  // https://w3c.github.io/ServiceWorker/#service-worker-start-status
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-start-status
   std::unique_ptr<std::string> start_status_;
 
   starboard::atomic_bool start_failed_;
diff --git a/cobalt/worker/service_worker_registration.cc b/cobalt/worker/service_worker_registration.cc
index 0728683..c63e99c 100644
--- a/cobalt/worker/service_worker_registration.cc
+++ b/cobalt/worker/service_worker_registration.cc
@@ -41,7 +41,7 @@
 script::HandlePromiseWrappable ServiceWorkerRegistration::Update() {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerRegistration::Update()");
   // Algorithm for update():
-  //   https://w3c.github.io/ServiceWorker/#service-worker-registration-update
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-registration-update
 
   script::HandlePromiseWrappable promise =
       environment_settings()
@@ -69,7 +69,7 @@
   DCHECK_EQ(base::MessageLoop::current(),
             environment_settings()->context()->message_loop());
   // Algorithm for update():
-  //   https://w3c.github.io/ServiceWorker/#service-worker-registration-update
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-registration-update
 
   // 1. Let registration be the service worker registration.
   // 2. Let newestWorker be the result of running Get Newest Worker algorithm
@@ -127,7 +127,7 @@
 
 script::HandlePromiseBool ServiceWorkerRegistration::Unregister() {
   // Algorithm for unregister():
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-unregister
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-unregister
   // 1. Let registration be the service worker registration.
   // 2. Let promise be a new promise.
   script::HandlePromiseBool promise = environment_settings()
@@ -151,7 +151,7 @@
 void ServiceWorkerRegistration::UnregisterTask(
     std::unique_ptr<script::ValuePromiseBool::Reference> promise_reference) {
   // Algorithm for unregister():
-  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-unregister
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-unregister
   // 3. Let job be the result of running Create Job with unregister,
   //    registration’s storage key, registration’s scope url, null, promise, and
   //    this's relevant settings object.
diff --git a/cobalt/worker/service_worker_registration.h b/cobalt/worker/service_worker_registration.h
index 58a2e3d..202647c 100644
--- a/cobalt/worker/service_worker_registration.h
+++ b/cobalt/worker/service_worker_registration.h
@@ -37,7 +37,7 @@
 
 // The ServiceWorkerRegistration interface represents a service worker
 // registration within a service worker client realm.
-//   https://w3c.github.io/ServiceWorker/#serviceworker-interface
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#serviceworker-interface
 class ServiceWorkerRegistration : public web::EventTarget {
  public:
   ServiceWorkerRegistration(
diff --git a/cobalt/worker/service_worker_registration_map.cc b/cobalt/worker/service_worker_registration_map.cc
index 5e8231e..68ede06 100644
--- a/cobalt/worker/service_worker_registration_map.cc
+++ b/cobalt/worker/service_worker_registration_map.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
@@ -40,6 +41,7 @@
 namespace worker {
 
 namespace {
+
 // Returns the serialized URL excluding the fragment.
 std::string SerializeExcludingFragment(const GURL& url) {
   url::Replacements<char> replacements;
@@ -49,6 +51,7 @@
   DCHECK(!no_fragment_url.is_empty());
   return no_fragment_url.spec();
 }
+
 }  // namespace
 
 scoped_refptr<ServiceWorkerRegistrationObject>
@@ -59,7 +62,7 @@
       "ServiceWorkerRegistrationMap::MatchServiceWorkerRegistration()");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // Algorithm for Match Service Worker Registration:
-  //   https://w3c.github.io/ServiceWorker/#scope-match-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#scope-match-algorithm
   GURL matching_scope;
 
   // 1. Run the following steps atomically.
@@ -123,7 +126,7 @@
                "ServiceWorkerRegistrationMap::GetRegistration()");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // Algorithm for Get Registration:
-  //   https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-registration-algorithm
 
   // 1. Run the following steps atomically.
   base::AutoLock lock(mutex_);
@@ -152,6 +155,21 @@
   return nullptr;
 }
 
+std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
+ServiceWorkerRegistrationMap::GetRegistrations(const url::Origin& storage_key) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerRegistrationMap::GetRegistrations()");
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  base::AutoLock lock(mutex_);
+  std::vector<scoped_refptr<ServiceWorkerRegistrationObject>> result;
+  for (const auto& entry : registration_map_) {
+    if (entry.first.first == storage_key) {
+      result.push_back(std::move(entry.second));
+    }
+  }
+  return result;
+}
+
 scoped_refptr<ServiceWorkerRegistrationObject>
 ServiceWorkerRegistrationMap::SetRegistration(
     const url::Origin& storage_key, const GURL& scope,
@@ -160,7 +178,7 @@
                "ServiceWorkerRegistrationMap::SetRegistration()");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // Algorithm for Set Registration:
-  //   https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#set-registration-algorithm
 
   // 1. Run the following steps atomically.
   base::AutoLock lock(mutex_);
@@ -202,7 +220,7 @@
   // A service worker registration is said to be unregistered if registration
   // map[this service worker registration's (storage key, serialized scope url)]
   // is not this service worker registration.
-  //   https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration-unregistered
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-service-worker-registration-unregistered
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   Key registration_key(registration->storage_key(),
                        registration->scope_url().spec());
@@ -218,7 +236,7 @@
                "ServiceWorkerRegistrationMap::HandleUserAgentShutdown()");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // Algorithm for Handle User Agent Shutdown:
-  //   https://w3c.github.io/ServiceWorker/#on-user-agent-shutdown-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-user-agent-shutdown-algorithm
 
   // 1. For each (storage key, scope) -> registration of registration map:
   for (auto& entry : registration_map_) {
@@ -249,5 +267,15 @@
   }
 }
 
+void ServiceWorkerRegistrationMap::AbortAllActive() {
+  for (auto& entry : registration_map_) {
+    const scoped_refptr<ServiceWorkerRegistrationObject>& registration =
+        entry.second;
+    if (registration->active_worker()) {
+      registration->active_worker()->Abort();
+    }
+  }
+}
+
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker_registration_map.h b/cobalt/worker/service_worker_registration_map.h
index 5f104b0..45f34e2 100644
--- a/cobalt/worker/service_worker_registration_map.h
+++ b/cobalt/worker/service_worker_registration_map.h
@@ -19,6 +19,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
@@ -38,39 +39,44 @@
 class ServiceWorkerJobs;
 
 // Algorithms for the service worker scope to registration map.
-//   https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-scope-to-registration-map
 class ServiceWorkerRegistrationMap {
  public:
   using Key = std::pair<url::Origin, std::string>;
 
-  // https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-registration-algorithm
   scoped_refptr<ServiceWorkerRegistrationObject> GetRegistration(
       const url::Origin& storage_key, const GURL& scope);
 
-  // https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+  std::vector<scoped_refptr<ServiceWorkerRegistrationObject>> GetRegistrations(
+      const url::Origin& storage_key);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#set-registration-algorithm
   scoped_refptr<ServiceWorkerRegistrationObject> SetRegistration(
       const url::Origin& storage_key, const GURL& scope,
       const ServiceWorkerUpdateViaCache& update_via_cache);
 
-  // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#scope-match-algorithm
   scoped_refptr<ServiceWorkerRegistrationObject> MatchServiceWorkerRegistration(
       const url::Origin& storage_key, const GURL& client_url);
 
   void RemoveRegistration(const url::Origin& storage_key, const GURL& scope);
 
-  // https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration-unregistered
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-service-worker-registration-unregistered
   bool IsUnregistered(ServiceWorkerRegistrationObject* registration);
 
-  // https://w3c.github.io/ServiceWorker/#on-user-agent-shutdown-algorithm
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-user-agent-shutdown-algorithm
   void HandleUserAgentShutdown(ServiceWorkerJobs* jobs);
 
+  void AbortAllActive();
+
  private:
   // ThreadChecker for use by the methods operating on the registration map.
   THREAD_CHECKER(thread_checker_);
 
   // A registration map is an ordered map where the keys are (storage key,
   // serialized scope urls) and the values are service worker registrations.
-  //   https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-scope-to-registration-map
   std::map<Key, scoped_refptr<ServiceWorkerRegistrationObject>>
       registration_map_;
 
diff --git a/cobalt/worker/service_worker_registration_object.cc b/cobalt/worker/service_worker_registration_object.cc
index 921eb8f..ab444e9 100644
--- a/cobalt/worker/service_worker_registration_object.cc
+++ b/cobalt/worker/service_worker_registration_object.cc
@@ -32,7 +32,7 @@
 
 ServiceWorkerObject* ServiceWorkerRegistrationObject::GetNewestWorker() {
   // Algorithm for Get Newest Worker:
-  //   https://w3c.github.io/ServiceWorker/#get-newest-worker
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-newest-worker
   // 1. Run the following steps atomically.
   base::AutoLock lock(mutex_);
 
diff --git a/cobalt/worker/service_worker_registration_object.h b/cobalt/worker/service_worker_registration_object.h
index ef1f83b..0a399e0 100644
--- a/cobalt/worker/service_worker_registration_object.h
+++ b/cobalt/worker/service_worker_registration_object.h
@@ -29,13 +29,13 @@
 namespace worker {
 
 // This class represents the 'service worker registration'.
-//   https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-service-worker-registration
 // Not to be confused with the ServiceWorkerRegistration JavaScript object,
 // this represents the registration of the service worker in the browser,
 // independent from the JavaScript realm. The lifetime of this object is beyond
 // that of the ServiceWorkerRegistration JavaScript object(s) that represent
 // this object in their service worker clients.
-//   https://w3c.github.io/ServiceWorker/#service-worker-registration-lifetime
+//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-registration-lifetime
 class ServiceWorkerRegistrationObject
     : public base::RefCountedThreadSafe<ServiceWorkerRegistrationObject> {
  public:
@@ -73,7 +73,7 @@
     return active_worker_;
   }
 
-  // https://w3c.github.io/ServiceWorker/#get-newest-worker
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#get-newest-worker
   ServiceWorkerObject* GetNewestWorker();
 
  private:
diff --git a/cobalt/worker/window_client.cc b/cobalt/worker/window_client.cc
index 194ef1d..aa5e26a 100644
--- a/cobalt/worker/window_client.cc
+++ b/cobalt/worker/window_client.cc
@@ -14,13 +14,19 @@
 
 #include "cobalt/worker/window_client.h"
 
+#include "cobalt/dom/document.h"
+#include "cobalt/dom/visibility_state.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
+
 namespace cobalt {
 namespace worker {
 
 WindowClient::WindowClient(const WindowData& window_data)
     : Client(window_data.client) {
   // Algorithm for Create Window Client:
-  //   https://w3c.github.io/ServiceWorker/#create-window-client
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-window-client
 
   // 1. Let windowClient be a new WindowClient object.
   // 2. Set windowClient’s service worker client to client.
@@ -31,7 +37,7 @@
 
   // 4. Set windowClient’s visibility state to visibilityState.
   // 5. Set windowClient’s focus state to focusState.
-  // TODO(b/235838698): Implement WindowCLient properties and methods.
+  // Determined in the property getter.
 
   // 6. Set windowClient’s ancestor origins array to a frozen array created from
   //    ancestorOriginsList.
@@ -40,5 +46,81 @@
   // 7. Return windowClient.
 }
 
+dom::VisibilityState WindowClient::visibility_state() {
+  DCHECK(event_target());
+  DCHECK(event_target()->environment_settings());
+  web::Context* context = event_target()->environment_settings()->context();
+  DCHECK(context);
+  base::MessageLoop* message_loop = context->message_loop();
+  if (!message_loop) {
+    return dom::kVisibilityStateHidden;
+  }
+
+  dom::VisibilityState visibility_state = dom::kVisibilityStateHidden;
+  if (message_loop->task_runner()->BelongsToCurrentThread()) {
+    DCHECK(context->GetWindowOrWorkerGlobalScope()->IsWindow());
+    DCHECK(context->GetWindowOrWorkerGlobalScope()->AsWindow()->document());
+    visibility_state = context->GetWindowOrWorkerGlobalScope()
+                           ->AsWindow()
+                           ->document()
+                           ->visibility_state();
+  } else {
+    message_loop->task_runner()->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(
+            [](web::Context* context, dom::VisibilityState* visibility_state) {
+              DCHECK(context->GetWindowOrWorkerGlobalScope()->IsWindow());
+              DCHECK(context->GetWindowOrWorkerGlobalScope()
+                         ->AsWindow()
+                         ->document());
+              *visibility_state = context->GetWindowOrWorkerGlobalScope()
+                                      ->AsWindow()
+                                      ->document()
+                                      ->visibility_state();
+            },
+            context, &visibility_state));
+  }
+
+  return visibility_state;
+}
+
+bool WindowClient::focused() {
+  DCHECK(event_target());
+  DCHECK(event_target()->environment_settings());
+  web::Context* context = event_target()->environment_settings()->context();
+  DCHECK(context);
+  base::MessageLoop* message_loop = context->message_loop();
+  if (!message_loop) {
+    return false;
+  }
+
+  bool focused = false;
+  if (message_loop->task_runner()->BelongsToCurrentThread()) {
+    DCHECK(context->GetWindowOrWorkerGlobalScope()->IsWindow());
+    DCHECK(context->GetWindowOrWorkerGlobalScope()->AsWindow()->document());
+    focused = context->GetWindowOrWorkerGlobalScope()
+                  ->AsWindow()
+                  ->document()
+                  ->HasFocus();
+  } else {
+    message_loop->task_runner()->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(
+            [](web::Context* context, bool* focused) {
+              DCHECK(context->GetWindowOrWorkerGlobalScope()->IsWindow());
+              DCHECK(context->GetWindowOrWorkerGlobalScope()
+                         ->AsWindow()
+                         ->document());
+              *focused = context->GetWindowOrWorkerGlobalScope()
+                             ->AsWindow()
+                             ->document()
+                             ->HasFocus();
+            },
+            context, &focused));
+  }
+
+  return focused;
+}
+
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/window_client.h b/cobalt/worker/window_client.h
index 4fcde90..a802684 100644
--- a/cobalt/worker/window_client.h
+++ b/cobalt/worker/window_client.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_WORKER_WINDOW_CLIENT_H_
 #define COBALT_WORKER_WINDOW_CLIENT_H_
 
+#include "cobalt/dom/visibility_state.h"
 #include "cobalt/web/environment_settings.h"
 #include "cobalt/worker/client.h"
 #include "cobalt/worker/frame_type.h"
@@ -23,22 +24,32 @@
 namespace worker {
 
 struct WindowData {
+ public:
+  WindowData(web::EnvironmentSettings* client = nullptr,
+             FrameType frame_type = kFrameTypeTopLevel)
+      : client(client), frame_type(frame_type) {}
+
   web::EnvironmentSettings* client = nullptr;
   FrameType frame_type = kFrameTypeTopLevel;
 };
 
 class WindowClient : public Client {
  public:
-  // https://w3c.github.io/ServiceWorker/#create-window-client
-  static WindowClient* Create(const WindowData& window_data) {
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-window-client
+  static scoped_refptr<Client> Create(const WindowData& window_data) {
     return new WindowClient(window_data);
   }
-  // TODO(b/235838698): Implement WindowCLient properties and methods.
+  dom::VisibilityState visibility_state();
+  bool focused();
 
+  // TODO(b/235838698): Implement WindowClient methods.
   DEFINE_WRAPPABLE_TYPE(WindowClient);
 
  private:
   explicit WindowClient(const WindowData& window_data);
+
+  dom::VisibilityState visibility_state_ = dom::kVisibilityStateVisible;
+  bool focused_ = true;
 };
 
 
diff --git a/cobalt/worker/window_client.idl b/cobalt/worker/window_client.idl
index 696b272..3804274 100644
--- a/cobalt/worker/window_client.idl
+++ b/cobalt/worker/window_client.idl
@@ -15,9 +15,9 @@
 // https://w3c.github.io/ServiceWorker/#client-interface
 
 [Exposed = ServiceWorker] interface WindowClient : Client {
-  // TODO(b/235838698): Implement WindowCLient properties and methods.
-  // readonly attribute VisibilityState visibilityState;
-  // readonly attribute boolean focused;
+  readonly attribute VisibilityState visibilityState;
+  readonly attribute boolean focused;
+  // TODO(b/235838698): Implement WindowClient methods.
   // [NewObject] Promise<WindowClient> focus();
   // [NewObject] Promise<WindowClient?> navigate(USVString url);
 };
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc
index 4c68bbe..c483c56 100644
--- a/cobalt/worker/worker_global_scope.cc
+++ b/cobalt/worker/worker_global_scope.cc
@@ -120,6 +120,8 @@
     // Todo: implement csp check (b/225037465)
     csp::SecurityCallback csp_callback = base::Bind(&PermitAnyURL);
 
+    bool skip_fetch_intercept =
+        context_->GetWindowOrWorkerGlobalScope()->IsServiceWorker();
     // If there is a request callback, call it to possibly retrieve previously
     // requested content.
     *loader = script_loader_factory_->CreateScriptLoader(
@@ -132,7 +134,8 @@
             },
             content),
         base::Bind(&ScriptLoader::LoadingCompleteCallback,
-                   base::Unretained(this), loader, error));
+                   base::Unretained(this), loader, error),
+        skip_fetch_intercept);
   }
 
   void LoadingCompleteCallback(std::unique_ptr<loader::Loader>* loader,
@@ -254,7 +257,7 @@
     ScriptResourceMap* new_resource_map) {
   bool has_updated_resources = false;
   // Steps from Algorithm for Update:
-  //   https://w3c.github.io/ServiceWorker/#update-algorithm
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-algorithm
   //   8.21.1. For each importUrl -> storedResponse of newestWorker’s script
   //           resource map:
   std::vector<GURL> request_urls;
diff --git a/cobalt/xhr/url_fetcher_buffer_writer.cc b/cobalt/xhr/url_fetcher_buffer_writer.cc
index fd31307..d841e3b 100644
--- a/cobalt/xhr/url_fetcher_buffer_writer.cc
+++ b/cobalt/xhr/url_fetcher_buffer_writer.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 Google Inc. All Rights Reserved.
+// Copyright 2019 The Cobalt Authors. 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.
@@ -14,6 +14,8 @@
 
 #include "cobalt/xhr/url_fetcher_buffer_writer.h"
 
+#include <algorithm>
+
 #include "base/logging.h"
 #include "net/base/net_errors.h"
 #include "starboard/memory.h"
@@ -81,7 +83,6 @@
 
   UpdateType_Locked(kString);
   allow_write_ = false;
-
   return data_as_string_;
 }
 
@@ -282,7 +283,7 @@
     data_as_array_buffer_.Resize(data_as_string_.capacity());
     data_as_array_buffer_size_ = data_as_string_.size();
     memcpy(data_as_array_buffer_.data(), data_as_string_.data(),
-                 data_as_array_buffer_size_);
+           data_as_array_buffer_size_);
 
     ReleaseMemory(&data_as_string_);
     ReleaseMemory(&copy_of_data_as_string_);
diff --git a/cobalt/xhr/xml_http_request.cc b/cobalt/xhr/xml_http_request.cc
index d943154..b731be3 100644
--- a/cobalt/xhr/xml_http_request.cc
+++ b/cobalt/xhr/xml_http_request.cc
@@ -27,13 +27,13 @@
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/performance.h"
 #include "cobalt/dom/progress_event.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/dom/xml_document.h"
 #include "cobalt/dom_parser/xml_decoder.h"
 #include "cobalt/loader/cors_preflight.h"
+#include "cobalt/loader/fetch_interceptor_coordinator.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/url_fetcher_string_writer.h"
 #include "cobalt/script/global_environment.h"
@@ -188,19 +188,12 @@
     : XMLHttpRequestEventTarget(settings) {
   // Determine which implementation of XHR to use based on being in a window or
   // worker.
-  if (environment_settings()
-          ->context()
-          ->GetWindowOrWorkerGlobalScope()
-          ->IsWindow()) {
+  web::WindowOrWorkerGlobalScope* window_or_worker_global_scope =
+      environment_settings()->context()->GetWindowOrWorkerGlobalScope();
+  if (window_or_worker_global_scope->IsWindow()) {
     xhr_impl_ = std::make_unique<DOMXMLHttpRequestImpl>(this);
-  } else if (environment_settings()
-                 ->context()
-                 ->GetWindowOrWorkerGlobalScope()
-                 ->IsDedicatedWorker() ||
-             environment_settings()
-                 ->context()
-                 ->GetWindowOrWorkerGlobalScope()
-                 ->IsServiceWorker()) {
+  } else if (window_or_worker_global_scope->IsDedicatedWorker() ||
+             window_or_worker_global_scope->IsServiceWorker()) {
     xhr_impl_ = std::make_unique<XMLHttpRequestImpl>(this);
   }
   xhr::GlobalStats::GetInstance()->Add(this);
@@ -365,9 +358,10 @@
       sent_(false),
       settings_(xhr->environment_settings()),
       stop_timeout_(false),
+      task_runner_(base::MessageLoop::current()->task_runner()),
       timeout_ms_(0),
       upload_complete_(false) {
-  DCHECK(settings_);
+  DCHECK(environment_settings());
 }
 
 void XMLHttpRequestImpl::Abort() {
@@ -411,7 +405,7 @@
     return;
   }
 
-  base_url_ = settings_->base_url();
+  base_url_ = environment_settings()->base_url();
 
   if (IsForbiddenMethod(method)) {
     web::DOMException::Raise(web::DOMException::kSecurityErr, exception_state);
@@ -510,6 +504,100 @@
 void XMLHttpRequestImpl::Send(
     const base::Optional<XMLHttpRequest::RequestBodyType>& request_body,
     script::ExceptionState* exception_state) {
+  error_ = false;
+  bool in_service_worker = environment_settings()
+                               ->context()
+                               ->GetWindowOrWorkerGlobalScope()
+                               ->IsServiceWorker();
+  if (!in_service_worker && method_ == net::URLFetcher::GET) {
+    loader::FetchInterceptorCoordinator::GetInstance()->TryIntercept(
+        request_url_,
+        std::make_unique<
+            base::OnceCallback<void(std::unique_ptr<std::string>)>>(
+            base::BindOnce(&XMLHttpRequestImpl::SendIntercepted,
+                           base::Unretained(this))),
+        std::make_unique<base::OnceCallback<void(const net::LoadTimingInfo&)>>(
+            base::BindOnce(&XMLHttpRequestImpl::ReportLoadTimingInfo,
+                           base::Unretained(this))),
+        std::make_unique<base::OnceClosure>(base::BindOnce(
+            &XMLHttpRequestImpl::SendFallback, base::Unretained(this),
+            request_body, exception_state)));
+    return;
+  }
+  SendFallback(request_body, exception_state);
+}
+
+void XMLHttpRequestImpl::SendIntercepted(
+    std::unique_ptr<std::string> response) {
+  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&XMLHttpRequestImpl::SendIntercepted,
+                                  base::Unretained(this), std::move(response)));
+    return;
+  }
+  sent_ = true;
+  // Now that a send is happening, prevent this object
+  // from being collected until it's complete or aborted
+  // if no currently active request has called it before.
+  // TODO: consider deduplicating code from |Send()|,
+  //       |OnURLFetchDownloadProgress()|, and |OnURLFetchComplete()|.
+
+  // Send().
+  IncrementActiveRequests();
+  FireProgressEvent(xhr_, base::Tokens::loadstart());
+  if (!upload_complete_) {
+    FireProgressEvent(upload_, base::Tokens::loadstart());
+  }
+
+  // OnURLFetchResponseStarted().
+  http_status_ = 200;
+  http_response_headers_ = new net::HttpResponseHeaders("");
+  ChangeState(XMLHttpRequest::kHeadersReceived);
+
+  // OnURLFetchDownloadProgress().
+  ChangeState(XMLHttpRequest::kLoading);
+  response_body_ = new URLFetcherResponseWriter::Buffer(
+      URLFetcherResponseWriter::Buffer::kString);
+  response_body_->Write(response->data(), response->size());
+  if (fetch_callback_) {
+    script::Handle<script::Uint8Array> data =
+        script::Uint8Array::New(settings_->context()->global_environment(),
+                                response->data(), response->size());
+    fetch_callback_->value().Run(data);
+  }
+
+  // OnURLFetchComplete().
+  if (!upload_complete_ && upload_listener_) {
+    upload_complete_ = true;
+    FireProgressEvent(upload_, base::Tokens::progress());
+    FireProgressEvent(upload_, base::Tokens::load());
+    FireProgressEvent(upload_, base::Tokens::loadend());
+  }
+  ChangeState(XMLHttpRequest::kDone);
+  size_t received_length = response->size();
+  FireProgressEvent(xhr_, base::Tokens::load(), received_length,
+                    received_length,
+                    /*length_computable=*/true);
+  FireProgressEvent(xhr_, base::Tokens::loadend(), received_length,
+                    received_length,
+                    /*length_computable=*/true);
+  // Undo the ref we added in Send()
+  DecrementActiveRequests();
+
+  fetch_callback_.reset();
+  fetch_mode_callback_.reset();
+}
+
+void XMLHttpRequestImpl::SendFallback(
+    const base::Optional<XMLHttpRequest::RequestBodyType>& request_body,
+    script::ExceptionState* exception_state) {
+  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&XMLHttpRequestImpl::SendFallback,
+                       base::Unretained(this), request_body, exception_state));
+    return;
+  }
   TRACK_MEMORY_SCOPE("XHR");
   // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#the-send()-method
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -547,17 +635,17 @@
                                    "text/plain;charset=UTF-8");
       }
     } else if (request_body
-                   ->IsType<script::Handle<script::ArrayBufferView> >()) {
+                   ->IsType<script::Handle<script::ArrayBufferView>>()) {
       script::Handle<script::ArrayBufferView> view =
-          request_body->AsType<script::Handle<script::ArrayBufferView> >();
+          request_body->AsType<script::Handle<script::ArrayBufferView>>();
       if (view->ByteLength()) {
         const char* start = reinterpret_cast<const char*>(view->RawData());
         request_body_text_.assign(start + view->ByteOffset(),
                                   view->ByteLength());
       }
-    } else if (request_body->IsType<script::Handle<script::ArrayBuffer> >()) {
+    } else if (request_body->IsType<script::Handle<script::ArrayBuffer>>()) {
       script::Handle<script::ArrayBuffer> array_buffer =
-          request_body->AsType<script::Handle<script::ArrayBuffer> >();
+          request_body->AsType<script::Handle<script::ArrayBuffer>>();
       if (array_buffer->ByteLength()) {
         const char* start = reinterpret_cast<const char*>(array_buffer->Data());
         request_body_text_.assign(start, array_buffer->ByteLength());
@@ -570,7 +658,7 @@
   if (upload_) {
     upload_listener_ = upload_->HasOneOrMoreAttributeEventListener();
   }
-  origin_ = settings_->GetOrigin();
+  origin_ = environment_settings()->GetOrigin();
   // Step 9
   sent_ = true;
   // Now that a send is happening, prevent this object
@@ -798,7 +886,7 @@
 
 scoped_refptr<XMLHttpRequestUpload> XMLHttpRequestImpl::upload() {
   if (!upload_) {
-    upload_ = new XMLHttpRequestUpload(settings_);
+    upload_ = new XMLHttpRequestUpload(environment_settings());
   }
   return upload_;
 }
@@ -849,7 +937,7 @@
   if (is_cross_origin_) {
     size_t iter = 0;
     std::string name, value;
-    std::vector<std::pair<std::string, std::string> > header_names_to_discard;
+    std::vector<std::pair<std::string, std::string>> header_names_to_discard;
     std::vector<std::string> expose_headers;
     loader::CORSPreflight::GetServerAllowedHeaders(*http_response_headers_,
                                                    &expose_headers);
@@ -867,7 +955,7 @@
   if (is_data_url_) {
     size_t iter = 0;
     std::string name, value;
-    std::vector<std::pair<std::string, std::string> > header_names_to_discard;
+    std::vector<std::pair<std::string, std::string>> header_names_to_discard;
     while (http_response_headers_->EnumerateHeaderLines(&iter, &name, &value)) {
       if (name != net::HttpRequestHeaders::kContentType) {
         header_names_to_discard.push_back(std::make_pair(name, value));
@@ -900,9 +988,9 @@
   if (fetch_callback_) {
     std::string downloaded_data;
     response_body_->GetAndResetDataAndDownloadProgress(&downloaded_data);
-    script::Handle<script::Uint8Array> data =
-        script::Uint8Array::New(settings_->context()->global_environment(),
-                                downloaded_data.data(), downloaded_data.size());
+    script::Handle<script::Uint8Array> data = script::Uint8Array::New(
+        environment_settings()->context()->global_environment(),
+        downloaded_data.data(), downloaded_data.size());
     fetch_callback_->value().Run(data);
   }
 
@@ -1111,10 +1199,10 @@
 }
 
 web::CspDelegate* XMLHttpRequestImpl::csp_delegate() const {
-  DCHECK(settings_);
-  DCHECK(settings_->context());
-  DCHECK(settings_->context()->GetWindowOrWorkerGlobalScope());
-  return settings_->context()->GetWindowOrWorkerGlobalScope()->csp_delegate();
+  return environment_settings()
+      ->context()
+      ->GetWindowOrWorkerGlobalScope()
+      ->csp_delegate();
 }
 
 void XMLHttpRequestImpl::TerminateRequest() {
@@ -1127,12 +1215,14 @@
     XMLHttpRequest::RequestErrorType request_error_type) {
   // https://www.w3.org/TR/XMLHttpRequest/#timeout-error
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DLOG_IF(INFO, verbose())
-      << __FUNCTION__ << " (" << RequestErrorTypeName(request_error_type)
-      << ") " << *xhr_ << std::endl
-      << script::StackTraceToString(
-             settings_->context()->global_environment()->GetStackTrace(
-                 0 /*max_frames*/));
+  DLOG_IF(INFO, verbose()) << __FUNCTION__ << " ("
+                           << RequestErrorTypeName(request_error_type) << ") "
+                           << *xhr_ << std::endl
+                           << script::StackTraceToString(
+                                  environment_settings()
+                                      ->context()
+                                      ->global_environment()
+                                      ->GetStackTrace(0 /*max_frames*/));
   stop_timeout_ = true;
   // Step 1
   TerminateRequest();
@@ -1209,7 +1299,8 @@
         new script::PreallocatedArrayBufferData());
     response_body_->GetAndResetData(downloaded_data.get());
     auto array_buffer = script::ArrayBuffer::New(
-        settings_->context()->global_environment(), std::move(downloaded_data));
+        environment_settings()->context()->global_environment(),
+        std::move(downloaded_data));
     response_array_buffer_reference_.reset(
         new script::ScriptValue<script::ArrayBuffer>::Reference(xhr_,
                                                                 array_buffer));
@@ -1252,7 +1343,7 @@
   response_array_buffer_reference_.reset();
 
   network::NetworkModule* network_module =
-      settings_->context()->fetcher_factory()->network_module();
+      environment_settings()->context()->fetcher_factory()->network_module();
   url_fetcher_ = net::URLFetcher::Create(request_url_, method_, xhr_);
   ++url_fetcher_generation_;
   url_fetcher_->SetRequestContext(network_module->url_request_context_getter());
@@ -1303,7 +1394,8 @@
         origin_.SerializedOrigin(),
         base::Bind(&DOMXMLHttpRequestImpl::CORSPreflightErrorCallback,
                    base::Unretained(this)),
-        settings_->context()
+        environment_settings()
+            ->context()
             ->GetWindowOrWorkerGlobalScope()
             ->get_preflight_cache()));
     corspreflight_->set_headers(request_headers_);
@@ -1326,19 +1418,20 @@
   }
   DLOG_IF(INFO, verbose()) << __FUNCTION__ << *xhr_;
   if (!dopreflight) {
-    DCHECK(settings_->context()->network_module());
-    StartURLFetcher(settings_->context()->network_module()->max_network_delay(),
+    DCHECK(environment_settings()->context()->network_module());
+    StartURLFetcher(environment_settings()
+                        ->context()
+                        ->network_module()
+                        ->max_network_delay(),
                     url_fetcher_generation_);
   }
 }
 
 void XMLHttpRequestImpl::IncrementActiveRequests() {
   if (active_requests_count_ == 0) {
-    DCHECK(settings_);
-    DCHECK(settings_->context());
     prevent_gc_until_send_complete_.reset(
         new script::GlobalEnvironment::ScopedPreventGarbageCollection(
-            settings_->context()->global_environment(), xhr_));
+            environment_settings()->context()->global_environment(), xhr_));
   }
   active_requests_count_++;
 }
@@ -1406,16 +1499,15 @@
 }
 
 void XMLHttpRequestImpl::CORSPreflightSuccessCallback() {
-  DCHECK(settings_->context()->network_module());
-  StartURLFetcher(settings_->context()->network_module()->max_network_delay(),
-                  url_fetcher_generation_);
+  DCHECK(environment_settings()->context()->network_module());
+  StartURLFetcher(
+      environment_settings()->context()->network_module()->max_network_delay(),
+      url_fetcher_generation_);
 }
 
 DOMXMLHttpRequestImpl::DOMXMLHttpRequestImpl(XMLHttpRequest* xhr)
-    : XMLHttpRequestImpl(xhr),
-      settings_(base::polymorphic_downcast<dom::DOMSettings*>(
-          xhr->environment_settings())) {
-  DCHECK(settings_);
+    : XMLHttpRequestImpl(xhr) {
+  DCHECK(environment_settings());
 }
 
 // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#the-responsexml-attribute
@@ -1445,22 +1537,34 @@
 }
 
 void DOMXMLHttpRequestImpl::GetLoadTimingInfoAndCreateResourceTiming() {
-  if (settings_->window()->performance() == nullptr) return;
-  settings_->window()->performance()->CreatePerformanceResourceTiming(
-      load_timing_info_, kPerformanceResourceTimingInitiatorType,
-      request_url_.spec());
+  web::WindowOrWorkerGlobalScope* window_or_worker_global_scope =
+      environment_settings()->context()->GetWindowOrWorkerGlobalScope();
+  if (!window_or_worker_global_scope->IsWindow()) return;
+  if (window_or_worker_global_scope->AsWindow()->performance() == nullptr)
+    return;
+  window_or_worker_global_scope->AsWindow()
+      ->performance()
+      ->CreatePerformanceResourceTiming(load_timing_info_,
+                                        kPerformanceResourceTimingInitiatorType,
+                                        request_url_.spec());
 }
 
 // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#document-response-entity-body
 scoped_refptr<dom::Document>
 DOMXMLHttpRequestImpl::GetDocumentResponseEntityBody() {
   DCHECK_EQ(state_, XMLHttpRequest::kDone);
+  if (!environment_settings()
+           ->context()
+           ->GetWindowOrWorkerGlobalScope()
+           ->IsWindow()) {
+    return nullptr;
+  }
 
   // Step 1..5
   const std::string final_mime_type =
       mime_type_override_.empty() ? response_mime_type_ : mime_type_override_;
   if (final_mime_type != "text/xml" && final_mime_type != "application/xml") {
-    return NULL;
+    return nullptr;
   }
 
   // 6. Otherwise, let document be a document that represents the result of
@@ -1468,9 +1572,15 @@
   // specifications. If that fails (unsupported character encoding, namespace
   // well-formedness error, etc.), return null.
   scoped_refptr<dom::XMLDocument> xml_document =
-      new dom::XMLDocument(settings_->window()->html_element_context());
+      new dom::XMLDocument(environment_settings()
+                               ->context()
+                               ->GetWindowOrWorkerGlobalScope()
+                               ->AsWindow()
+                               ->html_element_context());
   dom_parser::XMLDecoder xml_decoder(
-      xml_document, xml_document, NULL, settings_->max_dom_element_depth(),
+      xml_document, xml_document, NULL,
+      base::polymorphic_downcast<dom::DOMSettings*>(environment_settings())
+          ->max_dom_element_depth(),
       base::SourceLocation("[object XMLHttpRequest]", 1, 1),
       base::Bind(&DOMXMLHttpRequestImpl::XMLDecoderLoadCompleteCallback,
                  base::Unretained(this)));
diff --git a/cobalt/xhr/xml_http_request.h b/cobalt/xhr/xml_http_request.h
index 01979a6..0b0926a 100644
--- a/cobalt/xhr/xml_http_request.h
+++ b/cobalt/xhr/xml_http_request.h
@@ -310,6 +310,7 @@
  protected:
   void CORSPreflightErrorCallback();
   void CORSPreflightSuccessCallback();
+  web::EnvironmentSettings* environment_settings() const { return settings_; }
 
   // Return the CSP delegate from the Settings object.
   // virtual for use by tests.
@@ -376,6 +377,11 @@
 
   virtual void StartRequest(const std::string& request_body);
 
+  void SendFallback(
+      const base::Optional<XMLHttpRequest::RequestBodyType>& request_body,
+      script::ExceptionState* exception_state);
+  void SendIntercepted(std::unique_ptr<std::string> response);
+
   // The following two methods are used to determine if garbage collection is
   // needed. It is legal to reuse XHR and send a new request in last request's
   // onload event listener. We should not allow garbage collection until
@@ -424,6 +430,7 @@
   bool sent_;
   web::EnvironmentSettings* const settings_;
   bool stop_timeout_;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   uint32 timeout_ms_;
   bool upload_complete_;
 
@@ -453,7 +460,6 @@
   void XMLDecoderLoadCompleteCallback(
       const base::Optional<std::string>& status);
 
-  dom::DOMSettings* const settings_;
   bool has_xml_decoder_error_;
 };
 
diff --git a/components/prefs/BUILD.gn b/components/prefs/BUILD.gn
index 29f20c7..f29734c 100644
--- a/components/prefs/BUILD.gn
+++ b/components/prefs/BUILD.gn
@@ -26,6 +26,8 @@
     "in_memory_pref_store.h",
     "json_pref_store.cc",
     "json_pref_store.h",
+    "json_read_only_pref_store.cc",
+    "json_read_only_pref_store.h",
     "overlay_user_pref_store.cc",
     "overlay_user_pref_store.h",
     "persistent_pref_store.cc",
diff --git a/components/prefs/json_read_only_pref_store.cc b/components/prefs/json_read_only_pref_store.cc
new file mode 100644
index 0000000..9c50844
--- /dev/null
+++ b/components/prefs/json_read_only_pref_store.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2012 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 "components/prefs/json_read_only_pref_store.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task_runner_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "components/prefs/pref_filter.h"
+
+// Result returned from internal read tasks.
+struct JsonReadOnlyPrefStore::ReadResult {
+ public:
+  ReadResult();
+  ~ReadResult();
+
+  std::unique_ptr<base::Value> value;
+  PrefReadError error;
+  bool no_dir;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReadResult);
+};
+
+JsonReadOnlyPrefStore::ReadResult::ReadResult()
+    : error(PersistentPrefStore::PREF_READ_ERROR_NONE), no_dir(false) {}
+
+JsonReadOnlyPrefStore::ReadResult::~ReadResult() {}
+
+namespace {
+
+// Some extensions we'll tack on to copies of the Preferences files.
+const base::FilePath::CharType kBadExtension[] = FILE_PATH_LITERAL("bad");
+
+PersistentPrefStore::PrefReadError HandleReadErrors(
+    const base::Value* value,
+    const base::FilePath& path,
+    int error_code,
+    const std::string& error_msg) {
+  if (!value) {
+    DVLOG(1) << "Error while loading JSON file: " << error_msg
+             << ", file: " << path.value();
+    switch (error_code) {
+      case JSONFileValueDeserializer::JSON_ACCESS_DENIED:
+        return PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED;
+      case JSONFileValueDeserializer::JSON_CANNOT_READ_FILE:
+        return PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER;
+      case JSONFileValueDeserializer::JSON_FILE_LOCKED:
+        return PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED;
+      case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
+        return PersistentPrefStore::PREF_READ_ERROR_NO_FILE;
+      default:
+        // JSON errors indicate file corruption of some sort.
+        // Since the file is corrupt, move it to the side and continue with
+        // empty preferences.  This will result in them losing their settings.
+        // We keep the old file for possible support and debugging assistance
+        // as well as to detect if they're seeing these errors repeatedly.
+        // TODO(erikkay) Instead, use the last known good file.
+        base::FilePath bad = path.ReplaceExtension(kBadExtension);
+
+        // If they've ever had a parse error before, put them in another bucket.
+        // TODO(erikkay) if we keep this error checking for very long, we may
+        // want to differentiate between recent and long ago errors.
+        bool bad_existed = base::PathExists(bad);
+        base::CopyFile(path, bad);
+        base::DeleteFile(path, false);
+        return bad_existed ? PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT
+                           : PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE;
+    }
+  }
+  if (!value->is_dict())
+    return PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE;
+  return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+// Records a sample for |size| in the Settings.JsonDataReadSizeKilobytes
+// histogram suffixed with the base name of the JSON file under |path|.
+void RecordJsonDataSizeHistogram(const base::FilePath& path, size_t size) {
+  std::string spaceless_basename;
+  base::ReplaceChars(path.BaseName().MaybeAsASCII(), " ", "_",
+                     &spaceless_basename);
+
+  // The histogram below is an expansion of the UMA_HISTOGRAM_CUSTOM_COUNTS
+  // macro adapted to allow for a dynamically suffixed histogram name.
+  // Note: The factory creates and owns the histogram.
+  // This histogram is expired but the code was intentionally left behind so
+  // it can be re-enabled on Stable in a single config tweak if needed.
+  base::HistogramBase* histogram = base::Histogram::FactoryGet(
+      "Settings.JsonDataReadSizeKilobytes." + spaceless_basename, 1, 10000, 50,
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+  histogram->Add(static_cast<int>(size) / 1024);
+}
+
+std::unique_ptr<JsonReadOnlyPrefStore::ReadResult> ReadPrefsFromDisk(
+    const base::FilePath& path) {
+  int error_code;
+  std::string error_msg;
+  std::unique_ptr<JsonReadOnlyPrefStore::ReadResult> read_result(
+      new JsonReadOnlyPrefStore::ReadResult);
+  JSONFileValueDeserializer deserializer(path);
+  read_result->value = deserializer.Deserialize(&error_code, &error_msg);
+  read_result->error =
+      HandleReadErrors(read_result->value.get(), path, error_code, error_msg);
+  read_result->no_dir = !base::PathExists(path.DirName());
+
+  if (read_result->error == PersistentPrefStore::PREF_READ_ERROR_NONE)
+    RecordJsonDataSizeHistogram(path, deserializer.get_last_read_size());
+
+  return read_result;
+}
+
+}  // namespace
+
+JsonReadOnlyPrefStore::JsonReadOnlyPrefStore(
+    const base::FilePath& pref_filename,
+    std::unique_ptr<PrefFilter> pref_filter,
+    scoped_refptr<base::SequencedTaskRunner> file_task_runner)
+    : path_(pref_filename),
+      file_task_runner_(std::move(file_task_runner)),
+      prefs_(new base::DictionaryValue()),
+      pref_filter_(std::move(pref_filter)),
+      initialized_(false),
+      filtering_in_progress_(false),
+      read_error_(PREF_READ_ERROR_NONE) {
+  DCHECK(!path_.empty());
+}
+
+bool JsonReadOnlyPrefStore::GetValue(const std::string& key,
+                                     const base::Value** result) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::Value* tmp = nullptr;
+  if (!prefs_->Get(key, &tmp))
+    return false;
+
+  if (result)
+    *result = tmp;
+  return true;
+}
+
+std::unique_ptr<base::DictionaryValue> JsonReadOnlyPrefStore::GetValues()
+    const {
+  return prefs_->CreateDeepCopy();
+}
+
+void JsonReadOnlyPrefStore::AddObserver(PrefStore::Observer* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  observers_.AddObserver(observer);
+}
+
+void JsonReadOnlyPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  observers_.RemoveObserver(observer);
+}
+
+bool JsonReadOnlyPrefStore::HasObservers() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return observers_.might_have_observers();
+}
+
+bool JsonReadOnlyPrefStore::IsInitializationComplete() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return initialized_;
+}
+
+bool JsonReadOnlyPrefStore::GetMutableValue(const std::string& key,
+                                            base::Value** result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return prefs_->Get(key, result);
+}
+
+void JsonReadOnlyPrefStore::SetValue(const std::string& key,
+                                     std::unique_ptr<base::Value> value,
+                                     uint32_t flags) {
+  NOTIMPLEMENTED();
+}
+
+void JsonReadOnlyPrefStore::SetValueSilently(const std::string& key,
+                                             std::unique_ptr<base::Value> value,
+                                             uint32_t flags) {
+  NOTIMPLEMENTED();
+}
+
+void JsonReadOnlyPrefStore::RemoveValue(const std::string& key,
+                                        uint32_t flags) {
+  NOTIMPLEMENTED();
+}
+
+bool JsonReadOnlyPrefStore::ReadOnly() const {
+  return true;
+}
+
+PersistentPrefStore::PrefReadError JsonReadOnlyPrefStore::GetReadError() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return read_error_;
+}
+
+PersistentPrefStore::PrefReadError JsonReadOnlyPrefStore::ReadPrefs() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  OnFileRead(ReadPrefsFromDisk(path_));
+  return filtering_in_progress_ ? PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE
+                                : read_error_;
+}
+
+void JsonReadOnlyPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  initialized_ = false;
+  error_delegate_.reset(error_delegate);
+
+  // Weakly binds the read task so that it doesn't kick in during shutdown.
+  base::PostTaskAndReplyWithResult(
+      file_task_runner_.get(), FROM_HERE, base::Bind(&ReadPrefsFromDisk, path_),
+      base::Bind(&JsonReadOnlyPrefStore::OnFileRead, AsWeakPtr()));
+}
+
+void JsonReadOnlyPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  NOTIMPLEMENTED();
+}
+
+void JsonReadOnlyPrefStore::SchedulePendingLossyWrites() {
+  NOTIMPLEMENTED();
+}
+
+void JsonReadOnlyPrefStore::ReportValueChanged(const std::string& key,
+                                               uint32_t flags) {
+  NOTIMPLEMENTED();
+}
+
+void JsonReadOnlyPrefStore::ClearMutableValues() {
+  NOTIMPLEMENTED();
+}
+
+void JsonReadOnlyPrefStore::OnStoreDeletionFromDisk() {
+  if (pref_filter_)
+    pref_filter_->OnStoreDeletionFromDisk();
+}
+
+void JsonReadOnlyPrefStore::OnFileRead(
+    std::unique_ptr<ReadResult> read_result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(read_result);
+
+  std::unique_ptr<base::DictionaryValue> unfiltered_prefs(
+      new base::DictionaryValue);
+
+  read_error_ = read_result->error;
+
+  bool initialization_successful = !read_result->no_dir;
+
+  if (initialization_successful) {
+    switch (read_error_) {
+      case PREF_READ_ERROR_ACCESS_DENIED:
+      case PREF_READ_ERROR_FILE_OTHER:
+      case PREF_READ_ERROR_FILE_LOCKED:
+      case PREF_READ_ERROR_JSON_TYPE:
+      case PREF_READ_ERROR_FILE_NOT_SPECIFIED:
+        break;
+      case PREF_READ_ERROR_NONE:
+        DCHECK(read_result->value);
+        unfiltered_prefs.reset(
+            static_cast<base::DictionaryValue*>(read_result->value.release()));
+        break;
+      case PREF_READ_ERROR_NO_FILE:
+
+      // If the file just doesn't exist, maybe this is first run.  In any case
+      // there's no harm in writing out default prefs in this case.
+      case PREF_READ_ERROR_JSON_PARSE:
+      case PREF_READ_ERROR_JSON_REPEAT:
+        break;
+      case PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
+      // This is a special error code to be returned by ReadPrefs when it
+      // can't complete synchronously, it should never be returned by the read
+      // operation itself.
+      case PREF_READ_ERROR_MAX_ENUM:
+        NOTREACHED();
+        break;
+    }
+  }
+
+  if (pref_filter_) {
+    filtering_in_progress_ = true;
+    const PrefFilter::PostFilterOnLoadCallback post_filter_on_load_callback(
+        base::Bind(&JsonReadOnlyPrefStore::FinalizeFileRead, AsWeakPtr(),
+                   initialization_successful));
+    pref_filter_->FilterOnLoad(post_filter_on_load_callback,
+                               std::move(unfiltered_prefs));
+  } else {
+    FinalizeFileRead(initialization_successful, std::move(unfiltered_prefs),
+                     false);
+  }
+}
+
+void JsonReadOnlyPrefStore::FinalizeFileRead(
+    bool initialization_successful,
+    std::unique_ptr<base::DictionaryValue> prefs,
+    bool schedule_write) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  filtering_in_progress_ = false;
+
+  if (!initialization_successful) {
+    for (PrefStore::Observer& observer : observers_)
+      observer.OnInitializationCompleted(false);
+    return;
+  }
+
+  prefs_ = std::move(prefs);
+
+  initialized_ = true;
+
+  if (error_delegate_ && read_error_ != PREF_READ_ERROR_NONE)
+    error_delegate_->OnError(read_error_);
+
+  for (PrefStore::Observer& observer : observers_)
+    observer.OnInitializationCompleted(true);
+
+  return;
+}
diff --git a/components/prefs/json_read_only_pref_store.h b/components/prefs/json_read_only_pref_store.h
new file mode 100644
index 0000000..4577c01
--- /dev/null
+++ b/components/prefs/json_read_only_pref_store.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_JSON_READ_ONLY_PREF_STORE_H_
+#define COMPONENTS_PREFS_JSON_READ_ONLY_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/task/post_task.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefFilter;
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+class JsonPrefStoreCallbackTest;
+class SequencedTaskRunner;
+class Value;
+}  // namespace base
+
+// A readonly JSONPrefStore implementation that is used for
+// reading user preferences.
+class COMPONENTS_PREFS_EXPORT JsonReadOnlyPrefStore
+    : public PersistentPrefStore,
+      public base::SupportsWeakPtr<JsonReadOnlyPrefStore> {
+ public:
+  struct ReadResult;
+
+  JsonReadOnlyPrefStore(
+      const base::FilePath& pref_filename,
+      std::unique_ptr<PrefFilter> pref_filter = nullptr,
+      scoped_refptr<base::SequencedTaskRunner> file_task_runner =
+          base::CreateSequencedTaskRunnerWithTraits(
+              {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+               base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
+
+  // PrefStore overrides:
+  bool GetValue(const std::string& key,
+                const base::Value** result) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+  bool IsInitializationComplete() const override;
+
+  // PersistentPrefStore overrides:
+  bool GetMutableValue(const std::string& key, base::Value** result) override;
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override;
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override;
+  void RemoveValue(const std::string& key, uint32_t flags) override;
+  bool ReadOnly() const override;
+  PrefReadError GetReadError() const override;
+  // Note this method may be asynchronous if this instance has a |pref_filter_|
+  // in which case it will return PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE.
+  // See details in pref_filter.h.
+  PrefReadError ReadPrefs() override;
+  void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback =
+          base::OnceClosure()) override;
+  void SchedulePendingLossyWrites() override;
+  void ReportValueChanged(const std::string& key, uint32_t flags) override;
+
+  void ClearMutableValues() override;
+
+  void OnStoreDeletionFromDisk() override;
+
+ private:
+  friend class base::JsonPrefStoreCallbackTest;
+
+  // This method is called after the JSON file has been read.  It then hands
+  // |value| (or an empty dictionary in some read error cases) to the
+  // |pref_filter| if one is set. It also gives a callback pointing at
+  // FinalizeFileRead() to that |pref_filter_| which is then responsible for
+  // invoking it when done. If there is no |pref_filter_|, FinalizeFileRead()
+  // is invoked directly.
+  void OnFileRead(std::unique_ptr<ReadResult> read_result);
+
+  // This method is called after the JSON file has been read and the result has
+  // potentially been intercepted and modified by |pref_filter_|.
+  // |initialization_successful| is pre-determined by OnFileRead() and should
+  // be used when reporting OnInitializationCompleted().
+  // |schedule_write| indicates whether a write should be immediately scheduled
+  // (typically because the |pref_filter_| has already altered the |prefs|) --
+  // this will be ignored if this store is read-only.
+  void FinalizeFileRead(bool initialization_successful,
+                        std::unique_ptr<base::DictionaryValue> prefs,
+                        bool schedule_write);
+
+  const base::FilePath path_;
+  const scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+  std::unique_ptr<base::DictionaryValue> prefs_;
+
+  std::unique_ptr<PrefFilter> pref_filter_;
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+
+  std::unique_ptr<ReadErrorDelegate> error_delegate_;
+
+  bool initialized_;
+  bool filtering_in_progress_;
+  PrefReadError read_error_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(JsonReadOnlyPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_JSON_READ_ONLY_PREF_STORE_H_
diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml
new file mode 100644
index 0000000..6aff57b
--- /dev/null
+++ b/docker-compose-windows.yml
@@ -0,0 +1,111 @@
+# Copyright 2021 The Cobalt Authors. 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.
+
+version: '2.4'
+
+volumes:
+  container-sccache:
+
+x-common-definitions: &common-definitions
+  stdin_open: true
+  environment:
+    - SCCACHE_DIR=c:/root/sccache
+    - SCCACHE_CACHE_SIZE="30G"
+  tty: true
+  volumes:
+    - ${COBALT_SRC:-.}:c:/code/
+    - ${SCCACHE_DIR:-container-sccache}:c:/root/sccache/
+  cpus: ${DOCKER_CPUS:-12}
+  mem_limit: ${DOCKER_MEMLIMIT:-100000M}
+
+x-shared-build-env: &shared-build-env
+  IS_CI: ${IS_CI:-0}
+  IS_DOCKER: 1
+  PYTHONPATH: c:/code/
+  CONFIG: ${CONFIG:-devel}
+  TARGET: ${TARGET:-cobalt_install}
+  NINJA_FLAGS: ${NINJA_FLAGS}
+
+services:
+  visual-studio-base:
+    build:
+      context: ./docker/windows/base/visualstudio2017
+      dockerfile: ./Dockerfile
+      args:
+        - FROM_IMAGE=mcr.microsoft.com/windows/servercore:ltsc2019
+    image: visual-studio-base
+    scale: 0
+
+  cobalt-build-win-base:
+    build:
+      context: ./docker/windows/base/build
+      dockerfile: ./Dockerfile
+      args:
+        - FROM_IMAGE=visual-studio-base
+    image: cobalt-build-win-base
+    scale: 0
+    depends_on:
+      - visual-studio-base
+
+  visual-studio-win32-base:
+    build:
+      context: ./docker/windows/base/visualstudio2017
+      dockerfile: ./Dockerfile
+      args:
+        - FROM_IMAGE=mcr.microsoft.com/windows:1809
+    image: visual-studio-win32-base
+    scale: 0
+
+  cobalt-build-win32-base:
+    build:
+      context: ./docker/windows/base/build
+      dockerfile: ./Dockerfile
+      args:
+        - FROM_IMAGE=visual-studio-win32-base
+    image: cobalt-build-win32-base
+    scale: 0
+    depends_on:
+      - visual-studio-win32-base
+
+  build-win-win32:
+    <<: *common-definitions
+    build:
+      context: ./docker/windows/win32
+      dockerfile: ./Dockerfile
+    depends_on:
+      - cobalt-build-win32-base
+    image: cobalt-build-win-win32
+
+  win-win32:
+    <<: *common-definitions
+    build:
+      context: ./docker/windows/win32
+      dockerfile: ./Dockerfile
+    depends_on:
+      - cobalt-build-win32-base
+    environment:
+      <<: *shared-build-env
+      PLATFORM: win-win32
+    image: cobalt-build-win32
+
+  runner-win-win32:
+    <<: *common-definitions
+    build:
+      context: ./docker/windows/runner
+      dockerfile: ./Dockerfile
+      args:
+        - RUNNER_VERSION=2.294.0
+    depends_on:
+      - cobalt-build-win32-base
+    image: cobalt-runner-win-win32
diff --git a/docker-compose.yml b/docker-compose.yml
index 08f9df6..faff98a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -149,6 +149,16 @@
     image: cobalt-build-docsite-deploy
     entrypoint: ["/code/third_party/repo-publishing-toolkit-local/preview-site.sh", "deploy"]
 
+  build-linux-stub:
+    <<: *build-common-definitions
+    build:
+      context: ./docker/linux
+      dockerfile: stub/Dockerfile
+    image: cobalt-build-linux-stub
+    depends_on: [ build-base ]
+    environment:
+      <<: *shared-build-env
+
   stub:
     <<: *build-common-definitions
     build:
@@ -161,6 +171,16 @@
       PLATFORM: stub
       CONFIG: ${CONFIG:-debug}
 
+  build-linux:
+    <<: *build-common-definitions
+    build:
+      context: ./docker/linux
+      dockerfile: linux-x64x11/Dockerfile
+    image: cobalt-build-linux
+    depends_on: [ build-base ]
+    environment:
+      <<: *shared-build-env
+
   linux-x64x11:
     <<: *build-common-definitions
     build:
@@ -201,6 +221,16 @@
     depends_on:
       - linux-x64x11-bionic
 
+  build-linux-gcc:
+    <<: *common-definitions
+    <<: *build-volumes
+    build:
+      context: ./docker/linux
+      dockerfile: gcc-6-3/Dockerfile
+    image: cobalt-build-linux-gcc
+    depends_on:
+      - linux-x64x11-bionic
+
   linux-x64x11-clang-3-9:
     <<: *common-definitions
     <<: *build-volumes
@@ -215,6 +245,16 @@
     depends_on:
       - linux-x64x11-bionic
 
+  build-linux-clang-3-9:
+    <<: *common-definitions
+    <<: *build-volumes
+    build:
+      context: ./docker/linux/
+      dockerfile: clang-3-9/Dockerfile
+    image: cobalt-build-linux-clang-3-9
+    depends_on:
+      - linux-x64x11-bionic
+
   # Define common build container for Android
   build-android:
     <<: *build-common-definitions
@@ -254,6 +294,15 @@
       CONFIG: ${CONFIG:-debug}
       TARGET_CPU: ${TARGET_CPU:-arm64}
 
+  build-raspi:
+    <<: *build-common-definitions
+    build:
+      context: ./docker/linux/raspi
+      dockerfile: ./Dockerfile
+    image: cobalt-build-raspi
+    environment:
+      <<: *shared-build-env
+
   raspi:
     <<: *build-common-definitions
     build:
@@ -371,6 +420,19 @@
       TARGET_CPU: ${TARGET_CPU:-arm}
       SB_API_VERSION: 12
 
+  # Defined common build image for linux-evergreen
+  build-linux-evergreen:
+    <<: *build-common-definitions
+    build:
+      context: ./docker/linux
+      dockerfile: linux-x64x11/Dockerfile
+      args:
+        - FROM_IMAGE=cobalt-build-evergreen
+    image: cobalt-build-linux-evergreen
+    depends_on: [ build-evergreen ]
+    environment:
+      <<: *shared-build-env
+
   linux-x64x11-sbversion12-evergreen:
     <<: *build-common-definitions
     build:
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index a2d683e..d93caf6 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -39,9 +39,12 @@
    && echo ${NVM_SHA256SUM} | sha256sum --check \
    && . /tmp/install.sh
 
+# === Pinned Node version to v16, as this is latest one that will work on
+# legacy Debian systems
+ARG LTS_VERSION=v16
 RUN . $NVM_DIR/nvm.sh \
-   && nvm install --lts \
-   && nvm alias default lts/* \
+   && nvm install --lts ${LTS_VERSION} \
+   && nvm alias default ${LTS_VERSION}/* \
    && nvm use default
 
 ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
diff --git a/docker/linux/raspi/Dockerfile b/docker/linux/raspi/Dockerfile
index ad4a8e1..d8f51e8 100644
--- a/docker/linux/raspi/Dockerfile
+++ b/docker/linux/raspi/Dockerfile
@@ -37,7 +37,7 @@
     "https://storage.googleapis.com/cobalt-static-storage/${raspi_tools}" \
     && mkdir -p ${raspi_home} \
     && cd ${raspi_home} \
-    && tar xjvf /tmp/${raspi_tools} \
+    && tar xjvf /tmp/${raspi_tools} --no-same-owner \
     && rm /tmp/${raspi_tools}
 
 CMD gn gen ${OUTDIR}/${PLATFORM}_${CONFIG} --args="target_platform=\"${PLATFORM}\" build_type=\"${CONFIG}\" target_cpu=\"arm\" is_clang=false" && \
diff --git a/docker/linux/unittest/Dockerfile b/docker/linux/unittest/Dockerfile
index b915a10..c9f7915 100644
--- a/docker/linux/unittest/Dockerfile
+++ b/docker/linux/unittest/Dockerfile
@@ -18,21 +18,24 @@
     && apt install -qqy --no-install-recommends \
         libasound2 \
         libatomic1 \
-        libavcodec58 \
-        libavformat58 \
-        libavutil56 \
+        libavcodec-dev \
+        libavformat-dev \
+        libgles2-mesa \
         libegl1-mesa \
         libgl1-mesa-dri \
-        libgles2-mesa \
         libx11-6 \
         libxcomposite1 \
-        libxi6 \
+        libxi-dev \
         libxrender1 \
+        procps \
         unzip \
         xauth \
         xvfb \
     && /opt/clean-after-apt.sh
 
+COPY ./unittest/requirements.txt /opt/requirements.txt
+RUN python3 -m pip install --require-hashes --no-deps -r /opt/requirements.txt
+
 WORKDIR /out
 # Sets the locale in the environment. This is needed for NPLB unit tests.
 ENV LANG en_US.UTF-8
diff --git a/docker/linux/unittest/requirements.in b/docker/linux/unittest/requirements.in
new file mode 100644
index 0000000..5d8c046
--- /dev/null
+++ b/docker/linux/unittest/requirements.in
@@ -0,0 +1,2 @@
+selenium==3.141.0
+Brotli==1.0.9
diff --git a/docker/linux/unittest/requirements.txt b/docker/linux/unittest/requirements.txt
new file mode 100644
index 0000000..ef2d62e
--- /dev/null
+++ b/docker/linux/unittest/requirements.txt
@@ -0,0 +1,78 @@
+#
+# This file is autogenerated by pip-compile with python 3.10
+# To update, run:
+#
+#    pip-compile --generate-hashes requirements.in
+#
+brotli==1.0.9 \
+    --hash=sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d \
+    --hash=sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8 \
+    --hash=sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b \
+    --hash=sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c \
+    --hash=sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c \
+    --hash=sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70 \
+    --hash=sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f \
+    --hash=sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181 \
+    --hash=sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130 \
+    --hash=sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19 \
+    --hash=sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa \
+    --hash=sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429 \
+    --hash=sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126 \
+    --hash=sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4 \
+    --hash=sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0 \
+    --hash=sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b \
+    --hash=sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6 \
+    --hash=sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438 \
+    --hash=sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f \
+    --hash=sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389 \
+    --hash=sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6 \
+    --hash=sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26 \
+    --hash=sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7 \
+    --hash=sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14 \
+    --hash=sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2 \
+    --hash=sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430 \
+    --hash=sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296 \
+    --hash=sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12 \
+    --hash=sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f \
+    --hash=sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d \
+    --hash=sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a \
+    --hash=sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452 \
+    --hash=sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c \
+    --hash=sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761 \
+    --hash=sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649 \
+    --hash=sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b \
+    --hash=sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea \
+    --hash=sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c \
+    --hash=sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a \
+    --hash=sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031 \
+    --hash=sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267 \
+    --hash=sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5 \
+    --hash=sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7 \
+    --hash=sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d \
+    --hash=sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c \
+    --hash=sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43 \
+    --hash=sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa \
+    --hash=sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17 \
+    --hash=sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb \
+    --hash=sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb \
+    --hash=sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b \
+    --hash=sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4 \
+    --hash=sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3 \
+    --hash=sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7 \
+    --hash=sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1 \
+    --hash=sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb \
+    --hash=sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91 \
+    --hash=sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b \
+    --hash=sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1 \
+    --hash=sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806 \
+    --hash=sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3 \
+    --hash=sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1
+    # via -r requirements.in
+selenium==3.141.0 \
+    --hash=sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c \
+    --hash=sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d
+    # via -r requirements.in
+urllib3==1.26.12 \
+    --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \
+    --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997
+    # via selenium
diff --git a/docker/windows/runner/Dockerfile b/docker/windows/runner/Dockerfile
new file mode 100644
index 0000000..1df562f
--- /dev/null
+++ b/docker/windows/runner/Dockerfile
@@ -0,0 +1,34 @@
+# Copyright 2022 The Cobalt Authors. 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.
+FROM cobalt-build-win32-base
+
+SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]
+
+ARG RUNNER_VERSION
+
+RUN Invoke-WebRequest -Uri 'https://aka.ms/install-powershell.ps1' -OutFile install-powershell.ps1;`
+    powershell -ExecutionPolicy Unrestricted -File ./install-powershell.ps1 -AddToPath
+
+RUN Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v$env:RUNNER_VERSION/actions-runner-win-x64-$env:RUNNER_VERSION.zip -OutFile runner.zip;`
+    Expand-Archive -Path C:/runner.zip -DestinationPath C:/actions-runner;`
+    Remove-Item -Path C:\runner.zip;`
+    setx /M PATH $(${Env:PATH} + \";${Env:ProgramFiles}\Git\bin\")
+
+# Required for packaging artifacts.
+RUN choco install -f -y 7zip --version=19.0
+
+ENV TMP "C:\Users\ContainerAdministrator\AppData\Local\Temp2"
+
+ADD runner.ps1 C:/runner.ps1
+CMD ["pwsh", "-ExecutionPolicy", "Unrestricted", "-File", ".\\runner.ps1"]
diff --git a/docker/windows/runner/runner.ps1 b/docker/windows/runner/runner.ps1
new file mode 100644
index 0000000..1d30fd3
--- /dev/null
+++ b/docker/windows/runner/runner.ps1
@@ -0,0 +1,2 @@
+.\actions-runner\config.cmd --unattended --replace --url https://github.com/${env:RUNNER_REPO} --token $env:RUNNER_TOKEN --name $env:RUNNER_NAME --work $env:RUNNER_WORKDIR;
+.\actions-runner\run.cmd;
diff --git a/net/base/load_timing_info.h b/net/base/load_timing_info.h
index aacaac0..bdfd91f 100644
--- a/net/base/load_timing_info.h
+++ b/net/base/load_timing_info.h
@@ -163,6 +163,7 @@
 
 #if defined(STARBOARD)
   uint64_t encoded_body_size;
+  base::TimeTicks service_worker_start_time;
 #endif  // defined(STARBOARD)
 };
 
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.cc b/net/disk_cache/cobalt/cobalt_backend_impl.cc
index 8f58f6d..b60ab38 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.cc
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.cc
@@ -96,7 +96,7 @@
     : weak_factory_(this) {
   persistent_settings_ =
       std::make_unique<cobalt::persistent_storage::PersistentSettings>(
-          kPersistentSettingsJson, base::MessageLoop::current()->task_runner());
+          kPersistentSettingsJson);
   ReadDiskCacheSize(persistent_settings_.get(), max_bytes);
 
   // Initialize disk backend for each resource type.
@@ -182,6 +182,11 @@
                                           net::RequestPriority request_priority,
                                           Entry** entry,
                                           CompletionOnceCallback callback) {
+  ResourceType type = GetType(key);
+  auto quota = disk_cache::kTypeMetadata[type].max_size_bytes;
+  if (quota == 0) {
+    return net::Error::ERR_BLOCKED_BY_CLIENT;
+  }
   SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
   return simple_backend->CreateEntry(key, request_priority, entry,
                                      std::move(callback));
@@ -284,4 +289,10 @@
   return size;
 }
 
+net::Error CobaltBackendImpl::DoomAllEntriesOfType(disk_cache::ResourceType type,
+                        CompletionOnceCallback callback) {
+  SimpleBackendImpl* simple_backend = simple_backend_map_[type];
+  return simple_backend->DoomAllEntries(std::move(callback));
+}
+
 }  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h
index f76d0e9..b3b1b54 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.h
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -85,6 +85,8 @@
   size_t DumpMemoryStats(
       base::trace_event::ProcessMemoryDump* pmd,
       const std::string& parent_absolute_name) const override;
+  net::Error DoomAllEntriesOfType(disk_cache::ResourceType type,
+                          CompletionOnceCallback callback);
 
   // A refcounted class that runs a CompletionOnceCallback once it's destroyed.
   class RefCountedRunner : public base::RefCounted<RefCountedRunner> {
diff --git a/net/disk_cache/cobalt/resource_type.h b/net/disk_cache/cobalt/resource_type.h
index 60421ba..6397a9b 100644
--- a/net/disk_cache/cobalt/resource_type.h
+++ b/net/disk_cache/cobalt/resource_type.h
@@ -29,7 +29,8 @@
   kSplashScreen = 5,
   kUncompiledScript = 6,
   kCompiledScript = 7,
-  kTypeCount = 8
+  kCacheApi = 8,
+  kTypeCount = 9
 };
 
 struct ResourceTypeMetadata {
@@ -41,10 +42,11 @@
 // These values are updated on start up in application.cc, using the
 // persisted values saved in settings.json.
 static ResourceTypeMetadata kTypeMetadata[] = {
-    {"other", kInitialBytes},         {"html", kInitialBytes},
-    {"css", kInitialBytes},           {"image", kInitialBytes},
+    {"other", kInitialBytes},         {"html", 2 * 1024 * 1024},
+    {"css", 1 * 1024 * 1024},         {"image", kInitialBytes},
     {"font", kInitialBytes},          {"splash", 2 * 1024 * 1024},
     {"uncompiled_js", kInitialBytes}, {"compiled_js", kInitialBytes},
+    {"cache_api", kInitialBytes},
 };
 
 }  // namespace disk_cache
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 08346a8..ad07d5b 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -585,7 +585,6 @@
   // Strip out the reference, username, and password sections of the URL.
   std::string url = HttpUtil::SpecForRequest(request->url);
 
-  DCHECK_NE(DISABLE, mode_);
   // No valid URL can begin with numerals, so we should not have to worry
   // about collisions with normal URLs.
   if (request->upload_data_stream &&
diff --git a/net/socket/tcp_socket_starboard.cc b/net/socket/tcp_socket_starboard.cc
index 76795d2..c3c6bc5 100644
--- a/net/socket/tcp_socket_starboard.cc
+++ b/net/socket/tcp_socket_starboard.cc
@@ -330,7 +330,6 @@
   peer_address_.reset(new IPEndPoint(address));
 
   SbSocketError result = SbSocketConnect(socket_, &storage);
-  DCHECK_NE(kSbSocketErrorFailed, result);
 
   int rv = MapLastSocketError(socket_);
   if (rv != ERR_IO_PENDING) {
diff --git a/precommit_hooks/check_bug_in_description_wrapper.py b/precommit_hooks/check_bug_in_description_wrapper.py
index 706a127..c37ea47 100644
--- a/precommit_hooks/check_bug_in_description_wrapper.py
+++ b/precommit_hooks/check_bug_in_description_wrapper.py
@@ -25,4 +25,8 @@
 if __name__ == '__main__':
   from_ref = os.environ.get('PRE_COMMIT_FROM_REF')
   to_ref = os.environ.get('PRE_COMMIT_TO_REF')
+  if not from_ref or not to_ref:
+    print('Invalid from ref or to ref, exiting...')
+    sys.exit(0)
+
   sys.exit(CheckForBugInCommitDescription(from_ref, to_ref))
diff --git a/precommit_hooks/check_copyright_year.py b/precommit_hooks/check_copyright_year.py
old mode 100755
new mode 100644
index e682ac4..e6d90ea
--- a/precommit_hooks/check_copyright_year.py
+++ b/precommit_hooks/check_copyright_year.py
@@ -35,8 +35,10 @@
 
 
 def GetCreatedFiles() -> List[str]:
-  from_ref = os.environ['PRE_COMMIT_FROM_REF']
-  to_ref = os.environ['PRE_COMMIT_TO_REF']
+  from_ref = os.environ.get('PRE_COMMIT_FROM_REF')
+  to_ref = os.environ.get('PRE_COMMIT_TO_REF')
+  if not from_ref or not to_ref:
+    return []
 
   new_files = subprocess.check_output(
       ['git', 'diff', from_ref, to_ref, '--name-only',
diff --git a/precommit_hooks/clang_format_wrapper.py b/precommit_hooks/clang_format_wrapper.py
old mode 100755
new mode 100644
diff --git a/precommit_hooks/gcheckstyle_wrapper.py b/precommit_hooks/gcheckstyle_wrapper.py
old mode 100755
new mode 100644
diff --git a/precommit_hooks/google_java_format_wrapper.py b/precommit_hooks/google_java_format_wrapper.py
old mode 100755
new mode 100644
diff --git a/precommit_hooks/sync_keyboxes_wrapper.py b/precommit_hooks/sync_keyboxes_wrapper.py
old mode 100755
new mode 100644
index 002c012..dc4890f
--- a/precommit_hooks/sync_keyboxes_wrapper.py
+++ b/precommit_hooks/sync_keyboxes_wrapper.py
@@ -18,7 +18,7 @@
 import sys
 
 try:
-  import internal.sync_keyboxes as sync_keyboxes
+  from internal import sync_keyboxes
 except ImportError:
   print('sync_keyboxes.py not found, skipping.')
   sys.exit(0)
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle
index c78527f..832b8fe 100644
--- a/starboard/android/apk/app/build.gradle
+++ b/starboard/android/apk/app/build.gradle
@@ -181,6 +181,12 @@
 dependencies {
     implementation fileTree(include: ['*.jar'], dir: 'libs')
     implementation 'androidx.annotation:annotation:1.1.0'
+    implementation 'androidx.appcompat:appcompat:1.4.2'
+    implementation 'androidx.core:core:1.8.0'
+    // When updating the games-activity version here, make sure to update
+    // the C++ sources in //third_party/android_game_activity from the same
+    // release package.
+    implementation 'androidx.games:games-activity:1.2.1'
     implementation 'androidx.leanback:leanback:1.0.0'
     implementation 'androidx.legacy:legacy-support-v4:1.0.0'
     implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
diff --git a/starboard/android/apk/app/proguard-rules.pro b/starboard/android/apk/app/proguard-rules.pro
index a4b8a54..7038356 100644
--- a/starboard/android/apk/app/proguard-rules.pro
+++ b/starboard/android/apk/app/proguard-rules.pro
@@ -33,3 +33,15 @@
 -keepclasseswithmembers class * {
   @dev.cobalt.util.UsedByNative <fields>;
 }
+
+# Keep GameActivity APIs used by JNI (b/254102295).
+-keepclassmembers class com.google.androidgamesdk.GameActivity {
+   void setWindowFlags(int, int);
+   public androidx.core.graphics.Insets getWindowInsets(int);
+   public androidx.core.graphics.Insets getWaterfallInsets();
+   public void setImeEditorInfo(android.view.inputmethod.EditorInfo);
+   public void setImeEditorInfoFields(int, int, int);
+}
+-keep class androidx.core.graphics.Insets** { *; }
+-keep class androidx.core.view.WindowInsetsCompat** { *; }
+-keep class com.google.androidgamesdk.gametextinput.** { *; }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index 4b05f41..eae0f49 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -16,7 +16,7 @@
 
 import static dev.cobalt.util.Log.TAG;
 
-import android.app.NativeActivity;
+import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
@@ -29,6 +29,8 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.widget.FrameLayout;
+import androidx.annotation.CallSuper;
+import com.google.androidgamesdk.GameActivity;
 import dev.cobalt.media.AudioOutputManager;
 import dev.cobalt.media.MediaCodecUtil;
 import dev.cobalt.media.VideoSurfaceView;
@@ -41,7 +43,7 @@
 import java.util.Locale;
 
 /** Native activity that has the required JNI methods called by the Starboard implementation. */
-public abstract class CobaltActivity extends NativeActivity {
+public abstract class CobaltActivity extends GameActivity {
 
   // A place to put args while debugging so they're used even when starting from the launcher.
   // This should always be empty in submitted code.
@@ -72,6 +74,8 @@
 
   private static native void nativeLowMemoryEvent();
 
+  protected View mContentView = null;
+
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     // Record the application start timestamp.
@@ -110,6 +114,21 @@
   }
 
   /**
+   * Creates an empty View for the launching activity, and prevent GameActivity from creating the
+   * default SurfaceView.
+   */
+  @Override
+  protected void onCreateSurfaceView() {
+    mSurfaceView = null;
+
+    getWindow().takeSurface(this);
+
+    mContentView = new View(this);
+    setContentView(mContentView);
+    mContentView.requestFocus();
+  }
+
+  /**
    * Instantiates the StarboardBridge. Apps not supporting sign-in should inject an instance of
    * NoopUserAuthorizer. Apps may subclass StarboardBridge if they need to override anything.
    */
@@ -271,8 +290,10 @@
     return StarboardBridge.isReleaseBuild();
   }
 
+  @CallSuper
   @Override
   protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
     getStarboardBridge().handleDeepLink(getIntentUrlAsString(intent));
   }
 
@@ -290,6 +311,7 @@
     getStarboardBridge().onActivityResult(requestCode, resultCode, data);
   }
 
+  @SuppressLint("MissingSuperCall")
   @Override
   public void onRequestPermissionsResult(
       int requestCode, String[] permissions, int[] grantResults) {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/game_activity/games-activity-1.2.1.aar b/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/game_activity/games-activity-1.2.1.aar
new file mode 100644
index 0000000..c7d40aa
--- /dev/null
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/game_activity/games-activity-1.2.1.aar
Binary files differ
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
index f36dad3..0b28e08 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -78,7 +78,12 @@
   private static final int PLAYBACK_STATE_PLAYING = 0;
   private static final int PLAYBACK_STATE_PAUSED = 1;
   private static final int PLAYBACK_STATE_NONE = 2;
-  private static final String[] PLAYBACK_STATE_NAME = {"playing", "paused", "none"};
+  private static final String[] PLAYBACK_STATE_NAME = {"PLAYING", "PAUSED", "NONE"};
+  private static final int[] PLAYBACK_STATE_COMPAT_MAPPING = {
+    PlaybackStateCompat.STATE_PLAYING,
+    PlaybackStateCompat.STATE_PAUSED,
+    PlaybackStateCompat.STATE_NONE
+  };
 
   // Accessed on the main looper thread only.
   private int currentPlaybackState = PLAYBACK_STATE_NONE;
@@ -205,7 +210,7 @@
     MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();
     mediaSession.setMetadata(metadataBuilder.build());
     // |playbackStateBuilder| may still have no fields at this point.
-    mediaSession.setPlaybackState(playbackStateBuilder.build());
+    updatePlaybackState();
   }
 
   private static void checkMainLooperThread() {
@@ -220,10 +225,10 @@
    * are set together to stay coherent as playback state changes. This is idempotent as it may be
    * called multiple times during the course of a media session.
    */
-  private void configureMediaFocus(int playbackState) {
+  private void configureMediaFocus(int playbackState, long positionMs, float speed) {
     checkMainLooperThread();
     if (transientPause && playbackState == PLAYBACK_STATE_PAUSED) {
-      Log.i(TAG, "Media focus: paused (transient)");
+      Log.i(TAG, "Media focus: PAUSED (transient)");
       // Don't release media focus while transiently paused, otherwise we won't get audiofocus back
       // when the transient condition ends and we would leave playback paused.
       return;
@@ -232,6 +237,9 @@
     wakeLock(playbackState == PLAYBACK_STATE_PLAYING);
     audioFocus(playbackState == PLAYBACK_STATE_PLAYING);
 
+    playbackStateBuilder.setState(PLAYBACK_STATE_COMPAT_MAPPING[playbackState], positionMs, speed);
+    updatePlaybackState();
+
     boolean activating = playbackState != PLAYBACK_STATE_NONE;
     boolean deactivating = playbackState == PLAYBACK_STATE_NONE;
     if (mediaSession != null) {
@@ -383,9 +391,13 @@
 
   private void resumeInternal() {
     checkMainLooperThread();
+    Log.i(TAG, "Resume");
     suspended = false;
     // Undoing what may have been done in suspendInternal().
-    configureMediaFocus(currentPlaybackState);
+    configureMediaFocus(
+        currentPlaybackState,
+        PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN,
+        currentPlaybackState == PLAYBACK_STATE_PLAYING ? 1.0f : 0.0f);
   }
 
   public void suspend() {
@@ -404,6 +416,7 @@
 
   private void suspendInternal() {
     checkMainLooperThread();
+    Log.i(TAG, "Suspend");
     suspended = true;
 
     // We generally believe the HTML5 app playback state as the source of truth for configuring
@@ -412,11 +425,7 @@
     // active SbPlayer is destroyed and we release media focus, even if the HTML5 app still thinks
     // it's in a playing state. We'll configure it again in resumeInternal() and the HTML5 app will
     // be none the wiser.
-    playbackStateBuilder.setState(
-        currentPlaybackState,
-        PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN,
-        currentPlaybackState == PLAYBACK_STATE_PLAYING ? 1.0f : 0.0f);
-    configureMediaFocus(PLAYBACK_STATE_NONE);
+    configureMediaFocus(PLAYBACK_STATE_NONE, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0.0f);
   }
 
   private static void nativeInvokeAction(long action) {
@@ -469,6 +478,11 @@
       return;
     }
 
+    // Always update playback state.
+    playbackStateBuilder.setActions(actions);
+    playbackStateBuilder.setState(PLAYBACK_STATE_COMPAT_MAPPING[playbackState], positionMs, speed);
+    updatePlaybackState();
+
     if (hasStateChange) {
       if (playbackState == PLAYBACK_STATE_PLAYING) {
         // We don't want to request media focus if |explicitUserActionRequired| is true when we
@@ -479,14 +493,14 @@
         if (!explicitUserActionRequired
             || (activityHolder.get() != null && activityHolder.get().hasWindowFocus())) {
           explicitUserActionRequired = false;
-          configureMediaFocus(playbackState);
+          configureMediaFocus(playbackState, positionMs, speed);
         } else {
           Log.w(TAG, "Audiofocus action: PAUSE (explicit user action required)");
           nativeInvokeAction(PlaybackStateCompat.ACTION_PAUSE);
         }
       } else {
         // It's fine to abandon media focus anytime.
-        configureMediaFocus(playbackState);
+        configureMediaFocus(playbackState, positionMs, speed);
       }
     }
 
@@ -495,43 +509,40 @@
       return;
     }
 
-    int androidPlaybackState;
-    String stateName;
-    switch (playbackState) {
-      case PLAYBACK_STATE_PLAYING:
-        androidPlaybackState = PlaybackStateCompat.STATE_PLAYING;
+    // Let metadata hold onto the most recent metadata and add artwork later.
+    metadata.SetMetadata(title, artist, album, artworkLoader.getOrLoadArtwork(artwork), duration);
+
+    // Update the metadata as soon as we can - even before artwork is loaded.
+    updateMetadata(false);
+  }
+
+  private void updatePlaybackState() {
+    if (mediaSession == null) {
+      return;
+    }
+
+    PlaybackStateCompat playbackState = playbackStateBuilder.build();
+    String stateName = "UNKNOWN";
+    switch (playbackState.getState()) {
+      case PlaybackStateCompat.STATE_PLAYING:
         stateName = "PLAYING";
         break;
-      case PLAYBACK_STATE_PAUSED:
-        androidPlaybackState = PlaybackStateCompat.STATE_PAUSED;
+      case PlaybackStateCompat.STATE_PAUSED:
         stateName = "PAUSED";
         break;
-      case PLAYBACK_STATE_NONE:
-      default:
-        androidPlaybackState = PlaybackStateCompat.STATE_NONE;
+      case PlaybackStateCompat.STATE_NONE:
         stateName = "NONE";
         break;
     }
 
     Log.i(
         TAG,
-        "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms",
+        "MediaSession setPlaybackState: %s, position: %d ms, speed: %f x",
         stateName,
-        positionMs,
-        speed,
-        duration);
+        playbackState.getPosition(),
+        playbackState.getPlaybackSpeed());
 
-    playbackStateBuilder =
-        new PlaybackStateCompat.Builder()
-            .setActions(actions)
-            .setState(androidPlaybackState, positionMs, speed);
-    mediaSession.setPlaybackState(playbackStateBuilder.build());
-
-    // Let metadata hold onto the most recent metadata and add artwork later.
-    metadata.SetMetadata(title, artist, album, artworkLoader.getOrLoadArtwork(artwork), duration);
-
-    // Update the metadata as soon as we can - even before artwork is loaded.
-    updateMetadata(false);
+    mediaSession.setPlaybackState(playbackState);
   }
 
   private void updateMetadata(boolean resetMetadataWithEmptyBuilder) {
@@ -541,7 +552,9 @@
 
     MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();
     // Reset the metadata to make sure the artwork update correctly.
-    if (resetMetadataWithEmptyBuilder) mediaSession.setMetadata(metadataBuilder.build());
+    if (resetMetadataWithEmptyBuilder) {
+      mediaSession.setMetadata(metadataBuilder.build());
+    }
 
     metadataBuilder
         .putString(MediaMetadataCompat.METADATA_KEY_TITLE, metadata.title)
diff --git a/starboard/android/apk/app/src/main/res/values/styles.xml b/starboard/android/apk/app/src/main/res/values/styles.xml
index ef05d47..0f64c0a 100644
--- a/starboard/android/apk/app/src/main/res/values/styles.xml
+++ b/starboard/android/apk/app/src/main/res/values/styles.xml
@@ -16,7 +16,7 @@
 -->
 
 <resources>
-  <style name="CobaltTheme" parent="@style/android:Theme.Material.NoActionBar">
+  <style name="CobaltTheme" parent="Theme.AppCompat.NoActionBar">
     <!-- Color of the transition animation when launching the app. -->
     <item name="android:colorPrimary">@color/primary</item>
 
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn
index 5efe78e..ed227f0 100644
--- a/starboard/android/shared/BUILD.gn
+++ b/starboard/android/shared/BUILD.gn
@@ -262,6 +262,11 @@
     "//starboard/shared/stub/thread_sampler_is_supported.cc",
     "//starboard/shared/stub/thread_sampler_thaw.cc",
     "//starboard/shared/stub/ui_nav_get_interface.cc",
+    "//third_party/android_game_activity/include/game-activity/GameActivity.cpp",
+    "//third_party/android_game_activity/include/game-activity/GameActivity.h",
+    "//third_party/android_game_activity/include/game-text-input/gamecommon.h",
+    "//third_party/android_game_activity/include/game-text-input/gametextinput.cpp",
+    "//third_party/android_game_activity/include/game-text-input/gametextinput.h",
     "accessibility_get_caption_settings.cc",
     "accessibility_get_display_settings.cc",
     "accessibility_get_text_to_speech_settings.cc",
@@ -419,6 +424,8 @@
     "//starboard/shared/starboard/player/player_set_playback_rate.cc",
   ]
 
+  include_dirs = [ "//third_party/android_game_activity/include" ]
+
   configs += [ "//starboard/build/config:starboard_implementation" ]
 
   public_deps = [
diff --git a/starboard/android/shared/android_main.cc b/starboard/android/shared/android_main.cc
index 0c04c79..3890b50 100644
--- a/starboard/android/shared/android_main.cc
+++ b/starboard/android/shared/android_main.cc
@@ -18,8 +18,10 @@
 #include "starboard/android/shared/log_internal.h"
 #include "starboard/common/semaphore.h"
 #include "starboard/common/string.h"
+#include "starboard/log.h"
 #include "starboard/shared/starboard/command_line.h"
 #include "starboard/thread.h"
+#include "third_party/android_game_activity/include/game-activity/GameActivity.h"
 
 namespace starboard {
 namespace android {
@@ -84,7 +86,7 @@
   // allow sending the first AndroidCommand after onCreate() returns.
   g_app_running = true;
 
-  // Signal ANativeActivity_onCreate() that it may proceed.
+  // Signal GameActivity_onCreate() that it may proceed.
   app_created_semaphore->Put();
 
   // Enter the Starboard run loop until stopped.
@@ -107,13 +109,13 @@
   return NULL;
 }
 
-void OnStart(ANativeActivity* activity) {
+void OnStart(GameActivity* activity) {
   if (g_app_running) {
     ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStart);
   }
 }
 
-void OnResume(ANativeActivity* activity) {
+void OnResume(GameActivity* activity) {
   if (g_app_running) {
     // Stop the MediaPlaybackService if activity state transits from background
     // to foreground. Note that the MediaPlaybackService may already have
@@ -124,7 +126,7 @@
   }
 }
 
-void OnPause(ANativeActivity* activity) {
+void OnPause(GameActivity* activity) {
   if (g_app_running) {
     // Start the MediaPlaybackService before activity state transits from
     // foreground to background.
@@ -133,13 +135,28 @@
   }
 }
 
-void OnStop(ANativeActivity* activity) {
+void OnStop(GameActivity* activity) {
   if (g_app_running) {
     ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStop);
   }
 }
 
-void OnWindowFocusChanged(ANativeActivity* activity, int focused) {
+bool OnTouchEvent(GameActivity* activity,
+                  const GameActivityMotionEvent* event) {
+  if (g_app_running) {
+    return ApplicationAndroid::Get()->SendAndroidMotionEvent(event);
+  }
+  return false;
+}
+
+bool OnKey(GameActivity* activity, const GameActivityKeyEvent* event) {
+  if (g_app_running) {
+    return ApplicationAndroid::Get()->SendAndroidKeyEvent(event);
+  }
+  return false;
+}
+
+void OnWindowFocusChanged(GameActivity* activity, bool focused) {
   if (g_app_running) {
     ApplicationAndroid::Get()->SendAndroidCommand(
         focused ? AndroidCommand::kWindowFocusGained
@@ -147,36 +164,22 @@
   }
 }
 
-void OnNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
+void OnNativeWindowCreated(GameActivity* activity, ANativeWindow* window) {
   if (g_app_running) {
     ApplicationAndroid::Get()->SendAndroidCommand(
         AndroidCommand::kNativeWindowCreated, window);
   }
 }
 
-void OnNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
+void OnNativeWindowDestroyed(GameActivity* activity, ANativeWindow* window) {
   if (g_app_running) {
     ApplicationAndroid::Get()->SendAndroidCommand(
         AndroidCommand::kNativeWindowDestroyed);
   }
 }
 
-void OnInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
-  if (g_app_running) {
-    ApplicationAndroid::Get()->SendAndroidCommand(
-        AndroidCommand::kInputQueueChanged, queue);
-  }
-}
-
-void OnInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
-  if (g_app_running) {
-    ApplicationAndroid::Get()->SendAndroidCommand(
-        AndroidCommand::kInputQueueChanged, NULL);
-  }
-}
-
-extern "C" SB_EXPORT_PLATFORM void ANativeActivity_onCreate(
-    ANativeActivity* activity,
+extern "C" SB_EXPORT_PLATFORM void GameActivity_onCreate(
+    GameActivity* activity,
     void* savedState,
     size_t savedStateSize) {
   // Start the Starboard thread the first time an Activity is created.
@@ -195,11 +198,13 @@
   activity->callbacks->onResume = OnResume;
   activity->callbacks->onPause = OnPause;
   activity->callbacks->onStop = OnStop;
+  activity->callbacks->onTouchEvent = OnTouchEvent;
+  activity->callbacks->onKeyDown = OnKey;
+  activity->callbacks->onKeyUp = OnKey;
   activity->callbacks->onWindowFocusChanged = OnWindowFocusChanged;
   activity->callbacks->onNativeWindowCreated = OnNativeWindowCreated;
   activity->callbacks->onNativeWindowDestroyed = OnNativeWindowDestroyed;
-  activity->callbacks->onInputQueueCreated = OnInputQueueCreated;
-  activity->callbacks->onInputQueueDestroyed = OnInputQueueDestroyed;
+
   activity->instance = ApplicationAndroid::Get();
 }
 
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc
index fc79766..0f4cdb3 100644
--- a/starboard/android/shared/application_android.cc
+++ b/starboard/android/shared/application_android.cc
@@ -59,8 +59,6 @@
       return "Pause";
     case ApplicationAndroid::AndroidCommand::kStop:
       return "Stop";
-    case ApplicationAndroid::AndroidCommand::kInputQueueChanged:
-      return "InputQueueChanged";
     case ApplicationAndroid::AndroidCommand::kNativeWindowCreated:
       return "NativeWindowCreated";
     case ApplicationAndroid::AndroidCommand::kNativeWindowDestroyed:
@@ -83,7 +81,6 @@
 ApplicationAndroid::ApplicationAndroid(ALooper* looper)
     : looper_(looper),
       native_window_(NULL),
-      input_queue_(NULL),
       android_command_readfd_(-1),
       android_command_writefd_(-1),
       keyboard_inject_readfd_(-1),
@@ -100,6 +97,17 @@
   // from the assets. The use ICU is used in our logging.
   SbFileAndroidInitialize();
 
+  // Enable axes used by Cobalt.
+  static const unsigned int required_axes[] = {
+      AMOTION_EVENT_AXIS_Z,       AMOTION_EVENT_AXIS_RZ,
+      AMOTION_EVENT_AXIS_HAT_X,   AMOTION_EVENT_AXIS_HAT_Y,
+      AMOTION_EVENT_AXIS_HSCROLL, AMOTION_EVENT_AXIS_VSCROLL,
+      AMOTION_EVENT_AXIS_WHEEL,
+  };
+  for (auto axis : required_axes) {
+    GameActivityPointerAxes_enableAxis(axis);
+  }
+
   int pipefd[2];
   int err;
 
@@ -182,9 +190,6 @@
     case kLooperIdAndroidCommand:
       ProcessAndroidCommand();
       break;
-    case kLooperIdAndroidInput:
-      ProcessAndroidInput();
-      break;
     case kLooperIdKeyboardInject:
       ProcessKeyboardInject();
       break;
@@ -232,23 +237,6 @@
   switch (cmd.type) {
     case AndroidCommand::kUndefined:
       break;
-
-    case AndroidCommand::kInputQueueChanged: {
-      ScopedLock lock(android_command_mutex_);
-      if (input_queue_) {
-        AInputQueue_detachLooper(input_queue_);
-      }
-      input_queue_ = static_cast<AInputQueue*>(cmd.data);
-      if (input_queue_) {
-        AInputQueue_attachLooper(input_queue_, looper_, kLooperIdAndroidInput,
-                                 NULL, NULL);
-      }
-      // Now that we've swapped our use of the input queue, signal that the
-      // Android UI thread can continue.
-      android_command_condition_.Signal();
-      break;
-    }
-
     // Starboard resume/suspend is tied to the UI window being created/destroyed
     // (rather than to the Activity lifecycle) since Cobalt can't do anything at
     // all if it doesn't have a window surface to draw on.
@@ -385,11 +373,6 @@
   write(android_command_writefd_, &cmd, sizeof(cmd));
   // Synchronization only necessary when managing resources.
   switch (type) {
-    case AndroidCommand::kInputQueueChanged:
-      while (input_queue_ != data) {
-        android_command_condition_.Wait();
-      }
-      break;
     case AndroidCommand::kNativeWindowCreated:
     case AndroidCommand::kNativeWindowDestroyed:
       while (native_window_ != data) {
@@ -401,27 +384,41 @@
   }
 }
 
-void ApplicationAndroid::ProcessAndroidInput() {
-  AInputEvent* android_event = NULL;
-  while (AInputQueue_getEvent(input_queue_, &android_event) >= 0) {
-    SB_LOG(INFO) << "Android input: type="
-                 << AInputEvent_getType(android_event);
-    if (AInputQueue_preDispatchEvent(input_queue_, android_event)) {
-      continue;
-    }
-    if (!input_events_generator_) {
-      SB_DLOG(WARNING) << "Android input event ignored without an SbWindow.";
-      AInputQueue_finishEvent(input_queue_, android_event, false);
-      continue;
-    }
-    InputEventsGenerator::Events app_events;
-    bool handled = input_events_generator_->CreateInputEventsFromAndroidEvent(
-        android_event, &app_events);
-    for (int i = 0; i < app_events.size(); ++i) {
-      Inject(app_events[i].release());
-    }
-    AInputQueue_finishEvent(input_queue_, android_event, handled);
+bool ApplicationAndroid::SendAndroidMotionEvent(
+    const GameActivityMotionEvent* event) {
+  bool result = false;
+
+  // add motion event into the queue.
+  InputEventsGenerator::Events app_events;
+  result = input_events_generator_->CreateInputEventsFromGameActivityEvent(
+      const_cast<GameActivityMotionEvent*>(event), &app_events);
+
+  for (int i = 0; i < app_events.size(); ++i) {
+    Inject(app_events[i].release());
   }
+
+  return result;
+}
+
+bool ApplicationAndroid::SendAndroidKeyEvent(
+    const GameActivityKeyEvent* event) {
+  bool result = false;
+
+#ifdef STARBOARD_INPUT_EVENTS_FILTER
+  if (!input_events_filter_.ShouldProcessKeyEvent(event)) {
+    return result;
+  }
+#endif
+
+  // Add key event to the application queue.
+  InputEventsGenerator::Events app_events;
+  result = input_events_generator_->CreateInputEventsFromGameActivityEvent(
+      const_cast<GameActivityKeyEvent*>(event), &app_events);
+  for (int i = 0; i < app_events.size(); i++) {
+    Inject(app_events[i].release());
+  }
+
+  return result;
 }
 
 void ApplicationAndroid::ProcessKeyboardInject() {
diff --git a/starboard/android/shared/application_android.h b/starboard/android/shared/application_android.h
index 2d147eb..634cc29 100644
--- a/starboard/android/shared/application_android.h
+++ b/starboard/android/shared/application_android.h
@@ -22,6 +22,9 @@
 #include <vector>
 
 #include "starboard/android/shared/input_events_generator.h"
+#ifdef STARBOARD_INPUT_EVENTS_FILTER
+#include "starboard/android/shared/internal/input_events_filter.h"
+#endif
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/common/condition_variable.h"
 #include "starboard/common/mutex.h"
@@ -31,6 +34,7 @@
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/queue_application.h"
 #include "starboard/types.h"
+#include "third_party/android_game_activity/include/game-activity/GameActivity.h"
 
 namespace starboard {
 namespace android {
@@ -47,7 +51,6 @@
       kResume,
       kPause,
       kStop,
-      kInputQueueChanged,
       kNativeWindowCreated,
       kNativeWindowDestroyed,
       kWindowFocusGained,
@@ -80,6 +83,9 @@
   void SendAndroidCommand(AndroidCommand::CommandType type) {
     SendAndroidCommand(type, NULL);
   }
+  bool SendAndroidMotionEvent(const GameActivityMotionEvent* event);
+  bool SendAndroidKeyEvent(const GameActivityKeyEvent* event);
+
   void SendKeyboardInject(SbKey key);
 
   void SbWindowShowOnScreenKeyboard(SbWindow window,
@@ -125,7 +131,6 @@
  private:
   ALooper* looper_;
   ANativeWindow* native_window_;
-  AInputQueue* input_queue_;
 
   // Pipes attached to the looper.
   int android_command_readfd_;
@@ -134,7 +139,7 @@
   int keyboard_inject_writefd_;
 
   // Synchronization for commands that change availability of Android resources
-  // such as the input_queue_ and/or native_window_.
+  // such as the input and/or native_window_.
   Mutex android_command_mutex_;
   ConditionVariable android_command_condition_;
 
@@ -146,6 +151,10 @@
 
   scoped_ptr<InputEventsGenerator> input_events_generator_;
 
+#ifdef STARBOARD_INPUT_EVENTS_FILTER
+  internal::InputEventsFilter input_events_filter_;
+#endif
+
   bool last_is_accessibility_high_contrast_text_enabled_;
 
   jobject resource_overlay_;
@@ -157,7 +166,6 @@
 
   // Methods to process pipes attached to the Looper.
   void ProcessAndroidCommand();
-  void ProcessAndroidInput();
   void ProcessKeyboardInject();
 };
 
diff --git a/starboard/android/shared/audio_renderer_passthrough.h b/starboard/android/shared/audio_renderer_passthrough.h
index 2b96d52..d1d2d05 100644
--- a/starboard/android/shared/audio_renderer_passthrough.h
+++ b/starboard/android/shared/audio_renderer_passthrough.h
@@ -140,8 +140,11 @@
 
   std::atomic_bool audio_track_paused_{true};
 
-  std::unique_ptr<JobThread> audio_track_thread_;
+  // |audio_track_thread_| must be declared after |audio_track_bridge_| to
+  // ensure the thread completes all tasks before |audio_track_bridge_| is
+  // invalidated.
   std::unique_ptr<AudioTrackBridge> audio_track_bridge_;
+  std::unique_ptr<JobThread> audio_track_thread_;
 };
 
 }  // namespace shared
diff --git a/starboard/android/shared/cobalt/configuration.py b/starboard/android/shared/cobalt/configuration.py
index df2e568..b41afbc 100644
--- a/starboard/android/shared/cobalt/configuration.py
+++ b/starboard/android/shared/cobalt/configuration.py
@@ -60,7 +60,14 @@
       'layout_tests': [
           # Android relies of system fonts and some older Android builds do not
           # have the update (Emoji 11.0) NotoColorEmoji.ttf installed.
-          'CSS3FontsLayoutTests/Layout.Test/color_emojis_should_render_properly'
+          ('CSS3FontsLayoutTests/Layout.Test/'
+            'color_emojis_should_render_properly'),
+
+          # Android 12 changed the look of emojis.
+          # TODO(b/258830349) split this test into emoji & non-emoji parts so
+          # that the emoji portion can be disabled specifically.
+          ('CSS3FontsLayoutTests/Layout.Test/'
+            '5_2_use_system_fallback_if_no_matching_family_is_found'),
       ],
       'crypto_unittests': ['P224.*'],
       'renderer_test': [
@@ -68,6 +75,10 @@
           'PixelTest.SimpleTextInRed40PtChineseFont',
           'PixelTest.SimpleTextInRed40PtThaiFont',
 
+          # The Roboto variable font looks slightly different than the static
+          # font version. Android 12 and up use font variations for Roboto.
+          'PixelTest.ScalingUpAnOpacityFilterTextDoesNotPixellate',
+
           # Instead of returning an error when allocating too much texture
           # memory, Android instead just terminates the process.  Since this
           # test explicitly tries to allocate too much texture memory, we cannot
diff --git a/starboard/android/shared/file_open.cc b/starboard/android/shared/file_open.cc
index 3e0caa6..8d82ab5 100644
--- a/starboard/android/shared/file_open.cc
+++ b/starboard/android/shared/file_open.cc
@@ -47,13 +47,7 @@
   // system font file of the same name.
   const std::string fonts_xml("fonts.xml");
   const std::string system_fonts_dir("/system/fonts/");
-
-#if SB_IS(EVERGREEN_COMPATIBLE)
-  const std::string cobalt_fonts_dir(
-      "/cobalt/assets/app/cobalt/content/fonts/");
-#else
   const std::string cobalt_fonts_dir("/cobalt/assets/fonts/");
-#endif
 
   // Fonts fallback to the system fonts.
   if (path.compare(0, cobalt_fonts_dir.length(), cobalt_fonts_dir) == 0) {
diff --git a/starboard/android/shared/input_events_generator.cc b/starboard/android/shared/input_events_generator.cc
index f749a96..17805e9 100644
--- a/starboard/android/shared/input_events_generator.cc
+++ b/starboard/android/shared/input_events_generator.cc
@@ -14,7 +14,6 @@
 
 #include "starboard/android/shared/input_events_generator.h"
 
-#include <android/input.h>
 #include <android/keycodes.h>
 #include <jni.h>
 #include <math.h>
@@ -35,8 +34,8 @@
 
 namespace {
 
-SbKeyLocation AInputEventToSbKeyLocation(AInputEvent* event) {
-  int32_t keycode = AKeyEvent_getKeyCode(event);
+SbKeyLocation GameActivityKeyToSbKeyLocation(GameActivityKeyEvent* event) {
+  int32_t keycode = event->keyCode;
   switch (keycode) {
     case AKEYCODE_ALT_LEFT:
     case AKEYCODE_CTRL_LEFT:
@@ -52,8 +51,7 @@
   return kSbKeyLocationUnspecified;
 }
 
-unsigned int AInputEventToSbModifiers(AInputEvent* event) {
-  int32_t meta = AKeyEvent_getMetaState(event);
+unsigned int GameActivityInputEventMetaStateToSbModifiers(unsigned int meta) {
   unsigned int modifiers = kSbKeyModifiersNone;
   if (meta & AMETA_ALT_ON) {
     modifiers |= kSbKeyModifiersAlt;
@@ -114,8 +112,8 @@
          key == kSbKeyGamepadDPadLeft || key == kSbKeyGamepadDPadRight;
 }
 
-SbKey AInputEventToSbKey(AInputEvent* event) {
-  int32_t keycode = AKeyEvent_getKeyCode(event);
+SbKey AInputEventToSbKey(GameActivityKeyEvent* event) {
+  auto keycode = event->keyCode;
   switch (keycode) {
     // Modifiers
     case AKEYCODE_ALT_LEFT:
@@ -411,17 +409,19 @@
 //
 // On game pads with two analog joysticks, AMOTION_EVENT_AXIS_RZ is often
 // reinterpreted to report the absolute Y position of the second joystick.
-void InputEventsGenerator::ProcessJoyStickEvent(FlatAxis axis,
-                                                int32_t motion_axis,
-                                                AInputEvent* android_event,
-                                                Events* events) {
-  SB_DCHECK(AMotionEvent_getPointerCount(android_event) > 0);
+void InputEventsGenerator::ProcessJoyStickEvent(
+    FlatAxis axis,
+    int32_t motion_axis,
+    GameActivityMotionEvent* android_motion_event,
+    Events* events) {
+  SB_DCHECK(android_motion_event->pointerCount > 0);
 
-  int32_t device_id = AInputEvent_getDeviceId(android_event);
+  int32_t device_id = android_motion_event->deviceId;
   SB_DCHECK(device_flat_.find(device_id) != device_flat_.end());
 
   float flat = device_flat_[device_id][axis];
-  float offset = AMotionEvent_getAxisValue(android_event, motion_axis, 0);
+  float offset = GameActivityPointerAxes_getAxisValue(
+      &android_motion_event->pointers[0], motion_axis);
   int sign = offset < 0.0f ? -1 : 1;
 
   if (fabs(offset) < flat) {
@@ -474,13 +474,13 @@
 
 namespace {
 
-// Generate a Starboard event from an Android event, with the SbKey and
+// Generate a Starboard event from an Android motion event, with the SbKey and
 // SbInputEventType pre-specified (so that it can be used by event
 // synthesization as well.)
 void PushKeyEvent(SbKey key,
                   SbInputEventType type,
                   SbWindow window,
-                  AInputEvent* android_event,
+                  GameActivityMotionEvent* android_event,
                   Events* events) {
   if (key == kSbKeyUnknown) {
     SB_NOTREACHED();
@@ -497,12 +497,49 @@
   // device
   // TODO: differentiate gamepad, remote, etc.
   data->device_type = kSbInputDeviceTypeKeyboard;
-  data->device_id = AInputEvent_getDeviceId(android_event);
+  data->device_id = android_event->deviceId;
+
+  data->key = key;
+  data->key_location = kSbKeyLocationUnspecified;
+  data->key_modifiers =
+      GameActivityInputEventMetaStateToSbModifiers(android_event->metaState);
+
+  std::unique_ptr<Event> event(
+      new Event(kSbEventTypeInput, data.release(),
+                &Application::DeleteDestructor<SbInputData>));
+  events->push_back(std::move(event));
+}
+
+// Generate a Starboard event from an Android key  event, with the SbKey and
+// SbInputEventType pre-specified (so that it can be used by event
+// synthesization as well.)
+void PushKeyEvent(SbKey key,
+                  SbInputEventType type,
+                  SbWindow window,
+                  GameActivityKeyEvent* android_event,
+                  Events* events) {
+  if (key == kSbKeyUnknown) {
+    SB_NOTREACHED();
+    return;
+  }
+
+  std::unique_ptr<SbInputData> data(new SbInputData());
+  memset(data.get(), 0, sizeof(*data));
+
+  // window
+  data->window = window;
+  data->type = type;
+
+  // device
+  // TODO: differentiate gamepad, remote, etc.
+  data->device_type = kSbInputDeviceTypeKeyboard;
+  data->device_id = android_event->deviceId;
 
   // key
   data->key = key;
-  data->key_location = AInputEventToSbKeyLocation(android_event);
-  data->key_modifiers = AInputEventToSbModifiers(android_event);
+  data->key_location = GameActivityKeyToSbKeyLocation(android_event);
+  data->key_modifiers =
+      GameActivityInputEventMetaStateToSbModifiers(android_event->metaState);
 
   std::unique_ptr<Event> event(
       new Event(kSbEventTypeInput, data.release(),
@@ -588,7 +625,7 @@
                                     float old_value,
                                     float new_value,
                                     SbWindow window,
-                                    AInputEvent* android_event,
+                                    GameActivityMotionEvent* android_event,
                                     Events* events) {
   if (old_value == new_value) {
     // No events to generate if the hat motion value did not change.
@@ -607,16 +644,11 @@
 
 }  // namespace
 
-bool InputEventsGenerator::ProcessKeyEvent(AInputEvent* android_event,
-                                           Events* events) {
-#ifdef STARBOARD_INPUT_EVENTS_FILTER
-  if (!input_events_filter_.ShouldProcessKeyEvent(android_event)) {
-    return false;
-  }
-#endif
-
+bool InputEventsGenerator::CreateInputEventsFromGameActivityEvent(
+    GameActivityKeyEvent* android_event,
+    Events* events) {
   SbInputEventType type;
-  switch (AKeyEvent_getAction(android_event)) {
+  switch (android_event->action) {
     case AKEY_EVENT_ACTION_DOWN:
       type = kSbInputEventTypePress;
       break;
@@ -633,8 +665,7 @@
     return false;
   }
 
-  if (AKeyEvent_getFlags(android_event) & AKEY_EVENT_FLAG_FALLBACK &&
-      IsDPadKey(key)) {
+  if (android_event->flags & AKEY_EVENT_FLAG_FALLBACK && IsDPadKey(key)) {
     // For fallback DPad keys, we flow into special processing to manage the
     // differentiation between the actual DPad and the left thumbstick, since
     // Android conflates the key down/up events for these inputs.
@@ -642,6 +673,7 @@
   } else {
     PushKeyEvent(key, type, window_, android_event, events);
   }
+
   return true;
 }
 
@@ -685,12 +717,13 @@
 
 }  // namespace
 
-bool InputEventsGenerator::ProcessPointerEvent(AInputEvent* android_event,
-                                               Events* events) {
-  float offset_x =
-      AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_X, 0);
-  float offset_y =
-      AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_Y, 0);
+bool InputEventsGenerator::ProcessPointerEvent(
+    GameActivityMotionEvent* android_event,
+    Events* events) {
+  float offset_x = GameActivityPointerAxes_getAxisValue(
+      &android_event->pointers[0], AMOTION_EVENT_AXIS_X);
+  float offset_y = GameActivityPointerAxes_getAxisValue(
+      &android_event->pointers[0], AMOTION_EVENT_AXIS_Y);
 
   std::unique_ptr<SbInputData> data(new SbInputData());
   memset(data.get(), 0, sizeof(*data));
@@ -700,27 +733,28 @@
   data->pressure = NAN;
   data->size = {NAN, NAN};
   data->tilt = {NAN, NAN};
-  unsigned int button_state = AMotionEvent_getButtonState(android_event);
+  unsigned int button_state = android_event->buttonState;
   unsigned int button_modifiers = ButtonStateToSbModifiers(button_state);
 
   // Default to reporting pointer events as mouse events.
   data->device_type = kSbInputDeviceTypeMouse;
 
   // Report both stylus and touchscreen events as touchscreen device events.
-  int32_t event_source = AInputEvent_getSource(android_event);
+  int32_t event_source = android_event->source;
   if (((event_source & AINPUT_SOURCE_TOUCHSCREEN) != 0) ||
       ((event_source & AINPUT_SOURCE_STYLUS) != 0)) {
     data->device_type = kSbInputDeviceTypeTouchScreen;
   }
 
-  data->device_id = AInputEvent_getDeviceId(android_event);
+  data->device_id = android_event->deviceId;
   data->key_modifiers =
-      button_modifiers | AInputEventToSbModifiers(android_event);
+      button_modifiers |
+      GameActivityInputEventMetaStateToSbModifiers(android_event->metaState);
   data->position.x = offset_x;
   data->position.y = offset_y;
   data->key = ButtonStateToSbKey(button_state);
 
-  switch (AKeyEvent_getAction(android_event) & AMOTION_EVENT_ACTION_MASK) {
+  switch (android_event->action & AMOTION_EVENT_ACTION_MASK) {
     case AMOTION_EVENT_ACTION_UP:
       data->type = kSbInputEventTypeUnpress;
       break;
@@ -732,12 +766,15 @@
       data->type = kSbInputEventTypeMove;
       break;
     case AMOTION_EVENT_ACTION_SCROLL: {
-      float hscroll = AMotionEvent_getAxisValue(
-          android_event, AMOTION_EVENT_AXIS_HSCROLL, 0);  // left is -1
-      float vscroll = AMotionEvent_getAxisValue(
-          android_event, AMOTION_EVENT_AXIS_VSCROLL, 0);  // down is -1
-      float wheel =
-          AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_WHEEL, 0);
+      float hscroll = GameActivityPointerAxes_getAxisValue(
+          &android_event->pointers[0],
+          AMOTION_EVENT_AXIS_HSCROLL);  // left is -1
+      float vscroll = GameActivityPointerAxes_getAxisValue(
+          &android_event->pointers[0],
+          AMOTION_EVENT_AXIS_VSCROLL);  // down is -1
+      float wheel = GameActivityPointerAxes_getAxisValue(
+          &android_event->pointers[0],
+          AMOTION_EVENT_AXIS_WHEEL);  // this is not used
       data->type = kSbInputEventTypeWheel;
       data->key = kSbKeyUnknown;
       data->delta.y = -vscroll;
@@ -754,13 +791,14 @@
   return true;
 }
 
-bool InputEventsGenerator::ProcessMotionEvent(AInputEvent* android_event,
-                                              Events* events) {
-  int32_t event_source = AInputEvent_getSource(android_event);
-  if ((event_source & AINPUT_SOURCE_CLASS_POINTER) != 0) {
+bool InputEventsGenerator::CreateInputEventsFromGameActivityEvent(
+    GameActivityMotionEvent* android_event,
+    Events* events) {
+  if ((android_event->source & AINPUT_SOURCE_CLASS_POINTER) != 0) {
     return ProcessPointerEvent(android_event, events);
   }
-  if ((event_source & AINPUT_SOURCE_JOYSTICK) == 0) {
+
+  if ((android_event->source & AINPUT_SOURCE_JOYSTICK) == 0) {
     // Only handles joystick events in the code below.
     return false;
   }
@@ -784,11 +822,12 @@
 
 // Special processing to disambiguate between DPad events and left-thumbstick
 // direction key events.
-void InputEventsGenerator::ProcessFallbackDPadEvent(SbInputEventType type,
-                                                    SbKey key,
-                                                    AInputEvent* android_event,
-                                                    Events* events) {
-  SB_DCHECK(AKeyEvent_getFlags(android_event) & AKEY_EVENT_FLAG_FALLBACK);
+void InputEventsGenerator::ProcessFallbackDPadEvent(
+    SbInputEventType type,
+    SbKey key,
+    GameActivityKeyEvent* android_event,
+    Events* events) {
+  SB_DCHECK(android_event->flags & AKEY_EVENT_FLAG_FALLBACK);
   SB_DCHECK(IsDPadKey(key));
 
   HatAxis hat_axis = HatValueForDPadKey(key).axis;
@@ -797,8 +836,8 @@
     // Direction pad events are all assumed to be coming from the hat controls
     // if motion events for that hat DPAD is active, but we do still handle
     // repeat keys here.
-    if (AKeyEvent_getRepeatCount(android_event) > 0) {
-      SB_LOG(INFO) << AKeyEvent_getRepeatCount(android_event);
+    if ((android_event->repeatCount) > 0) {
+      SB_LOG(INFO) << android_event->repeatCount;
       PushKeyEvent(key, kSbInputEventTypePress, window_, android_event, events);
     }
     return;
@@ -833,24 +872,24 @@
 // event's data.  Possibly generate DPad events based on any changes in value
 // here.
 void InputEventsGenerator::UpdateHatValuesAndPossiblySynthesizeKeyEvents(
-    AInputEvent* android_event,
+    GameActivityMotionEvent* android_motion_event,
     Events* events) {
-  float new_hat_x =
-      AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_HAT_X, 0);
+  float new_hat_x = GameActivityPointerAxes_getAxisValue(
+      &android_motion_event->pointers[0], AMOTION_EVENT_AXIS_HAT_X);
   PossiblySynthesizeHatKeyEvents(kHatX, hat_value_[kHatX], new_hat_x, window_,
-                                 android_event, events);
+                                 android_motion_event, events);
   hat_value_[kHatX] = new_hat_x;
 
-  float new_hat_y =
-      AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_HAT_Y, 0);
+  float new_hat_y = GameActivityPointerAxes_getAxisValue(
+      &android_motion_event->pointers[0], AMOTION_EVENT_AXIS_HAT_Y);
   PossiblySynthesizeHatKeyEvents(kHatY, hat_value_[kHatY], new_hat_y, window_,
-                                 android_event, events);
+                                 android_motion_event, events);
   hat_value_[kHatY] = new_hat_y;
 }
 
 void InputEventsGenerator::UpdateDeviceFlatMapIfNecessary(
-    AInputEvent* android_event) {
-  int32_t device_id = AInputEvent_getDeviceId(android_event);
+    GameActivityMotionEvent* android_motion_event) {
+  int32_t device_id = android_motion_event->deviceId;
   if (device_flat_.find(device_id) != device_flat_.end()) {
     // |device_flat_| is already contains the device flat information.
     return;
@@ -867,28 +906,6 @@
   device_flat_[device_id] = std::vector<float>(flats, flats + kNumAxes);
 }
 
-bool InputEventsGenerator::CreateInputEventsFromAndroidEvent(
-    AInputEvent* android_event,
-    Events* events) {
-  if (android_event == NULL ||
-      (AInputEvent_getType(android_event) != AINPUT_EVENT_TYPE_KEY &&
-       AInputEvent_getType(android_event) != AINPUT_EVENT_TYPE_MOTION)) {
-    return false;
-  }
-
-  switch (AInputEvent_getType(android_event)) {
-    case AINPUT_EVENT_TYPE_KEY:
-      return ProcessKeyEvent(android_event, events);
-    case AINPUT_EVENT_TYPE_MOTION: {
-      return ProcessMotionEvent(android_event, events);
-    }
-    default:
-      SB_NOTREACHED();
-  }
-
-  return false;
-}
-
 void InputEventsGenerator::CreateInputEventsFromSbKey(SbKey key,
                                                       Events* events) {
   events->clear();
diff --git a/starboard/android/shared/input_events_generator.h b/starboard/android/shared/input_events_generator.h
index c192079..e39a72f 100644
--- a/starboard/android/shared/input_events_generator.h
+++ b/starboard/android/shared/input_events_generator.h
@@ -15,14 +15,11 @@
 #ifndef STARBOARD_ANDROID_SHARED_INPUT_EVENTS_GENERATOR_H_
 #define STARBOARD_ANDROID_SHARED_INPUT_EVENTS_GENERATOR_H_
 
-#include <android/input.h>
 #include <map>
 #include <memory>
 #include <vector>
 
-#ifdef STARBOARD_INPUT_EVENTS_FILTER
-#include "starboard/android/shared/internal/input_events_filter.h"
-#endif
+#include "third_party/android_game_activity/include/game-activity/GameActivity.h"
 
 #include "starboard/input.h"
 #include "starboard/shared/starboard/application.h"
@@ -43,8 +40,12 @@
   // Translates an Android input event into a series of Starboard application
   // events. The caller owns the new events and must delete them when done with
   // them.
-  bool CreateInputEventsFromAndroidEvent(AInputEvent* android_event,
-                                         Events* events);
+  bool CreateInputEventsFromGameActivityEvent(
+      GameActivityMotionEvent* android_event,
+      Events* events);
+  bool CreateInputEventsFromGameActivityEvent(
+      GameActivityKeyEvent* android_event,
+      Events* events);
 
   // Create press/unpress events from SbKey
   // (for use with CobaltA11yHelper injection)
@@ -59,28 +60,25 @@
     kNumAxes,
   };
 
-  bool ProcessKeyEvent(AInputEvent* android_event, Events* events);
-  bool ProcessPointerEvent(AInputEvent* android_event, Events* events);
-  bool ProcessMotionEvent(AInputEvent* android_event, Events* events);
+  bool ProcessPointerEvent(GameActivityMotionEvent* android_event,
+                           Events* events);
   void ProcessJoyStickEvent(FlatAxis axis,
                             int32_t motion_axis,
-                            AInputEvent* android_event,
+                            GameActivityMotionEvent* android_event,
                             Events* events);
-  void UpdateDeviceFlatMapIfNecessary(AInputEvent* android_event);
+  void UpdateDeviceFlatMapIfNecessary(GameActivityMotionEvent* android_event);
+  void UpdateDeviceFlatMapIfNecessary(GameActivityKeyEvent* android_event);
 
   void ProcessFallbackDPadEvent(SbInputEventType type,
                                 SbKey key,
-                                AInputEvent* android_event,
+                                GameActivityKeyEvent* android_event,
                                 Events* events);
-  void UpdateHatValuesAndPossiblySynthesizeKeyEvents(AInputEvent* android_event,
-                                                     Events* events);
+  void UpdateHatValuesAndPossiblySynthesizeKeyEvents(
+      GameActivityMotionEvent* android_event,
+      Events* events);
 
   SbWindow window_;
 
-#ifdef STARBOARD_INPUT_EVENTS_FILTER
-  internal::InputEventsFilter input_events_filter_;
-#endif
-
   // Map the device id with joystick flat position.
   // Cache the flat area of joystick to avoid calling jni functions frequently.
   std::map<int32_t, std::vector<float> > device_flat_;
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc
index e8090b5..2a5fc0e 100644
--- a/starboard/android/shared/media_capabilities_cache.cc
+++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -48,15 +48,15 @@
 }
 
 Range ConvertJavaRangeToRange(JniEnvExt* env, jobject j_range) {
-  jobject j_upper_comparable = env->CallObjectMethodOrAbort(
-      j_range, "getUpper", "()Ljava/lang/Comparable;");
+  ScopedLocalJavaRef<jobject> j_upper_comparable(env->CallObjectMethodOrAbort(
+      j_range, "getUpper", "()Ljava/lang/Comparable;"));
   jint j_upper_int =
-      env->CallIntMethodOrAbort(j_upper_comparable, "intValue", "()I");
+      env->CallIntMethodOrAbort(j_upper_comparable.Get(), "intValue", "()I");
 
-  jobject j_lower_comparable = env->CallObjectMethodOrAbort(
-      j_range, "getLower", "()Ljava/lang/Comparable;");
+  ScopedLocalJavaRef<jobject> j_lower_comparable(env->CallObjectMethodOrAbort(
+      j_range, "getLower", "()Ljava/lang/Comparable;"));
   jint j_lower_int =
-      env->CallIntMethodOrAbort(j_lower_comparable, "intValue", "()I");
+      env->CallIntMethodOrAbort(j_lower_comparable.Get(), "intValue", "()I");
   return Range(j_lower_int, j_upper_int);
 }
 
@@ -149,9 +149,9 @@
   SB_DCHECK(env);
   SB_DCHECK(j_codec_info);
 
-  name_ = env->GetStringStandardUTFOrAbort(
+  ScopedLocalJavaRef<jstring> j_decoder_name(
       env->GetStringFieldOrAbort(j_codec_info, "decoderName"));
-
+  name_ = env->GetStringStandardUTFOrAbort(j_decoder_name.Get());
   is_secure_required_ =
       env->CallBooleanMethodOrAbort(j_codec_info, "isSecureRequired", "()Z") ==
       JNI_TRUE;
@@ -174,9 +174,9 @@
   SB_DCHECK(j_codec_info);
   SB_DCHECK(j_audio_capabilities);
 
-  jobject j_bitrate_range = env->CallObjectMethodOrAbort(
-      j_audio_capabilities, "getBitrateRange", "()Landroid/util/Range;");
-  supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range);
+  ScopedLocalJavaRef<jobject> j_bitrate_range(env->CallObjectMethodOrAbort(
+      j_audio_capabilities, "getBitrateRange", "()Landroid/util/Range;"));
+  supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range.Get());
 
   // Overwrite the lower bound to 0.
   supported_bitrates_.minimum = 0;
@@ -200,24 +200,25 @@
   is_hdr_capable_ = env->CallBooleanMethodOrAbort(j_codec_info, "isHdrCapable",
                                                   "()Z") == JNI_TRUE;
 
-  j_video_capabilities_ = env->ConvertLocalRefToGlobalRef(j_video_capabilities);
+  j_video_capabilities_ = env->NewGlobalRef(j_video_capabilities);
 
-  jobject j_width_range = env->CallObjectMethodOrAbort(
-      j_video_capabilities_, "getSupportedWidths", "()Landroid/util/Range;");
-  supported_widths_ = ConvertJavaRangeToRange(env, j_width_range);
+  ScopedLocalJavaRef<jobject> j_width_range(env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getSupportedWidths", "()Landroid/util/Range;"));
+  supported_widths_ = ConvertJavaRangeToRange(env, j_width_range.Get());
 
-  jobject j_height_range = env->CallObjectMethodOrAbort(
-      j_video_capabilities_, "getSupportedHeights", "()Landroid/util/Range;");
-  supported_heights_ = ConvertJavaRangeToRange(env, j_height_range);
+  ScopedLocalJavaRef<jobject> j_height_range(env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getSupportedHeights", "()Landroid/util/Range;"));
+  supported_heights_ = ConvertJavaRangeToRange(env, j_height_range.Get());
 
-  jobject j_bitrate_range = env->CallObjectMethodOrAbort(
-      j_video_capabilities_, "getBitrateRange", "()Landroid/util/Range;");
-  supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range);
+  ScopedLocalJavaRef<jobject> j_bitrate_range(env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getBitrateRange", "()Landroid/util/Range;"));
+  supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range.Get());
 
-  jobject j_frame_rate_range = env->CallObjectMethodOrAbort(
+  ScopedLocalJavaRef<jobject> j_frame_rate_range(env->CallObjectMethodOrAbort(
       j_video_capabilities_, "getSupportedFrameRates",
-      "()Landroid/util/Range;");
-  supported_frame_rates_ = ConvertJavaRangeToRange(env, j_frame_rate_range);
+      "()Landroid/util/Range;"));
+  supported_frame_rates_ =
+      ConvertJavaRangeToRange(env, j_frame_rate_range.Get());
 }
 
 VideoCodecCapability::~VideoCodecCapability() {
@@ -504,7 +505,6 @@
   if (is_initialized_) {
     return;
   }
-
   is_widevine_supported_ = GetIsWidevineSupported();
   is_cbcs_supported_ = GetIsCbcsSupported();
   supported_transfer_ids_ = GetSupportedHdrTypes();
@@ -522,41 +522,44 @@
   mutex_.DCheckAcquired();
 
   JniEnvExt* env = JniEnvExt::Get();
-  jobjectArray j_codec_infos =
+  ScopedLocalJavaRef<jobjectArray> j_codec_infos(
       static_cast<jobjectArray>(env->CallStaticObjectMethodOrAbort(
           "dev/cobalt/media/MediaCodecUtil", "getAllCodecCapabilityInfos",
-          "()[Ldev/cobalt/media/MediaCodecUtil$CodecCapabilityInfo;"));
-  jsize length = env->GetArrayLength(j_codec_infos);
+          "()[Ldev/cobalt/media/MediaCodecUtil$CodecCapabilityInfo;")));
+  jsize length = env->GetArrayLength(j_codec_infos.Get());
   // Note: Codec infos are sorted by the framework such that the best
   // decoders come first.
   // This order is maintained in the cache.
   for (int i = 0; i < length; i++) {
-    jobject j_codec_info = env->GetObjectArrayElementOrAbort(j_codec_infos, i);
+    ScopedLocalJavaRef<jobject> j_codec_info(
+        env->GetObjectArrayElementOrAbort(j_codec_infos.Get(), i));
 
-    std::string mime_type = env->GetStringStandardUTFOrAbort(
-        env->GetStringFieldOrAbort(j_codec_info, "mimeType"));
-
+    ScopedLocalJavaRef<jstring> j_mime_type(
+        env->GetStringFieldOrAbort(j_codec_info.Get(), "mimeType"));
+    std::string mime_type = env->GetStringStandardUTFOrAbort(j_mime_type.Get());
     // Convert the mime type to lower case.
     ConvertStringToLowerCase(&mime_type);
 
-    jobject j_audio_capabilities = env->GetObjectFieldOrAbort(
-        j_codec_info, "audioCapabilities",
-        "Landroid/media/MediaCodecInfo$AudioCapabilities;");
+    ScopedLocalJavaRef<jobject> j_audio_capabilities(env->GetObjectFieldOrAbort(
+        j_codec_info.Get(), "audioCapabilities",
+        "Landroid/media/MediaCodecInfo$AudioCapabilities;"));
     if (j_audio_capabilities) {
       // Found an audio decoder.
       std::unique_ptr<AudioCodecCapability> audio_codec_capabilities(
-          new AudioCodecCapability(env, j_codec_info, j_audio_capabilities));
+          new AudioCodecCapability(env, j_codec_info.Get(),
+                                   j_audio_capabilities.Get()));
       audio_codec_capabilities_map_[mime_type].push_back(
           std::move(audio_codec_capabilities));
       continue;
     }
-    jobject j_video_capabilities = env->GetObjectFieldOrAbort(
-        j_codec_info, "videoCapabilities",
-        "Landroid/media/MediaCodecInfo$VideoCapabilities;");
+    ScopedLocalJavaRef<jobject> j_video_capabilities(env->GetObjectFieldOrAbort(
+        j_codec_info.Get(), "videoCapabilities",
+        "Landroid/media/MediaCodecInfo$VideoCapabilities;"));
     if (j_video_capabilities) {
       // Found a video decoder.
       std::unique_ptr<VideoCodecCapability> video_codec_capabilities(
-          new VideoCodecCapability(env, j_codec_info, j_video_capabilities));
+          new VideoCodecCapability(env, j_codec_info.Get(),
+                                   j_video_capabilities.Get()));
       video_codec_capabilities_map_[mime_type].push_back(
           std::move(video_codec_capabilities));
     }
diff --git a/starboard/android/shared/system_get_path.cc b/starboard/android/shared/system_get_path.cc
index 5e4610a..1bd30ae 100644
--- a/starboard/android/shared/system_get_path.cc
+++ b/starboard/android/shared/system_get_path.cc
@@ -77,6 +77,21 @@
       break;
     }
 
+    case kSbSystemPathFontConfigurationDirectory:
+    case kSbSystemPathFontDirectory:
+#if SB_IS(EVERGREEN_COMPATIBLE)
+      if (starboard::strlcat(path, g_app_assets_dir, kPathSize) >= kPathSize) {
+        return false;
+      }
+      if (starboard::strlcat(path, "/fonts", kPathSize) >= kPathSize) {
+        return false;
+      }
+      break;
+#else
+      SB_NOTIMPLEMENTED();
+      return false;
+#endif
+
     case kSbSystemPathStorageDirectory: {
       if (starboard::strlcpy(path, g_app_files_dir, kPathSize) >= kPathSize) {
         return false;
diff --git a/starboard/build/platforms.py b/starboard/build/platforms.py
index 2abbaf4..2c0e93c 100644
--- a/starboard/build/platforms.py
+++ b/starboard/build/platforms.py
@@ -41,6 +41,7 @@
     'evergreen-arm-hardfp': 'starboard/evergreen/arm/hardfp',
     'evergreen-arm-softfp': 'starboard/evergreen/arm/softfp',
     'evergreen-arm64': 'starboard/evergreen/arm64',
+    'win-win32': 'starboard/win/win32',
 }
 PLATFORMS.update(INTERNAL_PLATFORMS)
 
diff --git a/starboard/evergreen/shared/platform_configuration/BUILD.gn b/starboard/evergreen/shared/platform_configuration/BUILD.gn
index 43d8a69..db425fc 100644
--- a/starboard/evergreen/shared/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/shared/platform_configuration/BUILD.gn
@@ -167,6 +167,9 @@
 
 config("size") {
   cflags = [ "-Os" ]
+  if (is_qa || is_gold) {
+    ldflags = [ "--icf=safe" ]
+  }
 }
 
 config("pedantic_warnings") {
diff --git a/starboard/evergreen/testing/README.md b/starboard/evergreen/testing/README.md
index e401325..bf64968 100644
--- a/starboard/evergreen/testing/README.md
+++ b/starboard/evergreen/testing/README.md
@@ -75,6 +75,7 @@
 * `tests/update_fails_verification_test.sh`
 * `tests/update_works_for_only_one_app_test.sh`
 * `tests/valid_slot_overwritten_test.sh`
+* `tests/verify_qa_channel_compressed_update_test.sh`
 * `tests/verify_qa_channel_update_test.sh`
 
 How To Run
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh
index 91c53fb..1339884 100755
--- a/starboard/evergreen/testing/run_all_tests.sh
+++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -48,9 +48,10 @@
 
 if [[ "${USE_COMPRESSED_SYSTEM_IMAGE}" == "true" ]]; then
   # It would be valid to run all test cases using a compressed system image but
-  # is probably excessive. Instead, just the Evergreen Lite case is run to test
-  # that the compressed system image can be successfully loaded.
-  TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name 'evergreen_lite_test.sh'"))
+  # is probably excessive. Instead, just two cases are run: one to test that a
+  # compressed system image can be loaded and one to test that a compressed
+  # update can be upgraded to.
+  TESTS=($(eval "find ${DIR}/tests -maxdepth 1 \( -name 'evergreen_lite_test.sh' -o -name 'verify_qa_channel_compressed_update_test.sh' \)"))
 else
   TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
 fi
diff --git a/starboard/evergreen/testing/tests/verify_qa_channel_compressed_update_test.sh b/starboard/evergreen/testing/tests/verify_qa_channel_compressed_update_test.sh
new file mode 100755
index 0000000..61d8602
--- /dev/null
+++ b/starboard/evergreen/testing/tests/verify_qa_channel_compressed_update_test.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Copyright 2022 The Cobalt Authors. 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="VerifyQaChannelCompressedUpdate"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed" "--use_compressed_updates"
+
+  if [[ $? -ne 0 ]]; then
+    log "error" "Failed to download and install the test package"
+    return 1
+  fi
+
+  # It's not necessary to set --use_compressed_updates in this run, since that
+  # doesn't impact whether an update is available, but it doesn't hurt and is
+  # more realistic.
+  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.1.log" "App is up to date" "--use_compressed_updates"
+
+  if [[ $? -ne 0 ]]; then
+    log "error" "Failed to run the downloaded installation"
+    return 1
+  fi
+
+  if ! grep -Eq "Evergreen-Compressed" "${LOG_PATH}/${TEST_NAME}.1.log"; then
+    log "error" "According to the user-agent string, the installation run was not compressed"
+    return 1
+  fi
+
+  return 0
+}
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn
index 8e4a107..8a087b6 100644
--- a/starboard/linux/shared/BUILD.gn
+++ b/starboard/linux/shared/BUILD.gn
@@ -160,6 +160,10 @@
     "//starboard/shared/nouser/user_get_property.cc",
     "//starboard/shared/nouser/user_get_signed_in.cc",
     "//starboard/shared/nouser/user_internal.cc",
+    "//starboard/shared/openh264/openh264_library_loader.cc",
+    "//starboard/shared/openh264/openh264_library_loader.h",
+    "//starboard/shared/openh264/openh264_video_decoder.cc",
+    "//starboard/shared/openh264/openh264_video_decoder.h",
     "//starboard/shared/opus/opus_audio_decoder.cc",
     "//starboard/shared/opus/opus_audio_decoder.h",
     "//starboard/shared/posix/directory_create.cc",
diff --git a/starboard/linux/shared/player_components_factory.cc b/starboard/linux/shared/player_components_factory.cc
index c142f8f..4dcc0ca 100644
--- a/starboard/linux/shared/player_components_factory.cc
+++ b/starboard/linux/shared/player_components_factory.cc
@@ -23,6 +23,8 @@
 #include "starboard/shared/libdav1d/dav1d_video_decoder.h"
 #include "starboard/shared/libde265/de265_video_decoder.h"
 #include "starboard/shared/libvpx/vpx_video_decoder.h"
+#include "starboard/shared/openh264/openh264_library_loader.h"
+#include "starboard/shared/openh264/openh264_video_decoder.h"
 #include "starboard/shared/opus/opus_audio_decoder.h"
 #include "starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h"
 #include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
@@ -43,6 +45,8 @@
 
 namespace {
 
+using ::starboard::shared::openh264::is_openh264_supported;
+
 class PlayerComponentsFactory : public PlayerComponents::Factory {
  public:
   bool CreateSubComponents(
@@ -92,6 +96,8 @@
       typedef ::starboard::shared::de265::VideoDecoder H265VideoDecoderImpl;
       typedef ::starboard::shared::ffmpeg::VideoDecoder FfmpegVideoDecoderImpl;
       typedef ::starboard::shared::vpx::VideoDecoder VpxVideoDecoderImpl;
+      typedef ::starboard::shared::openh264::VideoDecoder
+          Openh264VideoDecoderImpl;
 
       const SbTime kVideoSinkRenderInterval = 10 * kSbTimeMillisecond;
 
@@ -118,6 +124,14 @@
             creation_parameters.video_codec(),
             creation_parameters.output_mode(),
             creation_parameters.decode_target_graphics_context_provider()));
+      } else if ((creation_parameters.video_codec() ==
+                  kSbMediaVideoCodecH264) &&
+                 is_openh264_supported()) {
+        SB_LOG(INFO) << "Playing video using openh264::VideoDecoder.";
+        video_decoder->reset(new Openh264VideoDecoderImpl(
+            creation_parameters.video_codec(),
+            creation_parameters.output_mode(),
+            creation_parameters.decode_target_graphics_context_provider()));
       } else {
         scoped_ptr<FfmpegVideoDecoderImpl> ffmpeg_video_decoder(
             FfmpegVideoDecoderImpl::Create(
@@ -125,6 +139,7 @@
                 creation_parameters.output_mode(),
                 creation_parameters.decode_target_graphics_context_provider()));
         if (ffmpeg_video_decoder && ffmpeg_video_decoder->is_valid()) {
+          SB_LOG(INFO) << "Playing video using ffmpeg::VideoDecoder.";
           video_decoder->reset(ffmpeg_video_decoder.release());
         } else {
           SB_LOG(ERROR) << "Failed to create video decoder for codec "
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn
index 3ef98fd..0f80d8a 100644
--- a/starboard/loader_app/BUILD.gn
+++ b/starboard/loader_app/BUILD.gn
@@ -41,6 +41,15 @@
 target(final_executable_type, "loader_app") {
   if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
       target_cpu == "arm64") {
+    data_deps = [ "//third_party/icu:icudata" ]
+    if (cobalt_font_package == "empty") {
+      data_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+    } else {
+      data_deps += [
+        "//cobalt/content/fonts:copy_fonts",
+        "//cobalt/content/fonts:fonts_xml",
+      ]
+    }
     sources = _common_loader_app_sources
     deps = [
       ":common_loader_app_dependencies",
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index 223a749..16f8c28 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -80,6 +80,44 @@
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 }
 
+TEST(SbMediaCanPlayMimeAndKeySystem, FloatFramerate) {
+  SbMediaSupportType int_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=640; "
+      "height=360; framerate=24;",
+      "");
+  SbMediaSupportType float_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=640; "
+      "height=360; framerate=23.976;",
+      "");
+  ASSERT_EQ(int_framerate_result, float_framerate_result);
+
+  int_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=60;", "");
+  float_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=59.976;",
+      "");
+  ASSERT_EQ(int_framerate_result, float_framerate_result);
+
+  int_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=9999;",
+      "");
+  float_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp9\"; width=1920; height=1080; "
+      "framerate=9998.976;",
+      "");
+  ASSERT_EQ(int_framerate_result, float_framerate_result);
+
+  int_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+      "framerate=60;",
+      "");
+  float_framerate_result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+      "framerate=59.976;",
+      "");
+  ASSERT_EQ(int_framerate_result, float_framerate_result);
+}
+
 TEST(SbMediaCanPlayMimeAndKeySystem, Invalid) {
   // Invalid codec
   SbMediaSupportType result =
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py
index 99fa3d2..3f6a2ba 100644
--- a/starboard/raspi/shared/launcher.py
+++ b/starboard/raspi/shared/launcher.py
@@ -104,6 +104,8 @@
     signal.signal(signal.SIGINT, functools.partial(_SigIntOrSigTermHandler))
     signal.signal(signal.SIGTERM, functools.partial(_SigIntOrSigTermHandler))
 
+    self.last_run_pexpect_cmd = ''
+
   def _InitPexpectCommands(self):
     """Initializes all of the pexpect commands needed for running the test."""
 
@@ -180,15 +182,15 @@
       try:
         i = self.pexpect_process.expect(expected_prompts)
         if i == 0:
-          self.pexpect_process.sendline('yes')
+          self._PexpectSendLine('yes')
         elif i == 1:
-          self.pexpect_process.sendline(Launcher._RASPI_PASSWORD)
+          self._PexpectSendLine(Launcher._RASPI_PASSWORD)
           break
         else:
           # If any other input comes in, maybe we've logged in with rsa key or
           # raspi does not have password. Check if we've logged in by echoing
           # a special sentence and expect it back.
-          self.pexpect_process.sendline('echo ' + Launcher._SSH_LOGIN_SIGNAL)
+          self._PexpectSendLine('echo ' + Launcher._SSH_LOGIN_SIGNAL)
           i = self.pexpect_process.expect([Launcher._SSH_LOGIN_SIGNAL])
           break
       except pexpect.TIMEOUT:
@@ -200,6 +202,11 @@
         if retry_count > Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES:
           raise
 
+  def _PexpectSendLine(self, cmd):
+    """Send lines to Pexpect and record the last command for logging purposes"""
+    self.last_run_pexpect_cmd = cmd
+    self.pexpect_process.sendline(cmd)
+
   def _PexpectReadLines(self):
     """Reads all lines from the pexpect process."""
 
@@ -231,8 +238,8 @@
           raise
 
   def _Sleep(self, val):
-    self.pexpect_process.sendline('sleep {};echo {}'.format(
-        val, Launcher._SSH_SLEEP_SIGNAL))
+    self._PexpectSendLine('sleep {};echo {}'.format(val,
+                                                    Launcher._SSH_SLEEP_SIGNAL))
     self.pexpect_process.expect([Launcher._SSH_SLEEP_SIGNAL])
 
   def _CleanupPexpectProcess(self):
@@ -242,16 +249,18 @@
       # Check if kernel logged OOM kill or any other system failure message
       if self.return_value:
         logging.info('Sending dmesg')
-        self.pexpect_process.sendline('dmesg -P --color=never | tail -n 100')
+        self._PexpectSendLine('dmesg -P --color=never | tail -n 100')
         time.sleep(3)
         try:
           self.pexpect_process.readlines()
         except pexpect.TIMEOUT:
+          logging.info('Timeout exception during cleanup command: %s',
+                       self.last_run_pexpect_cmd)
           pass
         logging.info('Done sending dmesg')
 
       # Send ctrl-c to the raspi and close the process.
-      self.pexpect_process.sendline(chr(3))
+      self._PexpectSendLine(chr(3))
       time.sleep(1)  # Allow a second for normal shutdown
       self.pexpect_process.close()
 
@@ -263,12 +272,14 @@
         self.pexpect_process.expect(self._RASPI_PROMPT)
         break
       except pexpect.TIMEOUT:
+        logging.info('Timeout exception during WaitForPrompt command: %s',
+                     self.last_run_pexpect_cmd)
         if self.shutdown_initiated.is_set():
           return
         retry_count -= 1
         if not retry_count:
           raise
-        self.pexpect_process.sendline('echo ' + Launcher._SSH_SLEEP_SIGNAL)
+        self._PexpectSendLine('echo ' + Launcher._SSH_SLEEP_SIGNAL)
         time.sleep(self._INTER_COMMAND_DELAY_SECONDS)
 
   def _KillExistingCobaltProcesses(self):
@@ -279,11 +290,11 @@
     cause other problems.
     """
     logging.info('Killing existing processes')
-    self.pexpect_process.sendline(
+    self._PexpectSendLine(
         'pkill -9 -ef "(cobalt)|(crashpad_handler)|(elf_loader)"')
     self._WaitForPrompt()
     # Print the return code of pkill. 0 if a process was halted
-    self.pexpect_process.sendline('echo PROCKILL:${?}')
+    self._PexpectSendLine('echo PROCKILL:${?}')
     i = self.pexpect_process.expect([r'PROCKILL:0', r'PROCKILL:(\d+)'])
     if i == 0:
       logging.warning('Forced to pkill existing instance(s) of cobalt. '
@@ -321,10 +332,14 @@
         self._PexpectSpawnAndConnect(self.ssh_command)
         self._Sleep(self._INTER_COMMAND_DELAY_SECONDS)
       # Execute debugging commands on the first run
+      first_run_commands = []
+      if self.test_result_xml_path:
+        first_run_commands.append('touch {}'.format(self.test_result_xml_path))
+      first_run_commands.extend(['free -mh', 'ps -ux', 'df -h'])
       if FirstRun():
-        for cmd in ['free -mh', 'ps -ux', 'df -h']:
+        for cmd in first_run_commands:
           if not self.shutdown_initiated.is_set():
-            self.pexpect_process.sendline(cmd)
+            self._PexpectSendLine(cmd)
             line = self.pexpect_process.readline()
             self.output_file.write(line)
         self._WaitForPrompt()
@@ -334,15 +349,18 @@
         self._Sleep(self._INTER_COMMAND_DELAY_SECONDS)
 
       if not self.shutdown_initiated.is_set():
-        self.pexpect_process.sendline(self.test_command)
+        self._PexpectSendLine(self.test_command)
         self._PexpectReadLines()
 
     except pexpect.EOF:
-      logging.exception('pexpect encountered EOF while reading line.')
+      logging.exception('pexpect encountered EOF while reading line. (cmd: %s)',
+                        self.last_run_pexpect_cmd)
     except pexpect.TIMEOUT:
-      logging.exception('pexpect timed out while reading line.')
+      logging.exception('pexpect timed out while reading line. (cmd: %s)',
+                        self.last_run_pexpect_cmd)
     except Exception:  # pylint: disable=broad-except
-      logging.exception('Error occurred while running test.')
+      logging.exception('Error occurred while running test. (cmd: %s)',
+                        self.last_run_pexpect_cmd)
     finally:
       self._CleanupPexpectProcess()
 
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc
index 49b7f5d..084d19a 100644
--- a/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc
@@ -66,16 +66,6 @@
   static_cast<T*>(user_data)->AsStdFunction()(u);
 }
 
-// A mock class representing a data source passed to the cobalt extension
-// demuxer.
-class MockDataSource {
- public:
-  MOCK_METHOD2(BlockingRead, int(uint8_t* data, int bytes_requested));
-  MOCK_METHOD1(SeekTo, void(int position));
-  MOCK_METHOD0(GetPosition, int64_t());
-  MOCK_METHOD0(GetSize, int64_t());
-};
-
 // These functions forward calls to a FstreamDataSource.
 int FstreamBlockingRead(uint8_t* data, int bytes_requested, void* user_data) {
   auto* input = static_cast<std::ifstream*>(user_data);
@@ -110,7 +100,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -135,7 +124,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -193,7 +181,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -251,7 +238,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -288,7 +274,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -337,7 +322,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -363,7 +347,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
@@ -389,7 +372,6 @@
 
   const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
   ASSERT_THAT(api, NotNull());
-  MockDataSource data_source;
   CobaltExtensionDemuxerDataSource c_data_source{
       &FstreamBlockingRead, &FstreamSeekTo, &FstreamGetPosition,
       &FstreamGetSize,      kIsStreaming,   &input};
diff --git a/starboard/shared/libdav1d/dav1d_video_decoder.cc b/starboard/shared/libdav1d/dav1d_video_decoder.cc
index dc0f8e7..7d768d5 100644
--- a/starboard/shared/libdav1d/dav1d_video_decoder.cc
+++ b/starboard/shared/libdav1d/dav1d_video_decoder.cc
@@ -131,7 +131,6 @@
     decoder_thread_.reset();
   }
 
-  error_occurred_ = false;
   stream_ended_ = false;
 
   CancelPendingJobs();
@@ -155,10 +154,13 @@
 }
 
 void VideoDecoder::ReportError(const std::string& error_message) {
-  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  SB_DCHECK(error_cb_);
 
-  error_occurred_ = true;
-  Schedule(std::bind(error_cb_, kSbPlayerErrorDecode, error_message));
+  if (!BelongsToCurrentThread()) {
+    Schedule(std::bind(error_cb_, kSbPlayerErrorDecode, error_message));
+    return;
+  }
+  error_cb_(kSbPlayerErrorDecode, error_message);
 }
 
 void VideoDecoder::InitializeCodec() {
diff --git a/starboard/shared/libdav1d/dav1d_video_decoder.h b/starboard/shared/libdav1d/dav1d_video_decoder.h
index 49d590a..b05b366 100644
--- a/starboard/shared/libdav1d/dav1d_video_decoder.h
+++ b/starboard/shared/libdav1d/dav1d_video_decoder.h
@@ -91,7 +91,6 @@
   Dav1dContext* dav1d_context_ = NULL;
 
   bool stream_ended_ = false;
-  bool error_occurred_ = false;
 
   // Working thread to avoid lengthy decoding work block the player thread.
   scoped_ptr<starboard::player::JobThread> decoder_thread_;
diff --git a/starboard/shared/openh264/openh264_library_loader.cc b/starboard/shared/openh264/openh264_library_loader.cc
new file mode 100644
index 0000000..e8ccc7c
--- /dev/null
+++ b/starboard/shared/openh264/openh264_library_loader.cc
@@ -0,0 +1,81 @@
+// Copyright 2022 The Cobalt Authors. 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 <dlfcn.h>
+
+#include "starboard/common/log.h"
+#include "starboard/shared/openh264/openh264_library_loader.h"
+
+namespace starboard {
+namespace shared {
+namespace openh264 {
+
+namespace {
+
+const char kOpenh264LibraryName[] = "libopenh264.so";
+
+class LibOpenh264Handle {
+ public:
+  LibOpenh264Handle() { LoadLibrary(); }
+  ~LibOpenh264Handle() {
+    if (handle_) {
+      dlclose(handle_);
+    }
+  }
+
+  bool IsLoaded() const { return handle_; }
+
+ private:
+  void ReportSymbolError() {
+    SB_LOG(ERROR) << "Openh264 load error: " << dlerror();
+    dlclose(handle_);
+    handle_ = NULL;
+  }
+
+  void LoadLibrary() {
+    SB_DCHECK(!handle_);
+
+    handle_ = dlopen(kOpenh264LibraryName, RTLD_LAZY);
+    if (!handle_) {
+      return;
+    }
+#define INITSYMBOL(symbol)                                              \
+  symbol = reinterpret_cast<decltype(symbol)>(dlsym(handle_, #symbol)); \
+  if (!symbol) {                                                        \
+    ReportSymbolError();                                                \
+    return;                                                             \
+  }
+    INITSYMBOL(WelsCreateDecoder);
+    INITSYMBOL(WelsDestroyDecoder);
+  }
+  void* handle_ = NULL;
+};
+
+LibOpenh264Handle* GetHandle() {
+  static LibOpenh264Handle instance;
+  return &instance;
+}
+}  // namespace
+
+int (*WelsCreateDecoder)(ISVCDecoder** ppDecoder);
+
+void (*WelsDestroyDecoder)(ISVCDecoder* pDecoder);
+
+bool is_openh264_supported() {
+  return GetHandle()->IsLoaded();
+}
+
+}  // namespace openh264
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/openh264/openh264_library_loader.h b/starboard/shared/openh264/openh264_library_loader.h
new file mode 100644
index 0000000..fee4559
--- /dev/null
+++ b/starboard/shared/openh264/openh264_library_loader.h
@@ -0,0 +1,37 @@
+// Copyright 2022 The Cobalt Authors. 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 STARBOARD_SHARED_OPENH264_OPENH264_LIBRARY_LOADER_H_
+#define STARBOARD_SHARED_OPENH264_OPENH264_LIBRARY_LOADER_H_
+
+#include "starboard/shared/internal_only.h"
+#include "third_party/openh264/include/codec_api.h"
+#include "third_party/openh264/include/codec_app_def.h"
+#include "third_party/openh264/include/codec_def.h"
+
+namespace starboard {
+namespace shared {
+namespace openh264 {
+
+bool is_openh264_supported();
+
+extern int (*WelsCreateDecoder)(ISVCDecoder** ppDecoder);
+
+extern void (*WelsDestroyDecoder)(ISVCDecoder* pDecoder);
+
+}  // namespace openh264
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_OPENH264_OPENH264_LIBRARY_LOADER_H_
diff --git a/starboard/shared/openh264/openh264_video_decoder.cc b/starboard/shared/openh264/openh264_video_decoder.cc
new file mode 100644
index 0000000..cf17fab
--- /dev/null
+++ b/starboard/shared/openh264/openh264_video_decoder.cc
@@ -0,0 +1,312 @@
+// Copyright 2022 The Cobalt Authors. 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 "starboard/shared/openh264/openh264_video_decoder.h"
+
+#include "starboard/common/log.h"
+#include "starboard/common/string.h"
+#include "starboard/linux/shared/decode_target_internal.h"
+#include "starboard/memory.h"
+#include "starboard/shared/openh264/openh264_library_loader.h"
+#include "starboard/shared/starboard/player/filter/cpu_video_frame.h"
+#include "starboard/shared/starboard/player/job_queue.h"
+
+namespace starboard {
+namespace shared {
+namespace openh264 {
+
+namespace {
+
+using shared::starboard::media::VideoConfig;
+using starboard::player::InputBuffer;
+using starboard::player::JobThread;
+using starboard::player::filter::CpuVideoFrame;
+
+}  // namespace
+
+VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec,
+                           SbPlayerOutputMode output_mode,
+                           SbDecodeTargetGraphicsContextProvider*
+                               decode_target_graphics_context_provider)
+    : output_mode_(output_mode),
+      decode_target_graphics_context_provider_(
+          decode_target_graphics_context_provider) {
+  SB_DCHECK(video_codec == kSbMediaVideoCodecH264);
+}
+
+VideoDecoder::~VideoDecoder() {
+  SB_DCHECK(BelongsToCurrentThread());
+  Reset();
+}
+
+void VideoDecoder::Initialize(const DecoderStatusCB& decoder_status_cb,
+                              const ErrorCB& error_cb) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(decoder_status_cb);
+  SB_DCHECK(!decoder_status_cb_);
+  SB_DCHECK(error_cb);
+  SB_DCHECK(!error_cb_);
+
+  decoder_status_cb_ = decoder_status_cb;
+  error_cb_ = error_cb;
+}
+
+void VideoDecoder::Reset() {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (decoder_thread_) {
+    decoder_thread_->job_queue()->Schedule(
+        std::bind(&VideoDecoder::TeardownCodec, this));
+    // Join the thread to ensure that all callbacks in process are finished.
+    decoder_thread_.reset();
+  }
+
+  video_config_ = nullopt;
+  stream_ended_ = false;
+
+  CancelPendingJobs();
+  frames_being_decoded_ = 0;
+
+  ScopedLock lock(decode_target_mutex_);
+  frames_ = std::queue<scoped_refptr<CpuVideoFrame>>();
+}
+
+void VideoDecoder::InitializeCodec() {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  SB_DCHECK(!decoder_);
+
+  int result = WelsCreateDecoder(&decoder_);
+  if (result != 0) {
+    decoder_ = nullptr;
+    ReportError(
+        FormatString("WelsCreateDecoder() failed with status %d.", result));
+    return;
+  }
+  SDecodingParam sDecParam;
+  sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC;
+  sDecParam.bParseOnly = false;
+  result = decoder_->Initialize(&sDecParam);
+  if (result != 0) {
+    WelsDestroyDecoder(decoder_);
+    decoder_ = nullptr;
+    ReportError(
+        FormatString("Decoder Initialize() failed with status %d.", result));
+    return;
+  }
+}
+
+void VideoDecoder::TeardownCodec() {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  if (decoder_) {
+    decoder_->Uninitialize();
+    WelsDestroyDecoder(decoder_);
+    decoder_ = nullptr;
+  }
+  if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
+    SbDecodeTarget decode_target_to_release;
+    {
+      ScopedLock lock(decode_target_mutex_);
+      decode_target_to_release = decode_target_;
+      decode_target_ = kSbDecodeTargetInvalid;
+    }
+
+    if (SbDecodeTargetIsValid(decode_target_to_release)) {
+      DecodeTargetRelease(decode_target_graphics_context_provider_,
+                          decode_target_to_release);
+    }
+  }
+}
+
+void VideoDecoder::WriteInputBuffer(
+    const scoped_refptr<InputBuffer>& input_buffer) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(input_buffer);
+  SB_DCHECK(decoder_status_cb_);
+  if (stream_ended_) {
+    ReportError("WriteInputBuffer() was called after WriteEndOfStream().");
+    return;
+  }
+  if (!decoder_thread_) {
+    decoder_thread_.reset(new JobThread("openh264_video_decoder"));
+    SB_DCHECK(decoder_thread_);
+  }
+  decoder_thread_->job_queue()->Schedule(
+      std::bind(&VideoDecoder::DecodeOneBuffer, this, input_buffer));
+}
+
+void VideoDecoder::DecodeOneBuffer(
+    const scoped_refptr<InputBuffer>& input_buffer) {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  SB_DCHECK(input_buffer);
+
+  const SbMediaVideoSampleInfo& sample_info = input_buffer->video_sample_info();
+  if (sample_info.is_key_frame) {
+    VideoConfig new_config(sample_info, input_buffer->data(),
+                           input_buffer->size());
+    if (!video_config_ || video_config_.value() != new_config) {
+      video_config_ = new_config;
+      if (decoder_) {
+        FlushFrames();
+      }
+      TeardownCodec();
+      InitializeCodec();
+    }
+  }
+  SB_DCHECK(decoder_);
+  unsigned char* decoded_frame[3];
+  SBufferInfo buffer_info;
+  buffer_info.uiInBsTimeStamp = input_buffer->timestamp();
+  DECODING_STATE status = decoder_->DecodeFrameNoDelay(
+      input_buffer->data(), input_buffer->size(), decoded_frame, &buffer_info);
+  if (status != dsErrorFree) {
+    ReportError(
+        FormatString("DecodeFrameNoDelay() failed with status %d.", status));
+    return;
+  }
+  ++frames_being_decoded_;
+
+  if (buffer_info.iBufferStatus == 1) {
+    ProcessDecodedImage(decoded_frame, buffer_info, false);
+  } else {
+    Schedule(std::bind(decoder_status_cb_, kNeedMoreInput, nullptr));
+  }
+}
+
+void VideoDecoder::FlushFrames() {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  SB_DCHECK(decoder_);
+
+  int num_of_frames_in_buffer = 0;
+  unsigned char* decoded_frame[3];
+  SBufferInfo buffer_info;
+  decoder_->GetOption(DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER,
+                      &num_of_frames_in_buffer);
+  for (int i = 0; i < num_of_frames_in_buffer; ++i) {
+    decoder_->FlushFrame(decoded_frame, &buffer_info);
+    if (buffer_info.iBufferStatus == 1) {
+      ProcessDecodedImage(decoded_frame, buffer_info, true);
+    } else {
+      SB_LOG(WARNING) << "Cannot get decoded frame by calling FlushFrame()!";
+    }
+  }
+  if (frames_being_decoded_ != 0) {
+    SB_LOG(WARNING) << "Inconsistency in the number of input/output frames";
+  }
+}
+
+void VideoDecoder::ProcessDecodedImage(unsigned char* decoded_frame[],
+                                       const SBufferInfo& buffer_info,
+                                       bool flushing) {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  if (buffer_info.UsrData.sSystemBuffer.iFormat != videoFormatI420) {
+    ReportError(FormatString("Invalid video format %d.",
+                             buffer_info.UsrData.sSystemBuffer.iFormat));
+    return;
+  }
+  for (int i = 0; i < 3; i++) {
+    SB_DCHECK(decoded_frame[i]);
+  }
+
+  --frames_being_decoded_;
+  scoped_refptr<CpuVideoFrame> frame = CpuVideoFrame::CreateYV12Frame(
+      kDefaultOpenH264BitsDepth, buffer_info.UsrData.sSystemBuffer.iWidth,
+      buffer_info.UsrData.sSystemBuffer.iHeight,
+      buffer_info.UsrData.sSystemBuffer.iStride[0],
+      buffer_info.UsrData.sSystemBuffer.iStride[1],
+      buffer_info.uiOutYuvTimeStamp, decoded_frame[0], decoded_frame[1],
+      decoded_frame[2]);
+  if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
+    ScopedLock lock(decode_target_mutex_);
+    frames_.push(frame);
+  }
+  if (flushing) {
+    Schedule(std::bind(decoder_status_cb_, kBufferFull, frame));
+  } else {
+    Schedule(std::bind(decoder_status_cb_, kNeedMoreInput, frame));
+  }
+}
+
+void VideoDecoder::WriteEndOfStream() {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(decoder_status_cb_);
+
+  // We have to flush the decoder to decode the rest frames and to ensure that
+  // Decode() is not called when the stream is ended.
+  stream_ended_ = true;
+
+  if (!decoder_thread_) {
+    // In case there is no WriteInputBuffer() call before WriteEndOfStream(),
+    // don't create the decoder thread and send the EOS frame directly.
+    decoder_status_cb_(kBufferFull, VideoFrame::CreateEOSFrame());
+    return;
+  }
+  decoder_thread_->job_queue()->Schedule(
+      std::bind(&VideoDecoder::DecodeEndOfStream, this));
+}
+
+void VideoDecoder::DecodeEndOfStream() {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
+  FlushFrames();
+  Schedule(
+      std::bind(decoder_status_cb_, kBufferFull, VideoFrame::CreateEOSFrame()));
+}
+
+void VideoDecoder::UpdateDecodeTarget_Locked(
+    const scoped_refptr<CpuVideoFrame>& frame) {
+  SbDecodeTarget decode_target = DecodeTargetCreate(
+      decode_target_graphics_context_provider_, frame, decode_target_);
+
+  // Lock only after the post to the renderer thread, to prevent deadlock.
+  decode_target_ = decode_target;
+
+  if (!SbDecodeTargetIsValid(decode_target)) {
+    SB_LOG(ERROR) << "Could not acquire a decode target from provider.";
+  }
+}
+
+// When in decode-to-texture mode, this returns the current decoded video frame.
+SbDecodeTarget VideoDecoder::GetCurrentDecodeTarget() {
+  SB_DCHECK(output_mode_ == kSbPlayerOutputModeDecodeToTexture);
+
+  // We must take a lock here since this function can be called from a
+  // separate thread.
+  ScopedLock lock(decode_target_mutex_);
+  while (frames_.size() > 1 && frames_.front()->HasOneRef()) {
+    frames_.pop();
+  }
+  if (!frames_.empty()) {
+    UpdateDecodeTarget_Locked(frames_.front());
+  }
+  if (SbDecodeTargetIsValid(decode_target_)) {
+    // Make a disposable copy, since the state is internally reused by this
+    // class (to avoid recreating GL objects).
+    return DecodeTargetCopy(decode_target_);
+  } else {
+    return kSbDecodeTargetInvalid;
+  }
+}
+
+void VideoDecoder::ReportError(const std::string& error_message) {
+  SB_DCHECK(error_cb_);
+
+  if (!BelongsToCurrentThread()) {
+    Schedule(std::bind(error_cb_, kSbPlayerErrorDecode, error_message));
+    return;
+  }
+  error_cb_(kSbPlayerErrorDecode, error_message);
+}
+
+}  // namespace openh264
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/openh264/openh264_video_decoder.h b/starboard/shared/openh264/openh264_video_decoder.h
new file mode 100644
index 0000000..29dccb8
--- /dev/null
+++ b/starboard/shared/openh264/openh264_video_decoder.h
@@ -0,0 +1,120 @@
+// Copyright 2022 The Cobalt Authors. 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 STARBOARD_SHARED_OPENH264_OPENH264_VIDEO_DECODER_H_
+#define STARBOARD_SHARED_OPENH264_OPENH264_VIDEO_DECODER_H_
+
+#include <queue>
+#include <string>
+
+#include "starboard/common/optional.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/decode_target.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/media/codec_util.h"
+#include "starboard/shared/starboard/player/filter/cpu_video_frame.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/job_thread.h"
+#include "third_party/openh264/include/codec_api.h"
+#include "third_party/openh264/include/codec_app_def.h"
+#include "third_party/openh264/include/codec_def.h"
+
+namespace starboard {
+namespace shared {
+namespace openh264 {
+
+class VideoDecoder : public starboard::player::filter::VideoDecoder,
+                     private starboard::player::JobQueue::JobOwner {
+ public:
+  VideoDecoder(SbMediaVideoCodec video_codec,
+               SbPlayerOutputMode output_mode,
+               SbDecodeTargetGraphicsContextProvider*
+                   decode_target_graphics_context_provider);
+  ~VideoDecoder() override;
+
+  void Initialize(const DecoderStatusCB& decoder_status_cb,
+                  const ErrorCB& error_cb) override;
+
+  // TODO: Verify if these values are correct.
+  size_t GetPrerollFrameCount() const override { return 8; }
+  SbTime GetPrerollTimeout() const override { return kSbTimeMax; }
+  size_t GetMaxNumberOfCachedFrames() const override { return 12; }
+
+  void WriteInputBuffer(
+      const scoped_refptr<InputBuffer>& input_buffer) override;
+  void WriteEndOfStream() override;
+  void Reset() override;
+
+  SbDecodeTarget GetCurrentDecodeTarget() override;
+
+ private:
+  static const int kDefaultOpenH264BitsDepth = 8;
+  typedef ::starboard::shared::starboard::player::filter::CpuVideoFrame
+      CpuVideoFrame;
+
+  void UpdateDecodeTarget_Locked(const scoped_refptr<CpuVideoFrame>& frame);
+
+  // The following functions are only called on the decoder thread.
+  void InitializeCodec();
+  void TeardownCodec();
+  void DecodeOneBuffer(const scoped_refptr<InputBuffer>& input_buffer);
+  void DecodeEndOfStream();
+  void ProcessDecodedImage(unsigned char* decoded_frame[],
+                           const SBufferInfo& buffer_info,
+                           bool flushing);
+  void FlushFrames();
+  void ReportError(const std::string& error_message);
+
+  SbDecodeTargetGraphicsContextProvider*
+      decode_target_graphics_context_provider_;
+  SbPlayerOutputMode output_mode_;
+
+  // The following callbacks will be initialized in Initialize() and won't be
+  // changed during the life time of this class.
+  DecoderStatusCB decoder_status_cb_;
+  ErrorCB error_cb_;
+
+  std::queue<scoped_refptr<CpuVideoFrame>> frames_;
+
+  bool stream_ended_ = false;
+
+  // If decode-to-texture is enabled, then we store the decode target texture
+  // inside of this |decode_target_| member.
+  SbDecodeTarget decode_target_ = kSbDecodeTargetInvalid;
+
+  // GetCurrentDecodeTarget() needs to be called from an arbitrary thread
+  // to obtain the current decode target (which ultimately ends up being a
+  // copy of |decode_target_|), we need to safe-guard access to |decode_target_|
+  // and we do so through this mutex.
+  Mutex decode_target_mutex_;
+
+  // Working thread to avoid lengthy decoding work block the player thread.
+  scoped_ptr<starboard::player::JobThread> decoder_thread_;
+
+  // Openh264 decode handler.
+  ISVCDecoder* decoder_ = nullptr;
+
+  // The number of frames which have been sent to decoder but not received yet.
+  int frames_being_decoded_ = 0;
+
+  // Store current avc level profile and resolution.
+  optional<shared::starboard::media::VideoConfig> video_config_;
+};
+
+}  // namespace openh264
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_OPENH264_OPENH264_VIDEO_DECODER_H_
diff --git a/starboard/shared/opus/opus_audio_decoder.cc b/starboard/shared/opus/opus_audio_decoder.cc
index 0ac7a17..0a450f1 100644
--- a/starboard/shared/opus/opus_audio_decoder.cc
+++ b/starboard/shared/opus/opus_audio_decoder.cc
@@ -16,8 +16,6 @@
 
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
-#include "starboard/memory.h"
-#include "starboard/shared/starboard/media/media_util.h"
 
 namespace starboard {
 namespace shared {
diff --git a/starboard/shared/starboard/media/parsed_mime_info.cc b/starboard/shared/starboard/media/parsed_mime_info.cc
index 8894289..e61cb84 100644
--- a/starboard/shared/starboard/media/parsed_mime_info.cc
+++ b/starboard/shared/starboard/media/parsed_mime_info.cc
@@ -14,6 +14,7 @@
 
 #include "starboard/shared/starboard/media/parsed_mime_info.h"
 
+#include <cmath>
 #include <string>
 
 #include "starboard/common/log.h"
@@ -150,7 +151,7 @@
 
   if (!mime_type_.ValidateIntParameter("width") ||
       !mime_type_.ValidateIntParameter("height") ||
-      !mime_type_.ValidateIntParameter("framerate") ||
+      !mime_type_.ValidateFloatParameter("framerate") ||
       !mime_type_.ValidateIntParameter("bitrate") ||
       !mime_type_.ValidateBoolParameter("decode-to-texture")) {
     return false;
@@ -158,7 +159,10 @@
 
   video_info_.frame_width = mime_type_.GetParamIntValue("width", 0);
   video_info_.frame_height = mime_type_.GetParamIntValue("height", 0);
-  video_info_.fps = mime_type_.GetParamIntValue("framerate", 0);
+  // TODO: Support float framerate. Our starboard implementation only supports
+  // integer framerate, but framerate could be float and we should support it.
+  float framerate = mime_type_.GetParamFloatValue("framerate", 0.0f);
+  video_info_.fps = std::round(framerate);
   video_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0);
   video_info_.decode_to_texture_required =
       mime_type_.GetParamBoolValue("decode-to-texture", false);
diff --git a/starboard/shared/win32/audio_transform.cc b/starboard/shared/win32/audio_transform.cc
index 81aebd0..6e1edbb 100644
--- a/starboard/shared/win32/audio_transform.cc
+++ b/starboard/shared/win32/audio_transform.cc
@@ -19,10 +19,10 @@
 #include <vector>
 
 #include "starboard/memory.h"
-#include "starboard/shared/uwp/wasapi_include.h"
 #include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/shared/win32/media_foundation_utils.h"
+#include "starboard/shared/win32/wasapi_include.h"
 
 namespace starboard {
 namespace shared {
diff --git a/starboard/shared/win32/hardware_decode_target_internal.cc b/starboard/shared/win32/hardware_decode_target_internal.cc
index 80859fc..1685aa0 100644
--- a/starboard/shared/win32/hardware_decode_target_internal.cc
+++ b/starboard/shared/win32/hardware_decode_target_internal.cc
@@ -37,7 +37,8 @@
 ComPtr<ID3D11Texture2D> AllocateTexture(const ComPtr<ID3D11Device>& d3d_device,
                                         SbDecodeTargetFormat format,
                                         int width,
-                                        int height) {
+                                        int height,
+                                        HRESULT* h_result) {
   ComPtr<ID3D11Texture2D> texture;
   D3D11_TEXTURE2D_DESC texture_desc = {};
   texture_desc.Width = width;
@@ -60,8 +61,8 @@
   texture_desc.Usage = D3D11_USAGE_DEFAULT;
   texture_desc.BindFlags =
       D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
-  CheckResult(d3d_device->CreateTexture2D(&texture_desc, nullptr,
-                                          texture.GetAddressOf()));
+  *h_result = d3d_device->CreateTexture2D(&texture_desc, nullptr,
+                                          texture.GetAddressOf());
   return texture;
 }
 
@@ -137,17 +138,19 @@
     info.format = kSbDecodeTargetFormat2PlaneYUVNV12;
   }
 
-  d3d_texture =
-      AllocateTexture(d3d_device, info.format, info.width, info.height);
-  if (video_sample) {
-    UpdateTexture(d3d_texture, video_device, video_context, video_enumerator,
-                  video_processor, video_sample, video_area);
-  }
+  d3d_texture = AllocateTexture(d3d_device, info.format, info.width,
+                                info.height, &create_texture_2d_h_result);
+  if (d3d_texture) {
+    if (video_sample) {
+      UpdateTexture(d3d_texture, video_device, video_context, video_enumerator,
+                    video_processor, video_sample, video_area);
+    }
 
-  if (texture_RGBA_) {
-    InitTextureRGBA();
-  } else {
-    InitTextureYUV();
+    if (texture_RGBA_) {
+      InitTextureRGBA();
+    } else {
+      InitTextureYUV();
+    }
   }
 }
 
diff --git a/starboard/shared/win32/hardware_decode_target_internal.h b/starboard/shared/win32/hardware_decode_target_internal.h
index 8fccdbe..a73b298 100644
--- a/starboard/shared/win32/hardware_decode_target_internal.h
+++ b/starboard/shared/win32/hardware_decode_target_internal.h
@@ -27,6 +27,10 @@
   template <typename T>
   using ComPtr = ::Microsoft::WRL::ComPtr<T>;
 
+  // Return value of CreateTexture2D. Stored here to report exact error codes to
+  // the video decoder when the call fails (b/257541360).
+  HRESULT create_texture_2d_h_result;
+
   ComPtr<ID3D11Texture2D> d3d_texture;
   bool texture_RGBA_;
 
diff --git a/starboard/shared/win32/video_decoder.cc b/starboard/shared/win32/video_decoder.cc
index 44f30c4..013dffb 100644
--- a/starboard/shared/win32/video_decoder.cc
+++ b/starboard/shared/win32/video_decoder.cc
@@ -24,8 +24,10 @@
 
 // Include this after all other headers to avoid introducing redundant
 // definitions from other header files.
-#include <codecapi.h>  // For CODECAPI_*
-#include <initguid.h>  // For DXVA_ModeVP9_VLD_Profile0
+// For CODECAPI_*
+#include <codecapi.h>  // NOLINT(build/include_order)
+// For DXVA_ModeVP9_VLD_Profile0
+#include <initguid.h>  // NOLINT(build/include_order)
 
 namespace starboard {
 namespace shared {
@@ -391,6 +393,15 @@
       decode_target = new HardwareDecodeTargetPrivate(
           d3d_device_, video_device_, video_context_, video_enumerator_,
           video_processor_, video_sample, video_area, is_hdr_supported_);
+      auto hardware_decode_target =
+          reinterpret_cast<HardwareDecodeTargetPrivate*>(decode_target);
+      if (!hardware_decode_target->d3d_texture) {
+        error_cb_(kSbPlayerErrorDecode,
+                  "Failed to allocate texture with error code: " +
+                      ::starboard::shared::win32::HResultToString(
+                          hardware_decode_target->create_texture_2d_h_result));
+        decode_target = kSbDecodeTargetInvalid;
+      }
     }
 
     // Release the video_sample before releasing the reset lock.
diff --git a/starboard/shared/win32/wasapi_include.h b/starboard/shared/win32/wasapi_include.h
new file mode 100644
index 0000000..9412600
--- /dev/null
+++ b/starboard/shared/win32/wasapi_include.h
@@ -0,0 +1,67 @@
+// Copyright 2022 The Cobalt Authors. 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 STARBOARD_SHARED_WIN32_WASAPI_INCLUDE_H_

+#define STARBOARD_SHARED_WIN32_WASAPI_INCLUDE_H_

+

+#include <guiddef.h>

+#include <initguid.h>

+

+#define KSAUDIO_SPEAKER_DIRECTOUT 0

+#define KSAUDIO_SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)

+#define KSAUDIO_SPEAKER_5POINT1                                      \

+  (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \

+   SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)

+

+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS,

+            0x0000000a,

+            0x0cea,

+            0x0010,

+            0x80,

+            0x00,

+            0x00,

+            0xaa,

+            0x00,

+            0x38,

+            0x9b,

+            0x71);

+

+// IEC 60958 specific values.

+constexpr int kAc3SamplesPerSecond = 48000;

+constexpr int kEac3SamplesPerSecond = 192000;

+constexpr int kIec60958Channels = 2;

+constexpr int kIec60958BitsPerSample = 16;

+constexpr int kIec60958BlockAlign = 4;

+// Values taken from the Dolby Audio Decoder MFT documentation

+// https://docs.microsoft.com/en-us/windows/win32/medfound/dolby-audio-decoder

+constexpr size_t kAc3BufferSize = 6144;

+constexpr size_t kEac3BufferSize = 24576;

+

+namespace starboard {

+namespace shared {

+namespace win32 {

+

+typedef struct {

+  WAVEFORMATEXTENSIBLE FormatExt;

+

+  DWORD dwEncodedSamplesPerSec;

+  DWORD dwEncodedChannelCount;

+  DWORD dwAverageBytesPerSec;

+} WAVEFORMATEXTENSIBLE_IEC61937, *PWAVEFORMATEXTENSIBLE_IEC61937;

+

+}  // namespace win32

+}  // namespace shared

+}  // namespace starboard

+

+#endif  // STARBOARD_SHARED_WIN32_WASAPI_INCLUDE_H_

diff --git a/starboard/shared/win32/win32_audio_decoder.cc b/starboard/shared/win32/win32_audio_decoder.cc
index fdfb0c0..c5dcb35 100644
--- a/starboard/shared/win32/win32_audio_decoder.cc
+++ b/starboard/shared/win32/win32_audio_decoder.cc
@@ -19,13 +19,13 @@
 
 #include "starboard/atomic.h"
 #include "starboard/shared/starboard/thread_checker.h"
-#include "starboard/shared/uwp/wasapi_include.h"
 #include "starboard/shared/win32/atomic_queue.h"
 #include "starboard/shared/win32/audio_decoder.h"
 #include "starboard/shared/win32/audio_transform.h"
 #include "starboard/shared/win32/decrypting_decoder.h"
 #include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/media_foundation_utils.h"
+#include "starboard/shared/win32/wasapi_include.h"
 
 namespace starboard {
 namespace shared {
diff --git a/starboard/system.h b/starboard/system.h
index 0263241..233cb4d 100644
--- a/starboard/system.h
+++ b/starboard/system.h
@@ -66,9 +66,10 @@
   // Full path to the executable file.
   kSbSystemPathExecutableFile,
 
-  // Path to a directory for permanent file storage. Both read and write
-  // access is required. This is where an app may store its persistent settings.
-  // The location should be user agnostic if possible.
+  // Path to the directory dedicated for Evergreen Full permanent file storage.
+  // Both read and write access is required.
+  // The directory should be used only for storing the updates.
+  // See starboard/doc/evergreen/cobalt_evergreen_overview.md
   kSbSystemPathStorageDirectory,
 } SbSystemPathId;
 
diff --git a/starboard/tools/abstract_launcher.py b/starboard/tools/abstract_launcher.py
index 3d7bd76..01bb410 100644
--- a/starboard/tools/abstract_launcher.py
+++ b/starboard/tools/abstract_launcher.py
@@ -52,6 +52,7 @@
                     output_file=None,
                     out_directory=None,
                     env_variables=None,
+                    test_result_xml_path=None,
                     **kwargs):
   """Creates the proper launcher based upon command line args.
 
@@ -90,6 +91,7 @@
       output_file=output_file,
       out_directory=out_directory,
       env_variables=env_variables,
+      test_result_xml_path=test_result_xml_path,
       **kwargs)
 
 
@@ -137,6 +139,8 @@
     # this variable during initialization.
     self.startup_timeout_seconds = 2 * 60
 
+    self.test_result_xml_path = kwargs.get("test_result_xml_path", None)
+
   @abc.abstractmethod
   def Run(self):
     """Runs the launcher's executable.
diff --git a/starboard/tools/testing/sharding_configuration.json b/starboard/tools/testing/sharding_configuration.json
index 0641b05..896fcd9 100644
--- a/starboard/tools/testing/sharding_configuration.json
+++ b/starboard/tools/testing/sharding_configuration.json
@@ -57,6 +57,57 @@
       "player_filter_tests": [2, 2]
     }
   ],
+  "xb1|ps4|ps5|nxswitch":[
+    {
+      "app_key_files_test":"*",
+      "app_key_test":"*",
+      "audio_test":"*",
+      "base_test":"*",
+      "bindings_test":"*",
+      "browser_test":"*",
+      "crypto_unittests":"*",
+      "csp_test":"*",
+      "css_parser_test":"*",
+      "cssom_test":"*",
+      "cwrappers_test":"*",
+      "dom_parser_test":"*",
+      "dom_test":"*",
+      "drain_file_test":"*",
+      "elf_loader_test":"*",
+      "extension_test":"*",
+      "eztime_test":"*",
+      "graphics_system_test":"*",
+      "installation_manager_test":"*",
+      "layout_test":"*",
+      "net_unittests":"*",
+      "nplb":"*",
+      "player_filter_tests":"*"
+    },
+    {
+      "base_unittests":"*",
+      "layout_tests":"*",
+      "loader_test":"*",
+      "math_test":"*",
+      "media_capture_test":"*",
+      "media_session_test":"*",
+      "media_stream_test":"*",
+      "memory_store_test":"*",
+      "nb_test":"*",
+      "network_test":"*",
+      "poem_unittests":"*",
+      "render_tree_test":"*",
+      "renderer_test":"*",
+      "slot_management_test":"*",
+      "starboard_platform_tests":"*",
+      "storage_test":"*",
+      "text_encoding_test":"*",
+      "web_animations_test":"*",
+      "webdriver_test":"*",
+      "websocket_test":"*",
+      "xhr_test":"*",
+      "zip_unittests":"*"
+    }
+  ],
   "raspi-2":[
     {
       "base_unittests": [1, 6],
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py
index 2fc8f24..406ce24 100755
--- a/starboard/tools/testing/test_runner.py
+++ b/starboard/tools/testing/test_runner.py
@@ -432,7 +432,12 @@
       test_params.append("--gtest_total_shards={}".format(shard_count))
       test_params.append("--gtest_shard_index={}".format(shard_index))
 
+    # Path to where the test results XML will be created (if applicable).
+    # For on-device testing, this is w.r.t on device storage.
+    test_result_xml_path = None
+
     def MakeLauncher():
+      logging.info("MakeLauncher(): %s", test_result_xml_path)
       return abstract_launcher.LauncherFactory(
           self.platform,
           target_name,
@@ -443,21 +448,26 @@
           out_directory=self.out_directory,
           coverage_directory=self.coverage_directory,
           env_variables=env,
+          test_result_xml_path=test_result_xml_path,
           loader_platform=self.loader_platform,
           loader_config=self.loader_config,
           loader_out_directory=self.loader_out_directory,
           launcher_args=self.launcher_args)
 
+    logging.info(
+        "XML test result logging: %s",
+        ("enabled" if
+         (self.log_xml_results or self.xml_output_dir) else "disabled"))
     if self.log_xml_results:
       out_path = MakeLauncher().GetDeviceOutputPath()
       xml_filename = "{}_testoutput.xml".format(target_name)
       if out_path:
-        xml_path = os.path.join(out_path, xml_filename)
+        test_result_xml_path = os.path.join(out_path, xml_filename)
       else:
-        xml_path = xml_filename
-      test_params.append("--gtest_output=xml:{}".format(xml_path))
+        test_result_xml_path = xml_filename
+      test_params.append("--gtest_output=xml:{}".format(test_result_xml_path))
       logging.info(("Xml results for this test will "
-                    "be logged to '%s'."), xml_path)
+                    "be logged to '%s'."), test_result_xml_path)
     elif self.xml_output_dir:
       # Have gtest create and save a test result xml
       xml_output_subdir = os.path.join(self.xml_output_dir, target_name)
@@ -465,10 +475,11 @@
         os.makedirs(xml_output_subdir)
       except OSError:
         pass
-      xml_output_path = os.path.join(xml_output_subdir, "sponge_log.xml")
+      test_result_xml_path = os.path.join(xml_output_subdir, "sponge_log.xml")
       logging.info("Xml output for this test will be saved to: %s",
-                   xml_output_path)
-      test_params.append("--gtest_output=xml:%s" % (xml_output_path))
+                   test_result_xml_path)
+      test_params.append("--gtest_output=xml:%s" % (test_result_xml_path))
+    logging.info("XML test result path: %s", test_result_xml_path)
 
     # Turn off color codes from output to make it easy to parse
     test_params.append("--gtest_color=no")
diff --git a/starboard/tools/testing/test_sharding.py b/starboard/tools/testing/test_sharding.py
index c96bfd3..a8bc90e 100644
--- a/starboard/tools/testing/test_sharding.py
+++ b/starboard/tools/testing/test_sharding.py
@@ -38,10 +38,12 @@
     try:
       with open(SHARDING_CONFIG_FILE, 'r') as file:
         sharding_json = json.load(file)
-      if not platform in sharding_json:
-        self.platform_sharding_config = sharding_json['default']
-      else:
-        self.platform_sharding_config = sharding_json[platform]
+      # Load this config by default.
+      self.platform_sharding_config = sharding_json['default']
+      # Check for specific platform if specified:
+      for platform_key in sharding_json:
+        if platform in platform_key:
+          self.platform_sharding_config = sharding_json[platform_key]
     except FileNotFoundError:
       raise RuntimeError('No sharding configuration file found.')
 
diff --git a/starboard/win/shared/BUILD.gn b/starboard/win/shared/BUILD.gn
index 162b1c5..c968e66 100644
--- a/starboard/win/shared/BUILD.gn
+++ b/starboard/win/shared/BUILD.gn
@@ -180,7 +180,6 @@
     "//starboard/shared/stub/window_set_on_screen_keyboard_keep_focus.cc",
     "//starboard/shared/stub/window_show_on_screen_keyboard.cc",
     "//starboard/shared/stub/window_update_on_screen_keyboard_suggestions.cc",
-    "//starboard/shared/uwp/wasapi_include.h",
     "//starboard/shared/win32/adapter_utils.cc",
     "//starboard/shared/win32/adapter_utils.h",
     "//starboard/shared/win32/atomic_public.h",
@@ -346,6 +345,7 @@
     "//starboard/shared/win32/time_zone_get_name.cc",
     "//starboard/shared/win32/video_decoder.cc",
     "//starboard/shared/win32/video_decoder.h",
+    "//starboard/shared/win32/wasapi_include.h",
     "//starboard/shared/win32/wchar_utils.h",
     "//starboard/shared/win32/win32_audio_decoder.cc",
     "//starboard/shared/win32/win32_audio_decoder.h",
@@ -370,7 +370,6 @@
     "//starboard/egl_and_gles",
     "//starboard/shared/starboard/media:media_util",
     "//starboard/shared/starboard/player/filter:filter_based_player_sources",
-    "//starboard/xb1/i18n",
     "//third_party/opus",
   ]
 
diff --git a/starboard/win/shared/platform_configuration/configuration.gni b/starboard/win/shared/platform_configuration/configuration.gni
index 3176613..1798610 100644
--- a/starboard/win/shared/platform_configuration/configuration.gni
+++ b/starboard/win/shared/platform_configuration/configuration.gni
@@ -28,6 +28,4 @@
 
 cobalt_platform_dependencies = [ "//starboard/egl_and_gles" ]
 
-platform_i18n_config_path = "//starboard/xb1/i18n:i18n"
-
 sb_enable_cpp17_audit = false
diff --git a/starboard/win/win32/BUILD.gn b/starboard/win/win32/BUILD.gn
index 0345ec9..bd41342 100644
--- a/starboard/win/win32/BUILD.gn
+++ b/starboard/win/win32/BUILD.gn
@@ -82,4 +82,6 @@
   ]
 
   public_deps = [ "//starboard/win/shared:starboard_platform" ]
+
+  deps = [ "//starboard/win/win32/i18n" ]
 }
diff --git a/starboard/win/win32/cobalt/configuration.py b/starboard/win/win32/cobalt/configuration.py
index e342987..3a44162 100644
--- a/starboard/win/win32/cobalt/configuration.py
+++ b/starboard/win/win32/cobalt/configuration.py
@@ -44,6 +44,10 @@
           '*TaskScheduler*',
           'TaskTraits*',
       ],
+      # Tracked by b/245347178
+      'persistent_settings_test': [
+          'PersistentSettingTest.*',
+      ],
       'renderer_test': [
           # Flaky test is still being counted as a fail.
           ('RendererPipelineTest.FLAKY_'
diff --git a/starboard/win/win32/i18n/BUILD.gn b/starboard/win/win32/i18n/BUILD.gn
new file mode 100644
index 0000000..5821363
--- /dev/null
+++ b/starboard/win/win32/i18n/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. 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.
+
+import("//starboard/build/convert_i18n_data.gni")
+
+convert_i18n_data("i18n") {
+  install_content = true
+  xlb_files = [ "en.xlb" ]
+}
diff --git a/starboard/win/win32/i18n/en.xlb b/starboard/win/win32/i18n/en.xlb
new file mode 100644
index 0000000..ce8f4e3
--- /dev/null
+++ b/starboard/win/win32/i18n/en.xlb
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<localizationbundle locale="en">
+<messages>
+<msg name="EXIT_BUTTON" desc="This is the text on a button which will exit the application.">Exit</msg>
+<msg name="UNABLE_TO_CONTACT_YOUTUBE_1" desc="This error message is shown to the user when the application is unable to make a request to a service that is required to use the application.">Sorry, could not connect to YouTube.</msg>
+<msg name="RETRY_BUTTON" desc="This is the text on a button which will retry a previously attempted action.">Retry</msg>
+</messages>
+</localizationbundle>
diff --git a/starboard/win/win32/platform_configuration/configuration.gni b/starboard/win/win32/platform_configuration/configuration.gni
index 02d30d7..4a3b479 100644
--- a/starboard/win/win32/platform_configuration/configuration.gni
+++ b/starboard/win/win32/platform_configuration/configuration.gni
@@ -13,3 +13,5 @@
 # limitations under the License.
 
 import("//starboard/win/shared/platform_configuration/configuration.gni")
+
+platform_i18n_config_path = "//starboard/win/win32/i18n:i18n"
diff --git a/starboard/win/win32/test_filters.py b/starboard/win/win32/test_filters.py
index 59732f3..6da9bf0 100644
--- a/starboard/win/win32/test_filters.py
+++ b/starboard/win/win32/test_filters.py
@@ -29,6 +29,18 @@
         # performs an optimization that defeats the SB_C_NOINLINE 'noinline'
         # attribute.
         'SbSystemGetStackTest.SunnyDayStackDirection',
+
+        # Failures tracked by b/256160416.
+        'SbSystemGetPathTest.ReturnsRequiredPaths',
+        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.SeekAndDestroy/audio__null__video_beneath_the_canopy_137_avc_dmp_output_DecodeToTexture',
+        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/audio__null__video_beneath_the_canopy_137_avc_dmp_output_DecodeToTexture',
+        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.SingleInput/audio__null__video_beneath_the_canopy_137_avc_dmp_output_DecodeToTexture',
+        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.MultipleInputs/audio__null__video_beneath_the_canopy_137_avc_dmp_output_DecodeToTexture',
+        'SbSocketAddressTypes/SbSocketBindTest.RainyDayBadInterface/type_ipv6_filter_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/type_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/type_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/type_ipv6',
+        'SbSocketAddressTypes/SbSocketResolveTest.SunnyDayFiltered/filter_ipv6_type_ipv6',
     ],
     'player_filter_tests': [
         # These tests fail on our VMs for win-win32 builds due to missing
diff --git a/testing/gtest/src/gtest.cc b/testing/gtest/src/gtest.cc
index 09bca96..71fbd6f 100644
--- a/testing/gtest/src/gtest.cc
+++ b/testing/gtest/src/gtest.cc
@@ -3237,38 +3237,35 @@
   }
 }
 
+#if !GTEST_OS_STARBOARD
 void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
                                                      int /*iteration*/) {
   ColoredPrintf(COLOR_GREEN,  "[==========] ");
-  posix::PrintF(
-      "%s from %s ran.",
-      FormatTestCount(unit_test.test_to_run_count()).c_str(),
-      FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str());
+  printf("%s from %s ran.",
+         FormatTestCount(unit_test.test_to_run_count()).c_str(),
+         FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str());
   if (GTEST_FLAG(print_time)) {
-    posix::PrintF(
-        " (%s ms total)",
-        internal::StreamableToString(unit_test.elapsed_time()).c_str());
+    printf(" (%s ms total)",
+           internal::StreamableToString(unit_test.elapsed_time()).c_str());
   }
-  posix::PrintF("\n");
+  printf("\n");
   ColoredPrintf(COLOR_GREEN,  "[  PASSED  ] ");
-  posix::PrintF("%s.\n",
-                FormatTestCount(unit_test.successful_test_count()).c_str());
+  printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str());
 
   int num_failures = unit_test.failed_test_count();
   if (!unit_test.Passed()) {
     const int failed_test_count = unit_test.failed_test_count();
     ColoredPrintf(COLOR_RED,  "[  FAILED  ] ");
-    posix::PrintF("%s, listed below:\n",
-                  FormatTestCount(failed_test_count).c_str());
+    printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str());
     PrintFailedTests(unit_test);
-    posix::PrintF("\n%2d FAILED %s\n", num_failures,
-                  num_failures == 1 ? "TEST" : "TESTS");
+    printf("\n%2d FAILED %s\n", num_failures,
+                        num_failures == 1 ? "TEST" : "TESTS");
   }
 
   int num_disabled = unit_test.reportable_disabled_test_count();
   if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) {
     if (!num_failures) {
-      posix::PrintF("\n");  // Add a spacer if no FAILURE banner is displayed.
+      printf("\n");  // Add a spacer if no FAILURE banner is displayed.
     }
     ColoredPrintf(COLOR_YELLOW,
                   "  YOU HAVE %d DISABLED %s\n\n",
@@ -3276,8 +3273,109 @@
                   num_disabled == 1 ? "TEST" : "TESTS");
   }
   // Ensure that Google Test output is printed before, e.g., heapchecker output.
+  fflush(stdout);
+}
+#else  // !GTEST_OS_STARBOARD
+void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+                                                     int /*iteration*/) {
+  // Due to the test processes relying on regex-parsing of GTEST test result
+  // summary output, we modify this step to atomically output the logs to stdout
+  // to avoid other external SbLogRaw calls from being inserted between calls to
+  // posix:PrintF().
+  // This batching guarantees that the output is well-formatted as the test
+  // runner scripts are setup to expect.
+  // See b/251827741
+  std::stringstream out_stream;
+  out_stream << "[==========] ";
+
+  out_stream << FormatTestCount(unit_test.test_to_run_count())
+             << " from "
+             << FormatTestCaseCount(unit_test.test_case_to_run_count())
+             << " ran.";
+
+  if (GTEST_FLAG(print_time)) {
+    out_stream << " ("
+               << internal::StreamableToString(unit_test.elapsed_time())
+               << " ms total)";
+  }
+  out_stream << std::endl;
+
+  out_stream << "[  PASSED  ] "
+             << FormatTestCount(unit_test.successful_test_count())
+             << "."
+             << std::endl;
+
+  int num_failures = unit_test.failed_test_count();
+  if (!unit_test.Passed()) {
+    const int failed_test_count = unit_test.failed_test_count();
+    out_stream << "[  FAILED  ] "
+               << FormatTestCount(failed_test_count)
+               << ", listed below:"
+               << std::endl;
+
+    // Print each failed test case within the unit test.
+    for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
+      const TestCase& test_case = *unit_test.GetTestCase(i);
+      if (!test_case.should_run() || (test_case.failed_test_count() == 0)) {
+        continue;
+      }
+      for (int j = 0; j < test_case.total_test_count(); ++j) {
+        const TestInfo& test_info = *test_case.GetTestInfo(j);
+        if (!test_info.should_run() || test_info.result()->Passed()) {
+          continue;
+        }
+        out_stream << "[  FAILED  ] "
+                   << test_case.name()
+                   << "."
+                   << test_info.name();
+
+        // Output the full test comment if present.
+        const char* const type_param = test_info.type_param();
+        const char* const value_param = test_info.value_param();
+        if (type_param != NULL || value_param != NULL) {
+          out_stream << ", where ";
+          if (type_param != NULL) {
+            out_stream << kTypeParamLabel
+                       << " = "
+                       << type_param;
+            if (value_param != NULL) {
+              out_stream << " and ";
+            }
+          }
+          if (value_param != NULL) {
+            out_stream << kValueParamLabel
+                       << " = "
+                       << value_param;
+          }
+        }
+        out_stream << std::endl;
+      }
+    }
+
+    out_stream << num_failures
+               << " FAILED "
+               << (num_failures == 1 ? "TEST" : "TESTS")
+               << std::endl;
+  }
+
+  int num_disabled = unit_test.reportable_disabled_test_count();
+  if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) {
+    if (!num_failures) {
+      // Add a spacer if no FAILURE banner is displayed.
+      out_stream << std::endl;
+    }
+    out_stream << "  YOU HAVE "
+               << num_disabled
+               << " DISABLED "
+               << (num_disabled == 1 ? "TEST" : "TESTS")
+               << std::endl
+               << std::endl;
+  }
+  // Ensure that Google Test output is printed before, e.g., heapchecker output.
+  posix::PrintF("%s", out_stream.str().c_str());
   posix::Flush();
 }
+#endif  // !GTEST_OS_STARBOARD
 
 // End PrettyUnitTestResultPrinter
 
diff --git a/third_party/android_game_activity/README.md b/third_party/android_game_activity/README.md
new file mode 100644
index 0000000..75ec583
--- /dev/null
+++ b/third_party/android_game_activity/README.md
@@ -0,0 +1,38 @@
+# Android GameActivity code description
+
+The source code in this directory is copied from the AndroidX GameActivity
+release package, matching the version specified in
+ `//starboard/android/apk/app/build.gradle`.
+
+To learn more about GameActivity, refer to [the official GameActivity
+documenation](https://d.android.com/games/agdk/game-activity).
+
+## Updating instructions
+
+To update GameActivity to the latest version, do the following:
+
+1. In
+   `//starboard/android/apk/app/build.gradle`, update the dependency version
+   for `androidx.games::games-activity`. The current version is `1.2.1`, and
+   you can find the latest version from [the AndroidX games release website]
+   (https://developer.android.com/jetpack/androidx/releases/games).
+1. Build Cobalt. This triggers gradle to downloaded the release package to
+   its local cache (normally under the `$HOME/.gradle` folder).
+1. Find the downloaded game-activity package, usually under
+   `$HOME/.gradle/caches/...`. The directory structure should match the
+   structure under `//third_party/android_game_activity/include/...`. You can
+   use `find` with a specific file to locate the exact path for the package,
+   as shown in the following example:
+   ```
+     find   ~/.gradle/caches   | grep   GameActivity.cpp
+   ```
+1. Copy all C++ files for the matching games-activity version to this
+   directory (the directory that is hosting this README.md file). For example,
+   with version 1.2.1, the path might be `$HOME/.gradle/caches/transforms-3/355ab20937e7dabe38cca2293f9f651b/transformed/jetified-games-activity-1.2.1/prefab/modules/game-activity/include/game-activity/GameActivity.cpp`, just pull the content from
+   `.../jetified-games-activity-1.2.1/prefab/modules/game-activity`:
+   ```
+   pushd ${cobalt_src_dir}/third_party/android_game_activity
+   rm -fr include module.json
+   cp -fr .../jetified-games-activity-1.2.1/prefab/modules/game-activity  third_party/android_game_activity/
+   popd
+   ```
diff --git a/third_party/android_game_activity/include/game-activity/GameActivity.cpp b/third_party/android_game_activity/include/game-activity/GameActivity.cpp
new file mode 100644
index 0000000..14ae78c
--- /dev/null
+++ b/third_party/android_game_activity/include/game-activity/GameActivity.cpp
@@ -0,0 +1,1275 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+#define LOG_TAG "GameActivity"
+
+#include "GameActivity.h"
+
+#include <android/api-level.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <android/log.h>
+#include <android/looper.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/system_properties.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <mutex>
+#include <string>
+
+// TODO(b/187147166): these functions were extracted from the Game SDK
+// (gamesdk/src/common/system_utils.h). system_utils.h/cpp should be used
+// instead.
+namespace {
+
+#if __ANDROID_API__ >= 26
+std::string getSystemPropViaCallback(const char *key,
+                                     const char *default_value = "") {
+    const prop_info *prop = __system_property_find(key);
+    if (prop == nullptr) {
+        return default_value;
+    }
+    std::string return_value;
+    auto thunk = [](void *cookie, const char * /*name*/, const char *value,
+                    uint32_t /*serial*/) {
+        if (value != nullptr) {
+            std::string *r = static_cast<std::string *>(cookie);
+            *r = value;
+        }
+    };
+    __system_property_read_callback(prop, thunk, &return_value);
+    return return_value;
+}
+#else
+std::string getSystemPropViaGet(const char *key,
+                                const char *default_value = "") {
+    char buffer[PROP_VALUE_MAX + 1] = "";  // +1 for terminator
+    int bufferLen = __system_property_get(key, buffer);
+    if (bufferLen > 0)
+        return buffer;
+    else
+        return "";
+}
+#endif
+
+std::string GetSystemProp(const char *key, const char *default_value = "") {
+#if __ANDROID_API__ >= 26
+    return getSystemPropViaCallback(key, default_value);
+#else
+    return getSystemPropViaGet(key, default_value);
+#endif
+}
+
+int GetSystemPropAsInt(const char *key, int default_value = 0) {
+    std::string prop = GetSystemProp(key);
+    return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
+}
+
+struct OwnedGameTextInputState {
+    OwnedGameTextInputState &operator=(const GameTextInputState &rhs) {
+        inner = rhs;
+        owned_string = std::string(rhs.text_UTF8, rhs.text_length);
+        inner.text_UTF8 = owned_string.data();
+        return *this;
+    }
+    GameTextInputState inner;
+    std::string owned_string;
+};
+
+}  // anonymous namespace
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
+#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
+#ifdef NDEBUG
+#define ALOGV(...)
+#else
+#define ALOGV(...) \
+    __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
+#endif
+
+/* Returns 2nd arg.  Used to substitute default value if caller's vararg list
+ * is empty.
+ */
+#define __android_second(first, second, ...) second
+
+/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
+ * returns nothing.
+ */
+#define __android_rest(first, ...) , ##__VA_ARGS__
+
+#define android_printAssert(cond, tag, fmt...) \
+    __android_log_assert(cond, tag,            \
+                         __android_second(0, ##fmt, NULL) __android_rest(fmt))
+
+#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
+
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...)                                \
+    ((CONDITION(cond))                                                \
+         ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
+         : (void)0)
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+    (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
+#endif
+
+/*
+ * Simplified macro to send a warning system log message using current LOG_TAG.
+ */
+#ifndef SLOGW
+#define SLOGW(...) \
+    ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGW_IF
+#define SLOGW_IF(cond, ...)                                                    \
+    ((__predict_false(cond))                                                   \
+         ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+         : (void)0)
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) ((void)0)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) ((void)0)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds.  Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
+#endif
+
+#define LOG_TRACE(...)
+
+#ifndef NELEM
+#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
+#endif
+
+/*
+ * JNI methods of the GameActivity Java class.
+ */
+static struct {
+    jmethodID finish;
+    jmethodID setWindowFlags;
+    jmethodID getWindowInsets;
+    jmethodID getWaterfallInsets;
+    jmethodID setImeEditorInfoFields;
+} gGameActivityClassInfo;
+
+/*
+ * JNI fields of the androidx.core.graphics.Insets Java class.
+ */
+static struct {
+    jfieldID left;
+    jfieldID right;
+    jfieldID top;
+    jfieldID bottom;
+} gInsetsClassInfo;
+
+/*
+ * JNI methods of the WindowInsetsCompat.Type Java class.
+ */
+static struct {
+    jmethodID methods[GAMECOMMON_INSETS_TYPE_COUNT];
+    jclass clazz;
+} gWindowInsetsCompatTypeClassInfo;
+
+/*
+ * Contains a command to be executed by the GameActivity
+ * on the application main thread.
+ */
+struct ActivityWork {
+    int32_t cmd;
+    int64_t arg1;
+    int64_t arg2;
+};
+
+/*
+ * The type of commands that can be passed to the GameActivity and that
+ * are executed on the application main thread.
+ */
+enum {
+    CMD_FINISH = 1,
+    CMD_SET_WINDOW_FORMAT,
+    CMD_SET_WINDOW_FLAGS,
+    CMD_SHOW_SOFT_INPUT,
+    CMD_HIDE_SOFT_INPUT,
+    CMD_SET_SOFT_INPUT_STATE
+};
+
+/*
+ * Write a command to be executed by the GameActivity on the application main
+ * thread.
+ */
+static void write_work(int fd, int32_t cmd, int64_t arg1 = 0,
+                       int64_t arg2 = 0) {
+    ActivityWork work;
+    work.cmd = cmd;
+    work.arg1 = arg1;
+    work.arg2 = arg2;
+
+    LOG_TRACE("write_work: cmd=%d", cmd);
+restart:
+    int res = write(fd, &work, sizeof(work));
+    if (res < 0 && errno == EINTR) {
+        goto restart;
+    }
+
+    if (res == sizeof(work)) return;
+
+    if (res < 0) {
+        ALOGW("Failed writing to work fd: %s", strerror(errno));
+    } else {
+        ALOGW("Truncated writing to work fd: %d", res);
+    }
+}
+
+/*
+ * Read commands to be executed by the GameActivity on the application main
+ * thread.
+ */
+static bool read_work(int fd, ActivityWork *outWork) {
+    int res = read(fd, outWork, sizeof(ActivityWork));
+    // no need to worry about EINTR, poll loop will just come back again.
+    if (res == sizeof(ActivityWork)) return true;
+
+    if (res < 0) {
+        ALOGW("Failed reading work fd: %s", strerror(errno));
+    } else {
+        ALOGW("Truncated reading work fd: %d", res);
+    }
+    return false;
+}
+
+/*
+ * Native state for interacting with the GameActivity class.
+ */
+struct NativeCode : public GameActivity {
+    NativeCode() {
+        memset((GameActivity *)this, 0, sizeof(GameActivity));
+        memset(&callbacks, 0, sizeof(callbacks));
+        memset(&insetsState, 0, sizeof(insetsState));
+        nativeWindow = NULL;
+        mainWorkRead = mainWorkWrite = -1;
+        gameTextInput = NULL;
+    }
+
+    ~NativeCode() {
+        if (callbacks.onDestroy != NULL) {
+            callbacks.onDestroy(this);
+        }
+        if (env != NULL) {
+            if (javaGameActivity != NULL) {
+                env->DeleteGlobalRef(javaGameActivity);
+            }
+            if (javaAssetManager != NULL) {
+                env->DeleteGlobalRef(javaAssetManager);
+            }
+        }
+        GameTextInput_destroy(gameTextInput);
+        if (looper != NULL && mainWorkRead >= 0) {
+            ALooper_removeFd(looper, mainWorkRead);
+        }
+        ALooper_release(looper);
+        looper = NULL;
+
+        setSurface(NULL);
+        if (mainWorkRead >= 0) close(mainWorkRead);
+        if (mainWorkWrite >= 0) close(mainWorkWrite);
+    }
+
+    void setSurface(jobject _surface) {
+        if (nativeWindow != NULL) {
+            ANativeWindow_release(nativeWindow);
+        }
+        if (_surface != NULL) {
+            nativeWindow = ANativeWindow_fromSurface(env, _surface);
+        } else {
+            nativeWindow = NULL;
+        }
+    }
+
+    GameActivityCallbacks callbacks;
+
+    std::string internalDataPathObj;
+    std::string externalDataPathObj;
+    std::string obbPathObj;
+
+    ANativeWindow *nativeWindow;
+    int32_t lastWindowWidth;
+    int32_t lastWindowHeight;
+
+    // These are used to wake up the main thread to process work.
+    int mainWorkRead;
+    int mainWorkWrite;
+    ALooper *looper;
+
+    // Need to hold on to a reference here in case the upper layers destroy our
+    // AssetManager.
+    jobject javaAssetManager;
+
+    GameTextInput *gameTextInput;
+    // Set by users in GameActivity_setTextInputState, then passed to
+    // GameTextInput.
+    OwnedGameTextInputState gameTextInputState;
+    std::mutex gameTextInputStateMutex;
+
+    ARect insetsState[GAMECOMMON_INSETS_TYPE_COUNT];
+};
+
+extern "C" void GameActivity_finish(GameActivity *activity) {
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    write_work(code->mainWorkWrite, CMD_FINISH, 0);
+}
+
+extern "C" void GameActivity_setWindowFlags(GameActivity *activity,
+                                            uint32_t values, uint32_t mask) {
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    write_work(code->mainWorkWrite, CMD_SET_WINDOW_FLAGS, values, mask);
+}
+
+extern "C" void GameActivity_showSoftInput(GameActivity *activity,
+                                           uint32_t flags) {
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags);
+}
+
+extern "C" void GameActivity_setTextInputState(
+    GameActivity *activity, const GameTextInputState *state) {
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    std::lock_guard<std::mutex> lock(code->gameTextInputStateMutex);
+    code->gameTextInputState = *state;
+    write_work(code->mainWorkWrite, CMD_SET_SOFT_INPUT_STATE);
+}
+
+extern "C" void GameActivity_getTextInputState(
+    GameActivity *activity, GameTextInputGetStateCallback callback,
+    void *context) {
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    return GameTextInput_getState(code->gameTextInput, callback, context);
+}
+
+extern "C" void GameActivity_hideSoftInput(GameActivity *activity,
+                                           uint32_t flags) {
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    write_work(code->mainWorkWrite, CMD_HIDE_SOFT_INPUT, flags);
+}
+
+extern "C" void GameActivity_getWindowInsets(GameActivity *activity,
+                                             GameCommonInsetsType type,
+                                             ARect *insets) {
+    if (type < 0 || type >= GAMECOMMON_INSETS_TYPE_COUNT) return;
+    NativeCode *code = static_cast<NativeCode *>(activity);
+    *insets = code->insetsState[type];
+}
+
+extern "C" GameTextInput *GameActivity_getTextInput(
+    const GameActivity *activity) {
+    const NativeCode *code = static_cast<const NativeCode *>(activity);
+    return code->gameTextInput;
+}
+
+/*
+ * Log the JNI exception, if any.
+ */
+static void checkAndClearException(JNIEnv *env, const char *methodName) {
+    if (env->ExceptionCheck()) {
+        ALOGE("Exception while running %s", methodName);
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+    }
+}
+
+/*
+ * Callback for handling native events on the application's main thread.
+ */
+static int mainWorkCallback(int fd, int events, void *data) {
+    ALOGD("************** mainWorkCallback *********");
+    NativeCode *code = (NativeCode *)data;
+    if ((events & POLLIN) == 0) {
+        return 1;
+    }
+
+    ActivityWork work;
+    if (!read_work(code->mainWorkRead, &work)) {
+        return 1;
+    }
+    LOG_TRACE("mainWorkCallback: cmd=%d", work.cmd);
+    switch (work.cmd) {
+        case CMD_FINISH: {
+            code->env->CallVoidMethod(code->javaGameActivity,
+                                      gGameActivityClassInfo.finish);
+            checkAndClearException(code->env, "finish");
+        } break;
+        case CMD_SET_WINDOW_FLAGS: {
+            code->env->CallVoidMethod(code->javaGameActivity,
+                                      gGameActivityClassInfo.setWindowFlags,
+                                      work.arg1, work.arg2);
+            checkAndClearException(code->env, "setWindowFlags");
+        } break;
+        case CMD_SHOW_SOFT_INPUT: {
+            GameTextInput_showIme(code->gameTextInput, work.arg1);
+        } break;
+        case CMD_SET_SOFT_INPUT_STATE: {
+            std::lock_guard<std::mutex> lock(code->gameTextInputStateMutex);
+            GameTextInput_setState(code->gameTextInput,
+                                   &code->gameTextInputState.inner);
+            checkAndClearException(code->env, "setTextInputState");
+        } break;
+        case CMD_HIDE_SOFT_INPUT: {
+            GameTextInput_hideIme(code->gameTextInput, work.arg1);
+        } break;
+        default:
+            ALOGW("Unknown work command: %d", work.cmd);
+            break;
+    }
+
+    return 1;
+}
+
+// ------------------------------------------------------------------------
+static thread_local std::string g_error_msg;
+
+static jlong initializeNativeCode_native(JNIEnv *env, jobject javaGameActivity,
+                                   jstring internalDataDir, jstring obbDir,
+                                   jstring externalDataDir, jobject jAssetMgr,
+                                   jbyteArray savedState) {
+    LOG_TRACE("initializeNativeCode_native");
+    NativeCode *code = NULL;
+
+    code = new NativeCode();
+
+    code->looper = ALooper_forThread();
+    if (code->looper == nullptr) {
+        g_error_msg = "Unable to retrieve native ALooper";
+        ALOGW("%s", g_error_msg.c_str());
+        delete code;
+        return 0;
+    }
+    ALooper_acquire(code->looper);
+
+    int msgpipe[2];
+    if (pipe(msgpipe)) {
+        g_error_msg = "could not create pipe: ";
+        g_error_msg += strerror(errno);
+
+        ALOGW("%s", g_error_msg.c_str());
+        delete code;
+        return 0;
+    }
+    code->mainWorkRead = msgpipe[0];
+    code->mainWorkWrite = msgpipe[1];
+    int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK);
+    SLOGW_IF(result != 0,
+             "Could not make main work read pipe "
+             "non-blocking: %s",
+             strerror(errno));
+    result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK);
+    SLOGW_IF(result != 0,
+             "Could not make main work write pipe "
+             "non-blocking: %s",
+             strerror(errno));
+    ALooper_addFd(code->looper, code->mainWorkRead, 0, ALOOPER_EVENT_INPUT,
+                  mainWorkCallback, code);
+
+    code->GameActivity::callbacks = &code->callbacks;
+    if (env->GetJavaVM(&code->vm) < 0) {
+        ALOGW("GameActivity GetJavaVM failed");
+        delete code;
+        return 0;
+    }
+    code->env = env;
+    code->javaGameActivity = env->NewGlobalRef(javaGameActivity);
+
+    const char *dirStr =
+        internalDataDir ? env->GetStringUTFChars(internalDataDir, NULL) : "";
+    code->internalDataPathObj = dirStr;
+    code->internalDataPath = code->internalDataPathObj.c_str();
+    if (internalDataDir) env->ReleaseStringUTFChars(internalDataDir, dirStr);
+
+    dirStr =
+        externalDataDir ? env->GetStringUTFChars(externalDataDir, NULL) : "";
+    code->externalDataPathObj = dirStr;
+    code->externalDataPath = code->externalDataPathObj.c_str();
+    if (externalDataDir) env->ReleaseStringUTFChars(externalDataDir, dirStr);
+
+    code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
+    code->assetManager = AAssetManager_fromJava(env, jAssetMgr);
+
+    dirStr = obbDir ? env->GetStringUTFChars(obbDir, NULL) : "";
+    code->obbPathObj = dirStr;
+    code->obbPath = code->obbPathObj.c_str();
+    if (obbDir) env->ReleaseStringUTFChars(obbDir, dirStr);
+
+    jbyte *rawSavedState = NULL;
+    jsize rawSavedSize = 0;
+    if (savedState != NULL) {
+        rawSavedState = env->GetByteArrayElements(savedState, NULL);
+        rawSavedSize = env->GetArrayLength(savedState);
+    }
+    GameActivity_onCreate(code, rawSavedState, rawSavedSize);
+
+    code->gameTextInput = GameTextInput_init(env, 0);
+    GameTextInput_setEventCallback(code->gameTextInput,
+                                   reinterpret_cast<GameTextInputEventCallback>(
+                                       code->callbacks.onTextInputEvent),
+                                   code);
+
+    if (rawSavedState != NULL) {
+        env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
+    }
+
+    return reinterpret_cast<jlong>(code);
+}
+
+static jstring getDlError_native(JNIEnv *env, jobject javaGameActivity) {
+    jstring result = env->NewStringUTF(g_error_msg.c_str());
+    g_error_msg.clear();
+    return result;
+}
+
+static void terminateNativeCode_native(JNIEnv *env, jobject javaGameActivity,
+                                    jlong handle) {
+    LOG_TRACE("terminateNativeCode_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        delete code;
+    }
+}
+
+static void onStart_native(JNIEnv *env, jobject javaGameActivity,
+                           jlong handle) {
+    ALOGV("onStart_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onStart != NULL) {
+            code->callbacks.onStart(code);
+        }
+    }
+}
+
+static void onResume_native(JNIEnv *env, jobject javaGameActivity,
+                            jlong handle) {
+    LOG_TRACE("onResume_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onResume != NULL) {
+            code->callbacks.onResume(code);
+        }
+    }
+}
+
+struct SaveInstanceLocals {
+    JNIEnv *env;
+    jbyteArray array;
+};
+
+static jbyteArray onSaveInstanceState_native(JNIEnv *env,
+                                             jobject javaGameActivity,
+                                             jlong handle) {
+    LOG_TRACE("onSaveInstanceState_native");
+
+    SaveInstanceLocals locals{
+        env, NULL};  // Passed through the user's state prep function.
+
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onSaveInstanceState != NULL) {
+            code->callbacks.onSaveInstanceState(
+                code,
+                [](const char *bytes, int len, void *context) {
+                    auto locals = static_cast<SaveInstanceLocals *>(context);
+                    if (len > 0) {
+                        locals->array = locals->env->NewByteArray(len);
+                        if (locals->array != NULL) {
+                            locals->env->SetByteArrayRegion(
+                                locals->array, 0, len, (const jbyte *)bytes);
+                        }
+                    }
+                },
+                &locals);
+        }
+    }
+    return locals.array;
+}
+
+static void onPause_native(JNIEnv *env, jobject javaGameActivity,
+                           jlong handle) {
+    LOG_TRACE("onPause_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onPause != NULL) {
+            code->callbacks.onPause(code);
+        }
+    }
+}
+
+static void onStop_native(JNIEnv *env, jobject javaGameActivity, jlong handle) {
+    LOG_TRACE("onStop_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onStop != NULL) {
+            code->callbacks.onStop(code);
+        }
+    }
+}
+
+static void onConfigurationChanged_native(JNIEnv *env, jobject javaGameActivity,
+                                          jlong handle) {
+    LOG_TRACE("onConfigurationChanged_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onConfigurationChanged != NULL) {
+            code->callbacks.onConfigurationChanged(code);
+        }
+    }
+}
+
+static void onTrimMemory_native(JNIEnv *env, jobject javaGameActivity,
+                                jlong handle, jint level) {
+    LOG_TRACE("onTrimMemory_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onTrimMemory != NULL) {
+            code->callbacks.onTrimMemory(code, level);
+        }
+    }
+}
+
+static void onWindowFocusChanged_native(JNIEnv *env, jobject javaGameActivity,
+                                        jlong handle, jboolean focused) {
+    LOG_TRACE("onWindowFocusChanged_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->callbacks.onWindowFocusChanged != NULL) {
+            code->callbacks.onWindowFocusChanged(code, focused ? 1 : 0);
+        }
+    }
+}
+
+static void onSurfaceCreated_native(JNIEnv *env, jobject javaGameActivity,
+                                    jlong handle, jobject surface) {
+    ALOGV("onSurfaceCreated_native");
+    LOG_TRACE("onSurfaceCreated_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        code->setSurface(surface);
+
+        if (code->nativeWindow != NULL &&
+            code->callbacks.onNativeWindowCreated != NULL) {
+            code->callbacks.onNativeWindowCreated(code, code->nativeWindow);
+        }
+    }
+}
+
+static void onSurfaceChanged_native(JNIEnv *env, jobject javaGameActivity,
+                                    jlong handle, jobject surface, jint format,
+                                    jint width, jint height) {
+    LOG_TRACE("onSurfaceChanged_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        ANativeWindow *oldNativeWindow = code->nativeWindow;
+        // Fix for window being destroyed behind the scenes on older Android
+        // versions.
+        if (oldNativeWindow != NULL) {
+            ANativeWindow_acquire(oldNativeWindow);
+        }
+        code->setSurface(surface);
+        if (oldNativeWindow != code->nativeWindow) {
+            if (oldNativeWindow != NULL &&
+                code->callbacks.onNativeWindowDestroyed != NULL) {
+                code->callbacks.onNativeWindowDestroyed(code, oldNativeWindow);
+            }
+            if (code->nativeWindow != NULL) {
+                if (code->callbacks.onNativeWindowCreated != NULL) {
+                    code->callbacks.onNativeWindowCreated(code,
+                                                          code->nativeWindow);
+                }
+
+                code->lastWindowWidth =
+                    ANativeWindow_getWidth(code->nativeWindow);
+                code->lastWindowHeight =
+                    ANativeWindow_getHeight(code->nativeWindow);
+            }
+        } else {
+            // Maybe it was resized?
+            int32_t newWidth = ANativeWindow_getWidth(code->nativeWindow);
+            int32_t newHeight = ANativeWindow_getHeight(code->nativeWindow);
+            if (newWidth != code->lastWindowWidth ||
+                newHeight != code->lastWindowHeight) {
+                if (code->callbacks.onNativeWindowResized != NULL) {
+                    code->callbacks.onNativeWindowResized(
+                        code, code->nativeWindow, newWidth, newHeight);
+                }
+            }
+        }
+        // Release the window we acquired earlier.
+        if (oldNativeWindow != NULL) {
+            ANativeWindow_release(oldNativeWindow);
+        }
+    }
+}
+
+static void onSurfaceRedrawNeeded_native(JNIEnv *env, jobject javaGameActivity,
+                                         jlong handle) {
+    LOG_TRACE("onSurfaceRedrawNeeded_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->nativeWindow != NULL &&
+            code->callbacks.onNativeWindowRedrawNeeded != NULL) {
+            code->callbacks.onNativeWindowRedrawNeeded(code,
+                                                       code->nativeWindow);
+        }
+    }
+}
+
+static void onSurfaceDestroyed_native(JNIEnv *env, jobject javaGameActivity,
+                                      jlong handle) {
+    LOG_TRACE("onSurfaceDestroyed_native");
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+        if (code->nativeWindow != NULL &&
+            code->callbacks.onNativeWindowDestroyed != NULL) {
+            code->callbacks.onNativeWindowDestroyed(code, code->nativeWindow);
+        }
+        code->setSurface(NULL);
+    }
+}
+
+static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
+    /* AMOTION_EVENT_AXIS_X */ true,
+    /* AMOTION_EVENT_AXIS_Y */ true,
+    // Disable all other axes by default (they can be enabled using
+    // `GameActivityPointerAxes_enableAxis`).
+    false};
+
+extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return;
+    }
+
+    enabledAxes[axis] = true;
+}
+
+extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return;
+    }
+
+    enabledAxes[axis] = false;
+}
+
+extern "C" void GameActivity_setImeEditorInfo(GameActivity *activity,
+                                              int inputType, int actionId,
+                                              int imeOptions) {
+    JNIEnv *env;
+    if (activity->vm->AttachCurrentThread(&env, NULL) == JNI_OK) {
+        env->CallVoidMethod(activity->javaGameActivity,
+                            gGameActivityClassInfo.setImeEditorInfoFields,
+                            inputType, actionId, imeOptions);
+    }
+}
+
+static struct {
+    jmethodID getDeviceId;
+    jmethodID getSource;
+    jmethodID getAction;
+
+    jmethodID getEventTime;
+    jmethodID getDownTime;
+
+    jmethodID getFlags;
+    jmethodID getMetaState;
+
+    jmethodID getActionButton;
+    jmethodID getButtonState;
+    jmethodID getClassification;
+    jmethodID getEdgeFlags;
+
+    jmethodID getPointerCount;
+    jmethodID getPointerId;
+    jmethodID getRawX;
+    jmethodID getRawY;
+    jmethodID getXPrecision;
+    jmethodID getYPrecision;
+    jmethodID getAxisValue;
+} gMotionEventClassInfo;
+
+extern "C" void GameActivityMotionEvent_fromJava(
+    JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event) {
+    static bool gMotionEventClassInfoInitialized = false;
+    if (!gMotionEventClassInfoInitialized) {
+        int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
+        gMotionEventClassInfo = {0};
+        jclass motionEventClass = env->FindClass("android/view/MotionEvent");
+        gMotionEventClassInfo.getDeviceId =
+            env->GetMethodID(motionEventClass, "getDeviceId", "()I");
+        gMotionEventClassInfo.getSource =
+            env->GetMethodID(motionEventClass, "getSource", "()I");
+        gMotionEventClassInfo.getAction =
+            env->GetMethodID(motionEventClass, "getAction", "()I");
+        gMotionEventClassInfo.getEventTime =
+            env->GetMethodID(motionEventClass, "getEventTime", "()J");
+        gMotionEventClassInfo.getDownTime =
+            env->GetMethodID(motionEventClass, "getDownTime", "()J");
+        gMotionEventClassInfo.getFlags =
+            env->GetMethodID(motionEventClass, "getFlags", "()I");
+        gMotionEventClassInfo.getMetaState =
+            env->GetMethodID(motionEventClass, "getMetaState", "()I");
+        if (sdkVersion >= 23) {
+            gMotionEventClassInfo.getActionButton =
+                env->GetMethodID(motionEventClass, "getActionButton", "()I");
+        }
+        if (sdkVersion >= 14) {
+            gMotionEventClassInfo.getButtonState =
+                env->GetMethodID(motionEventClass, "getButtonState", "()I");
+        }
+        if (sdkVersion >= 29) {
+            gMotionEventClassInfo.getClassification =
+                env->GetMethodID(motionEventClass, "getClassification", "()I");
+        }
+        gMotionEventClassInfo.getEdgeFlags =
+            env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
+        gMotionEventClassInfo.getPointerCount =
+            env->GetMethodID(motionEventClass, "getPointerCount", "()I");
+        gMotionEventClassInfo.getPointerId =
+            env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
+        if (sdkVersion >= 29) {
+            gMotionEventClassInfo.getRawX =
+                env->GetMethodID(motionEventClass, "getRawX", "(I)F");
+            gMotionEventClassInfo.getRawY =
+                env->GetMethodID(motionEventClass, "getRawY", "(I)F");
+        }
+        gMotionEventClassInfo.getXPrecision =
+            env->GetMethodID(motionEventClass, "getXPrecision", "()F");
+        gMotionEventClassInfo.getYPrecision =
+            env->GetMethodID(motionEventClass, "getYPrecision", "()F");
+        gMotionEventClassInfo.getAxisValue =
+            env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
+
+        gMotionEventClassInfoInitialized = true;
+    }
+
+    int pointerCount =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getPointerCount);
+    pointerCount =
+        std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
+    out_event->pointerCount = pointerCount;
+    for (int i = 0; i < pointerCount; ++i) {
+        out_event->pointers[i] = {
+            /*id=*/env->CallIntMethod(motionEvent,
+                                      gMotionEventClassInfo.getPointerId, i),
+            /*axisValues=*/{0},
+            /*rawX=*/gMotionEventClassInfo.getRawX
+                ? env->CallFloatMethod(motionEvent,
+                                       gMotionEventClassInfo.getRawX, i)
+                : 0,
+            /*rawY=*/gMotionEventClassInfo.getRawY
+                ? env->CallFloatMethod(motionEvent,
+                                       gMotionEventClassInfo.getRawY, i)
+                : 0,
+        };
+
+        for (int axisIndex = 0;
+             axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) {
+            if (enabledAxes[axisIndex]) {
+                out_event->pointers[i].axisValues[axisIndex] =
+                    env->CallFloatMethod(motionEvent,
+                                         gMotionEventClassInfo.getAxisValue,
+                                         axisIndex, i);
+            }
+        }
+    }
+
+    out_event->deviceId =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getDeviceId);
+    out_event->source =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getSource);
+    out_event->action =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getAction);
+    out_event->eventTime =
+        env->CallLongMethod(motionEvent, gMotionEventClassInfo.getEventTime) *
+        1000000;
+    out_event->downTime =
+        env->CallLongMethod(motionEvent, gMotionEventClassInfo.getDownTime) *
+        1000000;
+    out_event->flags =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getFlags);
+    out_event->metaState =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getMetaState);
+    out_event->actionButton =
+        gMotionEventClassInfo.getActionButton
+            ? env->CallIntMethod(motionEvent,
+                                 gMotionEventClassInfo.getActionButton)
+            : 0;
+    out_event->buttonState =
+        gMotionEventClassInfo.getButtonState
+            ? env->CallIntMethod(motionEvent,
+                                 gMotionEventClassInfo.getButtonState)
+            : 0;
+    out_event->classification =
+        gMotionEventClassInfo.getClassification
+            ? env->CallIntMethod(motionEvent,
+                                 gMotionEventClassInfo.getClassification)
+            : 0;
+    out_event->edgeFlags =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getEdgeFlags);
+    out_event->precisionX =
+        env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getXPrecision);
+    out_event->precisionY =
+        env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getYPrecision);
+}
+
+static struct {
+    jmethodID getDeviceId;
+    jmethodID getSource;
+    jmethodID getAction;
+
+    jmethodID getEventTime;
+    jmethodID getDownTime;
+
+    jmethodID getFlags;
+    jmethodID getMetaState;
+
+    jmethodID getModifiers;
+    jmethodID getRepeatCount;
+    jmethodID getKeyCode;
+    jmethodID getUnicodeChar;
+} gKeyEventClassInfo;
+
+extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent,
+                                              GameActivityKeyEvent *out_event) {
+    static bool gKeyEventClassInfoInitialized = false;
+    if (!gKeyEventClassInfoInitialized) {
+        int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
+        gKeyEventClassInfo = {0};
+        jclass keyEventClass = env->FindClass("android/view/KeyEvent");
+        gKeyEventClassInfo.getDeviceId =
+            env->GetMethodID(keyEventClass, "getDeviceId", "()I");
+        gKeyEventClassInfo.getSource =
+            env->GetMethodID(keyEventClass, "getSource", "()I");
+        gKeyEventClassInfo.getAction =
+            env->GetMethodID(keyEventClass, "getAction", "()I");
+        gKeyEventClassInfo.getEventTime =
+            env->GetMethodID(keyEventClass, "getEventTime", "()J");
+        gKeyEventClassInfo.getDownTime =
+            env->GetMethodID(keyEventClass, "getDownTime", "()J");
+        gKeyEventClassInfo.getFlags =
+            env->GetMethodID(keyEventClass, "getFlags", "()I");
+        gKeyEventClassInfo.getMetaState =
+            env->GetMethodID(keyEventClass, "getMetaState", "()I");
+        if (sdkVersion >= 13) {
+            gKeyEventClassInfo.getModifiers =
+                env->GetMethodID(keyEventClass, "getModifiers", "()I");
+        }
+        gKeyEventClassInfo.getRepeatCount =
+            env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
+        gKeyEventClassInfo.getKeyCode =
+            env->GetMethodID(keyEventClass, "getKeyCode", "()I");
+        gKeyEventClassInfo.getUnicodeChar =
+            env->GetMethodID(keyEventClass, "getUnicodeChar", "()I");
+
+        gKeyEventClassInfoInitialized = true;
+    }
+
+    *out_event = {
+        /*deviceId=*/env->CallIntMethod(keyEvent,
+                                        gKeyEventClassInfo.getDeviceId),
+        /*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
+        /*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
+        // TODO: introduce a millisecondsToNanoseconds helper:
+        /*eventTime=*/
+        env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) *
+            1000000,
+        /*downTime=*/
+        env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
+        /*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
+        /*metaState=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
+        /*modifiers=*/gKeyEventClassInfo.getModifiers
+            ? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
+            : 0,
+        /*repeatCount=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
+        /*keyCode=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode),
+        /*unicodeChar=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)};
+}
+
+static bool onTouchEvent_native(JNIEnv *env, jobject javaGameActivity,
+                                jlong handle, jobject motionEvent) {
+    if (handle == 0) return false;
+    NativeCode *code = (NativeCode *)handle;
+    if (code->callbacks.onTouchEvent == nullptr) return false;
+
+    static GameActivityMotionEvent c_event;
+    GameActivityMotionEvent_fromJava(env, motionEvent, &c_event);
+    return code->callbacks.onTouchEvent(code, &c_event);
+}
+
+static bool onKeyUp_native(JNIEnv *env, jobject javaGameActivity, jlong handle,
+                           jobject keyEvent) {
+    if (handle == 0) return false;
+    NativeCode *code = (NativeCode *)handle;
+    if (code->callbacks.onKeyUp == nullptr) return false;
+
+    static GameActivityKeyEvent c_event;
+    GameActivityKeyEvent_fromJava(env, keyEvent, &c_event);
+    return code->callbacks.onKeyUp(code, &c_event);
+}
+
+static bool onKeyDown_native(JNIEnv *env, jobject javaGameActivity,
+                             jlong handle, jobject keyEvent) {
+    if (handle == 0) return false;
+    NativeCode *code = (NativeCode *)handle;
+    if (code->callbacks.onKeyDown == nullptr) return false;
+
+    static GameActivityKeyEvent c_event;
+    GameActivityKeyEvent_fromJava(env, keyEvent, &c_event);
+    return code->callbacks.onKeyDown(code, &c_event);
+}
+
+static void onTextInput_native(JNIEnv *env, jobject activity, jlong handle,
+                               jobject textInputEvent) {
+    if (handle == 0) return;
+    NativeCode *code = (NativeCode *)handle;
+    GameTextInput_processEvent(code->gameTextInput, textInputEvent);
+}
+
+static void onWindowInsetsChanged_native(JNIEnv *env, jobject activity,
+                                         jlong handle) {
+    if (handle == 0) return;
+    NativeCode *code = (NativeCode *)handle;
+    if (code->callbacks.onWindowInsetsChanged == nullptr) return;
+    for (int type = 0; type < GAMECOMMON_INSETS_TYPE_COUNT; ++type) {
+        jobject jinsets;
+        // Note that waterfall insets are handled differently on the Java side.
+        if (type == GAMECOMMON_INSETS_TYPE_WATERFALL) {
+            jinsets = env->CallObjectMethod(
+                code->javaGameActivity,
+                gGameActivityClassInfo.getWaterfallInsets);
+        } else {
+            jint jtype = env->CallStaticIntMethod(
+                gWindowInsetsCompatTypeClassInfo.clazz,
+                gWindowInsetsCompatTypeClassInfo.methods[type]);
+            jinsets = env->CallObjectMethod(
+                code->javaGameActivity, gGameActivityClassInfo.getWindowInsets,
+                jtype);
+        }
+        ARect &insets = code->insetsState[type];
+        if (jinsets == nullptr) {
+            insets.left = 0;
+            insets.right = 0;
+            insets.top = 0;
+            insets.bottom = 0;
+        } else {
+            insets.left = env->GetIntField(jinsets, gInsetsClassInfo.left);
+            insets.right = env->GetIntField(jinsets, gInsetsClassInfo.right);
+            insets.top = env->GetIntField(jinsets, gInsetsClassInfo.top);
+            insets.bottom = env->GetIntField(jinsets, gInsetsClassInfo.bottom);
+        }
+    }
+    GameTextInput_processImeInsets(
+        code->gameTextInput, &code->insetsState[GAMECOMMON_INSETS_TYPE_IME]);
+    code->callbacks.onWindowInsetsChanged(code);
+}
+
+static void setInputConnection_native(JNIEnv *env, jobject activity,
+                                      jlong handle, jobject inputConnection) {
+    NativeCode *code = (NativeCode *)handle;
+    GameTextInput_setInputConnection(code->gameTextInput, inputConnection);
+}
+
+static const JNINativeMethod g_methods[] = {
+    {"initializeNativeCode",
+     "(Ljava/lang/String;Ljava/lang/String;"
+     "Ljava/lang/String;Landroid/content/res/AssetManager;[B)J",
+     (void *)initializeNativeCode_native},
+    {"getDlError", "()Ljava/lang/String;", (void *)getDlError_native},
+    {"terminateNativeCode", "(J)V", (void *)terminateNativeCode_native},
+    {"onStartNative", "(J)V", (void *)onStart_native},
+    {"onResumeNative", "(J)V", (void *)onResume_native},
+    {"onSaveInstanceStateNative", "(J)[B", (void *)onSaveInstanceState_native},
+    {"onPauseNative", "(J)V", (void *)onPause_native},
+    {"onStopNative", "(J)V", (void *)onStop_native},
+    {"onConfigurationChangedNative", "(J)V",
+     (void *)onConfigurationChanged_native},
+    {"onTrimMemoryNative", "(JI)V", (void *)onTrimMemory_native},
+    {"onWindowFocusChangedNative", "(JZ)V",
+     (void *)onWindowFocusChanged_native},
+    {"onSurfaceCreatedNative", "(JLandroid/view/Surface;)V",
+     (void *)onSurfaceCreated_native},
+    {"onSurfaceChangedNative", "(JLandroid/view/Surface;III)V",
+     (void *)onSurfaceChanged_native},
+    {"onSurfaceRedrawNeededNative", "(JLandroid/view/Surface;)V",
+     (void *)onSurfaceRedrawNeeded_native},
+    {"onSurfaceDestroyedNative", "(J)V", (void *)onSurfaceDestroyed_native},
+    {"onTouchEventNative", "(JLandroid/view/MotionEvent;)Z",
+     (void *)onTouchEvent_native},
+    {"onKeyDownNative", "(JLandroid/view/KeyEvent;)Z",
+     (void *)onKeyDown_native},
+    {"onKeyUpNative", "(JLandroid/view/KeyEvent;)Z", (void *)onKeyUp_native},
+    {"onTextInputEventNative",
+     "(JLcom/google/androidgamesdk/gametextinput/State;)V",
+     (void *)onTextInput_native},
+    {"onWindowInsetsChangedNative", "(J)V",
+     (void *)onWindowInsetsChanged_native},
+    {"setInputConnectionNative",
+     "(JLcom/google/androidgamesdk/gametextinput/InputConnection;)V",
+     (void *)setInputConnection_native},
+};
+
+static const char *const kGameActivityPathName =
+    "com/google/androidgamesdk/GameActivity";
+
+static const char *const kInsetsPathName = "androidx/core/graphics/Insets";
+
+static const char *const kWindowInsetsCompatTypePathName =
+    "androidx/core/view/WindowInsetsCompat$Type";
+
+#define FIND_CLASS(var, className)   \
+    var = env->FindClass(className); \
+    LOG_FATAL_IF(!var, "Unable to find class %s", className);
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor)  \
+    var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+    LOG_FATAL_IF(!var, "Unable to find method %s", methodName);
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+    var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \
+    LOG_FATAL_IF(!var, "Unable to find static method %s", methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor)  \
+    var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+    LOG_FATAL_IF(!var, "Unable to find field %s", fieldName);
+
+static int jniRegisterNativeMethods(JNIEnv *env, const char *className,
+                                    const JNINativeMethod *methods,
+                                    int numMethods) {
+    ALOGV("Registering %s's %d native methods...", className, numMethods);
+    jclass clazz = env->FindClass(className);
+    LOG_FATAL_IF(clazz == nullptr,
+                 "Native registration unable to find class '%s'; aborting...",
+                 className);
+    int result = env->RegisterNatives(clazz, methods, numMethods);
+    env->DeleteLocalRef(clazz);
+    if (result == 0) {
+        return 0;
+    }
+
+    // Failure to register natives is fatal. Try to report the corresponding
+    // exception, otherwise abort with generic failure message.
+    jthrowable thrown = env->ExceptionOccurred();
+    if (thrown != NULL) {
+        env->ExceptionDescribe();
+        env->DeleteLocalRef(thrown);
+    }
+    LOG_FATAL("RegisterNatives failed for '%s'; aborting...", className);
+}
+
+extern "C" int GameActivity_register(JNIEnv *env) {
+    ALOGD("GameActivity_register");
+    jclass activity_class;
+    FIND_CLASS(activity_class, kGameActivityPathName);
+    GET_METHOD_ID(gGameActivityClassInfo.finish, activity_class, "finish",
+                  "()V");
+    GET_METHOD_ID(gGameActivityClassInfo.setWindowFlags, activity_class,
+                  "setWindowFlags", "(II)V");
+    GET_METHOD_ID(gGameActivityClassInfo.getWindowInsets, activity_class,
+                  "getWindowInsets", "(I)Landroidx/core/graphics/Insets;");
+    GET_METHOD_ID(gGameActivityClassInfo.getWaterfallInsets, activity_class,
+                  "getWaterfallInsets", "()Landroidx/core/graphics/Insets;");
+    GET_METHOD_ID(gGameActivityClassInfo.setImeEditorInfoFields, activity_class,
+                  "setImeEditorInfoFields", "(III)V");
+    jclass insets_class;
+    FIND_CLASS(insets_class, kInsetsPathName);
+    GET_FIELD_ID(gInsetsClassInfo.left, insets_class, "left", "I");
+    GET_FIELD_ID(gInsetsClassInfo.right, insets_class, "right", "I");
+    GET_FIELD_ID(gInsetsClassInfo.top, insets_class, "top", "I");
+    GET_FIELD_ID(gInsetsClassInfo.bottom, insets_class, "bottom", "I");
+    jclass windowInsetsCompatType_class;
+    FIND_CLASS(windowInsetsCompatType_class, kWindowInsetsCompatTypePathName);
+    gWindowInsetsCompatTypeClassInfo.clazz =
+        (jclass)env->NewGlobalRef(windowInsetsCompatType_class);
+    // These names must match, in order, the GameCommonInsetsType enum fields
+    // Note that waterfall is handled differently by the insets API, so we
+    // exclude it here.
+    const char *methodNames[GAMECOMMON_INSETS_TYPE_WATERFALL] = {
+        "captionBar",
+        "displayCutout",
+        "ime",
+        "mandatorySystemGestures",
+        "navigationBars",
+        "statusBars",
+        "systemBars",
+        "systemGestures",
+        "tappableElement"};
+    for (int i = 0; i < GAMECOMMON_INSETS_TYPE_WATERFALL; ++i) {
+        GET_STATIC_METHOD_ID(gWindowInsetsCompatTypeClassInfo.methods[i],
+                             windowInsetsCompatType_class, methodNames[i],
+                             "()I");
+    }
+    return jniRegisterNativeMethods(env, kGameActivityPathName, g_methods,
+                                    NELEM(g_methods));
+}
+
+// Register this method so that GameActiviy_register does not need to be called
+// manually.
+extern "C" JNIEXPORT jlong JNICALL
+Java_com_google_androidgamesdk_GameActivity_initializeNativeCode(
+    JNIEnv *env, jobject javaGameActivity,
+    jstring internalDataDir, jstring obbDir, jstring externalDataDir,
+    jobject jAssetMgr, jbyteArray savedState) {
+    GameActivity_register(env);
+    jlong nativeCode = initializeNativeCode_native(
+        env, javaGameActivity,internalDataDir, obbDir,
+        externalDataDir, jAssetMgr, savedState);
+    return nativeCode;
+}
diff --git a/third_party/android_game_activity/include/game-activity/GameActivity.h b/third_party/android_game_activity/include/game-activity/GameActivity.h
new file mode 100644
index 0000000..814a68b
--- /dev/null
+++ b/third_party/android_game_activity/include/game-activity/GameActivity.h
@@ -0,0 +1,770 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/**
+ * @addtogroup GameActivity Game Activity
+ * The interface to use GameActivity.
+ * @{
+ */
+
+/**
+ * @file GameActivity.h
+ */
+
+#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_H
+#define ANDROID_GAME_SDK_GAME_ACTIVITY_H
+
+#include <android/asset_manager.h>
+#include <android/input.h>
+#include <android/native_window.h>
+#include <android/rect.h>
+#include <jni.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "game-text-input/gametextinput.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * {@link GameActivityCallbacks}
+ */
+struct GameActivityCallbacks;
+
+/**
+ * This structure defines the native side of an android.app.GameActivity.
+ * It is created by the framework, and handed to the application's native
+ * code as it is being launched.
+ */
+typedef struct GameActivity {
+    /**
+     * Pointer to the callback function table of the native application.
+     * You can set the functions here to your own callbacks.  The callbacks
+     * pointer itself here should not be changed; it is allocated and managed
+     * for you by the framework.
+     */
+    struct GameActivityCallbacks* callbacks;
+
+    /**
+     * The global handle on the process's Java VM.
+     */
+    JavaVM* vm;
+
+    /**
+     * JNI context for the main thread of the app.  Note that this field
+     * can ONLY be used from the main thread of the process; that is, the
+     * thread that calls into the GameActivityCallbacks.
+     */
+    JNIEnv* env;
+
+    /**
+     * The GameActivity object handle.
+     */
+    jobject javaGameActivity;
+
+    /**
+     * Path to this application's internal data directory.
+     */
+    const char* internalDataPath;
+
+    /**
+     * Path to this application's external (removable/mountable) data directory.
+     */
+    const char* externalDataPath;
+
+    /**
+     * The platform's SDK version code.
+     */
+    int32_t sdkVersion;
+
+    /**
+     * This is the native instance of the application.  It is not used by
+     * the framework, but can be set by the application to its own instance
+     * state.
+     */
+    void* instance;
+
+    /**
+     * Pointer to the Asset Manager instance for the application.  The
+     * application uses this to access binary assets bundled inside its own .apk
+     * file.
+     */
+    AAssetManager* assetManager;
+
+    /**
+     * Available starting with Honeycomb: path to the directory containing
+     * the application's OBB files (if any).  If the app doesn't have any
+     * OBB files, this directory may not exist.
+     */
+    const char* obbPath;
+} GameActivity;
+
+/**
+ * The maximum number of axes supported in an Android MotionEvent.
+ * See https://developer.android.com/ndk/reference/group/input.
+ */
+#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
+
+/**
+ * \brief Describe information about a pointer, found in a
+ * GameActivityMotionEvent.
+ *
+ * You can read values directly from this structure, or use helper functions
+ * (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and
+ * `GameActivityPointerAxes_getAxisValue`).
+ *
+ * The X axis and Y axis are enabled by default but any other axis that you want
+ * to read **must** be enabled first, using
+ * `GameActivityPointerAxes_enableAxis`.
+ *
+ * \see GameActivityMotionEvent
+ */
+typedef struct GameActivityPointerAxes {
+    int32_t id;
+    float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
+    float rawX;
+    float rawY;
+} GameActivityPointerAxes;
+
+/** \brief Get the current X coordinate of the pointer. */
+inline float GameActivityPointerAxes_getX(
+    const GameActivityPointerAxes* pointerInfo) {
+    return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
+}
+
+/** \brief Get the current Y coordinate of the pointer. */
+inline float GameActivityPointerAxes_getY(
+    const GameActivityPointerAxes* pointerInfo) {
+    return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
+}
+
+/**
+ * \brief Enable the specified axis, so that its value is reported in the
+ * GameActivityPointerAxes structures stored in a motion event.
+ *
+ * You must enable any axis that you want to read, apart from
+ * `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by
+ * default.
+ *
+ * If the axis index is out of range, nothing is done.
+ */
+void GameActivityPointerAxes_enableAxis(int32_t axis);
+
+/**
+ * \brief Disable the specified axis. Its value won't be reported in the
+ * GameActivityPointerAxes structures stored in a motion event anymore.
+ *
+ * Apart from X and Y, any axis that you want to read **must** be enabled first,
+ * using `GameActivityPointerAxes_enableAxis`.
+ *
+ * If the axis index is out of range, nothing is done.
+ */
+void GameActivityPointerAxes_disableAxis(int32_t axis);
+
+/**
+ * \brief Get the value of the requested axis.
+ *
+ * Apart from X and Y, any axis that you want to read **must** be enabled first,
+ * using `GameActivityPointerAxes_enableAxis`.
+ *
+ * Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`,
+ * `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...)
+ * in https://developer.android.com/ndk/reference/group/input.
+ *
+ * @param pointerInfo The structure containing information about the pointer,
+ * obtained from GameActivityMotionEvent.
+ * @param axis The axis to get the value from
+ * @return The value of the axis, or 0 if the axis is invalid or was not
+ * enabled.
+ */
+inline float GameActivityPointerAxes_getAxisValue(
+    GameActivityPointerAxes* pointerInfo, int32_t axis) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return 0;
+    }
+
+    return pointerInfo->axisValues[axis];
+}
+
+/**
+ * The maximum number of pointers returned inside a motion event.
+ */
+#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
+#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
+    GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
+#else
+#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
+#endif
+
+/**
+ * \brief Describe a motion event that happened on the GameActivity SurfaceView.
+ *
+ * This is 1:1 mapping to the information contained in a Java `MotionEvent`
+ * (see https://developer.android.com/reference/android/view/MotionEvent).
+ */
+typedef struct GameActivityMotionEvent {
+    int32_t deviceId;
+    int32_t source;
+    int32_t action;
+
+    int64_t eventTime;
+    int64_t downTime;
+
+    int32_t flags;
+    int32_t metaState;
+
+    int32_t actionButton;
+    int32_t buttonState;
+    int32_t classification;
+    int32_t edgeFlags;
+
+    uint32_t pointerCount;
+    GameActivityPointerAxes
+        pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
+
+    float precisionX;
+    float precisionY;
+} GameActivityMotionEvent;
+
+/**
+ * \brief Describe a key event that happened on the GameActivity SurfaceView.
+ *
+ * This is 1:1 mapping to the information contained in a Java `KeyEvent`
+ * (see https://developer.android.com/reference/android/view/KeyEvent).
+ */
+typedef struct GameActivityKeyEvent {
+    int32_t deviceId;
+    int32_t source;
+    int32_t action;
+
+    int64_t eventTime;
+    int64_t downTime;
+
+    int32_t flags;
+    int32_t metaState;
+
+    int32_t modifiers;
+    int32_t repeatCount;
+    int32_t keyCode;
+    int32_t unicodeChar;
+} GameActivityKeyEvent;
+
+/**
+ * A function the user should call from their callback with the data, its length
+ * and the library- supplied context.
+ */
+typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len,
+                                            void* context);
+
+/**
+ * These are the callbacks the framework makes into a native application.
+ * All of these callbacks happen on the main thread of the application.
+ * By default, all callbacks are NULL; set to a pointer to your own function
+ * to have it called.
+ */
+typedef struct GameActivityCallbacks {
+    /**
+     * GameActivity has started.  See Java documentation for Activity.onStart()
+     * for more information.
+     */
+    void (*onStart)(GameActivity* activity);
+
+    /**
+     * GameActivity has resumed.  See Java documentation for Activity.onResume()
+     * for more information.
+     */
+    void (*onResume)(GameActivity* activity);
+
+    /**
+     * The framework is asking GameActivity to save its current instance state.
+     * See the Java documentation for Activity.onSaveInstanceState() for more
+     * information. The user should call the recallback with their data, its
+     * length and the provided context; they retain ownership of the data. Note
+     * that the saved state will be persisted, so it can not contain any active
+     * entities (pointers to memory, file descriptors, etc).
+     */
+    void (*onSaveInstanceState)(GameActivity* activity,
+                                SaveInstanceStateRecallback recallback,
+                                void* context);
+
+    /**
+     * GameActivity has paused.  See Java documentation for Activity.onPause()
+     * for more information.
+     */
+    void (*onPause)(GameActivity* activity);
+
+    /**
+     * GameActivity has stopped.  See Java documentation for Activity.onStop()
+     * for more information.
+     */
+    void (*onStop)(GameActivity* activity);
+
+    /**
+     * GameActivity is being destroyed.  See Java documentation for
+     * Activity.onDestroy() for more information.
+     */
+    void (*onDestroy)(GameActivity* activity);
+
+    /**
+     * Focus has changed in this GameActivity's window.  This is often used,
+     * for example, to pause a game when it loses input focus.
+     */
+    void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus);
+
+    /**
+     * The drawing window for this native activity has been created.  You
+     * can use the given native window object to start drawing.
+     */
+    void (*onNativeWindowCreated)(GameActivity* activity,
+                                  ANativeWindow* window);
+
+    /**
+     * The drawing window for this native activity has been resized.  You should
+     * retrieve the new size from the window and ensure that your rendering in
+     * it now matches.
+     */
+    void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window,
+                                  int32_t newWidth, int32_t newHeight);
+
+    /**
+     * The drawing window for this native activity needs to be redrawn.  To
+     * avoid transient artifacts during screen changes (such resizing after
+     * rotation), applications should not return from this function until they
+     * have finished drawing their window in its current state.
+     */
+    void (*onNativeWindowRedrawNeeded)(GameActivity* activity,
+                                       ANativeWindow* window);
+
+    /**
+     * The drawing window for this native activity is going to be destroyed.
+     * You MUST ensure that you do not touch the window object after returning
+     * from this function: in the common case of drawing to the window from
+     * another thread, that means the implementation of this callback must
+     * properly synchronize with the other thread to stop its drawing before
+     * returning from here.
+     */
+    void (*onNativeWindowDestroyed)(GameActivity* activity,
+                                    ANativeWindow* window);
+
+    /**
+     * The current device AConfiguration has changed.  The new configuration can
+     * be retrieved from assetManager.
+     */
+    void (*onConfigurationChanged)(GameActivity* activity);
+
+    /**
+     * The system is running low on memory.  Use this callback to release
+     * resources you do not need, to help the system avoid killing more
+     * important processes.
+     */
+    void (*onTrimMemory)(GameActivity* activity, int level);
+
+    /**
+     * Callback called for every MotionEvent done on the GameActivity
+     * SurfaceView. Ownership of `event` is maintained by the library and it is
+     * only valid during the callback.
+     */
+    bool (*onTouchEvent)(GameActivity* activity,
+                         const GameActivityMotionEvent* event);
+
+    /**
+     * Callback called for every key down event on the GameActivity SurfaceView.
+     * Ownership of `event` is maintained by the library and it is only valid
+     * during the callback.
+     */
+    bool (*onKeyDown)(GameActivity* activity,
+                      const GameActivityKeyEvent* event);
+
+    /**
+     * Callback called for every key up event on the GameActivity SurfaceView.
+     * Ownership of `event` is maintained by the library and it is only valid
+     * during the callback.
+     */
+    bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event);
+
+    /**
+     * Callback called for every soft-keyboard text input event.
+     * Ownership of `state` is maintained by the library and it is only valid
+     * during the callback.
+     */
+    void (*onTextInputEvent)(GameActivity* activity,
+                             const GameTextInputState* state);
+
+    /**
+     * Callback called when WindowInsets of the main app window have changed.
+     * Call GameActivity_getWindowInsets to retrieve the insets themselves.
+     */
+    void (*onWindowInsetsChanged)(GameActivity* activity);
+} GameActivityCallbacks;
+
+/**
+ * \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
+ *
+ * This is done automatically by the GameActivity: see `onTouchEvent` to set
+ * a callback to consume the received events.
+ * This function can be used if you re-implement events handling in your own
+ * activity.
+ * Ownership of out_event is maintained by the caller.
+ */
+void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
+                                      GameActivityMotionEvent* out_event);
+
+/**
+ * \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
+ *
+ * This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
+ * to set a callback to consume the received events.
+ * This function can be used if you re-implement events handling in your own
+ * activity.
+ * Ownership of out_event is maintained by the caller.
+ */
+void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
+                                   GameActivityKeyEvent* out_event);
+
+/**
+ * This is the function that must be in the native code to instantiate the
+ * application's native activity.  It is called with the activity instance (see
+ * above); if the code is being instantiated from a previously saved instance,
+ * the savedState will be non-NULL and point to the saved data.  You must make
+ * any copy of this data you need -- it will be released after you return from
+ * this function.
+ */
+typedef void GameActivity_createFunc(GameActivity* activity, void* savedState,
+                                     size_t savedStateSize);
+
+/**
+ * The name of the function that NativeInstance looks for when launching its
+ * native code.  This is the default function that is used, you can specify
+ * "android.app.func_name" string meta-data in your manifest to use a different
+ * function.
+ */
+extern GameActivity_createFunc GameActivity_onCreate;
+
+/**
+ * Finish the given activity.  Its finish() method will be called, causing it
+ * to be stopped and destroyed.  Note that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.
+ */
+void GameActivity_finish(GameActivity* activity);
+
+/**
+ * Flags for GameActivity_setWindowFlags,
+ * as per the Java API at android.view.WindowManager.LayoutParams.
+ */
+enum GameActivitySetWindowFlags {
+    /**
+     * As long as this window is visible to the user, allow the lock
+     * screen to activate while the screen is on.  This can be used
+     * independently, or in combination with {@link
+     * GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
+     * GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
+     */
+    GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
+    /** Everything behind this window will be dimmed. */
+    GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
+    /**
+     * Blur everything behind this window.
+     * @deprecated Blurring is no longer supported.
+     */
+    GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
+    /**
+     * This window won't ever get key input focus, so the
+     * user can not send key or other button events to it.  Those will
+     * instead go to whatever focusable window is behind it.  This flag
+     * will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
+     * that is explicitly set.
+     *
+     * Setting this flag also implies that the window will not need to
+     * interact with
+     * a soft input method, so it will be Z-ordered and positioned
+     * independently of any active input method (typically this means it
+     * gets Z-ordered on top of the input method, so it can use the full
+     * screen for its content and cover the input method if needed.  You
+     * can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
+     * behavior.
+     */
+    GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
+    /** This window can never receive touch events. */
+    GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
+    /**
+     * Even when this window is focusable (its
+     * {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
+     * events outside of the window to be sent to the windows behind it.
+     * Otherwise it will consume all pointer events itself, regardless of
+     * whether they are inside of the window.
+     */
+    GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
+    /**
+     * When set, if the device is asleep when the touch
+     * screen is pressed, you will receive this first touch event.  Usually
+     * the first touch event is consumed by the system since the user can
+     * not see what they are pressing on.
+     *
+     * @deprecated This flag has no effect.
+     */
+    GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+    /**
+     * As long as this window is visible to the user, keep
+     * the device's screen turned on and bright.
+     */
+    GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
+    /**
+     * Place the window within the entire screen, ignoring
+     * decorations around the border (such as the status bar).  The
+     * window must correctly position its contents to take the screen
+     * decoration into account.
+     */
+    GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
+    /** Allows the window to extend outside of the screen. */
+    GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
+    /**
+     * Hide all screen decorations (such as the status
+     * bar) while this window is displayed.  This allows the window to
+     * use the entire display space for itself -- the status bar will
+     * be hidden when an app window with this flag set is on the top
+     * layer. A fullscreen window will ignore a value of {@link
+     * GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
+     * fullscreen and will not resize.
+     */
+    GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
+    /**
+     * Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
+     * screen decorations (such as the status bar) to be shown.
+     */
+    GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
+    /**
+     * Turn on dithering when compositing this window to
+     * the screen.
+     * @deprecated This flag is no longer used.
+     */
+    GAMEACTIVITY_FLAG_DITHER = 0x00001000,
+    /**
+     * Treat the content of the window as secure, preventing
+     * it from appearing in screenshots or from being viewed on non-secure
+     * displays.
+     */
+    GAMEACTIVITY_FLAG_SECURE = 0x00002000,
+    /**
+     * A special mode where the layout parameters are used
+     * to perform scaling of the surface when it is composited to the
+     * screen.
+     */
+    GAMEACTIVITY_FLAG_SCALED = 0x00004000,
+    /**
+     * Intended for windows that will often be used when the user is
+     * holding the screen against their face, it will aggressively
+     * filter the event stream to prevent unintended presses in this
+     * situation that may not be desired for a particular window, when
+     * such an event stream is detected, the application will receive
+     * a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
+     * applications can handle this accordingly by taking no action on
+     * the event until the finger is released.
+     */
+    GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
+    /**
+     * A special option only for use in combination with
+     * {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}.  When requesting layout in
+     * the screen your window may appear on top of or behind screen decorations
+     * such as the status bar.  By also including this flag, the window
+     * manager will report the inset rectangle needed to ensure your
+     * content is not covered by screen decorations.
+     */
+    GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+    /**
+     * Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
+     * respect to how this window interacts with the current method.
+     * That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
+     * then the window will behave as if it needs to interact with the
+     * input method and thus be placed behind/away from it; if {@link
+     * GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
+     * then the window will behave as if it doesn't need to interact
+     * with the input method and can be placed to use more space and
+     * cover the input method.
+     */
+    GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+    /**
+     * If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
+     * can set this flag to receive a single special MotionEvent with
+     * the action
+     * {@link AMOTION_EVENT_ACTION_OUTSIDE} for
+     * touches that occur outside of your window.  Note that you will not
+     * receive the full down/move/up gesture, only the location of the
+     * first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
+     */
+    GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+    /**
+     * Special flag to let windows be shown when the screen
+     * is locked. This will let application windows take precedence over
+     * key guard or any other lock screens. Can be used with
+     * {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
+     * windows directly before showing the key guard window.  Can be used with
+     * {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
+     * dismisss non-secure keyguards.  This flag only applies to the top-most
+     * full-screen window.
+     */
+    GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+    /**
+     * Ask that the system wallpaper be shown behind
+     * your window.  The window surface must be translucent to be able
+     * to actually see the wallpaper behind it; this flag just ensures
+     * that the wallpaper surface will be there if this window actually
+     * has translucent regions.
+     */
+    GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
+    /**
+     * When set as a window is being added or made
+     * visible, once the window has been shown then the system will
+     * poke the power manager's user activity (as if the user had woken
+     * up the device) to turn the screen on.
+     */
+    GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
+    /**
+     * When set the window will cause the keyguard to
+     * be dismissed, only if it is not a secure lock keyguard.  Because such
+     * a keyguard is not needed for security, it will never re-appear if
+     * the user navigates to another window (in contrast to
+     * {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
+     * hide both secure and non-secure keyguards but ensure they reappear
+     * when the user moves to another UI that doesn't hide them).
+     * If the keyguard is currently active and is secure (requires an
+     * unlock pattern) than the user will still need to confirm it before
+     * seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
+     * also been set.
+     */
+    GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
+};
+
+/**
+ * Change the window flags of the given activity.  Calls getWindow().setFlags()
+ * of the given activity.
+ * Note that some flags must be set before the window decoration is created,
+ * see
+ * https://developer.android.com/reference/android/view/Window#setFlags(int,%20int).
+ * Note also that this method can be called from
+ * *any* thread; it will send a message to the main thread of the process
+ * where the Java finish call will take place.
+ */
+void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags,
+                                 uint32_t removeFlags);
+
+/**
+ * Flags for GameActivity_showSoftInput; see the Java InputMethodManager
+ * API for documentation.
+ */
+enum GameActivityShowSoftInputFlags {
+    /**
+     * Implicit request to show the input window, not as the result
+     * of a direct request by the user.
+     */
+    GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
+
+    /**
+     * The user has forced the input method open (such as by
+     * long-pressing menu) so it should not be closed until they
+     * explicitly do so.
+     */
+    GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
+};
+
+/**
+ * Show the IME while in the given activity.  Calls
+ * InputMethodManager.showSoftInput() for the given activity.  Note that this
+ * method can be called from *any* thread; it will send a message to the main
+ * thread of the process where the Java call will take place.
+ */
+void GameActivity_showSoftInput(GameActivity* activity, uint32_t flags);
+
+/**
+ * Set the text entry state (see documentation of the GameTextInputState struct
+ * in the Game Text Input library reference).
+ *
+ * Ownership of the state is maintained by the caller.
+ */
+void GameActivity_setTextInputState(GameActivity* activity,
+                                    const GameTextInputState* state);
+
+/**
+ * Get the last-received text entry state (see documentation of the
+ * GameTextInputState struct in the Game Text Input library reference).
+ *
+ */
+void GameActivity_getTextInputState(GameActivity* activity,
+                                    GameTextInputGetStateCallback callback,
+                                    void* context);
+
+/**
+ * Get a pointer to the GameTextInput library instance.
+ */
+GameTextInput* GameActivity_getTextInput(const GameActivity* activity);
+
+/**
+ * Flags for GameActivity_hideSoftInput; see the Java InputMethodManager
+ * API for documentation.
+ */
+enum GameActivityHideSoftInputFlags {
+    /**
+     * The soft input window should only be hidden if it was not
+     * explicitly shown by the user.
+     */
+    GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
+    /**
+     * The soft input window should normally be hidden, unless it was
+     * originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
+     */
+    GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
+};
+
+/**
+ * Hide the IME while in the given activity.  Calls
+ * InputMethodManager.hideSoftInput() for the given activity.  Note that this
+ * method can be called from *any* thread; it will send a message to the main
+ * thread of the process where the Java finish call will take place.
+ */
+void GameActivity_hideSoftInput(GameActivity* activity, uint32_t flags);
+
+/**
+ * Get the current window insets of the particular component. See
+ * https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
+ * for more details.
+ * You can use these insets to influence what you show on the screen.
+ */
+void GameActivity_getWindowInsets(GameActivity* activity,
+                                  GameCommonInsetsType type, ARect* insets);
+
+/**
+ * Set options on how the IME behaves when it is requested for text input.
+ * See
+ * https://developer.android.com/reference/android/view/inputmethod/EditorInfo
+ * for the meaning of inputType, actionId and imeOptions.
+ *
+ * Note that this function will attach the current thread to the JVM if it is
+ * not already attached, so the caller must detach the thread from the JVM
+ * before the thread is destroyed using DetachCurrentThread.
+ */
+void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
+                                   int actionId, int imeOptions);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif  // ANDROID_GAME_SDK_GAME_ACTIVITY_H
diff --git a/third_party/android_game_activity/include/game-activity/native_app_glue/android_native_app_glue.c b/third_party/android_game_activity/include/game-activity/native_app_glue/android_native_app_glue.c
new file mode 100644
index 0000000..282aa18
--- /dev/null
+++ b/third_party/android_game_activity/include/game-activity/native_app_glue/android_native_app_glue.c
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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 "android_native_app_glue.h"
+
+#include <android/log.h>
+#include <errno.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define LOGI(...) \
+    ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
+#define LOGE(...) \
+    ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
+#define LOGW(...) \
+    ((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__))
+#define LOGW_ONCE(...)                                        \
+    do {                                                       \
+        static bool alogw_once##__FILE__##__LINE__##__ = true; \
+        if (alogw_once##__FILE__##__LINE__##__) {              \
+            alogw_once##__FILE__##__LINE__##__ = false;        \
+            LOGW(__VA_ARGS__);                                \
+        }                                                      \
+    } while (0)
+
+/* For debug builds, always enable the debug traces in this library */
+#ifndef NDEBUG
+#define LOGV(...)                                                   \
+    ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", \
+                               __VA_ARGS__))
+#else
+#define LOGV(...) ((void)0)
+#endif
+
+static void free_saved_state(struct android_app* android_app) {
+    pthread_mutex_lock(&android_app->mutex);
+    if (android_app->savedState != NULL) {
+        free(android_app->savedState);
+        android_app->savedState = NULL;
+        android_app->savedStateSize = 0;
+    }
+    pthread_mutex_unlock(&android_app->mutex);
+}
+
+int8_t android_app_read_cmd(struct android_app* android_app) {
+    int8_t cmd;
+    if (read(android_app->msgread, &cmd, sizeof(cmd)) != sizeof(cmd)) {
+        LOGE("No data on command pipe!");
+        return -1;
+    }
+    if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app);
+    return cmd;
+}
+
+static void print_cur_config(struct android_app* android_app) {
+    char lang[2], country[2];
+    AConfiguration_getLanguage(android_app->config, lang);
+    AConfiguration_getCountry(android_app->config, country);
+
+    LOGV(
+        "Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
+        "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
+        "modetype=%d modenight=%d",
+        AConfiguration_getMcc(android_app->config),
+        AConfiguration_getMnc(android_app->config), lang[0], lang[1],
+        country[0], country[1],
+        AConfiguration_getOrientation(android_app->config),
+        AConfiguration_getTouchscreen(android_app->config),
+        AConfiguration_getDensity(android_app->config),
+        AConfiguration_getKeyboard(android_app->config),
+        AConfiguration_getNavigation(android_app->config),
+        AConfiguration_getKeysHidden(android_app->config),
+        AConfiguration_getNavHidden(android_app->config),
+        AConfiguration_getSdkVersion(android_app->config),
+        AConfiguration_getScreenSize(android_app->config),
+        AConfiguration_getScreenLong(android_app->config),
+        AConfiguration_getUiModeType(android_app->config),
+        AConfiguration_getUiModeNight(android_app->config));
+}
+
+void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
+    switch (cmd) {
+        case UNUSED_APP_CMD_INPUT_CHANGED:
+            LOGV("UNUSED_APP_CMD_INPUT_CHANGED");
+            // Do nothing. This can be used in the future to handle AInputQueue
+            // natively, like done in NativeActivity.
+            break;
+
+        case APP_CMD_INIT_WINDOW:
+            LOGV("APP_CMD_INIT_WINDOW");
+            pthread_mutex_lock(&android_app->mutex);
+            android_app->window = android_app->pendingWindow;
+            pthread_cond_broadcast(&android_app->cond);
+            pthread_mutex_unlock(&android_app->mutex);
+            break;
+
+        case APP_CMD_TERM_WINDOW:
+            LOGV("APP_CMD_TERM_WINDOW");
+            pthread_cond_broadcast(&android_app->cond);
+            break;
+
+        case APP_CMD_RESUME:
+        case APP_CMD_START:
+        case APP_CMD_PAUSE:
+        case APP_CMD_STOP:
+            LOGV("activityState=%d", cmd);
+            pthread_mutex_lock(&android_app->mutex);
+            android_app->activityState = cmd;
+            pthread_cond_broadcast(&android_app->cond);
+            pthread_mutex_unlock(&android_app->mutex);
+            break;
+
+        case APP_CMD_CONFIG_CHANGED:
+            LOGV("APP_CMD_CONFIG_CHANGED");
+            AConfiguration_fromAssetManager(
+                android_app->config, android_app->activity->assetManager);
+            print_cur_config(android_app);
+            break;
+
+        case APP_CMD_DESTROY:
+            LOGV("APP_CMD_DESTROY");
+            android_app->destroyRequested = 1;
+            break;
+    }
+}
+
+void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) {
+    switch (cmd) {
+        case APP_CMD_TERM_WINDOW:
+            LOGV("APP_CMD_TERM_WINDOW");
+            pthread_mutex_lock(&android_app->mutex);
+            android_app->window = NULL;
+            pthread_cond_broadcast(&android_app->cond);
+            pthread_mutex_unlock(&android_app->mutex);
+            break;
+
+        case APP_CMD_SAVE_STATE:
+            LOGV("APP_CMD_SAVE_STATE");
+            pthread_mutex_lock(&android_app->mutex);
+            android_app->stateSaved = 1;
+            pthread_cond_broadcast(&android_app->cond);
+            pthread_mutex_unlock(&android_app->mutex);
+            break;
+
+        case APP_CMD_RESUME:
+            free_saved_state(android_app);
+            break;
+    }
+}
+
+void app_dummy() {}
+
+static void android_app_destroy(struct android_app* android_app) {
+    LOGV("android_app_destroy!");
+    free_saved_state(android_app);
+    pthread_mutex_lock(&android_app->mutex);
+
+    AConfiguration_delete(android_app->config);
+    android_app->destroyed = 1;
+    pthread_cond_broadcast(&android_app->cond);
+    pthread_mutex_unlock(&android_app->mutex);
+    // Can't touch android_app object after this.
+}
+
+static void process_cmd(struct android_app* app,
+                        struct android_poll_source* source) {
+    int8_t cmd = android_app_read_cmd(app);
+    android_app_pre_exec_cmd(app, cmd);
+    if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
+    android_app_post_exec_cmd(app, cmd);
+}
+
+// This is run on a separate thread (i.e: not the main thread).
+static void* android_app_entry(void* param) {
+    struct android_app* android_app = (struct android_app*)param;
+
+    LOGV("android_app_entry called");
+    android_app->config = AConfiguration_new();
+    LOGV("android_app = %p", android_app);
+    LOGV("config = %p", android_app->config);
+    LOGV("activity = %p", android_app->activity);
+    LOGV("assetmanager = %p", android_app->activity->assetManager);
+    AConfiguration_fromAssetManager(android_app->config,
+                                    android_app->activity->assetManager);
+
+    print_cur_config(android_app);
+
+    android_app->cmdPollSource.id = LOOPER_ID_MAIN;
+    android_app->cmdPollSource.app = android_app;
+    android_app->cmdPollSource.process = process_cmd;
+
+    ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+    ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,
+                  ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
+    android_app->looper = looper;
+
+    pthread_mutex_lock(&android_app->mutex);
+    android_app->running = 1;
+    pthread_cond_broadcast(&android_app->cond);
+    pthread_mutex_unlock(&android_app->mutex);
+
+    android_main(android_app);
+
+    android_app_destroy(android_app);
+    return NULL;
+}
+
+// Codes from https://developer.android.com/reference/android/view/KeyEvent
+#define KEY_EVENT_KEYCODE_VOLUME_DOWN 25
+#define KEY_EVENT_KEYCODE_VOLUME_MUTE 164
+#define KEY_EVENT_KEYCODE_VOLUME_UP 24
+#define KEY_EVENT_KEYCODE_CAMERA 27
+#define KEY_EVENT_KEYCODE_ZOOM_IN 168
+#define KEY_EVENT_KEYCODE_ZOOM_OUT 169
+
+// Double-buffer the key event filter to avoid race condition.
+static bool default_key_filter(const GameActivityKeyEvent* event) {
+    // Ignore camera, volume, etc. buttons
+    return !(event->keyCode == KEY_EVENT_KEYCODE_VOLUME_DOWN ||
+             event->keyCode == KEY_EVENT_KEYCODE_VOLUME_MUTE ||
+             event->keyCode == KEY_EVENT_KEYCODE_VOLUME_UP ||
+             event->keyCode == KEY_EVENT_KEYCODE_CAMERA ||
+             event->keyCode == KEY_EVENT_KEYCODE_ZOOM_IN ||
+             event->keyCode == KEY_EVENT_KEYCODE_ZOOM_OUT);
+}
+
+// See
+// https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHSCREEN
+#define SOURCE_TOUCHSCREEN 0x00001002
+
+static bool default_motion_filter(const GameActivityMotionEvent* event) {
+    // Ignore any non-touch events.
+    return event->source == SOURCE_TOUCHSCREEN;
+}
+
+// --------------------------------------------------------------------
+// Native activity interaction (called from main thread)
+// --------------------------------------------------------------------
+
+static struct android_app* android_app_create(GameActivity* activity,
+                                              void* savedState,
+                                              size_t savedStateSize) {
+    //  struct android_app* android_app = calloc(1, sizeof(struct android_app));
+    struct android_app* android_app =
+        (struct android_app*)malloc(sizeof(struct android_app));
+    memset(android_app, 0, sizeof(struct android_app));
+    android_app->activity = activity;
+
+    pthread_mutex_init(&android_app->mutex, NULL);
+    pthread_cond_init(&android_app->cond, NULL);
+
+    if (savedState != NULL) {
+        android_app->savedState = malloc(savedStateSize);
+        android_app->savedStateSize = savedStateSize;
+        memcpy(android_app->savedState, savedState, savedStateSize);
+    }
+
+    int msgpipe[2];
+    if (pipe(msgpipe)) {
+        LOGE("could not create pipe: %s", strerror(errno));
+        return NULL;
+    }
+    android_app->msgread = msgpipe[0];
+    android_app->msgwrite = msgpipe[1];
+
+    android_app->keyEventFilter = default_key_filter;
+    android_app->motionEventFilter = default_motion_filter;
+
+    LOGV("Launching android_app_entry in a thread");
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+    pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
+
+    // Wait for thread to start.
+    pthread_mutex_lock(&android_app->mutex);
+    while (!android_app->running) {
+        pthread_cond_wait(&android_app->cond, &android_app->mutex);
+    }
+    pthread_mutex_unlock(&android_app->mutex);
+
+    return android_app;
+}
+
+static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
+    if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
+        LOGE("Failure writing android_app cmd: %s", strerror(errno));
+    }
+}
+
+static void android_app_set_window(struct android_app* android_app,
+                                   ANativeWindow* window) {
+    LOGV("android_app_set_window called");
+    pthread_mutex_lock(&android_app->mutex);
+    if (android_app->pendingWindow != NULL) {
+        android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
+    }
+    android_app->pendingWindow = window;
+    if (window != NULL) {
+        android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
+    }
+    while (android_app->window != android_app->pendingWindow) {
+        pthread_cond_wait(&android_app->cond, &android_app->mutex);
+    }
+    pthread_mutex_unlock(&android_app->mutex);
+}
+
+static void android_app_set_activity_state(struct android_app* android_app,
+                                           int8_t cmd) {
+    pthread_mutex_lock(&android_app->mutex);
+    android_app_write_cmd(android_app, cmd);
+    while (android_app->activityState != cmd) {
+        pthread_cond_wait(&android_app->cond, &android_app->mutex);
+    }
+    pthread_mutex_unlock(&android_app->mutex);
+}
+
+static void android_app_free(struct android_app* android_app) {
+    pthread_mutex_lock(&android_app->mutex);
+    android_app_write_cmd(android_app, APP_CMD_DESTROY);
+    while (!android_app->destroyed) {
+        pthread_cond_wait(&android_app->cond, &android_app->mutex);
+    }
+    pthread_mutex_unlock(&android_app->mutex);
+
+    close(android_app->msgread);
+    close(android_app->msgwrite);
+    pthread_cond_destroy(&android_app->cond);
+    pthread_mutex_destroy(&android_app->mutex);
+    free(android_app);
+}
+
+static inline struct android_app* ToApp(GameActivity* activity) {
+    return (struct android_app*)activity->instance;
+}
+
+static void onDestroy(GameActivity* activity) {
+    LOGV("Destroy: %p", activity);
+    android_app_free(ToApp(activity));
+}
+
+static void onStart(GameActivity* activity) {
+    LOGV("Start: %p", activity);
+    android_app_set_activity_state(ToApp(activity), APP_CMD_START);
+}
+
+static void onResume(GameActivity* activity) {
+    LOGV("Resume: %p", activity);
+    android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME);
+}
+
+static void onSaveInstanceState(GameActivity* activity,
+                                SaveInstanceStateRecallback recallback,
+                                void* context) {
+    LOGV("SaveInstanceState: %p", activity);
+
+    struct android_app* android_app = ToApp(activity);
+    void* savedState = NULL;
+    pthread_mutex_lock(&android_app->mutex);
+    android_app->stateSaved = 0;
+    android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
+    while (!android_app->stateSaved) {
+        pthread_cond_wait(&android_app->cond, &android_app->mutex);
+    }
+
+    if (android_app->savedState != NULL) {
+        // Tell the Java side about our state.
+        recallback((const char*)android_app->savedState,
+                   android_app->savedStateSize, context);
+        // Now we can free it.
+        free(android_app->savedState);
+        android_app->savedState = NULL;
+        android_app->savedStateSize = 0;
+    }
+
+    pthread_mutex_unlock(&android_app->mutex);
+}
+
+static void onPause(GameActivity* activity) {
+    LOGV("Pause: %p", activity);
+    android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE);
+}
+
+static void onStop(GameActivity* activity) {
+    LOGV("Stop: %p", activity);
+    android_app_set_activity_state(ToApp(activity), APP_CMD_STOP);
+}
+
+static void onConfigurationChanged(GameActivity* activity) {
+    LOGV("ConfigurationChanged: %p", activity);
+    android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED);
+}
+
+static void onTrimMemory(GameActivity* activity, int level) {
+    LOGV("TrimMemory: %p %d", activity, level);
+    android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY);
+}
+
+static void onWindowFocusChanged(GameActivity* activity, bool focused) {
+    LOGV("WindowFocusChanged: %p -- %d", activity, focused);
+    android_app_write_cmd(ToApp(activity),
+                          focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
+}
+
+static void onNativeWindowCreated(GameActivity* activity,
+                                  ANativeWindow* window) {
+    LOGV("NativeWindowCreated: %p -- %p", activity, window);
+    android_app_set_window(ToApp(activity), window);
+}
+
+static void onNativeWindowDestroyed(GameActivity* activity,
+                                    ANativeWindow* window) {
+    LOGV("NativeWindowDestroyed: %p -- %p", activity, window);
+    android_app_set_window(ToApp(activity), NULL);
+}
+
+static void onNativeWindowRedrawNeeded(GameActivity* activity,
+                                       ANativeWindow* window) {
+    LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window);
+    android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED);
+}
+
+static void onNativeWindowResized(GameActivity* activity, ANativeWindow* window,
+                                  int32_t width, int32_t height) {
+    LOGV("NativeWindowResized: %p -- %p ( %d x %d )", activity, window, width,
+         height);
+    android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED);
+}
+
+void android_app_set_motion_event_filter(struct android_app* app,
+                                         android_motion_event_filter filter) {
+    pthread_mutex_lock(&app->mutex);
+    app->motionEventFilter = filter;
+    pthread_mutex_unlock(&app->mutex);
+}
+
+static bool onTouchEvent(GameActivity* activity,
+                         const GameActivityMotionEvent* event) {
+    struct android_app* android_app = ToApp(activity);
+    pthread_mutex_lock(&android_app->mutex);
+
+    if (android_app->motionEventFilter != NULL &&
+        !android_app->motionEventFilter(event)) {
+        pthread_mutex_unlock(&android_app->mutex);
+        return false;
+    }
+
+    struct android_input_buffer* inputBuffer =
+        &android_app->inputBuffers[android_app->currentInputBuffer];
+
+    // Add to the list of active motion events
+    if (inputBuffer->motionEventsCount <
+        NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS) {
+        int new_ix = inputBuffer->motionEventsCount;
+        memcpy(&inputBuffer->motionEvents[new_ix], event,
+               sizeof(GameActivityMotionEvent));
+        ++inputBuffer->motionEventsCount;
+    } else {
+        LOGW_ONCE("Motion event will be dropped because the number of unconsumed motion"
+             " events exceeded NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS (%d). Consider setting"
+             " NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE to a larger value",
+             NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS);
+    }
+    pthread_mutex_unlock(&android_app->mutex);
+    return true;
+}
+
+struct android_input_buffer* android_app_swap_input_buffers(
+    struct android_app* android_app) {
+    pthread_mutex_lock(&android_app->mutex);
+
+    struct android_input_buffer* inputBuffer =
+        &android_app->inputBuffers[android_app->currentInputBuffer];
+
+    if (inputBuffer->motionEventsCount == 0 &&
+        inputBuffer->keyEventsCount == 0) {
+        inputBuffer = NULL;
+    } else {
+        android_app->currentInputBuffer =
+            (android_app->currentInputBuffer + 1) %
+            NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
+    }
+
+    pthread_mutex_unlock(&android_app->mutex);
+
+    return inputBuffer;
+}
+
+void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) {
+    inputBuffer->motionEventsCount = 0;
+}
+
+void android_app_set_key_event_filter(struct android_app* app,
+                                      android_key_event_filter filter) {
+    pthread_mutex_lock(&app->mutex);
+    app->keyEventFilter = filter;
+    pthread_mutex_unlock(&app->mutex);
+}
+
+static bool onKey(GameActivity* activity, const GameActivityKeyEvent* event) {
+    struct android_app* android_app = ToApp(activity);
+    pthread_mutex_lock(&android_app->mutex);
+
+    if (android_app->keyEventFilter != NULL &&
+        !android_app->keyEventFilter(event)) {
+        pthread_mutex_unlock(&android_app->mutex);
+        return false;
+    }
+
+    struct android_input_buffer* inputBuffer =
+        &android_app->inputBuffers[android_app->currentInputBuffer];
+
+    // Add to the list of active key down events
+    if (inputBuffer->keyEventsCount < NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS) {
+        int new_ix = inputBuffer->keyEventsCount;
+        memcpy(&inputBuffer->keyEvents[new_ix], event,
+               sizeof(GameActivityKeyEvent));
+        ++inputBuffer->keyEventsCount;
+    } else {
+        LOGW_ONCE("Key event will be dropped because the number of unconsumed key events exceeded"
+             " NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS (%d). Consider setting"
+             " NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE to a larger value",
+             NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS);
+    }
+
+    pthread_mutex_unlock(&android_app->mutex);
+    return true;
+}
+
+void android_app_clear_key_events(struct android_input_buffer* inputBuffer) {
+    inputBuffer->keyEventsCount = 0;
+}
+
+static void onTextInputEvent(GameActivity* activity,
+                             const GameTextInputState* state) {
+    struct android_app* android_app = ToApp(activity);
+    pthread_mutex_lock(&android_app->mutex);
+
+    android_app->textInputState = 1;
+    pthread_mutex_unlock(&android_app->mutex);
+}
+
+static void onWindowInsetsChanged(GameActivity* activity) {
+    LOGV("WindowInsetsChanged: %p", activity);
+    android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED);
+}
+
+JNIEXPORT
+void GameActivity_onCreate(GameActivity* activity, void* savedState,
+                           size_t savedStateSize) {
+    LOGV("Creating: %p", activity);
+    activity->callbacks->onDestroy = onDestroy;
+    activity->callbacks->onStart = onStart;
+    activity->callbacks->onResume = onResume;
+    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
+    activity->callbacks->onPause = onPause;
+    activity->callbacks->onStop = onStop;
+    activity->callbacks->onTouchEvent = onTouchEvent;
+    activity->callbacks->onKeyDown = onKey;
+    activity->callbacks->onKeyUp = onKey;
+    activity->callbacks->onTextInputEvent = onTextInputEvent;
+    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
+    activity->callbacks->onTrimMemory = onTrimMemory;
+    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
+    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
+    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
+    activity->callbacks->onNativeWindowRedrawNeeded =
+        onNativeWindowRedrawNeeded;
+    activity->callbacks->onNativeWindowResized = onNativeWindowResized;
+    activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged;
+    LOGV("Callbacks set: %p", activity->callbacks);
+
+    activity->instance =
+        android_app_create(activity, savedState, savedStateSize);
+}
diff --git a/third_party/android_game_activity/include/game-activity/native_app_glue/android_native_app_glue.h b/third_party/android_game_activity/include/game-activity/native_app_glue/android_native_app_glue.h
new file mode 100644
index 0000000..9dfe3a5
--- /dev/null
+++ b/third_party/android_game_activity/include/game-activity/native_app_glue/android_native_app_glue.h
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+/**
+ * @addtogroup android_native_app_glue Native App Glue library
+ * The glue library to interface your game loop with GameActivity.
+ * @{
+ */
+
+#include <android/configuration.h>
+#include <android/looper.h>
+#include <poll.h>
+#include <pthread.h>
+#include <sched.h>
+
+#include "game-activity/GameActivity.h"
+
+#if (defined NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE)
+#define NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS \
+    NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE
+#else
+#define NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS 16
+#endif
+
+#if (defined NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE)
+#define NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS \
+    NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE
+#else
+#define NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS 4
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The GameActivity interface provided by <game-activity/GameActivity.h>
+ * is based on a set of application-provided callbacks that will be called
+ * by the Activity's main thread when certain events occur.
+ *
+ * This means that each one of this callbacks _should_ _not_ block, or they
+ * risk having the system force-close the application. This programming
+ * model is direct, lightweight, but constraining.
+ *
+ * The 'android_native_app_glue' static library is used to provide a different
+ * execution model where the application can implement its own main event
+ * loop in a different thread instead. Here's how it works:
+ *
+ * 1/ The application must provide a function named "android_main()" that
+ *    will be called when the activity is created, in a new thread that is
+ *    distinct from the activity's main thread.
+ *
+ * 2/ android_main() receives a pointer to a valid "android_app" structure
+ *    that contains references to other important objects, e.g. the
+ *    GameActivity obejct instance the application is running in.
+ *
+ * 3/ the "android_app" object holds an ALooper instance that already
+ *    listens to activity lifecycle events (e.g. "pause", "resume").
+ *    See APP_CMD_XXX declarations below.
+ *
+ *    This corresponds to an ALooper identifier returned by
+ *    ALooper_pollOnce with value LOOPER_ID_MAIN.
+ *
+ *    Your application can use the same ALooper to listen to additional
+ *    file-descriptors.  They can either be callback based, or with return
+ *    identifiers starting with LOOPER_ID_USER.
+ *
+ * 4/ Whenever you receive a LOOPER_ID_MAIN event,
+ *    the returned data will point to an android_poll_source structure.  You
+ *    can call the process() function on it, and fill in android_app->onAppCmd
+ *    to be called for your own processing of the event.
+ *
+ *    Alternatively, you can call the low-level functions to read and process
+ *    the data directly...  look at the process_cmd() and process_input()
+ *    implementations in the glue to see how to do this.
+ *
+ * See the sample named "native-activity" that comes with the NDK with a
+ * full usage example.  Also look at the documentation of GameActivity.
+ */
+
+struct android_app;
+
+/**
+ * Data associated with an ALooper fd that will be returned as the "outData"
+ * when that source has data ready.
+ */
+struct android_poll_source {
+    /**
+     * The identifier of this source.  May be LOOPER_ID_MAIN or
+     * LOOPER_ID_INPUT.
+     */
+    int32_t id;
+
+    /** The android_app this ident is associated with. */
+    struct android_app* app;
+
+    /**
+     * Function to call to perform the standard processing of data from
+     * this source.
+     */
+    void (*process)(struct android_app* app,
+                    struct android_poll_source* source);
+};
+
+struct android_input_buffer {
+    /**
+     * Pointer to a read-only array of pointers to GameActivityMotionEvent.
+     * Only the first motionEventsCount events are valid.
+     */
+    GameActivityMotionEvent motionEvents[NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS];
+
+    /**
+     * The number of valid motion events in `motionEvents`.
+     */
+    uint64_t motionEventsCount;
+
+    /**
+     * Pointer to a read-only array of pointers to GameActivityKeyEvent.
+     * Only the first keyEventsCount events are valid.
+     */
+    GameActivityKeyEvent keyEvents[NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS];
+
+    /**
+     * The number of valid "Key" events in `keyEvents`.
+     */
+    uint64_t keyEventsCount;
+};
+
+/**
+ * Function pointer declaration for the filtering of key events.
+ * A function with this signature should be passed to
+ * android_app_set_key_event_filter and return false for any events that should
+ * not be handled by android_native_app_glue. These events will be handled by
+ * the system instead.
+ */
+typedef bool (*android_key_event_filter)(const GameActivityKeyEvent*);
+
+/**
+ * Function pointer definition for the filtering of motion events.
+ * A function with this signature should be passed to
+ * android_app_set_motion_event_filter and return false for any events that
+ * should not be handled by android_native_app_glue. These events will be
+ * handled by the system instead.
+ */
+typedef bool (*android_motion_event_filter)(const GameActivityMotionEvent*);
+
+/**
+ * This is the interface for the standard glue code of a threaded
+ * application.  In this model, the application's code is running
+ * in its own thread separate from the main thread of the process.
+ * It is not required that this thread be associated with the Java
+ * VM, although it will need to be in order to make JNI calls any
+ * Java objects.
+ */
+struct android_app {
+    /**
+     * An optional pointer to application-defined state.
+     */
+    void* userData;
+
+    /**
+     * A required callback for processing main app commands (`APP_CMD_*`).
+     * This is called each frame if there are app commands that need processing.
+     */
+    void (*onAppCmd)(struct android_app* app, int32_t cmd);
+
+    /** The GameActivity object instance that this app is running in. */
+    GameActivity* activity;
+
+    /** The current configuration the app is running in. */
+    AConfiguration* config;
+
+    /**
+     * The last activity saved state, as provided at creation time.
+     * It is NULL if there was no state.  You can use this as you need; the
+     * memory will remain around until you call android_app_exec_cmd() for
+     * APP_CMD_RESUME, at which point it will be freed and savedState set to
+     * NULL. These variables should only be changed when processing a
+     * APP_CMD_SAVE_STATE, at which point they will be initialized to NULL and
+     * you can malloc your state and place the information here.  In that case
+     * the memory will be freed for you later.
+     */
+    void* savedState;
+
+    /**
+     * The size of the activity saved state. It is 0 if `savedState` is NULL.
+     */
+    size_t savedStateSize;
+
+    /** The ALooper associated with the app's thread. */
+    ALooper* looper;
+
+    /** When non-NULL, this is the window surface that the app can draw in. */
+    ANativeWindow* window;
+
+    /**
+     * Current content rectangle of the window; this is the area where the
+     * window's content should be placed to be seen by the user.
+     */
+    ARect contentRect;
+
+    /**
+     * Current state of the app's activity.  May be either APP_CMD_START,
+     * APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
+     */
+    int activityState;
+
+    /**
+     * This is non-zero when the application's GameActivity is being
+     * destroyed and waiting for the app thread to complete.
+     */
+    int destroyRequested;
+
+#define NATIVE_APP_GLUE_MAX_INPUT_BUFFERS 2
+
+    /**
+     * This is used for buffering input from GameActivity. Once ready, the
+     * application thread switches the buffers and processes what was
+     * accumulated.
+     */
+    struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS];
+
+    int currentInputBuffer;
+
+    /**
+     * 0 if no text input event is outstanding, 1 if it is.
+     * Use `GameActivity_getTextInputState` to get information
+     * about the text entered by the user.
+     */
+    int textInputState;
+
+    // Below are "private" implementation of the glue code.
+    /** @cond INTERNAL */
+
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+
+    int msgread;
+    int msgwrite;
+
+    pthread_t thread;
+
+    struct android_poll_source cmdPollSource;
+
+    int running;
+    int stateSaved;
+    int destroyed;
+    int redrawNeeded;
+    ANativeWindow* pendingWindow;
+    ARect pendingContentRect;
+
+    android_key_event_filter keyEventFilter;
+    android_motion_event_filter motionEventFilter;
+
+    /** @endcond */
+};
+
+/**
+ * Looper ID of commands coming from the app's main thread, an AInputQueue or
+ * user-defined sources.
+ */
+enum NativeAppGlueLooperId {
+    /**
+     * Looper data ID of commands coming from the app's main thread, which
+     * is returned as an identifier from ALooper_pollOnce().  The data for this
+     * identifier is a pointer to an android_poll_source structure.
+     * These can be retrieved and processed with android_app_read_cmd()
+     * and android_app_exec_cmd().
+     */
+    LOOPER_ID_MAIN = 1,
+
+    /**
+     * Unused. Reserved for future use when usage of AInputQueue will be
+     * supported.
+     */
+    LOOPER_ID_INPUT = 2,
+
+    /**
+     * Start of user-defined ALooper identifiers.
+     */
+    LOOPER_ID_USER = 3,
+};
+
+/**
+ * Commands passed from the application's main Java thread to the game's thread.
+ */
+enum NativeAppGlueAppCmd {
+    /**
+     * Unused. Reserved for future use when usage of AInputQueue will be
+     * supported.
+     */
+    UNUSED_APP_CMD_INPUT_CHANGED,
+
+    /**
+     * Command from main thread: a new ANativeWindow is ready for use.  Upon
+     * receiving this command, android_app->window will contain the new window
+     * surface.
+     */
+    APP_CMD_INIT_WINDOW,
+
+    /**
+     * Command from main thread: the existing ANativeWindow needs to be
+     * terminated.  Upon receiving this command, android_app->window still
+     * contains the existing window; after calling android_app_exec_cmd
+     * it will be set to NULL.
+     */
+    APP_CMD_TERM_WINDOW,
+
+    /**
+     * Command from main thread: the current ANativeWindow has been resized.
+     * Please redraw with its new size.
+     */
+    APP_CMD_WINDOW_RESIZED,
+
+    /**
+     * Command from main thread: the system needs that the current ANativeWindow
+     * be redrawn.  You should redraw the window before handing this to
+     * android_app_exec_cmd() in order to avoid transient drawing glitches.
+     */
+    APP_CMD_WINDOW_REDRAW_NEEDED,
+
+    /**
+     * Command from main thread: the content area of the window has changed,
+     * such as from the soft input window being shown or hidden.  You can
+     * find the new content rect in android_app::contentRect.
+     */
+    APP_CMD_CONTENT_RECT_CHANGED,
+
+    /**
+     * Command from main thread: the app's activity window has gained
+     * input focus.
+     */
+    APP_CMD_GAINED_FOCUS,
+
+    /**
+     * Command from main thread: the app's activity window has lost
+     * input focus.
+     */
+    APP_CMD_LOST_FOCUS,
+
+    /**
+     * Command from main thread: the current device configuration has changed.
+     */
+    APP_CMD_CONFIG_CHANGED,
+
+    /**
+     * Command from main thread: the system is running low on memory.
+     * Try to reduce your memory use.
+     */
+    APP_CMD_LOW_MEMORY,
+
+    /**
+     * Command from main thread: the app's activity has been started.
+     */
+    APP_CMD_START,
+
+    /**
+     * Command from main thread: the app's activity has been resumed.
+     */
+    APP_CMD_RESUME,
+
+    /**
+     * Command from main thread: the app should generate a new saved state
+     * for itself, to restore from later if needed.  If you have saved state,
+     * allocate it with malloc and place it in android_app.savedState with
+     * the size in android_app.savedStateSize.  The will be freed for you
+     * later.
+     */
+    APP_CMD_SAVE_STATE,
+
+    /**
+     * Command from main thread: the app's activity has been paused.
+     */
+    APP_CMD_PAUSE,
+
+    /**
+     * Command from main thread: the app's activity has been stopped.
+     */
+    APP_CMD_STOP,
+
+    /**
+     * Command from main thread: the app's activity is being destroyed,
+     * and waiting for the app thread to clean up and exit before proceeding.
+     */
+    APP_CMD_DESTROY,
+
+    /**
+     * Command from main thread: the app's insets have changed.
+     */
+    APP_CMD_WINDOW_INSETS_CHANGED,
+
+};
+
+/**
+ * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
+ * app command message.
+ */
+int8_t android_app_read_cmd(struct android_app* android_app);
+
+/**
+ * Call with the command returned by android_app_read_cmd() to do the
+ * initial pre-processing of the given command.  You can perform your own
+ * actions for the command after calling this function.
+ */
+void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd);
+
+/**
+ * Call with the command returned by android_app_read_cmd() to do the
+ * final post-processing of the given command.  You must have done your own
+ * actions for the command before calling this function.
+ */
+void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
+
+/**
+ * Call this before processing input events to get the events buffer.
+ * The function returns NULL if there are no events to process.
+ */
+struct android_input_buffer* android_app_swap_input_buffers(
+    struct android_app* android_app);
+
+/**
+ * Clear the array of motion events that were waiting to be handled, and release
+ * each of them.
+ *
+ * This method should be called after you have processed the motion events in
+ * your game loop. You should handle events at each iteration of your game loop.
+ */
+void android_app_clear_motion_events(struct android_input_buffer* inputBuffer);
+
+/**
+ * Clear the array of key events that were waiting to be handled, and release
+ * each of them.
+ *
+ * This method should be called after you have processed the key up events in
+ * your game loop. You should handle events at each iteration of your game loop.
+ */
+void android_app_clear_key_events(struct android_input_buffer* inputBuffer);
+
+/**
+ * This is the function that application code must implement, representing
+ * the main entry to the app.
+ */
+extern void android_main(struct android_app* app);
+
+/**
+ * Set the filter to use when processing key events.
+ * Any events for which the filter returns false will be ignored by
+ * android_native_app_glue. If filter is set to NULL, no filtering is done.
+ *
+ * The default key filter will filter out volume and camera button presses.
+ */
+void android_app_set_key_event_filter(struct android_app* app,
+                                      android_key_event_filter filter);
+
+/**
+ * Set the filter to use when processing touch and motion events.
+ * Any events for which the filter returns false will be ignored by
+ * android_native_app_glue. If filter is set to NULL, no filtering is done.
+ *
+ * Note that the default motion event filter will only allow touchscreen events
+ * through, in order to mimic NativeActivity's behaviour, so for controller
+ * events to be passed to the app, set the filter to NULL.
+ */
+void android_app_set_motion_event_filter(struct android_app* app,
+                                         android_motion_event_filter filter);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
diff --git a/third_party/android_game_activity/include/game-text-input/gamecommon.h b/third_party/android_game_activity/include/game-text-input/gamecommon.h
new file mode 100644
index 0000000..38bffff
--- /dev/null
+++ b/third_party/android_game_activity/include/game-text-input/gamecommon.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup game_common Game Common
+ * Common structures and functions used within AGDK
+ * @{
+ */
+
+#pragma once
+
+/**
+ * The type of a component for which to retrieve insets. See
+ * https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
+ */
+typedef enum GameCommonInsetsType {
+    GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0,
+    GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT,
+    GAMECOMMON_INSETS_TYPE_IME,
+    GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES,
+    GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS,
+    GAMECOMMON_INSETS_TYPE_STATUS_BARS,
+    GAMECOMMON_INSETS_TYPE_SYSTEM_BARS,
+    GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES,
+    GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT,
+    GAMECOMMON_INSETS_TYPE_WATERFALL,
+    GAMECOMMON_INSETS_TYPE_COUNT
+} GameCommonInsetsType;
diff --git a/third_party/android_game_activity/include/game-text-input/gametextinput.cpp b/third_party/android_game_activity/include/game-text-input/gametextinput.cpp
new file mode 100644
index 0000000..6a39943
--- /dev/null
+++ b/third_party/android_game_activity/include/game-text-input/gametextinput.cpp
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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 "game-text-input/gametextinput.h"
+
+#include <android/log.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#define LOG_TAG "GameTextInput"
+
+static constexpr int32_t DEFAULT_MAX_STRING_SIZE = 1 << 16;
+
+// Cache of field ids in the Java GameTextInputState class
+struct StateClassInfo {
+    jfieldID text;
+    jfieldID selectionStart;
+    jfieldID selectionEnd;
+    jfieldID composingRegionStart;
+    jfieldID composingRegionEnd;
+};
+
+// Main GameTextInput object.
+struct GameTextInput {
+   public:
+    GameTextInput(JNIEnv *env, uint32_t max_string_size);
+    ~GameTextInput();
+    void setState(const GameTextInputState &state);
+    const GameTextInputState &getState() const { return currentState_; }
+    void setInputConnection(jobject inputConnection);
+    void processEvent(jobject textInputEvent);
+    void showIme(uint32_t flags);
+    void hideIme(uint32_t flags);
+    void setEventCallback(GameTextInputEventCallback callback, void *context);
+    jobject stateToJava(const GameTextInputState &state) const;
+    void stateFromJava(jobject textInputEvent,
+                       GameTextInputGetStateCallback callback,
+                       void *context) const;
+    void setImeInsetsCallback(GameTextInputImeInsetsCallback callback,
+                              void *context);
+    void processImeInsets(const ARect *insets);
+    const ARect &getImeInsets() const { return currentInsets_; }
+
+   private:
+    // Copy string and set other fields
+    void setStateInner(const GameTextInputState &state);
+    static void processCallback(void *context, const GameTextInputState *state);
+    JNIEnv *env_ = nullptr;
+    // Cached at initialization from
+    // com/google/androidgamesdk/gametextinput/State.
+    jclass stateJavaClass_ = nullptr;
+    // The latest text input update.
+    GameTextInputState currentState_ = {};
+    // An instance of gametextinput.InputConnection.
+    jclass inputConnectionClass_ = nullptr;
+    jobject inputConnection_ = nullptr;
+    jmethodID inputConnectionSetStateMethod_;
+    jmethodID setSoftKeyboardActiveMethod_;
+    void (*eventCallback_)(void *context,
+                           const struct GameTextInputState *state) = nullptr;
+    void *eventCallbackContext_ = nullptr;
+    void (*insetsCallback_)(void *context,
+                            const struct ARect *insets) = nullptr;
+    ARect currentInsets_ = {};
+    void *insetsCallbackContext_ = nullptr;
+    StateClassInfo stateClassInfo_ = {};
+    // Constant-sized buffer used to store state text.
+    std::vector<char> stateStringBuffer_;
+};
+
+std::unique_ptr<GameTextInput> s_gameTextInput;
+
+extern "C" {
+
+///////////////////////////////////////////////////////////
+/// GameTextInputState C Functions
+///////////////////////////////////////////////////////////
+
+// Convert to a Java structure.
+jobject currentState_toJava(const GameTextInput *gameTextInput,
+                            const GameTextInputState *state) {
+    if (state == nullptr) return NULL;
+    return gameTextInput->stateToJava(*state);
+}
+
+// Convert from Java structure.
+void currentState_fromJava(const GameTextInput *gameTextInput,
+                           jobject textInputEvent,
+                           GameTextInputGetStateCallback callback,
+                           void *context) {
+    gameTextInput->stateFromJava(textInputEvent, callback, context);
+}
+
+///////////////////////////////////////////////////////////
+/// GameTextInput C Functions
+///////////////////////////////////////////////////////////
+
+struct GameTextInput *GameTextInput_init(JNIEnv *env,
+                                         uint32_t max_string_size) {
+    if (s_gameTextInput.get() != nullptr) {
+        __android_log_print(ANDROID_LOG_WARN, LOG_TAG,
+                            "Warning: called GameTextInput_init twice without "
+                            "calling GameTextInput_destroy");
+        return s_gameTextInput.get();
+    }
+    // Don't use make_unique, for C++11 compatibility
+    s_gameTextInput =
+        std::unique_ptr<GameTextInput>(new GameTextInput(env, max_string_size));
+    return s_gameTextInput.get();
+}
+
+void GameTextInput_destroy(GameTextInput *input) {
+    if (input == nullptr || s_gameTextInput.get() == nullptr) return;
+    s_gameTextInput.reset();
+}
+
+void GameTextInput_setState(GameTextInput *input,
+                            const GameTextInputState *state) {
+    if (state == nullptr) return;
+    input->setState(*state);
+}
+
+void GameTextInput_getState(GameTextInput *input,
+                            GameTextInputGetStateCallback callback,
+                            void *context) {
+    callback(context, &input->getState());
+}
+
+void GameTextInput_setInputConnection(GameTextInput *input,
+                                      jobject inputConnection) {
+    input->setInputConnection(inputConnection);
+}
+
+void GameTextInput_processEvent(GameTextInput *input, jobject textInputEvent) {
+    input->processEvent(textInputEvent);
+}
+
+void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets) {
+    input->processImeInsets(insets);
+}
+
+void GameTextInput_showIme(struct GameTextInput *input, uint32_t flags) {
+    input->showIme(flags);
+}
+
+void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) {
+    input->hideIme(flags);
+}
+
+void GameTextInput_setEventCallback(struct GameTextInput *input,
+                                    GameTextInputEventCallback callback,
+                                    void *context) {
+    input->setEventCallback(callback, context);
+}
+
+void GameTextInput_setImeInsetsCallback(struct GameTextInput *input,
+                                        GameTextInputImeInsetsCallback callback,
+                                        void *context) {
+    input->setImeInsetsCallback(callback, context);
+}
+
+void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets) {
+    *insets = input->getImeInsets();
+}
+
+}  // extern "C"
+
+///////////////////////////////////////////////////////////
+/// GameTextInput C++ class Implementation
+///////////////////////////////////////////////////////////
+
+GameTextInput::GameTextInput(JNIEnv *env, uint32_t max_string_size)
+    : env_(env),
+      stateStringBuffer_(max_string_size == 0 ? DEFAULT_MAX_STRING_SIZE
+                                              : max_string_size) {
+    stateJavaClass_ = (jclass)env_->NewGlobalRef(
+        env_->FindClass("com/google/androidgamesdk/gametextinput/State"));
+    inputConnectionClass_ = (jclass)env_->NewGlobalRef(env_->FindClass(
+        "com/google/androidgamesdk/gametextinput/InputConnection"));
+    inputConnectionSetStateMethod_ =
+        env_->GetMethodID(inputConnectionClass_, "setState",
+                          "(Lcom/google/androidgamesdk/gametextinput/State;)V");
+    setSoftKeyboardActiveMethod_ = env_->GetMethodID(
+        inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V");
+
+    stateClassInfo_.text =
+        env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
+    stateClassInfo_.selectionStart =
+        env_->GetFieldID(stateJavaClass_, "selectionStart", "I");
+    stateClassInfo_.selectionEnd =
+        env_->GetFieldID(stateJavaClass_, "selectionEnd", "I");
+    stateClassInfo_.composingRegionStart =
+        env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
+    stateClassInfo_.composingRegionEnd =
+        env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
+}
+
+GameTextInput::~GameTextInput() {
+    if (stateJavaClass_ != NULL) {
+        env_->DeleteGlobalRef(stateJavaClass_);
+        stateJavaClass_ = NULL;
+    }
+    if (inputConnectionClass_ != NULL) {
+        env_->DeleteGlobalRef(inputConnectionClass_);
+        inputConnectionClass_ = NULL;
+    }
+    if (inputConnection_ != NULL) {
+        env_->DeleteGlobalRef(inputConnection_);
+        inputConnection_ = NULL;
+    }
+}
+
+void GameTextInput::setState(const GameTextInputState &state) {
+    if (inputConnection_ == nullptr) return;
+    jobject jstate = stateToJava(state);
+    env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_,
+                         jstate);
+    env_->DeleteLocalRef(jstate);
+    setStateInner(state);
+}
+
+void GameTextInput::setStateInner(const GameTextInputState &state) {
+    // Check if we're setting using our own string (other parts may be
+    // different)
+    if (state.text_UTF8 == currentState_.text_UTF8) {
+        currentState_ = state;
+        return;
+    }
+    // Otherwise, copy across the string.
+    auto bytes_needed =
+        std::min(static_cast<uint32_t>(state.text_length + 1),
+                 static_cast<uint32_t>(stateStringBuffer_.size()));
+    currentState_.text_UTF8 = stateStringBuffer_.data();
+    std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1,
+              stateStringBuffer_.data());
+    currentState_.text_length = state.text_length;
+    currentState_.selection = state.selection;
+    currentState_.composingRegion = state.composingRegion;
+    stateStringBuffer_[bytes_needed - 1] = 0;
+}
+
+void GameTextInput::setInputConnection(jobject inputConnection) {
+    if (inputConnection_ != NULL) {
+        env_->DeleteGlobalRef(inputConnection_);
+    }
+    inputConnection_ = env_->NewGlobalRef(inputConnection);
+}
+
+/*static*/ void GameTextInput::processCallback(
+    void *context, const GameTextInputState *state) {
+    auto thiz = static_cast<GameTextInput *>(context);
+    if (state != nullptr) thiz->setStateInner(*state);
+}
+
+void GameTextInput::processEvent(jobject textInputEvent) {
+    stateFromJava(textInputEvent, processCallback, this);
+    if (eventCallback_) {
+        eventCallback_(eventCallbackContext_, &currentState_);
+    }
+}
+
+void GameTextInput::showIme(uint32_t flags) {
+    if (inputConnection_ == nullptr) return;
+    env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true,
+                         flags);
+}
+
+void GameTextInput::setEventCallback(GameTextInputEventCallback callback,
+                                     void *context) {
+    eventCallback_ = callback;
+    eventCallbackContext_ = context;
+}
+
+void GameTextInput::setImeInsetsCallback(
+    GameTextInputImeInsetsCallback callback, void *context) {
+    insetsCallback_ = callback;
+    insetsCallbackContext_ = context;
+}
+
+void GameTextInput::processImeInsets(const ARect *insets) {
+    currentInsets_ = *insets;
+    if (insetsCallback_) {
+        insetsCallback_(insetsCallbackContext_, &currentInsets_);
+    }
+}
+
+void GameTextInput::hideIme(uint32_t flags) {
+    if (inputConnection_ == nullptr) return;
+    env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
+                         flags);
+}
+
+jobject GameTextInput::stateToJava(const GameTextInputState &state) const {
+    static jmethodID constructor = nullptr;
+    if (constructor == nullptr) {
+        constructor = env_->GetMethodID(stateJavaClass_, "<init>",
+                                        "(Ljava/lang/String;IIII)V");
+        if (constructor == nullptr) {
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                                "Can't find gametextinput.State constructor");
+            return nullptr;
+        }
+    }
+    const char *text = state.text_UTF8;
+    if (text == nullptr) {
+        static char empty_string[] = "";
+        text = empty_string;
+    }
+    // Note that this expects 'modified' UTF-8 which is not the same as UTF-8
+    // https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
+    jstring jtext = env_->NewStringUTF(text);
+    jobject jobj =
+        env_->NewObject(stateJavaClass_, constructor, jtext,
+                        state.selection.start, state.selection.end,
+                        state.composingRegion.start, state.composingRegion.end);
+    env_->DeleteLocalRef(jtext);
+    return jobj;
+}
+
+void GameTextInput::stateFromJava(jobject textInputEvent,
+                                  GameTextInputGetStateCallback callback,
+                                  void *context) const {
+    jstring text =
+        (jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text);
+    // Note this is 'modified' UTF-8, not true UTF-8. It has no NULLs in it,
+    // except at the end. It's actually not specified whether the value returned
+    // by GetStringUTFChars includes a null at the end, but it *seems to* on
+    // Android.
+    const char *text_chars = env_->GetStringUTFChars(text, NULL);
+    int text_len = env_->GetStringUTFLength(
+        text);  // Length in bytes, *not* including the null.
+    int selectionStart =
+        env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart);
+    int selectionEnd =
+        env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd);
+    int composingRegionStart =
+        env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart);
+    int composingRegionEnd =
+        env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd);
+    GameTextInputState state{text_chars,
+                             text_len,
+                             {selectionStart, selectionEnd},
+                             {composingRegionStart, composingRegionEnd}};
+    callback(context, &state);
+    env_->ReleaseStringUTFChars(text, text_chars);
+    env_->DeleteLocalRef(text);
+}
diff --git a/third_party/android_game_activity/include/game-text-input/gametextinput.h b/third_party/android_game_activity/include/game-text-input/gametextinput.h
new file mode 100644
index 0000000..a85265e
--- /dev/null
+++ b/third_party/android_game_activity/include/game-text-input/gametextinput.h
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup game_text_input Game Text Input
+ * The interface to use GameTextInput.
+ * @{
+ */
+
+#pragma once
+
+#include <android/rect.h>
+#include <jni.h>
+#include <stdint.h>
+
+#include "gamecommon.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This struct holds a span within a region of text from start (inclusive) to
+ * end (exclusive). An empty span or cursor position is specified with
+ * start==end. An undefined span is specified with start = end = SPAN_UNDEFINED.
+ */
+typedef struct GameTextInputSpan {
+    /** The start of the region (inclusive). */
+    int32_t start;
+    /** The end of the region (exclusive). */
+    int32_t end;
+} GameTextInputSpan;
+
+/**
+ * Values with special meaning in a GameTextInputSpan.
+ */
+enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 };
+
+/**
+ * This struct holds the state of an editable section of text.
+ * The text can have a selection and a composing region defined on it.
+ * A composing region is used by IMEs that allow input using multiple steps to
+ * compose a glyph or word. Use functions GameTextInput_getState and
+ * GameTextInput_setState to read and modify the state that an IME is editing.
+ */
+typedef struct GameTextInputState {
+    /**
+     * Text owned by the state, as a modified UTF-8 string. Null-terminated.
+     * https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
+     */
+    const char *text_UTF8;
+    /**
+     * Length in bytes of text_UTF8, *not* including the null at end.
+     */
+    int32_t text_length;
+    /**
+     * A selection defined on the text.
+     */
+    GameTextInputSpan selection;
+    /**
+     * A composing region defined on the text.
+     */
+    GameTextInputSpan composingRegion;
+} GameTextInputState;
+
+/**
+ * A callback called by GameTextInput_getState.
+ * @param context User-defined context.
+ * @param state State, owned by the library, that will be valid for the duration
+ * of the callback.
+ */
+typedef void (*GameTextInputGetStateCallback)(
+    void *context, const struct GameTextInputState *state);
+
+/**
+ * Opaque handle to the GameTextInput API.
+ */
+typedef struct GameTextInput GameTextInput;
+
+/**
+ * Initialize the GameTextInput library.
+ * If called twice without GameTextInput_destroy being called, the same pointer
+ * will be returned and a warning will be issued.
+ * @param env A JNI env valid on the calling thread.
+ * @param max_string_size The maximum length of a string that can be edited. If
+ * zero, the maximum defaults to 65536 bytes. A buffer of this size is allocated
+ * at initialization.
+ * @return A handle to the library.
+ */
+GameTextInput *GameTextInput_init(JNIEnv *env, uint32_t max_string_size);
+
+/**
+ * When using GameTextInput, you need to create a gametextinput.InputConnection
+ * on the Java side and pass it using this function to the library, unless using
+ * GameActivity in which case this will be done for you. See the GameActivity
+ * source code or GameTextInput samples for examples of usage.
+ * @param input A valid GameTextInput library handle.
+ * @param inputConnection A gametextinput.InputConnection object.
+ */
+void GameTextInput_setInputConnection(GameTextInput *input,
+                                      jobject inputConnection);
+
+/**
+ * Unless using GameActivity, it is required to call this function from your
+ * Java gametextinput.Listener.stateChanged method to convert eventState and
+ * trigger any event callbacks. When using GameActivity, this does not need to
+ * be called as event processing is handled by the Activity.
+ * @param input A valid GameTextInput library handle.
+ * @param eventState A Java gametextinput.State object.
+ */
+void GameTextInput_processEvent(GameTextInput *input, jobject eventState);
+
+/**
+ * Free any resources owned by the GameTextInput library.
+ * Any subsequent calls to the library will fail until GameTextInput_init is
+ * called again.
+ * @param input A valid GameTextInput library handle.
+ */
+void GameTextInput_destroy(GameTextInput *input);
+
+/**
+ * Flags to be passed to GameTextInput_showIme.
+ */
+enum ShowImeFlags {
+    SHOW_IME_UNDEFINED = 0,  // Default value.
+    SHOW_IMPLICIT =
+        1,  // Indicates that the user has forced the input method open so it
+            // should not be closed until they explicitly do so.
+    SHOW_FORCED = 2  // Indicates that this is an implicit request to show the
+                     // input window, not as the result of a direct request by
+                     // the user. The window may not be shown in this case.
+};
+
+/**
+ * Show the IME. Calls InputMethodManager.showSoftInput().
+ * @param input A valid GameTextInput library handle.
+ * @param flags Defined in ShowImeFlags above. For more information see:
+ * https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
+ */
+void GameTextInput_showIme(GameTextInput *input, uint32_t flags);
+
+/**
+ * Flags to be passed to GameTextInput_hideIme.
+ */
+enum HideImeFlags {
+    HIDE_IME_UNDEFINED = 0,  // Default value.
+    HIDE_IMPLICIT_ONLY =
+        1,  // Indicates that the soft input window should only be hidden if it
+            // was not explicitly shown by the user.
+    HIDE_NOT_ALWAYS =
+        2,  // Indicates that the soft input window should normally be hidden,
+            // unless it was originally shown with SHOW_FORCED.
+};
+
+/**
+ * Show the IME. Calls InputMethodManager.hideSoftInputFromWindow().
+ * @param input A valid GameTextInput library handle.
+ * @param flags Defined in HideImeFlags above. For more information see:
+ * https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
+ */
+void GameTextInput_hideIme(GameTextInput *input, uint32_t flags);
+
+/**
+ * Call a callback with the current GameTextInput state, which may have been
+ * modified by changes in the IME and calls to GameTextInput_setState. We use a
+ * callback rather than returning the state in order to simplify ownership of
+ * text_UTF8 strings. These strings are only valid during the calling of the
+ * callback.
+ * @param input A valid GameTextInput library handle.
+ * @param callback A function that will be called with valid state.
+ * @param context Context used by the callback.
+ */
+void GameTextInput_getState(GameTextInput *input,
+                            GameTextInputGetStateCallback callback,
+                            void *context);
+
+/**
+ * Set the current GameTextInput state. This state is reflected to any active
+ * IME.
+ * @param input A valid GameTextInput library handle.
+ * @param state The state to set. Ownership is maintained by the caller and must
+ * remain valid for the duration of the call.
+ */
+void GameTextInput_setState(GameTextInput *input,
+                            const GameTextInputState *state);
+
+/**
+ * Type of the callback needed by GameTextInput_setEventCallback that will be
+ * called every time the IME state changes.
+ * @param context User-defined context set in GameTextInput_setEventCallback.
+ * @param current_state Current IME state, owned by the library and valid during
+ * the callback.
+ */
+typedef void (*GameTextInputEventCallback)(
+    void *context, const GameTextInputState *current_state);
+
+/**
+ * Optionally set a callback to be called whenever the IME state changes.
+ * Not necessary if you are using GameActivity, which handles these callbacks
+ * for you.
+ * @param input A valid GameTextInput library handle.
+ * @param callback Called by the library when the IME state changes.
+ * @param context Context passed as first argument to the callback.
+ */
+void GameTextInput_setEventCallback(GameTextInput *input,
+                                    GameTextInputEventCallback callback,
+                                    void *context);
+
+/**
+ * Type of the callback needed by GameTextInput_setImeInsetsCallback that will
+ * be called every time the IME window insets change.
+ * @param context User-defined context set in
+ * GameTextInput_setImeWIndowInsetsCallback.
+ * @param current_insets Current IME insets, owned by the library and valid
+ * during the callback.
+ */
+typedef void (*GameTextInputImeInsetsCallback)(void *context,
+                                               const ARect *current_insets);
+
+/**
+ * Optionally set a callback to be called whenever the IME insets change.
+ * Not necessary if you are using GameActivity, which handles these callbacks
+ * for you.
+ * @param input A valid GameTextInput library handle.
+ * @param callback Called by the library when the IME insets change.
+ * @param context Context passed as first argument to the callback.
+ */
+void GameTextInput_setImeInsetsCallback(GameTextInput *input,
+                                        GameTextInputImeInsetsCallback callback,
+                                        void *context);
+
+/**
+ * Get the current window insets for the IME.
+ * @param input A valid GameTextInput library handle.
+ * @param insets Filled with the current insets by this function.
+ */
+void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets);
+
+/**
+ * Unless using GameActivity, it is required to call this function from your
+ * Java gametextinput.Listener.onImeInsetsChanged method to
+ * trigger any event callbacks. When using GameActivity, this does not need to
+ * be called as insets processing is handled by the Activity.
+ * @param input A valid GameTextInput library handle.
+ * @param eventState A Java gametextinput.State object.
+ */
+void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets);
+
+/**
+ * Convert a GameTextInputState struct to a Java gametextinput.State object.
+ * Don't forget to delete the returned Java local ref when you're done.
+ * @param input A valid GameTextInput library handle.
+ * @param state Input state to convert.
+ * @return A Java object of class gametextinput.State. The caller is required to
+ * delete this local reference.
+ */
+jobject GameTextInputState_toJava(const GameTextInput *input,
+                                  const GameTextInputState *state);
+
+/**
+ * Convert from a Java gametextinput.State object into a C GameTextInputState
+ * struct.
+ * @param input A valid GameTextInput library handle.
+ * @param state A Java gametextinput.State object.
+ * @param callback A function called with the C struct, valid for the duration
+ * of the call.
+ * @param context Context passed to the callback.
+ */
+void GameTextInputState_fromJava(const GameTextInput *input, jobject state,
+                                 GameTextInputGetStateCallback callback,
+                                 void *context);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
diff --git a/third_party/android_game_activity/module.json b/third_party/android_game_activity/module.json
new file mode 100644
index 0000000..b571794
--- /dev/null
+++ b/third_party/android_game_activity/module.json
@@ -0,0 +1 @@
+{"export_libraries":[],"library_name":null,"android":{"export_libraries":null,"library_name":null}}
\ No newline at end of file
diff --git a/third_party/boringssl/src/crypto/x509/x509_test.cc b/third_party/boringssl/src/crypto/x509/x509_test.cc
index 551bd8ca..52e6c95 100644
--- a/third_party/boringssl/src/crypto/x509/x509_test.cc
+++ b/third_party/boringssl/src/crypto/x509/x509_test.cc
@@ -1504,3 +1504,182 @@
   EXPECT_EQ(X509_V_ERR_INVALID_CA,
             Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0));
 }
+
+TEST(X509Test, GeneralName)  {
+  const std::vector<uint8_t> kNames[] = {
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 }
+      //   [0] {
+      //     SEQUENCE {}
+      //   }
+      // }
+      {0xa0, 0x13, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x02, 0x30, 0x00},
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 }
+      //   [0] {
+      //     [APPLICATION 0] {}
+      //   }
+      // }
+      {0xa0, 0x13, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x02, 0x60, 0x00},
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 }
+      //   [0] {
+      //     UTF8String { "a" }
+      //   }
+      // }
+      {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x0c, 0x01, 0x61},
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.2 }
+      //   [0] {
+      //     UTF8String { "a" }
+      //   }
+      // }
+      {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x02, 0xa0, 0x03, 0x0c, 0x01, 0x61},
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 }
+      //   [0] {
+      //     UTF8String { "b" }
+      //   }
+      // }
+      {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x0c, 0x01, 0x62},
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 }
+      //   [0] {
+      //     BOOLEAN { TRUE }
+      //   }
+      // }
+      {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x01, 0x01, 0xff},
+      // [0] {
+      //   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 }
+      //   [0] {
+      //     BOOLEAN { FALSE }
+      //   }
+      // }
+      {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+       0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x01, 0x01, 0x00},
+      // [1 PRIMITIVE] { "a" }
+      {0x81, 0x01, 0x61},
+      // [1 PRIMITIVE] { "b" }
+      {0x81, 0x01, 0x62},
+      // [2 PRIMITIVE] { "a" }
+      {0x82, 0x01, 0x61},
+      // [2 PRIMITIVE] { "b" }
+      {0x82, 0x01, 0x62},
+      // [4] {
+      //   SEQUENCE {
+      //     SET {
+      //       SEQUENCE {
+      //         # commonName
+      //         OBJECT_IDENTIFIER { 2.5.4.3 }
+      //         UTF8String { "a" }
+      //       }
+      //     }
+      //   }
+      // }
+      {0xa4, 0x0e, 0x30, 0x0c, 0x31, 0x0a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04,
+       0x03, 0x0c, 0x01, 0x61},
+      // [4] {
+      //   SEQUENCE {
+      //     SET {
+      //       SEQUENCE {
+      //         # commonName
+      //         OBJECT_IDENTIFIER { 2.5.4.3 }
+      //         UTF8String { "b" }
+      //       }
+      //     }
+      //   }
+      // }
+      {0xa4, 0x0e, 0x30, 0x0c, 0x31, 0x0a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04,
+       0x03, 0x0c, 0x01, 0x62},
+      // [5] {
+      //   [1] {
+      //     UTF8String { "a" }
+      //   }
+      // }
+      {0xa5, 0x05, 0xa1, 0x03, 0x0c, 0x01, 0x61},
+      // [5] {
+      //   [1] {
+      //     UTF8String { "b" }
+      //   }
+      // }
+      {0xa5, 0x05, 0xa1, 0x03, 0x0c, 0x01, 0x62},
+      // [5] {
+      //   [0] {
+      //     UTF8String {}
+      //   }
+      //   [1] {
+      //     UTF8String { "a" }
+      //   }
+      // }
+      {0xa5, 0x09, 0xa0, 0x02, 0x0c, 0x00, 0xa1, 0x03, 0x0c, 0x01, 0x61},
+      // [5] {
+      //   [0] {
+      //     UTF8String { "a" }
+      //   }
+      //   [1] {
+      //     UTF8String { "a" }
+      //   }
+      // }
+      {0xa5, 0x0a, 0xa0, 0x03, 0x0c, 0x01, 0x61, 0xa1, 0x03, 0x0c, 0x01, 0x61},
+      // [5] {
+      //   [0] {
+      //     UTF8String { "b" }
+      //   }
+      //   [1] {
+      //     UTF8String { "a" }
+      //   }
+      // }
+      {0xa5, 0x0a, 0xa0, 0x03, 0x0c, 0x01, 0x62, 0xa1, 0x03, 0x0c, 0x01, 0x61},
+      // [6 PRIMITIVE] { "a" }
+      {0x86, 0x01, 0x61},
+      // [6 PRIMITIVE] { "b" }
+      {0x86, 0x01, 0x62},
+      // [7 PRIMITIVE] { `11111111` }
+      {0x87, 0x04, 0x11, 0x11, 0x11, 0x11},
+      // [7 PRIMITIVE] { `22222222`}
+      {0x87, 0x04, 0x22, 0x22, 0x22, 0x22},
+      // [7 PRIMITIVE] { `11111111111111111111111111111111` }
+      {0x87, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+       0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+      // [7 PRIMITIVE] { `22222222222222222222222222222222` }
+      {0x87, 0x10, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+       0x22, 0x22, 0x22, 0x22, 0x22, 0x22},
+      // [8 PRIMITIVE] { 1.2.840.113554.4.1.72585.2.1 }
+      {0x88, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7,
+       0x09, 0x02, 0x01},
+      // [8 PRIMITIVE] { 1.2.840.113554.4.1.72585.2.2 }
+      {0x88, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7,
+       0x09, 0x02, 0x02},
+  };
+
+  // Every name should be equal to itself and not equal to any others.
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kNames); i++) {
+    SCOPED_TRACE(Bytes(kNames[i]));
+
+    const uint8_t *ptr = kNames[i].data();
+    bssl::UniquePtr<GENERAL_NAME> a(
+        d2i_GENERAL_NAME(nullptr, &ptr, kNames[i].size()));
+    ASSERT_TRUE(a);
+
+    for (size_t j = 0; j < OPENSSL_ARRAY_SIZE(kNames); j++) {
+      SCOPED_TRACE(Bytes(kNames[j]));
+
+      ptr = kNames[j].data();
+      bssl::UniquePtr<GENERAL_NAME> b(
+          d2i_GENERAL_NAME(nullptr, &ptr, kNames[j].size()));
+      ASSERT_TRUE(b);
+
+      if (i == j) {
+        EXPECT_EQ(GENERAL_NAME_cmp(a.get(), b.get()), 0);
+      } else {
+        EXPECT_NE(GENERAL_NAME_cmp(a.get(), b.get()), 0);
+      }
+    }
+  }
+}
diff --git a/third_party/boringssl/src/crypto/x509v3/v3_genn.c b/third_party/boringssl/src/crypto/x509v3/v3_genn.c
index 8c92687..0c7900c 100644
--- a/third_party/boringssl/src/crypto/x509v3/v3_genn.c
+++ b/third_party/boringssl/src/crypto/x509v3/v3_genn.c
@@ -72,8 +72,9 @@
 IMPLEMENT_ASN1_FUNCTIONS(OTHERNAME)
 
 ASN1_SEQUENCE(EDIPARTYNAME) = {
-        ASN1_IMP_OPT(EDIPARTYNAME, nameAssigner, DIRECTORYSTRING, 0),
-        ASN1_IMP_OPT(EDIPARTYNAME, partyName, DIRECTORYSTRING, 1)
+        /* DirectoryString is a CHOICE type, so use explicit tagging. */
+        ASN1_EXP_OPT(EDIPARTYNAME, nameAssigner, DIRECTORYSTRING, 0),
+        ASN1_EXP(EDIPARTYNAME, partyName, DIRECTORYSTRING, 1)
 } ASN1_SEQUENCE_END(EDIPARTYNAME)
 
 IMPLEMENT_ASN1_FUNCTIONS(EDIPARTYNAME)
@@ -107,6 +108,24 @@
                                     (char *)a);
 }
 
+static int edipartyname_cmp(const EDIPARTYNAME *a, const EDIPARTYNAME *b)
+{
+    /* nameAssigner is optional and may be NULL. */
+    if (a->nameAssigner == NULL) {
+        if (b->nameAssigner != NULL) {
+            return -1;
+        }
+    } else {
+        if (b->nameAssigner == NULL ||
+            ASN1_STRING_cmp(a->nameAssigner, b->nameAssigner) != 0) {
+            return -1;
+        }
+    }
+
+    /* partyName may not be NULL. */
+    return ASN1_STRING_cmp(a->partyName, b->partyName);
+}
+
 /* Returns 0 if they are equal, != 0 otherwise. */
 int GENERAL_NAME_cmp(GENERAL_NAME *a, GENERAL_NAME *b)
 {
@@ -116,8 +135,11 @@
         return -1;
     switch (a->type) {
     case GEN_X400:
+        result = ASN1_TYPE_cmp(a->d.x400Address, b->d.x400Address);
+        break;
+
     case GEN_EDIPARTY:
-        result = ASN1_TYPE_cmp(a->d.other, b->d.other);
+        result = edipartyname_cmp(a->d.ediPartyName, b->d.ediPartyName);
         break;
 
     case GEN_OTHERNAME:
@@ -164,8 +186,11 @@
 {
     switch (type) {
     case GEN_X400:
+        a->d.x400Address = value;
+        break;
+
     case GEN_EDIPARTY:
-        a->d.other = value;
+        a->d.ediPartyName = value;
         break;
 
     case GEN_OTHERNAME:
@@ -199,8 +224,10 @@
         *ptype = a->type;
     switch (a->type) {
     case GEN_X400:
+        return a->d.x400Address;
+
     case GEN_EDIPARTY:
-        return a->d.other;
+        return a->d.ediPartyName;
 
     case GEN_OTHERNAME:
         return a->d.otherName;
diff --git a/third_party/boringssl/src/include/openssl/x509v3.h b/third_party/boringssl/src/include/openssl/x509v3.h
index 1af439d..e376050 100644
--- a/third_party/boringssl/src/include/openssl/x509v3.h
+++ b/third_party/boringssl/src/include/openssl/x509v3.h
@@ -534,6 +534,12 @@
 
 DECLARE_ASN1_FUNCTIONS(GENERAL_NAME)
 OPENSSL_EXPORT GENERAL_NAME *GENERAL_NAME_dup(GENERAL_NAME *a);
+
+// GENERAL_NAME_cmp returns zero if |a| and |b| are equal and a non-zero
+// value otherwise. Note this function does not provide a comparison suitable
+// for sorting.
+//
+// TODO(davidben): Const-correct this function.
 OPENSSL_EXPORT int GENERAL_NAME_cmp(GENERAL_NAME *a, GENERAL_NAME *b);
 
 
diff --git a/third_party/crashpad/tools/BUILD.gn b/third_party/crashpad/tools/BUILD.gn
index ae8b2c4..a002795 100644
--- a/third_party/crashpad/tools/BUILD.gn
+++ b/third_party/crashpad/tools/BUILD.gn
@@ -27,8 +27,12 @@
   deps = [ "../third_party/mini_chromium:base" ]
 }
 
-if (!crashpad_is_ios && !crashpad_is_in_starboard) {
+# TODO(b/251521595): resolve GN error and enable this target to be built for
+# android.
+if (!crashpad_is_ios && !crashpad_is_android) {
   crashpad_executable("crashpad_database_util") {
+    check_includes = !crashpad_is_in_starboard
+
     sources = [ "crashpad_database_util.cc" ]
 
     deps = [
@@ -39,18 +43,26 @@
       "../third_party/mini_chromium:base",
       "../util",
     ]
+
+    if (crashpad_is_in_starboard) {
+      deps += [
+        "//starboard",
+      ]
+    }
   }
 
-  crashpad_executable("crashpad_http_upload") {
-    sources = [ "crashpad_http_upload.cc" ]
+  if (!crashpad_is_in_starboard) {
+    crashpad_executable("crashpad_http_upload") {
+      sources = [ "crashpad_http_upload.cc" ]
 
-    deps = [
-      ":tool_support",
-      "../build:default_exe_manifest_win",
-      "../compat",
-      "../third_party/mini_chromium:base",
-      "../util",
-    ]
+      deps = [
+        ":tool_support",
+        "../build:default_exe_manifest_win",
+        "../compat",
+        "../third_party/mini_chromium:base",
+        "../util",
+      ]
+    }
   }
 }
 
diff --git a/third_party/crashpad/tools/crashpad_database_util.cc b/third_party/crashpad/tools/crashpad_database_util.cc
index a003616..0fc8a70 100644
--- a/third_party/crashpad/tools/crashpad_database_util.cc
+++ b/third_party/crashpad/tools/crashpad_database_util.cc
@@ -40,6 +40,12 @@
 #include "util/misc/uuid.h"
 #include "util/stdlib/string_number_conversion.h"
 
+#if defined(STARBOARD)
+// Stub out required starboard implementation as this is built in parallel.
+#include "starboard/event.h"
+void SbEventHandle(const SbEvent* event) {}
+#endif  // defined(STARBOARD)
+
 namespace crashpad {
 namespace {
 
diff --git a/third_party/jinja2/filters.py b/third_party/jinja2/filters.py
index fd0db04..8dbad84 100644
--- a/third_party/jinja2/filters.py
+++ b/third_party/jinja2/filters.py
@@ -21,7 +21,7 @@
 from jinja2._compat import next, imap, string_types, text_type, iteritems
 
 
-_word_re = re.compile(r'\w+(?u)')
+_word_re = re.compile(r'\w+')
 
 
 def contextfilter(f):
@@ -183,7 +183,7 @@
     uppercase letters, all remaining characters are lowercase.
     """
     rv = []
-    for item in re.compile(r'([-\s]+)(?u)').split(s):
+    for item in re.compile(r'([-\s]+)').split(s):
         if not item:
             continue
         rv.append(item[0].upper() + item[1:].lower())
diff --git a/third_party/libjpeg-turbo/BUILD.gn b/third_party/libjpeg-turbo/BUILD.gn
index fe28aff..f1ff3f3 100644
--- a/third_party/libjpeg-turbo/BUILD.gn
+++ b/third_party/libjpeg-turbo/BUILD.gn
@@ -21,9 +21,7 @@
     "jpeglib.h",
     "jpeglibmangler.h",
   ]
-  if (!is_starboard) {
-    defines = [ "MANGLE_JPEG_NAMES" ]
-  } else {
+  if (is_starboard) {
     public_deps = [
       "//starboard:starboard_headers_only",
       "//starboard/common",
@@ -158,6 +156,11 @@
 static_library("simd") {
   include_dirs = [ "." ]
   deps = [ ":libjpeg_headers" ]
+  if (!is_starboard) {
+    defines = [ "MANGLE_JPEG_NAMES" ]
+  } else {
+    defines = []
+  }
 
   if (current_cpu == "x86" && yasm_exists) {
     deps += [ ":simd_asm" ]
@@ -195,7 +198,7 @@
       ]
     }
 
-    defines = [ "NEON_INTRINSICS" ]
+    defines += [ "NEON_INTRINSICS" ]
 
     if (!is_starboard) {
       configs -= [ "//build/config/compiler:default_optimization" ]
@@ -216,6 +219,9 @@
 
 config("libjpeg_config") {
   include_dirs = [ "." ]
+  if (!is_starboard) {
+    defines = [ "MANGLE_JPEG_NAMES" ]
+  }
 }
 
 static_library("libjpeg") {
@@ -278,6 +284,7 @@
   defines = [
     "WITH_SIMD",
     "NO_GETENV",
+    "NO_PUTENV",
   ]
 
   if (is_starboard) {
@@ -296,6 +303,7 @@
 
     # This is defined in code.
     defines -= [ "NO_GETENV" ]
+    defines -= [ "NO_PUTENV" ]
   }
 
   configs += [ ":libjpeg_config" ]
@@ -402,5 +410,9 @@
 
     configs -= [ "//build/config/compiler:chromium_code" ]
     configs += [ "//build/config/compiler:no_chromium_code" ]
+
+    if (is_win) {
+      cflags = ["-U_CRT_SECURE_NO_DEPRECATE"]
+    }
   }
 }
diff --git a/third_party/libjpeg-turbo/BUILDING.md b/third_party/libjpeg-turbo/BUILDING.md
index f91abcd..2ce65d6 100644
--- a/third_party/libjpeg-turbo/BUILDING.md
+++ b/third_party/libjpeg-turbo/BUILDING.md
@@ -10,35 +10,24 @@
 
 - [CMake](http://www.cmake.org) v2.8.12 or later
 
-- [NASM](http://www.nasm.us) or [YASM](http://yasm.tortall.net)
+- [NASM](http://www.nasm.us) or [Yasm](http://yasm.tortall.net)
   (if building x86 or x86-64 SIMD extensions)
   * If using NASM, 2.13 or later is required.
-  * If using YASM, 1.2.0 or later is required.
-  * If building on macOS, NASM or YASM can be obtained from
+  * If using Yasm, 1.2.0 or later is required.
+  * If building on macOS, NASM or Yasm can be obtained from
     [MacPorts](http://www.macports.org/) or [Homebrew](http://brew.sh/).
      - NOTE: Currently, if it is desirable to hide the SIMD function symbols in
        Mac executables or shared libraries that statically link with
-       libjpeg-turbo, then NASM 2.14 or later or YASM must be used when
+       libjpeg-turbo, then NASM 2.14 or later or Yasm must be used when
        building libjpeg-turbo.
-  * If building on Windows, **nasm.exe**/**yasm.exe** should be in your `PATH`.
-  * NASM and YASM are located in the CRB (Code Ready Builder) repository on
-    Red Hat Enterprise Linux 8 and in the PowerTools repository on CentOS 8,
-    which is not enabled by default.
-
-  The binary RPMs released by the NASM project do not work on older Linux
-  systems, such as Red Hat Enterprise Linux 5.  On such systems, you can easily
-  build and install NASM from a source RPM by downloading one of the SRPMs from
-
-  <http://www.nasm.us/pub/nasm/releasebuilds>
-
-  and executing the following as root:
-
-        ARCH=`uname -m`
-        rpmbuild --rebuild nasm-{version}.src.rpm
-        rpm -Uvh /usr/src/redhat/RPMS/$ARCH/nasm-{version}.$ARCH.rpm
-
-  NOTE: the NASM build will fail if texinfo is not installed.
-
+  * If NASM or Yasm is not in your `PATH`, then you can specify the full path
+    to the assembler by using either the `CMAKE_ASM_NASM_COMPILER` CMake
+    variable or the `ASM_NASM` environment variable.  On Windows, use forward
+    slashes rather than backslashes in the path (for example,
+    **c:/nasm/nasm.exe**).
+  * NASM and Yasm are located in the CRB (Code Ready Builder) repository on
+    Red Hat Enterprise Linux 8 and in the PowerTools repository on RHEL
+    derivatives, which is not enabled by default.
 
 ### Un*x Platforms (including Linux, Mac, FreeBSD, Solaris, and Cygwin)
 
@@ -90,6 +79,14 @@
   * If using JDK 11 or later, CMake 3.10.x or later must also be used.
 
 
+Sub-Project Builds
+------------------
+
+The libjpeg-turbo build system does not support being included as a sub-project
+using the CMake `add_subdirectory()` function.  Use the CMake
+`ExternalProject_Add()` function instead.
+
+
 Out-of-Tree Builds
 ------------------
 
@@ -106,8 +103,9 @@
 Ninja
 -----
 
-In all of the procedures and recipes below, replace `make` with `ninja` and
-`Unix Makefiles` with `Ninja` if using Ninja.
+If using Ninja, then replace `make` or `nmake` with `ninja`, and replace the
+CMake generator (specified with the `-G` option) with `Ninja`, in all of the
+procedures and recipes below.
 
 
 Build Procedure
diff --git a/third_party/libjpeg-turbo/ChangeLog.md b/third_party/libjpeg-turbo/ChangeLog.md
index ca5208b..b0d166e 100644
--- a/third_party/libjpeg-turbo/ChangeLog.md
+++ b/third_party/libjpeg-turbo/ChangeLog.md
@@ -1,3 +1,94 @@
+2.1.4
+=====
+
+### Significant changes relative to 2.1.3
+
+1. Fixed a regression introduced in 2.1.3 that caused build failures with
+Visual Studio 2010.
+
+2. The `tjDecompressHeader3()` function in the TurboJPEG C API and the
+`TJDecompressor.setSourceImage()` method in the TurboJPEG Java API now accept
+"abbreviated table specification" (AKA "tables-only") datastreams, which can be
+used to prime the decompressor with quantization and Huffman tables that can be
+used when decompressing subsequent "abbreviated image" datastreams.
+
+3. libjpeg-turbo now performs run-time detection of AltiVec instructions on
+OS X/PowerPC systems if AltiVec instructions are not enabled at compile time.
+This allows both AltiVec-equipped (PowerPC G4 and G5) and non-AltiVec-equipped
+(PowerPC G3) CPUs to be supported using the same build of libjpeg-turbo.
+
+4. Fixed an error ("Bogus virtual array access") that occurred when attempting
+to decompress a progressive JPEG image with a height less than or equal to one
+iMCU (8 * the vertical sampling factor) using buffered-image mode with
+interblock smoothing enabled.  This was a regression introduced by
+2.1 beta1[6(b)].
+
+5. Fixed two issues that prevented partial image decompression from working
+properly with buffered-image mode:
+
+     - Attempting to call `jpeg_crop_scanline()` after
+`jpeg_start_decompress()` but before `jpeg_start_output()` resulted in an error
+("Improper call to JPEG library in state 207".)
+     - Attempting to use `jpeg_skip_scanlines()` resulted in an error ("Bogus
+virtual array access") under certain circumstances.
+
+
+2.1.3
+=====
+
+### Significant changes relative to 2.1.2
+
+1. Fixed a regression introduced by 2.0 beta1[7] whereby cjpeg compressed PGM
+input files into full-color JPEG images unless the `-grayscale` option was
+used.
+
+2. cjpeg now automatically compresses GIF and 8-bit BMP input files into
+grayscale JPEG images if the input files contain only shades of gray.
+
+3. The build system now enables the intrinsics implementation of the AArch64
+(Arm 64-bit) Neon SIMD extensions by default when using GCC 12 or later.
+
+4. Fixed a segfault that occurred while decompressing a 4:2:0 JPEG image using
+the merged (non-fancy) upsampling algorithms (that is, with
+`cinfo.do_fancy_upsampling` set to `FALSE`) along with `jpeg_crop_scanline()`.
+Specifically, the segfault occurred if the number of bytes remaining in the
+output buffer was less than the number of bytes required to represent one
+uncropped scanline of the output image.  For that reason, the issue could only
+be reproduced using the libjpeg API, not using djpeg.
+
+
+2.1.2
+=====
+
+### Significant changes relative to 2.1.1
+
+1. Fixed a regression introduced by 2.1 beta1[13] that caused the remaining
+GAS implementations of AArch64 (Arm 64-bit) Neon SIMD functions (which are used
+by default with GCC for performance reasons) to be placed in the `.rodata`
+section rather than in the `.text` section.  This caused the GNU linker to
+automatically place the `.rodata` section in an executable segment, which
+prevented libjpeg-turbo from working properly with other linkers and also
+represented a potential security risk.
+
+2. Fixed an issue whereby the `tjTransform()` function incorrectly computed the
+MCU block size for 4:4:4 JPEG images with non-unary sampling factors and thus
+unduly rejected some cropping regions, even though those regions aligned with
+8x8 MCU block boundaries.
+
+3. Fixed a regression introduced by 2.1 beta1[13] that caused the build system
+to enable the Arm Neon SIMD extensions when targetting Armv6 and other legacy
+architectures that do not support Neon instructions.
+
+4. libjpeg-turbo now performs run-time detection of AltiVec instructions on
+FreeBSD/PowerPC systems if AltiVec instructions are not enabled at compile
+time.  This allows both AltiVec-equipped and non-AltiVec-equipped CPUs to be
+supported using the same build of libjpeg-turbo.
+
+5. cjpeg now accepts a `-strict` argument similar to that of djpeg and
+jpegtran, which causes the compressor to abort if an LZW-compressed GIF input
+image contains incomplete or corrupt image data.
+
+
 2.1.1
 =====
 
@@ -15,6 +106,17 @@
 Android systems when running AArch32/Thumb builds of libjpeg-turbo built with
 recent versions of Clang.
 
+4. Added a command-line argument (`-copy icc`) to jpegtran that causes it to
+copy only the ICC profile markers from the source file and discard any other
+metadata.
+
+5. libjpeg-turbo should now build and run on CHERI-enabled architectures, which
+use capability pointers that are larger than the size of `size_t`.
+
+6. Fixed a regression (CVE-2021-37972) introduced by 2.1 beta1[5] that caused a
+segfault in the 64-bit SSE2 Huffman encoder when attempting to losslessly
+transform a specially-crafted malformed JPEG image.
+
 
 2.1.0
 =====
@@ -598,7 +700,7 @@
 now produces bitwise-identical results to the unmerged algorithms.
 
 12. The SIMD function symbols for x86[-64]/ELF, MIPS/ELF, macOS/x86[-64] (if
-libjpeg-turbo is built with YASM), and iOS/Arm[64] builds are now private.
+libjpeg-turbo is built with Yasm), and iOS/Arm[64] builds are now private.
 This prevents those symbols from being exposed in applications or shared
 libraries that link statically with libjpeg-turbo.
 
@@ -1483,8 +1585,8 @@
 
 ### Significant changes relative to 1.2 beta1:
 
-1. Fixed build issue with YASM on Unix systems (the libjpeg-turbo build system
-was not adding the current directory to the assembler include path, so YASM
+1. Fixed build issue with Yasm on Unix systems (the libjpeg-turbo build system
+was not adding the current directory to the assembler include path, so Yasm
 was not able to find jsimdcfg.inc.)
 
 2. Fixed out-of-bounds read in SSE2 SIMD code that occurred when decompressing
@@ -1552,7 +1654,7 @@
 8. All legacy VirtualGL code has been re-factored, and this has allowed
 libjpeg-turbo, in its entirety, to be re-licensed under a BSD-style license.
 
-9. libjpeg-turbo can now be built with YASM.
+9. libjpeg-turbo can now be built with Yasm.
 
 10. Added SIMD acceleration for ARM Linux and iOS platforms that support
 NEON instructions.
diff --git a/third_party/libjpeg-turbo/LICENSE.md b/third_party/libjpeg-turbo/LICENSE.md
index a1cdad5..d753e1d 100644
--- a/third_party/libjpeg-turbo/LICENSE.md
+++ b/third_party/libjpeg-turbo/LICENSE.md
@@ -91,7 +91,7 @@
 The Modified (3-clause) BSD License
 ===================================
 
-Copyright (C)2009-2021 D. R. Commander.  All Rights Reserved.<br>
+Copyright (C)2009-2022 D. R. Commander.  All Rights Reserved.<br>
 Copyright (C)2015 Viktor Szathmáry.  All Rights Reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/third_party/libjpeg-turbo/README.chromium b/third_party/libjpeg-turbo/README.chromium
index de1fe85..f0159a0 100644
--- a/third_party/libjpeg-turbo/README.chromium
+++ b/third_party/libjpeg-turbo/README.chromium
@@ -1,6 +1,6 @@
 Name: libjpeg-turbo
 URL: https://github.com/libjpeg-turbo/libjpeg-turbo/
-Version: b201838d8b5f2f80c9f86ec8405a62a002232b2c (post 2.1.0)
+Version: 2.1.4
 License: Custom license
 License File: LICENSE.md
 Security Critical: yes
@@ -8,18 +8,20 @@
 
 Description:
 This consists of the components:
-* libjpeg-turbo b201838d8b5f2f80c9f86ec8405a62a002232b2c (post 2.1.0)
+* libjpeg-turbo 2.1.4
 * This file (README.chromium)
 * A build file (BUILD.gn)
 * An OWNERS file
 * A codereview.settings file
+* A DIR_METADATA file
 * Patched header files used by Chromium
 * Deleted unused directories: cmakescripts, doc, fuzz, java, release,
-  sharedlib, simd/loongson, simd/mips, simd/powerpc, and win
-* Deleted unused files: appveyor.yml, CMakeLists.txt, doxygen.config,
-  doxygen-extra.css, .gitattributes, md5/CMakeLists.txt, md5/md5cmp.c,
-  simd/CMakeLists.txt, tjexample.c, tjexampletest.in, tjexampletest.java.in and
-  .travis.yml
+  sharedlib, simd/mips, simd/mips64, simd/powerpc, and win
+* Deleted unused files: appveyor.yml, CMakeLists.txt, cjpeg.1, croptest.in,
+  djpeg.1, doxygen.config, doxygen-extra.css, .gitattributes, jpegtran.1,
+  md5/CMakeLists.txt, md5/md5cmp.c, rdjpgcom.1, simd/CMakeLists.txt, strtest.c,
+  tjbenchtest.in, tjbenchtest.java.in, tjexample.c, tjexampletest.in,
+  tjexampletest.java.in and wrjpgcom.1
 * Deleted legacy Arm Neon assembly files (supporting old compiler versions that
   do not generate performant code from intrinsics):
   simd/arm/aarch32/jsimd_neon.S, simd/arm/aarch64/jsimd_neon.S.
diff --git a/third_party/libjpeg-turbo/cdjpeg.c b/third_party/libjpeg-turbo/cdjpeg.c
index 5278c1d..304a665 100644
--- a/third_party/libjpeg-turbo/cdjpeg.c
+++ b/third_party/libjpeg-turbo/cdjpeg.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2019, D. R. Commander.
+ * Copyright (C) 2019, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -34,7 +34,7 @@
     int scan_no = ((j_decompress_ptr)cinfo)->input_scan_number;
 
     if (scan_no > (int)prog->max_scans) {
-      fprintf(stderr, "Scan number %d exceeds maximum scans (%d)\n", scan_no,
+      fprintf(stderr, "Scan number %d exceeds maximum scans (%u)\n", scan_no,
               prog->max_scans);
       exit(EXIT_FAILURE);
     }
diff --git a/third_party/libjpeg-turbo/cjpeg.1 b/third_party/libjpeg-turbo/cjpeg.1
deleted file mode 100644
index 569dc3f..0000000
--- a/third_party/libjpeg-turbo/cjpeg.1
+++ /dev/null
@@ -1,360 +0,0 @@
-.TH CJPEG 1 "4 November 2020"
-.SH NAME
-cjpeg \- compress an image file to a JPEG file
-.SH SYNOPSIS
-.B cjpeg
-[
-.I options
-]
-[
-.I filename
-]
-.LP
-.SH DESCRIPTION
-.LP
-.B cjpeg
-compresses the named image file, or the standard input if no file is
-named, and produces a JPEG/JFIF file on the standard output.
-The currently supported input file formats are: PPM (PBMPLUS color
-format), PGM (PBMPLUS grayscale format), BMP, GIF, and Targa.
-.SH OPTIONS
-All switch names may be abbreviated; for example,
-.B \-grayscale
-may be written
-.B \-gray
-or
-.BR \-gr .
-Most of the "basic" switches can be abbreviated to as little as one letter.
-Upper and lower case are equivalent (thus
-.B \-BMP
-is the same as
-.BR \-bmp ).
-British spellings are also accepted (e.g.,
-.BR \-greyscale ),
-though for brevity these are not mentioned below.
-.PP
-The basic switches are:
-.TP
-.BI \-quality " N[,...]"
-Scale quantization tables to adjust image quality.  Quality is 0 (worst) to
-100 (best); default is 75.  (See below for more info.)
-.TP
-.B \-grayscale
-Create monochrome JPEG file from color input.  Be sure to use this switch when
-compressing a grayscale BMP or GIF file, because
-.B cjpeg
-isn't bright enough to notice whether a BMP or GIF file uses only shades of
-gray.  By saying
-.BR \-grayscale,
-you'll get a smaller JPEG file that takes less time to process.
-.TP
-.B \-rgb
-Create RGB JPEG file.
-Using this switch suppresses the conversion from RGB
-colorspace input to the default YCbCr JPEG colorspace.
-.TP
-.B \-optimize
-Perform optimization of entropy encoding parameters.  Without this, default
-encoding parameters are used.
-.B \-optimize
-usually makes the JPEG file a little smaller, but
-.B cjpeg
-runs somewhat slower and needs much more memory.  Image quality and speed of
-decompression are unaffected by
-.BR \-optimize .
-.TP
-.B \-progressive
-Create progressive JPEG file (see below).
-.TP
-.B \-targa
-Input file is Targa format.  Targa files that contain an "identification"
-field will not be automatically recognized by
-.BR cjpeg ;
-for such files you must specify
-.B \-targa
-to make
-.B cjpeg
-treat the input as Targa format.
-For most Targa files, you won't need this switch.
-.PP
-The
-.B \-quality
-switch lets you trade off compressed file size against quality of the
-reconstructed image: the higher the quality setting, the larger the JPEG file,
-and the closer the output image will be to the original input.  Normally you
-want to use the lowest quality setting (smallest file) that decompresses into
-something visually indistinguishable from the original image.  For this
-purpose the quality setting should generally be between 50 and 95 (the default
-is 75) for photographic images.  If you see defects at
-.B \-quality
-75, then go up 5 or 10 counts at a time until you are happy with the output
-image.  (The optimal setting will vary from one image to another.)
-.PP
-.B \-quality
-100 will generate a quantization table of all 1's, minimizing loss in the
-quantization step (but there is still information loss in subsampling, as well
-as roundoff error.)  For most images, specifying a quality value above
-about 95 will increase the size of the compressed file dramatically, and while
-the quality gain from these higher quality values is measurable (using metrics
-such as PSNR or SSIM), it is rarely perceivable by human vision.
-.PP
-In the other direction, quality values below 50 will produce very small files
-of low image quality.  Settings around 5 to 10 might be useful in preparing an
-index of a large image library, for example.  Try
-.B \-quality
-2 (or so) for some amusing Cubist effects.  (Note: quality
-values below about 25 generate 2-byte quantization tables, which are
-considered optional in the JPEG standard.
-.B cjpeg
-emits a warning message when you give such a quality value, because some
-other JPEG programs may be unable to decode the resulting file.  Use
-.B \-baseline
-if you need to ensure compatibility at low quality values.)
-.PP
-The \fB-quality\fR option has been extended in this version of \fBcjpeg\fR to
-support separate quality settings for luminance and chrominance (or, in
-general, separate settings for every quantization table slot.)  The principle
-is the same as chrominance subsampling:  since the human eye is more sensitive
-to spatial changes in brightness than spatial changes in color, the chrominance
-components can be quantized more than the luminance components without
-incurring any visible image quality loss.  However, unlike subsampling, this
-feature reduces data in the frequency domain instead of the spatial domain,
-which allows for more fine-grained control.  This option is useful in
-quality-sensitive applications, for which the artifacts generated by
-subsampling may be unacceptable.
-.PP
-The \fB-quality\fR option accepts a comma-separated list of parameters, which
-respectively refer to the quality levels that should be assigned to the
-quantization table slots.  If there are more q-table slots than parameters,
-then the last parameter is replicated.  Thus, if only one quality parameter is
-given, this is used for both luminance and chrominance (slots 0 and 1,
-respectively), preserving the legacy behavior of cjpeg v6b and prior.
-More (or customized) quantization tables can be set with the \fB-qtables\fR
-option and assigned to components with the \fB-qslots\fR option (see the
-"wizard" switches below.)
-.PP
-JPEG files generated with separate luminance and chrominance quality are fully
-compliant with standard JPEG decoders.
-.PP
-.BR CAUTION:
-For this setting to be useful, be sure to pass an argument of \fB-sample 1x1\fR
-to \fBcjpeg\fR to disable chrominance subsampling.  Otherwise, the default
-subsampling level (2x2, AKA "4:2:0") will be used.
-.PP
-The
-.B \-progressive
-switch creates a "progressive JPEG" file.  In this type of JPEG file, the data
-is stored in multiple scans of increasing quality.  If the file is being
-transmitted over a slow communications link, the decoder can use the first
-scan to display a low-quality image very quickly, and can then improve the
-display with each subsequent scan.  The final image is exactly equivalent to a
-standard JPEG file of the same quality setting, and the total file size is
-about the same --- often a little smaller.
-.PP
-Switches for advanced users:
-.TP
-.B \-arithmetic
-Use arithmetic coding.
-.B Caution:
-arithmetic coded JPEG is not yet widely implemented, so many decoders will be
-unable to view an arithmetic coded JPEG file at all.
-.TP
-.B \-dct int
-Use accurate integer DCT method (default).
-.TP
-.B \-dct fast
-Use less accurate integer DCT method [legacy feature].
-When the Independent JPEG Group's software was first released in 1991, the
-compression time for a 1-megapixel JPEG image on a mainstream PC was measured
-in minutes.  Thus, the \fBfast\fR integer DCT algorithm provided noticeable
-performance benefits.  On modern CPUs running libjpeg-turbo, however, the
-compression time for a 1-megapixel JPEG image is measured in milliseconds, and
-thus the performance benefits of the \fBfast\fR algorithm are much less
-noticeable.  On modern x86/x86-64 CPUs that support AVX2 instructions, the
-\fBfast\fR and \fBint\fR methods have similar performance.  On other types of
-CPUs, the \fBfast\fR method is generally about 5-15% faster than the \fBint\fR
-method.
-
-For quality levels of 90 and below, there should be little or no perceptible
-quality difference between the two algorithms.  For quality levels above 90,
-however, the difference between the \fBfast\fR and \fBint\fR methods becomes
-more pronounced.  With quality=97, for instance, the \fBfast\fR method incurs
-generally about a 1-3 dB loss in PSNR relative to the \fBint\fR method, but
-this can be larger for some images.  Do not use the \fBfast\fR method with
-quality levels above 97.  The algorithm often degenerates at quality=98 and
-above and can actually produce a more lossy image than if lower quality levels
-had been used.  Also, in libjpeg-turbo, the \fBfast\fR method is not fully
-accelerated for quality levels above 97, so it will be slower than the
-\fBint\fR method.
-.TP
-.B \-dct float
-Use floating-point DCT method [legacy feature].
-The \fBfloat\fR method does not produce significantly more accurate results
-than the \fBint\fR method, and it is much slower.  The \fBfloat\fR method may
-also give different results on different machines due to varying roundoff
-behavior, whereas the integer methods should give the same results on all
-machines.
-.TP
-.BI \-icc " file"
-Embed ICC color management profile contained in the specified file.
-.TP
-.BI \-restart " N"
-Emit a JPEG restart marker every N MCU rows, or every N MCU blocks if "B" is
-attached to the number.
-.B \-restart 0
-(the default) means no restart markers.
-.TP
-.BI \-smooth " N"
-Smooth the input image to eliminate dithering noise.  N, ranging from 1 to
-100, indicates the strength of smoothing.  0 (the default) means no smoothing.
-.TP
-.BI \-maxmemory " N"
-Set limit for amount of memory to use in processing large images.  Value is
-in thousands of bytes, or millions of bytes if "M" is attached to the
-number.  For example,
-.B \-max 4m
-selects 4000000 bytes.  If more space is needed, an error will occur.
-.TP
-.BI \-outfile " name"
-Send output image to the named file, not to standard output.
-.TP
-.BI \-memdst
-Compress to memory instead of a file.  This feature was implemented mainly as a
-way of testing the in-memory destination manager (jpeg_mem_dest()), but it is
-also useful for benchmarking, since it reduces the I/O overhead.
-.TP
-.BI \-report
-Report compression progress.
-.TP
-.B \-verbose
-Enable debug printout.  More
-.BR \-v 's
-give more output.  Also, version information is printed at startup.
-.TP
-.B \-debug
-Same as
-.BR \-verbose .
-.TP
-.B \-version
-Print version information and exit.
-.PP
-The
-.B \-restart
-option inserts extra markers that allow a JPEG decoder to resynchronize after
-a transmission error.  Without restart markers, any damage to a compressed
-file will usually ruin the image from the point of the error to the end of the
-image; with restart markers, the damage is usually confined to the portion of
-the image up to the next restart marker.  Of course, the restart markers
-occupy extra space.  We recommend
-.B \-restart 1
-for images that will be transmitted across unreliable networks such as Usenet.
-.PP
-The
-.B \-smooth
-option filters the input to eliminate fine-scale noise.  This is often useful
-when converting dithered images to JPEG: a moderate smoothing factor of 10 to
-50 gets rid of dithering patterns in the input file, resulting in a smaller
-JPEG file and a better-looking image.  Too large a smoothing factor will
-visibly blur the image, however.
-.PP
-Switches for wizards:
-.TP
-.B \-baseline
-Force baseline-compatible quantization tables to be generated.  This clamps
-quantization values to 8 bits even at low quality settings.  (This switch is
-poorly named, since it does not ensure that the output is actually baseline
-JPEG.  For example, you can use
-.B \-baseline
-and
-.B \-progressive
-together.)
-.TP
-.BI \-qtables " file"
-Use the quantization tables given in the specified text file.
-.TP
-.BI \-qslots " N[,...]"
-Select which quantization table to use for each color component.
-.TP
-.BI \-sample " HxV[,...]"
-Set JPEG sampling factors for each color component.
-.TP
-.BI \-scans " file"
-Use the scan script given in the specified text file.
-.PP
-The "wizard" switches are intended for experimentation with JPEG.  If you
-don't know what you are doing, \fBdon't use them\fR.  These switches are
-documented further in the file wizard.txt.
-.SH EXAMPLES
-.LP
-This example compresses the PPM file foo.ppm with a quality factor of
-60 and saves the output as foo.jpg:
-.IP
-.B cjpeg \-quality
-.I 60 foo.ppm
-.B >
-.I foo.jpg
-.SH HINTS
-Color GIF files are not the ideal input for JPEG; JPEG is really intended for
-compressing full-color (24-bit) images.  In particular, don't try to convert
-cartoons, line drawings, and other images that have only a few distinct
-colors.  GIF works great on these, JPEG does not.  If you want to convert a
-GIF to JPEG, you should experiment with
-.BR cjpeg 's
-.B \-quality
-and
-.B \-smooth
-options to get a satisfactory conversion.
-.B \-smooth 10
-or so is often helpful.
-.PP
-Avoid running an image through a series of JPEG compression/decompression
-cycles.  Image quality loss will accumulate; after ten or so cycles the image
-may be noticeably worse than it was after one cycle.  It's best to use a
-lossless format while manipulating an image, then convert to JPEG format when
-you are ready to file the image away.
-.PP
-The
-.B \-optimize
-option to
-.B cjpeg
-is worth using when you are making a "final" version for posting or archiving.
-It's also a win when you are using low quality settings to make very small
-JPEG files; the percentage improvement is often a lot more than it is on
-larger files.  (At present,
-.B \-optimize
-mode is always selected when generating progressive JPEG files.)
-.SH ENVIRONMENT
-.TP
-.B JPEGMEM
-If this environment variable is set, its value is the default memory limit.
-The value is specified as described for the
-.B \-maxmemory
-switch.
-.B JPEGMEM
-overrides the default value specified when the program was compiled, and
-itself is overridden by an explicit
-.BR \-maxmemory .
-.SH SEE ALSO
-.BR djpeg (1),
-.BR jpegtran (1),
-.BR rdjpgcom (1),
-.BR wrjpgcom (1)
-.br
-.BR ppm (5),
-.BR pgm (5)
-.br
-Wallace, Gregory K.  "The JPEG Still Picture Compression Standard",
-Communications of the ACM, April 1991 (vol. 34, no. 4), pp. 30-44.
-.SH AUTHOR
-Independent JPEG Group
-.PP
-This file was modified by The libjpeg-turbo Project to include only information
-relevant to libjpeg-turbo, to wordsmith certain sections, and to describe
-features not present in libjpeg.
-.SH ISSUES
-Not all variants of BMP and Targa file formats are supported.
-.PP
-The
-.B \-targa
-switch is not a bug, it's a feature.  (It would be a bug if the Targa format
-designers had not been clueless.)
diff --git a/third_party/libjpeg-turbo/cjpeg.c b/third_party/libjpeg-turbo/cjpeg.c
index 66ac28f..12eb4ab 100644
--- a/third_party/libjpeg-turbo/cjpeg.c
+++ b/third_party/libjpeg-turbo/cjpeg.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1998, Thomas G. Lane.
  * Modified 2003-2011 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2013-2014, 2017, 2019-2021, D. R. Commander.
+ * Copyright (C) 2010, 2013-2014, 2017, 2019-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -27,6 +27,10 @@
  * works regardless of which command line style is used.
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #ifdef CJPEG_FUZZER
 #define JPEG_INTERNALS
 #endif
@@ -34,21 +38,6 @@
 #include "jversion.h"           /* for version message */
 #include "jconfigint.h"
 
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc(),free() */
-extern void *malloc(size_t size);
-extern void free(void *ptr);
-#endif
-
-#ifdef USE_CCOMMAND             /* command-line reader for Macintosh */
-#ifdef __MWERKS__
-#include <SIOUX.h>              /* Metrowerks needs this */
-#include <console.h>            /* ... and this */
-#endif
-#ifdef THINK_C
-#include <console.h>            /* Think declares it here */
-#endif
-#endif
-
 
 /* Create the add-on message string table. */
 
@@ -147,6 +136,7 @@
 static char *outfilename;       /* for -outfile switch */
 boolean memdst;                 /* for -memdst switch */
 boolean report;                 /* for -report switch */
+boolean strict;                 /* for -strict switch */
 
 
 #ifdef CJPEG_FUZZER
@@ -165,7 +155,7 @@
   longjmp(myerr->setjmp_buffer, 1);
 }
 
-static void my_emit_message(j_common_ptr cinfo, int msg_level)
+static void my_emit_message_fuzzer(j_common_ptr cinfo, int msg_level)
 {
   if (msg_level < 0)
     cinfo->err->num_warnings++;
@@ -240,6 +230,7 @@
   fprintf(stderr, "  -memdst        Compress to memory instead of file (useful for benchmarking)\n");
 #endif
   fprintf(stderr, "  -report        Report compression progress\n");
+  fprintf(stderr, "  -strict        Treat all warnings as fatal\n");
   fprintf(stderr, "  -verbose  or  -debug   Emit debug output\n");
   fprintf(stderr, "  -version       Print version information and exit\n");
   fprintf(stderr, "Switches for wizards:\n");
@@ -285,6 +276,7 @@
   outfilename = NULL;
   memdst = FALSE;
   report = FALSE;
+  strict = FALSE;
   cinfo->err->trace_level = 0;
 
   /* Scan command line options, adjust parameters */
@@ -493,6 +485,9 @@
         usage();
       cinfo->smoothing_factor = val;
 
+    } else if (keymatch(arg, "strict", 2)) {
+      strict = TRUE;
+
     } else if (keymatch(arg, "targa", 1)) {
       /* Input file is Targa format. */
       is_targa = TRUE;
@@ -540,6 +535,19 @@
 }
 
 
+METHODDEF(void)
+my_emit_message(j_common_ptr cinfo, int msg_level)
+{
+  if (msg_level < 0) {
+    /* Treat warning as fatal */
+    cinfo->err->error_exit(cinfo);
+  } else {
+    if (cinfo->err->trace_level >= msg_level)
+      cinfo->err->output_message(cinfo);
+  }
+}
+
+
 /*
  * The main program.
  */
@@ -570,11 +578,6 @@
   unsigned long outsize = 0;
   JDIMENSION num_scanlines;
 
-  /* On Mac, fetch a command line. */
-#ifdef USE_CCOMMAND
-  argc = ccommand(&argv);
-#endif
-
   progname = argv[0];
   if (progname == NULL || progname[0] == 0)
     progname = "cjpeg";         /* in case C library doesn't provide it */
@@ -604,6 +607,9 @@
 
   file_index = parse_switches(&cinfo, argc, argv, 0, FALSE);
 
+  if (strict)
+    jerr.emit_message = my_emit_message;
+
 #ifdef TWO_FILE_COMMANDLINE
   if (!memdst) {
     /* Must have either -outfile switch or explicit output file name */
@@ -681,7 +687,7 @@
 
 #ifdef CJPEG_FUZZER
   jerr.error_exit = my_error_exit;
-  jerr.emit_message = my_emit_message;
+  jerr.emit_message = my_emit_message_fuzzer;
   if (setjmp(myerr.setjmp_buffer))
     HANDLE_ERROR()
 #endif
diff --git a/third_party/libjpeg-turbo/croptest.in b/third_party/libjpeg-turbo/croptest.in
deleted file mode 100644
index 7e3c293..0000000
--- a/third_party/libjpeg-turbo/croptest.in
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/bin/bash
-
-set -u
-set -e
-trap onexit INT
-trap onexit TERM
-trap onexit EXIT
-
-onexit()
-{
-	if [ -d $OUTDIR ]; then
-		rm -rf $OUTDIR
-	fi
-}
-
-runme()
-{
-	echo \*\*\* $*
-	$*
-}
-
-IMAGE=vgl_6548_0026a.bmp
-WIDTH=128
-HEIGHT=95
-IMGDIR=@CMAKE_CURRENT_SOURCE_DIR@/testimages
-OUTDIR=`mktemp -d /tmp/__croptest_output.XXXXXX`
-EXEDIR=@CMAKE_CURRENT_BINARY_DIR@
-
-if [ -d $OUTDIR ]; then
-	rm -rf $OUTDIR
-fi
-mkdir -p $OUTDIR
-
-exec >$EXEDIR/croptest.log
-
-echo "============================================================"
-echo "$IMAGE ($WIDTH x $HEIGHT)"
-echo "============================================================"
-echo
-
-for PROGARG in "" -progressive; do
-
-	cp $IMGDIR/$IMAGE $OUTDIR
-	basename=`basename $IMAGE .bmp`
-	echo "------------------------------------------------------------"
-	echo "Generating test images"
-	echo "------------------------------------------------------------"
-	echo
-	runme $EXEDIR/cjpeg $PROGARG -grayscale -outfile $OUTDIR/${basename}_GRAY.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg $PROGARG -sample 2x2 -outfile $OUTDIR/${basename}_420.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg $PROGARG -sample 2x1 -outfile $OUTDIR/${basename}_422.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg $PROGARG -sample 1x2 -outfile $OUTDIR/${basename}_440.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg $PROGARG -sample 1x1 -outfile $OUTDIR/${basename}_444.jpg $IMGDIR/${basename}.bmp
-	echo
-
-	for NSARG in "" -nosmooth; do
-
-		for COLORSARG in "" "-colors 256 -dither none -onepass"; do
-
-			for Y in {0..16}; do
-
-				for H in {1..16}; do
-
-					X=$(( (Y*16)%128 ))
-					W=$(( WIDTH-X-7 ))
-					if [ $Y -le 15 ]; then
-						CROPSPEC="${W}x${H}+${X}+${Y}"
-					else
-						Y2=$(( HEIGHT-H ));
-						CROPSPEC="${W}x${H}+${X}+${Y2}"
-					fi
-
-					echo "------------------------------------------------------------"
-					echo $PROGARG $NSARG $COLORSARG -crop $CROPSPEC
-					echo "------------------------------------------------------------"
-					echo
-					for samp in GRAY 420 422 440 444; do
-						$EXEDIR/djpeg $NSARG $COLORSARG -rgb -outfile $OUTDIR/${basename}_${samp}_full.ppm $OUTDIR/${basename}_${samp}.jpg
-						convert -crop $CROPSPEC $OUTDIR/${basename}_${samp}_full.ppm $OUTDIR/${basename}_${samp}_ref.ppm
-						runme $EXEDIR/djpeg $NSARG $COLORSARG -crop $CROPSPEC -rgb -outfile $OUTDIR/${basename}_${samp}.ppm $OUTDIR/${basename}_${samp}.jpg
-						runme cmp $OUTDIR/${basename}_${samp}.ppm $OUTDIR/${basename}_${samp}_ref.ppm
-					done
-					echo
-
-				done
-
-			done
-
-		done
-
-	done
-
-done
-
-echo SUCCESS!
diff --git a/third_party/libjpeg-turbo/djpeg.1 b/third_party/libjpeg-turbo/djpeg.1
deleted file mode 100644
index 31431b9..0000000
--- a/third_party/libjpeg-turbo/djpeg.1
+++ /dev/null
@@ -1,320 +0,0 @@
-.TH DJPEG 1 "4 November 2020"
-.SH NAME
-djpeg \- decompress a JPEG file to an image file
-.SH SYNOPSIS
-.B djpeg
-[
-.I options
-]
-[
-.I filename
-]
-.LP
-.SH DESCRIPTION
-.LP
-.B djpeg
-decompresses the named JPEG file, or the standard input if no file is named,
-and produces an image file on the standard output.  PBMPLUS (PPM/PGM), BMP,
-GIF, or Targa output format can be selected.
-.SH OPTIONS
-All switch names may be abbreviated; for example,
-.B \-grayscale
-may be written
-.B \-gray
-or
-.BR \-gr .
-Most of the "basic" switches can be abbreviated to as little as one letter.
-Upper and lower case are equivalent (thus
-.B \-BMP
-is the same as
-.BR \-bmp ).
-British spellings are also accepted (e.g.,
-.BR \-greyscale ),
-though for brevity these are not mentioned below.
-.PP
-The basic switches are:
-.TP
-.BI \-colors " N"
-Reduce image to at most N colors.  This reduces the number of colors used in
-the output image, so that it can be displayed on a colormapped display or
-stored in a colormapped file format.  For example, if you have an 8-bit
-display, you'd need to reduce to 256 or fewer colors.
-.TP
-.BI \-quantize " N"
-Same as
-.BR \-colors .
-.B \-colors
-is the recommended name,
-.B \-quantize
-is provided only for backwards compatibility.
-.TP
-.B \-fast
-Select recommended processing options for fast, low quality output.  (The
-default options are chosen for highest quality output.)  Currently, this is
-equivalent to \fB\-dct fast \-nosmooth \-onepass \-dither ordered\fR.
-.TP
-.B \-grayscale
-Force grayscale output even if JPEG file is color.  Useful for viewing on
-monochrome displays; also,
-.B djpeg
-runs noticeably faster in this mode.
-.TP
-.B \-rgb
-Force RGB output even if JPEG file is grayscale.
-.TP
-.BI \-scale " M/N"
-Scale the output image by a factor M/N.  Currently the scale factor must be
-M/8, where M is an integer between 1 and 16 inclusive, or any reduced fraction
-thereof (such as 1/2, 3/4, etc.)  Scaling is handy if the image is larger than
-your screen; also,
-.B djpeg
-runs much faster when scaling down the output.
-.TP
-.B \-bmp
-Select BMP output format (Windows flavor).  8-bit colormapped format is
-emitted if
-.B \-colors
-or
-.B \-grayscale
-is specified, or if the JPEG file is grayscale; otherwise, 24-bit full-color
-format is emitted.
-.TP
-.B \-gif
-Select GIF output format (LZW-compressed).  Since GIF does not support more
-than 256 colors,
-.B \-colors 256
-is assumed (unless you specify a smaller number of colors).  If you specify
-.BR \-fast,
-the default number of colors is 216.
-.TP
-.B \-gif0
-Select GIF output format (uncompressed).  Since GIF does not support more than
-256 colors,
-.B \-colors 256
-is assumed (unless you specify a smaller number of colors).  If you specify
-.BR \-fast,
-the default number of colors is 216.
-.TP
-.B \-os2
-Select BMP output format (OS/2 1.x flavor).  8-bit colormapped format is
-emitted if
-.B \-colors
-or
-.B \-grayscale
-is specified, or if the JPEG file is grayscale; otherwise, 24-bit full-color
-format is emitted.
-.TP
-.B \-pnm
-Select PBMPLUS (PPM/PGM) output format (this is the default format).
-PGM is emitted if the JPEG file is grayscale or if
-.B \-grayscale
-is specified; otherwise PPM is emitted.
-.TP
-.B \-targa
-Select Targa output format.  Grayscale format is emitted if the JPEG file is
-grayscale or if
-.B \-grayscale
-is specified; otherwise, colormapped format is emitted if
-.B \-colors
-is specified; otherwise, 24-bit full-color format is emitted.
-.PP
-Switches for advanced users:
-.TP
-.B \-dct int
-Use accurate integer DCT method (default).
-.TP
-.B \-dct fast
-Use less accurate integer DCT method [legacy feature].
-When the Independent JPEG Group's software was first released in 1991, the
-decompression time for a 1-megapixel JPEG image on a mainstream PC was measured
-in minutes.  Thus, the \fBfast\fR integer DCT algorithm provided noticeable
-performance benefits.  On modern CPUs running libjpeg-turbo, however, the
-decompression time for a 1-megapixel JPEG image is measured in milliseconds,
-and thus the performance benefits of the \fBfast\fR algorithm are much less
-noticeable.  On modern x86/x86-64 CPUs that support AVX2 instructions, the
-\fBfast\fR and \fBint\fR methods have similar performance.  On other types of
-CPUs, the \fBfast\fR method is generally about 5-15% faster than the \fBint\fR
-method.
-
-If the JPEG image was compressed using a quality level of 85 or below, then
-there should be little or no perceptible quality difference between the two
-algorithms.  When decompressing images that were compressed using quality
-levels above 85, however, the difference between the \fBfast\fR and \fBint\fR
-methods becomes more pronounced.  With images compressed using quality=97, for
-instance, the \fBfast\fR method incurs generally about a 4-6 dB loss in PSNR
-relative to the \fBint\fR method, but this can be larger for some images.  If
-you can avoid it, do not use the \fBfast\fR method when decompressing images
-that were compressed using quality levels above 97.  The algorithm often
-degenerates for such images and can actually produce a more lossy output image
-than if the JPEG image had been compressed using lower quality levels.
-.TP
-.B \-dct float
-Use floating-point DCT method [legacy feature].
-The \fBfloat\fR method does not produce significantly more accurate results
-than the \fBint\fR method, and it is much slower.  The \fBfloat\fR method may
-also give different results on different machines due to varying roundoff
-behavior, whereas the integer methods should give the same results on all
-machines.
-.TP
-.B \-dither fs
-Use Floyd-Steinberg dithering in color quantization.
-.TP
-.B \-dither ordered
-Use ordered dithering in color quantization.
-.TP
-.B \-dither none
-Do not use dithering in color quantization.
-By default, Floyd-Steinberg dithering is applied when quantizing colors; this
-is slow but usually produces the best results.  Ordered dither is a compromise
-between speed and quality; no dithering is fast but usually looks awful.  Note
-that these switches have no effect unless color quantization is being done.
-Ordered dither is only available in
-.B \-onepass
-mode.
-.TP
-.BI \-icc " file"
-Extract ICC color management profile to the specified file.
-.TP
-.BI \-map " file"
-Quantize to the colors used in the specified image file.  This is useful for
-producing multiple files with identical color maps, or for forcing a
-predefined set of colors to be used.  The
-.I file
-must be a GIF or PPM file. This option overrides
-.B \-colors
-and
-.BR \-onepass .
-.TP
-.B \-nosmooth
-Use a faster, lower-quality upsampling routine.
-.TP
-.B \-onepass
-Use one-pass instead of two-pass color quantization.  The one-pass method is
-faster and needs less memory, but it produces a lower-quality image.
-.B \-onepass
-is ignored unless you also say
-.B \-colors
-.IR N .
-Also, the one-pass method is always used for grayscale output (the two-pass
-method is no improvement then).
-.TP
-.BI \-maxmemory " N"
-Set limit for amount of memory to use in processing large images.  Value is
-in thousands of bytes, or millions of bytes if "M" is attached to the
-number.  For example,
-.B \-max 4m
-selects 4000000 bytes.  If more space is needed, an error will occur.
-.TP
-.BI \-maxscans " N"
-Abort if the JPEG image contains more than
-.I N
-scans.  This feature demonstrates a method by which applications can guard
-against denial-of-service attacks instigated by specially-crafted malformed
-JPEG images containing numerous scans with missing image data or image data
-consisting only of "EOB runs" (a feature of progressive JPEG images that allows
-potentially hundreds of thousands of adjoining zero-value pixels to be
-represented using only a few bytes.)  Attempting to decompress such malformed
-JPEG images can cause excessive CPU activity, since the decompressor must fully
-process each scan (even if the scan is corrupt) before it can proceed to the
-next scan.
-.TP
-.BI \-outfile " name"
-Send output image to the named file, not to standard output.
-.TP
-.BI \-memsrc
-Load input file into memory before decompressing.  This feature was implemented
-mainly as a way of testing the in-memory source manager (jpeg_mem_src().)
-.TP
-.BI \-report
-Report decompression progress.
-.TP
-.BI \-skip " Y0,Y1"
-Decompress all rows of the JPEG image except those between Y0 and Y1
-(inclusive.)  Note that if decompression scaling is being used, then Y0 and Y1
-are relative to the scaled image dimensions.
-.TP
-.BI \-crop " WxH+X+Y"
-Decompress only a rectangular subregion of the image, starting at point X,Y
-with width W and height H.  If necessary, X will be shifted left to the nearest
-iMCU boundary, and the width will be increased accordingly.  Note that if
-decompression scaling is being used, then X, Y, W, and H are relative to the
-scaled image dimensions.  Currently this option only works with the
-PBMPLUS (PPM/PGM), GIF, and Targa output formats.
-.TP
-.BI \-strict
-Treat all warnings as fatal.  This feature also demonstrates a method by which
-applications can guard against attacks instigated by specially-crafted
-malformed JPEG images.  Enabling this option will cause the decompressor to
-abort if the JPEG image contains incomplete or corrupt image data.
-.TP
-.B \-verbose
-Enable debug printout.  More
-.BR \-v 's
-give more output.  Also, version information is printed at startup.
-.TP
-.B \-debug
-Same as
-.BR \-verbose .
-.TP
-.B \-version
-Print version information and exit.
-.SH EXAMPLES
-.LP
-This example decompresses the JPEG file foo.jpg, quantizes it to
-256 colors, and saves the output in 8-bit BMP format in foo.bmp:
-.IP
-.B djpeg \-colors 256 \-bmp
-.I foo.jpg
-.B >
-.I foo.bmp
-.SH HINTS
-To get a quick preview of an image, use the
-.B \-grayscale
-and/or
-.B \-scale
-switches.
-.B \-grayscale \-scale 1/8
-is the fastest case.
-.PP
-Several options are available that trade off image quality to gain speed.
-.B \-fast
-turns on the recommended settings.
-.PP
-.B \-dct fast
-and/or
-.B \-nosmooth
-gain speed at a small sacrifice in quality.
-When producing a color-quantized image,
-.B \-onepass \-dither ordered
-is fast but much lower quality than the default behavior.
-.B \-dither none
-may give acceptable results in two-pass mode, but is seldom tolerable in
-one-pass mode.
-.SH ENVIRONMENT
-.TP
-.B JPEGMEM
-If this environment variable is set, its value is the default memory limit.
-The value is specified as described for the
-.B \-maxmemory
-switch.
-.B JPEGMEM
-overrides the default value specified when the program was compiled, and
-itself is overridden by an explicit
-.BR \-maxmemory .
-.SH SEE ALSO
-.BR cjpeg (1),
-.BR jpegtran (1),
-.BR rdjpgcom (1),
-.BR wrjpgcom (1)
-.br
-.BR ppm (5),
-.BR pgm (5)
-.br
-Wallace, Gregory K.  "The JPEG Still Picture Compression Standard",
-Communications of the ACM, April 1991 (vol. 34, no. 4), pp. 30-44.
-.SH AUTHOR
-Independent JPEG Group
-.PP
-This file was modified by The libjpeg-turbo Project to include only information
-relevant to libjpeg-turbo, to wordsmith certain sections, and to describe
-features not present in libjpeg.
diff --git a/third_party/libjpeg-turbo/djpeg.c b/third_party/libjpeg-turbo/djpeg.c
index cc2eb9d..68ffed2 100644
--- a/third_party/libjpeg-turbo/djpeg.c
+++ b/third_party/libjpeg-turbo/djpeg.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2013-2019 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010-2011, 2013-2017, 2019-2020, D. R. Commander.
+ * Copyright (C) 2010-2011, 2013-2017, 2019-2020, 2022, D. R. Commander.
  * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -28,26 +28,16 @@
  * works regardless of which command line style is used.
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #include "cdjpeg.h"             /* Common decls for cjpeg/djpeg applications */
 #include "jversion.h"           /* for version message */
 #include "jconfigint.h"
 
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare free() */
-extern void free(void *ptr);
-#endif
-
 #include <ctype.h>              /* to declare isprint() */
 
-#ifdef USE_CCOMMAND             /* command-line reader for Macintosh */
-#ifdef __MWERKS__
-#include <SIOUX.h>              /* Metrowerks needs this */
-#include <console.h>            /* ... and this */
-#endif
-#ifdef THINK_C
-#include <console.h>            /* Think declares it here */
-#endif
-#endif
-
 
 /* Create the add-on message string table. */
 
@@ -554,11 +544,6 @@
 #endif
   JDIMENSION num_scanlines;
 
-  /* On Mac, fetch a command line. */
-#ifdef USE_CCOMMAND
-  argc = ccommand(&argv);
-#endif
-
   progname = argv[0];
   if (progname == NULL || progname[0] == 0)
     progname = "djpeg";         /* in case C library doesn't provide it */
@@ -655,7 +640,7 @@
         fprintf(stderr, "%s: memory allocation failure\n", progname);
         return EXIT_FAILURE;
       }
-      nbytes = JFREAD(input_file, &inbuffer[insize], INPUT_BUF_SIZE);
+      nbytes = fread(&inbuffer[insize], 1, INPUT_BUF_SIZE, input_file);
       if (nbytes < INPUT_BUF_SIZE && ferror(input_file)) {
         if (file_index < argc)
           fprintf(stderr, "%s: can't read from %s\n", progname,
@@ -725,7 +710,7 @@
      * that skip_start <= skip_end.
      */
     if (skip_end > cinfo.output_height - 1) {
-      fprintf(stderr, "%s: skip region exceeds image height %d\n", progname,
+      fprintf(stderr, "%s: skip region exceeds image height %u\n", progname,
               cinfo.output_height);
       return EXIT_FAILURE;
     }
@@ -746,7 +731,7 @@
     }
     if ((tmp = jpeg_skip_scanlines(&cinfo, skip_end - skip_start + 1)) !=
         skip_end - skip_start + 1) {
-      fprintf(stderr, "%s: jpeg_skip_scanlines() returned %d rather than %d\n",
+      fprintf(stderr, "%s: jpeg_skip_scanlines() returned %u rather than %u\n",
               progname, tmp, skip_end - skip_start + 1);
       return EXIT_FAILURE;
     }
@@ -765,7 +750,7 @@
      */
     if (crop_x + crop_width > cinfo.output_width ||
         crop_y + crop_height > cinfo.output_height) {
-      fprintf(stderr, "%s: crop dimensions exceed image dimensions %d x %d\n",
+      fprintf(stderr, "%s: crop dimensions exceed image dimensions %u x %u\n",
               progname, cinfo.output_width, cinfo.output_height);
       return EXIT_FAILURE;
     }
@@ -786,7 +771,7 @@
 
     /* Process data */
     if ((tmp = jpeg_skip_scanlines(&cinfo, crop_y)) != crop_y) {
-      fprintf(stderr, "%s: jpeg_skip_scanlines() returned %d rather than %d\n",
+      fprintf(stderr, "%s: jpeg_skip_scanlines() returned %u rather than %u\n",
               progname, tmp, crop_y);
       return EXIT_FAILURE;
     }
@@ -799,7 +784,7 @@
          jpeg_skip_scanlines(&cinfo,
                              cinfo.output_height - crop_y - crop_height)) !=
         cinfo.output_height - crop_y - crop_height) {
-      fprintf(stderr, "%s: jpeg_skip_scanlines() returned %d rather than %d\n",
+      fprintf(stderr, "%s: jpeg_skip_scanlines() returned %u rather than %u\n",
               progname, tmp, cinfo.output_height - crop_y - crop_height);
       return EXIT_FAILURE;
     }
diff --git a/third_party/libjpeg-turbo/jcapimin.c b/third_party/libjpeg-turbo/jcapimin.c
index 178c55b..84e7ecc 100644
--- a/third_party/libjpeg-turbo/jcapimin.c
+++ b/third_party/libjpeg-turbo/jcapimin.c
@@ -4,8 +4,8 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1998, Thomas G. Lane.
  * Modified 2003-2010 by Guido Vollbeding.
- * It was modified by The libjpeg-turbo Project to include only code relevant
- * to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -52,7 +52,7 @@
   {
     struct jpeg_error_mgr *err = cinfo->err;
     void *client_data = cinfo->client_data; /* ignore Purify complaint here */
-    MEMZERO(cinfo, sizeof(struct jpeg_compress_struct));
+    memset(cinfo, 0, sizeof(struct jpeg_compress_struct));
     cinfo->err = err;
     cinfo->client_data = client_data;
   }
diff --git a/third_party/libjpeg-turbo/jcarith.c b/third_party/libjpeg-turbo/jcarith.c
index b6d093f..b172052 100644
--- a/third_party/libjpeg-turbo/jcarith.c
+++ b/third_party/libjpeg-turbo/jcarith.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Developed 1997-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015, 2018, D. R. Commander.
+ * Copyright (C) 2015, 2018, 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -338,14 +338,14 @@
     compptr = cinfo->cur_comp_info[ci];
     /* DC needs no table for refinement scan */
     if (cinfo->progressive_mode == 0 || (cinfo->Ss == 0 && cinfo->Ah == 0)) {
-      MEMZERO(entropy->dc_stats[compptr->dc_tbl_no], DC_STAT_BINS);
+      memset(entropy->dc_stats[compptr->dc_tbl_no], 0, DC_STAT_BINS);
       /* Reset DC predictions to 0 */
       entropy->last_dc_val[ci] = 0;
       entropy->dc_context[ci] = 0;
     }
     /* AC needs no table when not present */
     if (cinfo->progressive_mode == 0 || cinfo->Se) {
-      MEMZERO(entropy->ac_stats[compptr->ac_tbl_no], AC_STAT_BINS);
+      memset(entropy->ac_stats[compptr->ac_tbl_no], 0, AC_STAT_BINS);
     }
   }
 
@@ -836,7 +836,7 @@
      * We are fully adaptive here and need no extra
      * statistics gathering pass!
      */
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
+    ERREXIT(cinfo, JERR_NOTIMPL);
 
   /* We assume jcmaster.c already validated the progressive scan parameters. */
 
@@ -867,7 +867,7 @@
       if (entropy->dc_stats[tbl] == NULL)
         entropy->dc_stats[tbl] = (unsigned char *)(*cinfo->mem->alloc_small)
           ((j_common_ptr)cinfo, JPOOL_IMAGE, DC_STAT_BINS);
-      MEMZERO(entropy->dc_stats[tbl], DC_STAT_BINS);
+      memset(entropy->dc_stats[tbl], 0, DC_STAT_BINS);
       /* Initialize DC predictions to 0 */
       entropy->last_dc_val[ci] = 0;
       entropy->dc_context[ci] = 0;
@@ -880,7 +880,7 @@
       if (entropy->ac_stats[tbl] == NULL)
         entropy->ac_stats[tbl] = (unsigned char *)(*cinfo->mem->alloc_small)
           ((j_common_ptr)cinfo, JPOOL_IMAGE, AC_STAT_BINS);
-      MEMZERO(entropy->ac_stats[tbl], AC_STAT_BINS);
+      memset(entropy->ac_stats[tbl], 0, AC_STAT_BINS);
 #ifdef CALCULATE_SPECTRAL_CONDITIONING
       if (cinfo->progressive_mode)
         /* Section G.1.3.2: Set appropriate arithmetic conditioning value Kx */
diff --git a/third_party/libjpeg-turbo/jchuff.c b/third_party/libjpeg-turbo/jchuff.c
index 8ff817b..f4dfa1c 100644
--- a/third_party/libjpeg-turbo/jchuff.c
+++ b/third_party/libjpeg-turbo/jchuff.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2009-2011, 2014-2016, 2018-2021, D. R. Commander.
+ * Copyright (C) 2009-2011, 2014-2016, 2018-2022, D. R. Commander.
  * Copyright (C) 2015, Matthieu Darbois.
  * Copyright (C) 2018, Matthias Räncker.
  * Copyright (C) 2020, Arm Limited.
@@ -200,12 +200,12 @@
         entropy->dc_count_ptrs[dctbl] = (long *)
           (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                       257 * sizeof(long));
-      MEMZERO(entropy->dc_count_ptrs[dctbl], 257 * sizeof(long));
+      memset(entropy->dc_count_ptrs[dctbl], 0, 257 * sizeof(long));
       if (entropy->ac_count_ptrs[actbl] == NULL)
         entropy->ac_count_ptrs[actbl] = (long *)
           (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                       257 * sizeof(long));
-      MEMZERO(entropy->ac_count_ptrs[actbl], 257 * sizeof(long));
+      memset(entropy->ac_count_ptrs[actbl], 0, 257 * sizeof(long));
 #endif
     } else {
       /* Compute derived values for Huffman tables */
@@ -315,8 +315,8 @@
    * this lets us detect duplicate VAL entries here, and later
    * allows emit_bits to detect any attempt to emit such symbols.
    */
-  MEMZERO(dtbl->ehufco, sizeof(dtbl->ehufco));
-  MEMZERO(dtbl->ehufsi, sizeof(dtbl->ehufsi));
+  memset(dtbl->ehufco, 0, sizeof(dtbl->ehufco));
+  memset(dtbl->ehufsi, 0, sizeof(dtbl->ehufsi));
 
   /* This is also a convenient place to check for out-of-range
    * and duplicated VAL entries.  We allow 0..255 for AC symbols
@@ -478,7 +478,7 @@
     buffer = _buffer; \
     while (bytes > 0) { \
       bytestocopy = MIN(bytes, state->free_in_buffer); \
-      MEMCOPY(state->next_output_byte, buffer, bytestocopy); \
+      memcpy(state->next_output_byte, buffer, bytestocopy); \
       state->next_output_byte += bytestocopy; \
       buffer += bytestocopy; \
       state->free_in_buffer -= bytestocopy; \
@@ -941,8 +941,8 @@
 
   /* This algorithm is explained in section K.2 of the JPEG standard */
 
-  MEMZERO(bits, sizeof(bits));
-  MEMZERO(codesize, sizeof(codesize));
+  memset(bits, 0, sizeof(bits));
+  memset(codesize, 0, sizeof(codesize));
   for (i = 0; i < 257; i++)
     others[i] = -1;             /* init links to empty */
 
@@ -1044,7 +1044,7 @@
   bits[i]--;
 
   /* Return final symbol counts (only for lengths 0..16) */
-  MEMCOPY(htbl->bits, bits, sizeof(htbl->bits));
+  memcpy(htbl->bits, bits, sizeof(htbl->bits));
 
   /* Return a list of the symbols sorted by code length */
   /* It's not real clear to me why we don't need to consider the codelength
@@ -1083,8 +1083,8 @@
   /* It's important not to apply jpeg_gen_optimal_table more than once
    * per table, because it clobbers the input frequency counts!
    */
-  MEMZERO(did_dc, sizeof(did_dc));
-  MEMZERO(did_ac, sizeof(did_ac));
+  memset(did_dc, 0, sizeof(did_dc));
+  memset(did_ac, 0, sizeof(did_ac));
 
   for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
     compptr = cinfo->cur_comp_info[ci];
diff --git a/third_party/libjpeg-turbo/jconfig.h b/third_party/libjpeg-turbo/jconfig.h
index b197503..319d99e 100644
--- a/third_party/libjpeg-turbo/jconfig.h
+++ b/third_party/libjpeg-turbo/jconfig.h
@@ -4,10 +4,10 @@
 #define JPEG_LIB_VERSION  62
 
 /* libjpeg-turbo version */
-#define LIBJPEG_TURBO_VERSION  2.1.0
+#define LIBJPEG_TURBO_VERSION  2.1.4
 
 /* libjpeg-turbo version in integer form */
-#define LIBJPEG_TURBO_VERSION_NUMBER  2001000
+#define LIBJPEG_TURBO_VERSION_NUMBER  2001004
 
 /* Support arithmetic encoding */
 #define C_ARITH_CODING_SUPPORTED 1
@@ -39,42 +39,6 @@
 
 #define BITS_IN_JSAMPLE  8      /* use 8 or 12 */
 
-/* Define to 1 if you have the <locale.h> header file. */
-#define HAVE_LOCALE_H 1
-
-/* Define to 1 if you have the <stddef.h> header file. */
-#define HAVE_STDDEF_H 1
-
-/* Define to 1 if you have the <stdlib.h> header file. */
-#define HAVE_STDLIB_H 1
-
-/* Define if you need to include <sys/types.h> to get size_t. */
-#define NEED_SYS_TYPES_H
-
-/* Define if you have BSD-like bzero and bcopy in <strings.h> rather than
-   memset/memcpy in <string.h>. */
-/* #undef NEED_BSD_STRINGS */
-
-/* Define to 1 if the system has the type `unsigned char'. */
-#define HAVE_UNSIGNED_CHAR 1
-
-/* Define to 1 if the system has the type `unsigned short'. */
-#define HAVE_UNSIGNED_SHORT 1
-
-/* Compiler does not support pointers to undefined structures. */
-/* #undef INCOMPLETE_TYPES_BROKEN */
-
 /* Define if your (broken) compiler shifts signed values as if they were
    unsigned. */
 /* #undef RIGHT_SHIFT_IS_UNSIGNED */
-
-/* Define to 1 if type `char' is unsigned and you are not using gcc.  */
-#ifndef __CHAR_UNSIGNED__
-/* #undef __CHAR_UNSIGNED__ */
-#endif
-
-/* Define to empty if `const' does not conform to ANSI C. */
-/* #undef const */
-
-/* Define to `unsigned int' if <sys/types.h> does not define. */
-/* #undef size_t */
diff --git a/third_party/libjpeg-turbo/jconfig.h.in b/third_party/libjpeg-turbo/jconfig.h.in
index d4284d9..e018012 100644
--- a/third_party/libjpeg-turbo/jconfig.h.in
+++ b/third_party/libjpeg-turbo/jconfig.h.in
@@ -32,37 +32,6 @@
 
 #define BITS_IN_JSAMPLE  @BITS_IN_JSAMPLE@      /* use 8 or 12 */
 
-/* Define to 1 if you have the <locale.h> header file. */
-#cmakedefine HAVE_LOCALE_H 1
-
-/* Define to 1 if you have the <stddef.h> header file. */
-#cmakedefine HAVE_STDDEF_H 1
-
-/* Define to 1 if you have the <stdlib.h> header file. */
-#cmakedefine HAVE_STDLIB_H 1
-
-/* Define if you need to include <sys/types.h> to get size_t. */
-#cmakedefine NEED_SYS_TYPES_H 1
-
-/* Define if you have BSD-like bzero and bcopy in <strings.h> rather than
-   memset/memcpy in <string.h>. */
-#cmakedefine NEED_BSD_STRINGS 1
-
-/* Define to 1 if the system has the type `unsigned char'. */
-#cmakedefine HAVE_UNSIGNED_CHAR 1
-
-/* Define to 1 if the system has the type `unsigned short'. */
-#cmakedefine HAVE_UNSIGNED_SHORT 1
-
-/* Compiler does not support pointers to undefined structures. */
-#cmakedefine INCOMPLETE_TYPES_BROKEN 1
-
 /* Define if your (broken) compiler shifts signed values as if they were
    unsigned. */
 #cmakedefine RIGHT_SHIFT_IS_UNSIGNED 1
-
-/* Define to empty if `const' does not conform to ANSI C. */
-/* #undef const */
-
-/* Define to `unsigned int' if <sys/types.h> does not define. */
-/* #undef size_t */
diff --git a/third_party/libjpeg-turbo/jconfig.txt b/third_party/libjpeg-turbo/jconfig.txt
index 21f35c1..d593da9 100644
--- a/third_party/libjpeg-turbo/jconfig.txt
+++ b/third_party/libjpeg-turbo/jconfig.txt
@@ -26,50 +26,6 @@
  * #define the symbol if yes, #undef it if no.
  */
 
-/* Does your compiler support the declaration "unsigned char" ?
- * How about "unsigned short" ?
- */
-#define HAVE_UNSIGNED_CHAR
-#define HAVE_UNSIGNED_SHORT
-
-/* Define "void" as "char" if your compiler doesn't know about type void.
- * NOTE: be sure to define void such that "void *" represents the most general
- * pointer type, e.g., that returned by malloc().
- */
-/* #define void char */
-
-/* Define "const" as empty if your compiler doesn't know the "const" keyword.
- */
-/* #define const */
-
-/* Define this if your system has an ANSI-conforming <stddef.h> file.
- */
-#define HAVE_STDDEF_H
-
-/* Define this if your system has an ANSI-conforming <stdlib.h> file.
- */
-#define HAVE_STDLIB_H
-
-/* Define this if your system does not have an ANSI/SysV <string.h>,
- * but does have a BSD-style <strings.h>.
- */
-#undef NEED_BSD_STRINGS
-
-/* Define this if your system does not provide typedef size_t in any of the
- * ANSI-standard places (stddef.h, stdlib.h, or stdio.h), but places it in
- * <sys/types.h> instead.
- */
-#undef NEED_SYS_TYPES_H
-
-/* Although a real ANSI C compiler can deal perfectly well with pointers to
- * unspecified structures (see "incomplete types" in the spec), a few pre-ANSI
- * and pseudo-ANSI compilers get confused.  To keep one of these bozos happy,
- * define INCOMPLETE_TYPES_BROKEN.  This is not recommended unless you
- * actually get "missing structure definition" warnings or errors while
- * compiling the JPEG code.
- */
-#undef INCOMPLETE_TYPES_BROKEN
-
 /* Define "boolean" as unsigned char, not int, on Windows systems.
  */
 #ifdef _WIN32
diff --git a/third_party/libjpeg-turbo/jconfigint.h b/third_party/libjpeg-turbo/jconfigint.h
index c0ae709..a755e57 100644
--- a/third_party/libjpeg-turbo/jconfigint.h
+++ b/third_party/libjpeg-turbo/jconfigint.h
@@ -28,9 +28,10 @@
 #define PACKAGE_NAME  "libjpeg-turbo"
 
 /* Version number of package */
-#define VERSION  "2.1.0"
+#define VERSION  "2.1.4"
 
 /* The size of `size_t', as computed by sizeof. */
+#include <stdint.h>
 #if __WORDSIZE==64 || defined(_WIN64)
 #define SIZEOF_SIZE_T  8
 #else
diff --git a/third_party/libjpeg-turbo/jcphuff.c b/third_party/libjpeg-turbo/jcphuff.c
index 9bf9612..872e570 100644
--- a/third_party/libjpeg-turbo/jcphuff.c
+++ b/third_party/libjpeg-turbo/jcphuff.c
@@ -4,9 +4,10 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1995-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2011, 2015, 2018, 2021, D. R. Commander.
+ * Copyright (C) 2011, 2015, 2018, 2021-2022, D. R. Commander.
  * Copyright (C) 2016, 2018, Matthieu Darbois.
  * Copyright (C) 2020, Arm Limited.
+ * Copyright (C) 2021, Alex Richardson.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -274,7 +275,7 @@
         entropy->count_ptrs[tbl] = (long *)
           (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                       257 * sizeof(long));
-      MEMZERO(entropy->count_ptrs[tbl], 257 * sizeof(long));
+      memset(entropy->count_ptrs[tbl], 0, 257 * sizeof(long));
     } else {
       /* Compute derived values for Huffman table */
       /* We may do this more than once for a table, but it's not expensive */
@@ -583,8 +584,8 @@
       continue; \
     /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ \
     temp2 ^= temp; \
-    values[k] = temp; \
-    values[k + DCTSIZE2] = temp2; \
+    values[k] = (JCOEF)temp; \
+    values[k + DCTSIZE2] = (JCOEF)temp2; \
     zerobits |= ((size_t)1U) << k; \
   } \
 }
@@ -680,7 +681,7 @@
       emit_restart(entropy, entropy->next_restart_num);
 
 #ifdef WITH_SIMD
-  cvalue = values = (JCOEF *)PAD((size_t)values_unaligned, 16);
+  cvalue = values = (JCOEF *)PAD((JUINTPTR)values_unaligned, 16);
 #else
   /* Not using SIMD, so alignment is not needed */
   cvalue = values = values_unaligned;
@@ -945,7 +946,7 @@
       emit_restart(entropy, entropy->next_restart_num);
 
 #ifdef WITH_SIMD
-  cabsvalue = absvalues = (JCOEF *)PAD((size_t)absvalues_unaligned, 16);
+  cabsvalue = absvalues = (JCOEF *)PAD((JUINTPTR)absvalues_unaligned, 16);
 #else
   /* Not using SIMD, so alignment is not needed */
   cabsvalue = absvalues = absvalues_unaligned;
@@ -1061,7 +1062,7 @@
   /* It's important not to apply jpeg_gen_optimal_table more than once
    * per table, because it clobbers the input frequency counts!
    */
-  MEMZERO(did, sizeof(did));
+  memset(did, 0, sizeof(did));
 
   for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
     compptr = cinfo->cur_comp_info[ci];
diff --git a/third_party/libjpeg-turbo/jcprepct.c b/third_party/libjpeg-turbo/jcprepct.c
index d59713a..f27cc34 100644
--- a/third_party/libjpeg-turbo/jcprepct.c
+++ b/third_party/libjpeg-turbo/jcprepct.c
@@ -3,8 +3,8 @@
  *
  * This file is part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1996, Thomas G. Lane.
- * It was modified by The libjpeg-turbo Project to include only code relevant
- * to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -289,8 +289,8 @@
                      cinfo->max_h_samp_factor) / compptr->h_samp_factor),
        (JDIMENSION)(3 * rgroup_height));
     /* Copy true buffer row pointers into the middle of the fake row array */
-    MEMCOPY(fake_buffer + rgroup_height, true_buffer,
-            3 * rgroup_height * sizeof(JSAMPROW));
+    memcpy(fake_buffer + rgroup_height, true_buffer,
+           3 * rgroup_height * sizeof(JSAMPROW));
     /* Fill in the above and below wraparound pointers */
     for (i = 0; i < rgroup_height; i++) {
       fake_buffer[i] = true_buffer[2 * rgroup_height + i];
diff --git a/third_party/libjpeg-turbo/jctrans.c b/third_party/libjpeg-turbo/jctrans.c
index ab6a218..e121028 100644
--- a/third_party/libjpeg-turbo/jctrans.c
+++ b/third_party/libjpeg-turbo/jctrans.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1995-1998, Thomas G. Lane.
  * Modified 2000-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2020, D. R. Commander.
+ * Copyright (C) 2020, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -100,8 +100,8 @@
       qtblptr = &dstinfo->quant_tbl_ptrs[tblno];
       if (*qtblptr == NULL)
         *qtblptr = jpeg_alloc_quant_table((j_common_ptr)dstinfo);
-      MEMCOPY((*qtblptr)->quantval, srcinfo->quant_tbl_ptrs[tblno]->quantval,
-              sizeof((*qtblptr)->quantval));
+      memcpy((*qtblptr)->quantval, srcinfo->quant_tbl_ptrs[tblno]->quantval,
+             sizeof((*qtblptr)->quantval));
       (*qtblptr)->sent_table = FALSE;
     }
   }
diff --git a/third_party/libjpeg-turbo/jdapimin.c b/third_party/libjpeg-turbo/jdapimin.c
index 4609b13..f50c27e 100644
--- a/third_party/libjpeg-turbo/jdapimin.c
+++ b/third_party/libjpeg-turbo/jdapimin.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1998, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2016, D. R. Commander.
+ * Copyright (C) 2016, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -53,7 +53,7 @@
   {
     struct jpeg_error_mgr *err = cinfo->err;
     void *client_data = cinfo->client_data; /* ignore Purify complaint here */
-    MEMZERO(cinfo, sizeof(struct jpeg_decompress_struct));
+    memset(cinfo, 0, sizeof(struct jpeg_decompress_struct));
     cinfo->err = err;
     cinfo->client_data = client_data;
   }
@@ -92,7 +92,7 @@
   cinfo->master = (struct jpeg_decomp_master *)
     (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
                                 sizeof(my_decomp_master));
-  MEMZERO(cinfo->master, sizeof(my_decomp_master));
+  memset(cinfo->master, 0, sizeof(my_decomp_master));
 }
 
 
diff --git a/third_party/libjpeg-turbo/jdapistd.c b/third_party/libjpeg-turbo/jdapistd.c
index 695a620..02cd0cb 100644
--- a/third_party/libjpeg-turbo/jdapistd.c
+++ b/third_party/libjpeg-turbo/jdapistd.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1996, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2015-2020, D. R. Commander.
+ * Copyright (C) 2010, 2015-2020, 2022, D. R. Commander.
  * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -159,8 +159,12 @@
   JDIMENSION input_xoffset;
   boolean reinit_upsampler = FALSE;
   jpeg_component_info *compptr;
+#ifdef UPSAMPLE_MERGING_SUPPORTED
+  my_master_ptr master = (my_master_ptr)cinfo->master;
+#endif
 
-  if (cinfo->global_state != DSTATE_SCANNING || cinfo->output_scanline != 0)
+  if ((cinfo->global_state != DSTATE_SCANNING &&
+       cinfo->global_state != DSTATE_BUFIMAGE) || cinfo->output_scanline != 0)
     ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
 
   if (!xoffset || !width)
@@ -208,6 +212,13 @@
    */
   *width = *width + input_xoffset - *xoffset;
   cinfo->output_width = *width;
+#ifdef UPSAMPLE_MERGING_SUPPORTED
+  if (master->using_merged_upsample && cinfo->max_v_samp_factor == 2) {
+    my_merged_upsample_ptr upsample = (my_merged_upsample_ptr)cinfo->upsample;
+    upsample->out_row_width =
+      cinfo->output_width * cinfo->out_color_components;
+  }
+#endif
 
   /* Set the first and last iMCU columns that we must decompress.  These values
    * will be used in single-scan decompressions.
@@ -318,7 +329,9 @@
 read_and_discard_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines)
 {
   JDIMENSION n;
+#ifdef UPSAMPLE_MERGING_SUPPORTED
   my_master_ptr master = (my_master_ptr)cinfo->master;
+#endif
   JSAMPLE dummy_sample[1] = { 0 };
   JSAMPROW dummy_row = dummy_sample;
   JSAMPARRAY scanlines = NULL;
@@ -342,10 +355,12 @@
     cinfo->cquantize->color_quantize = noop_quantize;
   }
 
+#ifdef UPSAMPLE_MERGING_SUPPORTED
   if (master->using_merged_upsample && cinfo->max_v_samp_factor == 2) {
     my_merged_upsample_ptr upsample = (my_merged_upsample_ptr)cinfo->upsample;
     scanlines = &upsample->spare_row;
   }
+#endif
 
   for (n = 0; n < num_lines; n++)
     jpeg_read_scanlines(cinfo, scanlines, 1);
@@ -511,7 +526,7 @@
    * all of the entropy decoding occurs in jpeg_start_decompress(), assuming
    * that the input data source is non-suspending.  This makes skipping easy.
    */
-  if (cinfo->inputctl->has_multiple_scans) {
+  if (cinfo->inputctl->has_multiple_scans || cinfo->buffered_image) {
     if (cinfo->upsample->need_context_rows) {
       cinfo->output_scanline += lines_to_skip;
       cinfo->output_iMCU_row += lines_to_skip / lines_per_iMCU_row;
diff --git a/third_party/libjpeg-turbo/jdarith.c b/third_party/libjpeg-turbo/jdarith.c
index 7f0d3a7..21575e8 100644
--- a/third_party/libjpeg-turbo/jdarith.c
+++ b/third_party/libjpeg-turbo/jdarith.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Developed 1997-2015 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015-2020, D. R. Commander.
+ * Copyright (C) 2015-2020, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -210,13 +210,13 @@
   for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
     compptr = cinfo->cur_comp_info[ci];
     if (!cinfo->progressive_mode || (cinfo->Ss == 0 && cinfo->Ah == 0)) {
-      MEMZERO(entropy->dc_stats[compptr->dc_tbl_no], DC_STAT_BINS);
+      memset(entropy->dc_stats[compptr->dc_tbl_no], 0, DC_STAT_BINS);
       /* Reset DC predictions to 0 */
       entropy->last_dc_val[ci] = 0;
       entropy->dc_context[ci] = 0;
     }
     if (!cinfo->progressive_mode || cinfo->Ss) {
-      MEMZERO(entropy->ac_stats[compptr->ac_tbl_no], AC_STAT_BINS);
+      memset(entropy->ac_stats[compptr->ac_tbl_no], 0, AC_STAT_BINS);
     }
   }
 
@@ -471,17 +471,17 @@
       if (*thiscoef) {                          /* previously nonzero coef */
         if (arith_decode(cinfo, st + 2)) {
           if (*thiscoef < 0)
-            *thiscoef += m1;
+            *thiscoef += (JCOEF)m1;
           else
-            *thiscoef += p1;
+            *thiscoef += (JCOEF)p1;
         }
         break;
       }
       if (arith_decode(cinfo, st + 1)) {        /* newly nonzero coef */
         if (arith_decode(cinfo, entropy->fixed_bin))
-          *thiscoef = m1;
+          *thiscoef = (JCOEF)m1;
         else
-          *thiscoef = p1;
+          *thiscoef = (JCOEF)p1;
         break;
       }
       st += 3;  k++;
@@ -698,8 +698,8 @@
     /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG.
      * This ought to be an error condition, but we make it a warning.
      */
-    if (cinfo->Ss != 0 || cinfo->Ah != 0 || cinfo->Al != 0 ||
-        (cinfo->Se < DCTSIZE2 && cinfo->Se != DCTSIZE2 - 1))
+    if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2 - 1 ||
+        cinfo->Ah != 0 || cinfo->Al != 0)
       WARNMS(cinfo, JWRN_NOT_SEQUENTIAL);
     /* Select MCU decoding routine */
     entropy->pub.decode_mcu = decode_mcu;
@@ -715,7 +715,7 @@
       if (entropy->dc_stats[tbl] == NULL)
         entropy->dc_stats[tbl] = (unsigned char *)(*cinfo->mem->alloc_small)
           ((j_common_ptr)cinfo, JPOOL_IMAGE, DC_STAT_BINS);
-      MEMZERO(entropy->dc_stats[tbl], DC_STAT_BINS);
+      memset(entropy->dc_stats[tbl], 0, DC_STAT_BINS);
       /* Initialize DC predictions to 0 */
       entropy->last_dc_val[ci] = 0;
       entropy->dc_context[ci] = 0;
@@ -727,7 +727,7 @@
       if (entropy->ac_stats[tbl] == NULL)
         entropy->ac_stats[tbl] = (unsigned char *)(*cinfo->mem->alloc_small)
           ((j_common_ptr)cinfo, JPOOL_IMAGE, AC_STAT_BINS);
-      MEMZERO(entropy->ac_stats[tbl], AC_STAT_BINS);
+      memset(entropy->ac_stats[tbl], 0, AC_STAT_BINS);
     }
   }
 
diff --git a/third_party/libjpeg-turbo/jdatadst-tj.c b/third_party/libjpeg-turbo/jdatadst-tj.c
index 9bdc7c8..e10d981 100644
--- a/third_party/libjpeg-turbo/jdatadst-tj.c
+++ b/third_party/libjpeg-turbo/jdatadst-tj.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1994-1996, Thomas G. Lane.
  * Modified 2009-2012 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2011, 2014, 2016, 2019, D. R. Commander.
+ * Copyright (C) 2011, 2014, 2016, 2019, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -23,15 +23,6 @@
 #include "jpeglib.h"
 #include "jerror.h"
 
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc(),free() */
-#ifdef STARBOARD
-#include "starboard/client_porting/poem/stdio_poem.h"
-#else
-extern void *malloc(size_t size);
-extern void free(void *ptr);
-#endif
-#endif
-
 void jpeg_mem_dest_tj(j_compress_ptr cinfo, unsigned char **outbuffer,
                       unsigned long *outsize, boolean alloc);
 
@@ -106,7 +97,7 @@
   if (nextbuffer == NULL)
     ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
 
-  MEMCOPY(nextbuffer, dest->buffer, dest->bufsize);
+  memcpy(nextbuffer, dest->buffer, dest->bufsize);
 
   free(dest->newbuffer);
 
diff --git a/third_party/libjpeg-turbo/jdatadst.c b/third_party/libjpeg-turbo/jdatadst.c
index 246fffb..6b4fed2 100644
--- a/third_party/libjpeg-turbo/jdatadst.c
+++ b/third_party/libjpeg-turbo/jdatadst.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1994-1996, Thomas G. Lane.
  * Modified 2009-2012 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2013, 2016, D. R. Commander.
+ * Copyright (C) 2013, 2016, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -23,11 +23,6 @@
 #include "jpeglib.h"
 #include "jerror.h"
 
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc(),free() */
-extern void *malloc(size_t size);
-extern void free(void *ptr);
-#endif
-
 
 /* Expanded data destination object for stdio output */
 
@@ -116,7 +111,7 @@
 {
   my_dest_ptr dest = (my_dest_ptr)cinfo->dest;
 
-  if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) !=
+  if (fwrite(dest->buffer, 1, OUTPUT_BUF_SIZE, dest->outfile) !=
       (size_t)OUTPUT_BUF_SIZE)
     ERREXIT(cinfo, JERR_FILE_WRITE);
 
@@ -141,7 +136,7 @@
   if (nextbuffer == NULL)
     ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
 
-  MEMCOPY(nextbuffer, dest->buffer, dest->bufsize);
+  memcpy(nextbuffer, dest->buffer, dest->bufsize);
 
   free(dest->newbuffer);
 
@@ -175,7 +170,7 @@
 
   /* Write any data remaining in the buffer */
   if (datacount > 0) {
-    if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount)
+    if (fwrite(dest->buffer, 1, datacount, dest->outfile) != datacount)
       ERREXIT(cinfo, JERR_FILE_WRITE);
   }
   fflush(dest->outfile);
diff --git a/third_party/libjpeg-turbo/jdatasrc.c b/third_party/libjpeg-turbo/jdatasrc.c
index eadb4a2..e36a30d 100644
--- a/third_party/libjpeg-turbo/jdatasrc.c
+++ b/third_party/libjpeg-turbo/jdatasrc.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1994-1996, Thomas G. Lane.
  * Modified 2009-2011 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2013, 2016, D. R. Commander.
+ * Copyright (C) 2013, 2016, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -104,7 +104,7 @@
   my_src_ptr src = (my_src_ptr)cinfo->src;
   size_t nbytes;
 
-  nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE);
+  nbytes = fread(src->buffer, 1, INPUT_BUF_SIZE, src->infile);
 
   if (nbytes <= 0) {
     if (src->start_of_file)     /* Treat empty input file as fatal error */
diff --git a/third_party/libjpeg-turbo/jdcoefct.c b/third_party/libjpeg-turbo/jdcoefct.c
index 15e6cde..88e10c0 100644
--- a/third_party/libjpeg-turbo/jdcoefct.c
+++ b/third_party/libjpeg-turbo/jdcoefct.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1994-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
- * Copyright (C) 2010, 2015-2016, 2019-2020, D. R. Commander.
+ * Copyright (C) 2010, 2015-2016, 2019-2020, 2022, D. R. Commander.
  * Copyright (C) 2015, 2020, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -475,7 +475,7 @@
     if (!compptr->component_needed)
       continue;
     /* Count non-dummy DCT block rows in this iMCU row. */
-    if (cinfo->output_iMCU_row < last_iMCU_row - 1) {
+    if (cinfo->output_iMCU_row + 1 < last_iMCU_row) {
       block_rows = compptr->v_samp_factor;
       access_rows = block_rows * 3; /* this and next two iMCU rows */
     } else if (cinfo->output_iMCU_row < last_iMCU_row) {
@@ -560,7 +560,7 @@
         next_block_row = buffer_ptr;
 
       if (block_row < block_rows - 2 ||
-          cinfo->output_iMCU_row < last_iMCU_row - 1)
+          cinfo->output_iMCU_row + 1 < last_iMCU_row)
         next_next_block_row =
           buffer[block_row + 2] + cinfo->master->first_MCU_col[ci];
       else
diff --git a/third_party/libjpeg-turbo/jddctmgr.c b/third_party/libjpeg-turbo/jddctmgr.c
index 266f446..e78d7be 100644
--- a/third_party/libjpeg-turbo/jddctmgr.c
+++ b/third_party/libjpeg-turbo/jddctmgr.c
@@ -6,7 +6,7 @@
  * Modified 2002-2010 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
- * Copyright (C) 2010, 2015, D. R. Commander.
+ * Copyright (C) 2010, 2015, 2022, D. R. Commander.
  * Copyright (C) 2013, MIPS Technologies, Inc., California.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -345,7 +345,7 @@
     compptr->dct_table =
       (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                   sizeof(multiplier_table));
-    MEMZERO(compptr->dct_table, sizeof(multiplier_table));
+    memset(compptr->dct_table, 0, sizeof(multiplier_table));
     /* Mark multiplier table not yet set up for any method */
     idct->cur_method[ci] = -1;
   }
diff --git a/third_party/libjpeg-turbo/jdhuff.c b/third_party/libjpeg-turbo/jdhuff.c
index f786c10..679d221 100644
--- a/third_party/libjpeg-turbo/jdhuff.c
+++ b/third_party/libjpeg-turbo/jdhuff.c
@@ -584,7 +584,7 @@
        * behavior is, to the best of our understanding, innocuous, and it is
        * unclear how to work around it without potentially affecting
        * performance.  Thus, we (hopefully temporarily) suppress UBSan integer
-       * overflow errors for this function.
+       * overflow errors for this function and decode_mcu_fast().
        */
       s += state.last_dc_val[ci];
       state.last_dc_val[ci] = s;
@@ -651,6 +651,12 @@
 }
 
 
+#if defined(__has_feature)
+#if __has_feature(undefined_behavior_sanitizer)
+__attribute__((no_sanitize("signed-integer-overflow"),
+               no_sanitize("unsigned-integer-overflow")))
+#endif
+#endif
 LOCAL(boolean)
 decode_mcu_fast(j_decompress_ptr cinfo, JBLOCKROW *MCU_data)
 {
@@ -681,6 +687,9 @@
 
     if (entropy->dc_needed[blkn]) {
       int ci = cinfo->MCU_membership[blkn];
+      /* Refer to the comment in decode_mcu_slow() regarding the supression of
+       * a UBSan integer overflow error in this line of code.
+       */
       s += state.last_dc_val[ci];
       state.last_dc_val[ci] = s;
       if (block)
diff --git a/third_party/libjpeg-turbo/jdicc.c b/third_party/libjpeg-turbo/jdicc.c
index a1a5b86..50aa9a9 100644
--- a/third_party/libjpeg-turbo/jdicc.c
+++ b/third_party/libjpeg-turbo/jdicc.c
@@ -18,10 +18,6 @@
 #include "jpeglib.h"
 #include "jerror.h"
 
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc() */
-extern void *malloc(size_t size);
-#endif
-
 
 #define ICC_MARKER  (JPEG_APP0 + 2)     /* JPEG marker code for ICC */
 #define ICC_OVERHEAD_LEN  14            /* size of non-profile data in APP2 */
diff --git a/third_party/libjpeg-turbo/jdinput.c b/third_party/libjpeg-turbo/jdinput.c
index deec618..1bc5aff 100644
--- a/third_party/libjpeg-turbo/jdinput.c
+++ b/third_party/libjpeg-turbo/jdinput.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2016, 2018, D. R. Commander.
+ * Copyright (C) 2010, 2016, 2018, 2022, D. R. Commander.
  * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -264,7 +264,7 @@
     qtbl = (JQUANT_TBL *)
       (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                   sizeof(JQUANT_TBL));
-    MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], sizeof(JQUANT_TBL));
+    memcpy(qtbl, cinfo->quant_tbl_ptrs[qtblno], sizeof(JQUANT_TBL));
     compptr->quant_table = qtbl;
   }
 }
diff --git a/third_party/libjpeg-turbo/jdmarker.c b/third_party/libjpeg-turbo/jdmarker.c
index b964c3a..f7eba61 100644
--- a/third_party/libjpeg-turbo/jdmarker.c
+++ b/third_party/libjpeg-turbo/jdmarker.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1998, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2012, 2015, D. R. Commander.
+ * Copyright (C) 2012, 2015, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -473,7 +473,7 @@
     for (i = 0; i < count; i++)
       INPUT_BYTE(cinfo, huffval[i], return FALSE);
 
-    MEMZERO(&huffval[count], (256 - count) * sizeof(UINT8));
+    memset(&huffval[count], 0, (256 - count) * sizeof(UINT8));
 
     length -= count;
 
@@ -491,8 +491,8 @@
     if (*htblptr == NULL)
       *htblptr = jpeg_alloc_huff_table((j_common_ptr)cinfo);
 
-    MEMCOPY((*htblptr)->bits, bits, sizeof((*htblptr)->bits));
-    MEMCOPY((*htblptr)->huffval, huffval, sizeof((*htblptr)->huffval));
+    memcpy((*htblptr)->bits, bits, sizeof((*htblptr)->bits));
+    memcpy((*htblptr)->huffval, huffval, sizeof((*htblptr)->huffval));
   }
 
   if (length != 0)
diff --git a/third_party/libjpeg-turbo/jdmaster.c b/third_party/libjpeg-turbo/jdmaster.c
index cbc8774..a3690bf 100644
--- a/third_party/libjpeg-turbo/jdmaster.c
+++ b/third_party/libjpeg-turbo/jdmaster.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2002-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2009-2011, 2016, 2019, D. R. Commander.
+ * Copyright (C) 2009-2011, 2016, 2019, 2022, D. R. Commander.
  * Copyright (C) 2013, Linaro Limited.
  * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
@@ -417,7 +417,7 @@
   table += (MAXJSAMPLE + 1);    /* allow negative subscripts of simple table */
   cinfo->sample_range_limit = table;
   /* First segment of "simple" table: limit[x] = 0 for x < 0 */
-  MEMZERO(table - (MAXJSAMPLE + 1), (MAXJSAMPLE + 1) * sizeof(JSAMPLE));
+  memset(table - (MAXJSAMPLE + 1), 0, (MAXJSAMPLE + 1) * sizeof(JSAMPLE));
   /* Main part of "simple" table: limit[x] = x */
   for (i = 0; i <= MAXJSAMPLE; i++)
     table[i] = (JSAMPLE)i;
@@ -426,10 +426,10 @@
   for (i = CENTERJSAMPLE; i < 2 * (MAXJSAMPLE + 1); i++)
     table[i] = MAXJSAMPLE;
   /* Second half of post-IDCT table */
-  MEMZERO(table + (2 * (MAXJSAMPLE + 1)),
-          (2 * (MAXJSAMPLE + 1) - CENTERJSAMPLE) * sizeof(JSAMPLE));
-  MEMCOPY(table + (4 * (MAXJSAMPLE + 1) - CENTERJSAMPLE),
-          cinfo->sample_range_limit, CENTERJSAMPLE * sizeof(JSAMPLE));
+  memset(table + (2 * (MAXJSAMPLE + 1)), 0,
+         (2 * (MAXJSAMPLE + 1) - CENTERJSAMPLE) * sizeof(JSAMPLE));
+  memcpy(table + (4 * (MAXJSAMPLE + 1) - CENTERJSAMPLE),
+         cinfo->sample_range_limit, CENTERJSAMPLE * sizeof(JSAMPLE));
 }
 
 
diff --git a/third_party/libjpeg-turbo/jdphuff.c b/third_party/libjpeg-turbo/jdphuff.c
index c6d82ca..9680ebc 100644
--- a/third_party/libjpeg-turbo/jdphuff.c
+++ b/third_party/libjpeg-turbo/jdphuff.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1995-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015-2016, 2018-2021, D. R. Commander.
+ * Copyright (C) 2015-2016, 2018-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -578,9 +578,9 @@
             if (GET_BITS(1)) {
               if ((*thiscoef & p1) == 0) { /* do nothing if already set it */
                 if (*thiscoef >= 0)
-                  *thiscoef += p1;
+                  *thiscoef += (JCOEF)p1;
                 else
-                  *thiscoef += m1;
+                  *thiscoef += (JCOEF)m1;
               }
             }
           } else {
@@ -612,9 +612,9 @@
           if (GET_BITS(1)) {
             if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */
               if (*thiscoef >= 0)
-                *thiscoef += p1;
+                *thiscoef += (JCOEF)p1;
               else
-                *thiscoef += m1;
+                *thiscoef += (JCOEF)m1;
             }
           }
         }
diff --git a/third_party/libjpeg-turbo/jerror.c b/third_party/libjpeg-turbo/jerror.c
index 7a3b8e4..9011270 100644
--- a/third_party/libjpeg-turbo/jerror.c
+++ b/third_party/libjpeg-turbo/jerror.c
@@ -3,8 +3,8 @@
  *
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1998, Thomas G. Lane.
- * It was modified by The libjpeg-turbo Project to include only code relevant
- * to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -199,13 +199,13 @@
 
   /* Format the message into the passed buffer */
   if (isstring)
-    sprintf(buffer, msgtext, err->msg_parm.s);
+    SNPRINTF(buffer, JMSG_LENGTH_MAX, msgtext, err->msg_parm.s);
   else
-    sprintf(buffer, msgtext,
-            err->msg_parm.i[0], err->msg_parm.i[1],
-            err->msg_parm.i[2], err->msg_parm.i[3],
-            err->msg_parm.i[4], err->msg_parm.i[5],
-            err->msg_parm.i[6], err->msg_parm.i[7]);
+    SNPRINTF(buffer, JMSG_LENGTH_MAX, msgtext,
+             err->msg_parm.i[0], err->msg_parm.i[1],
+             err->msg_parm.i[2], err->msg_parm.i[3],
+             err->msg_parm.i[4], err->msg_parm.i[5],
+             err->msg_parm.i[6], err->msg_parm.i[7]);
 }
 
 
diff --git a/third_party/libjpeg-turbo/jerror.h b/third_party/libjpeg-turbo/jerror.h
index 4476df2..eb44a11 100644
--- a/third_party/libjpeg-turbo/jerror.h
+++ b/third_party/libjpeg-turbo/jerror.h
@@ -5,7 +5,7 @@
  * Copyright (C) 1994-1997, Thomas G. Lane.
  * Modified 1997-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2014, 2017, D. R. Commander.
+ * Copyright (C) 2014, 2017, 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -103,7 +103,7 @@
          "Cannot transcode due to multiple use of quantization table %d")
 JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data")
 JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change")
-JMESSAGE(JERR_NOTIMPL, "Not implemented yet")
+JMESSAGE(JERR_NOTIMPL, "Requested features are incompatible")
 JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time")
 #if JPEG_LIB_VERSION >= 70
 JMESSAGE(JERR_NO_ARITH_TABLE, "Arithmetic table 0x%02x was not defined")
@@ -268,6 +268,7 @@
 #define ERREXITS(cinfo, code, str) \
   ((cinfo)->err->msg_code = (code), \
    strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \
+   (cinfo)->err->msg_parm.s[JMSG_STR_PARM_MAX - 1] = '\0', \
    (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo)))
 
 #define MAKESTMT(stuff)         do { stuff } while (0)
@@ -324,6 +325,7 @@
 #define TRACEMSS(cinfo, lvl, code, str) \
   ((cinfo)->err->msg_code = (code), \
    strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \
+   (cinfo)->err->msg_parm.s[JMSG_STR_PARM_MAX - 1] = '\0', \
    (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl)))
 
 #endif /* JERROR_H */
diff --git a/third_party/libjpeg-turbo/jinclude.h b/third_party/libjpeg-turbo/jinclude.h
index 67502b9..4e0b1e0 100644
--- a/third_party/libjpeg-turbo/jinclude.h
+++ b/third_party/libjpeg-turbo/jinclude.h
@@ -3,8 +3,8 @@
  *
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1994, Thomas G. Lane.
- * It was modified by The libjpeg-turbo Project to include only code relevant
- * to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -17,96 +17,137 @@
  * JPEG library.  Most applications need only include jpeglib.h.
  */
 
+#ifndef __JINCLUDE_H__
+#define __JINCLUDE_H__
 
 /* Include auto-config file to find out which system include files we need. */
 
 #include "jconfig.h"            /* auto configuration options */
+#include "jconfigint.h"
 #define JCONFIG_INCLUDED        /* so that jpeglib.h doesn't do it again */
 
 /*
- * We need the NULL macro and size_t typedef.
- * On an ANSI-conforming system it is sufficient to include <stddef.h>.
- * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to
- * pull in <sys/types.h> as well.
  * Note that the core JPEG library does not require <stdio.h>;
  * only the default error handler and data source/destination modules do.
  * But we must pull it in because of the references to FILE in jpeglib.h.
  * You can remove those references if you want to compile without <stdio.h>.
  */
-
 #ifdef STARBOARD
-#ifdef HAVE_STDDEF_H
-//#include <stddef.h>
-#endif
-
-#include "starboard/client_porting/poem/stdio_poem.h"
-#include "starboard/file.h"
-
-#else
-#ifdef HAVE_STDDEF_H
-#include <stddef.h>
-#endif
-
-#ifdef NEED_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#include <stdio.h>
-#endif
-
-/*
- * We need memory copying and zeroing functions, plus strncpy().
- * ANSI and System V implementations declare these in <string.h>.
- * BSD doesn't have the mem() functions, but it does have bcopy()/bzero().
- * Some systems may declare memset and memcpy in <memory.h>.
- *
- * NOTE: we assume the size parameters to these functions are of type size_t.
- * Change the casts in these macros if not!
- */
-
-#if defined(NEED_STARBOARD_MEMORY)
-
-#include "starboard/memory.h"
-#define MEMZERO(target,size)   memset((void *)(target), 0, (size_t)(size))
-#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size))
-
-#elif NEED_BSD_STRINGS
-
-#include <strings.h>
-#define MEMZERO(target, size) \
-  bzero((void *)(target), (size_t)(size))
-#define MEMCOPY(dest, src, size) \
-  bcopy((const void *)(src), (void *)(dest), (size_t)(size))
-
-#else /* not BSD, assume ANSI/SysV string lib */
-
-#ifdef HAVE_STDLIB_H
 #include <stdlib.h>
-#endif
-
+#include "starboard/client_porting/poem/stdio_poem.h"
+#include "starboard/client_porting/poem/string_poem.h"
+#include "starboard/client_porting/poem/strings_poem.h"
+#include "starboard/file.h"
+#define SNPRINTF snprintf
+#else
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
-#define MEMZERO(target, size) \
-  memset((void *)(target), 0, (size_t)(size))
-#define MEMCOPY(dest, src, size) \
-  memcpy((void *)(dest), (const void *)(src), (size_t)(size))
-
-#endif
 
 /*
- * The modules that use fread() and fwrite() always invoke them through
- * these macros.  On some systems you may need to twiddle the argument casts.
- * CAUTION: argument order is different from underlying functions!
+ * These macros/inline functions facilitate using Microsoft's "safe string"
+ * functions with Visual Studio builds without the need to scatter #ifdefs
+ * throughout the code base.
  */
 
-#ifdef STARBOARD
-#define JFREAD(file, buf, sizeofbuf) \
-  ((size_t)SbFileRead((file), (void *)(buf), (size_t)(sizeofbuf)))
-#define JFWRITE(file, buf, sizeofbuf) \
-  ((size_t)SbFileWrite((file), (const void *)(buf), (size_t)(sizeofbuf)))
+
+#ifdef _MSC_VER
+
+#define SNPRINTF(str, n, format, ...) \
+  _snprintf_s(str, n, _TRUNCATE, format, ##__VA_ARGS__)
 
 #else
-#define JFREAD(file, buf, sizeofbuf) \
-  ((size_t)fread((void *)(buf), (size_t)1, (size_t)(sizeofbuf), (file)))
-#define JFWRITE(file, buf, sizeofbuf) \
-  ((size_t)fwrite((const void *)(buf), (size_t)1, (size_t)(sizeofbuf), (file)))
+
+#define SNPRINTF  snprintf
+
 #endif
+#endif
+
+
+#ifndef NO_GETENV
+
+#ifdef _MSC_VER
+
+static INLINE int GETENV_S(char *buffer, size_t buffer_size, const char *name)
+{
+  size_t required_size;
+
+  return (int)getenv_s(&required_size, buffer, buffer_size, name);
+}
+
+#else /* _MSC_VER */
+
+#include <errno.h>
+
+/* This provides a similar interface to the Microsoft/C11 getenv_s() function,
+ * but other than parameter validation, it has no advantages over getenv().
+ */
+
+static INLINE int GETENV_S(char *buffer, size_t buffer_size, const char *name)
+{
+  char *env;
+
+  if (!buffer) {
+    if (buffer_size == 0)
+      return 0;
+    else
+      return (errno = EINVAL);
+  }
+  if (buffer_size == 0)
+    return (errno = EINVAL);
+  if (!name) {
+    *buffer = 0;
+    return 0;
+  }
+
+  env = getenv(name);
+  if (!env)
+  {
+    *buffer = 0;
+    return 0;
+  }
+
+  if (strlen(env) + 1 > buffer_size) {
+    *buffer = 0;
+    return ERANGE;
+  }
+
+  strncpy(buffer, env, buffer_size);
+
+  return 0;
+}
+
+#endif /* _MSC_VER */
+
+#endif /* NO_GETENV */
+
+
+#ifndef NO_PUTENV
+
+#ifdef _WIN32
+
+#define PUTENV_S(name, value)  _putenv_s(name, value)
+
+#else
+
+/* This provides a similar interface to the Microsoft _putenv_s() function, but
+ * other than parameter validation, it has no advantages over setenv().
+ */
+
+static INLINE int PUTENV_S(const char *name, const char *value)
+{
+  if (!name || !value)
+    return (errno = EINVAL);
+
+  setenv(name, value, 1);
+
+  return errno;
+}
+
+#endif /* _WIN32 */
+
+#endif /* NO_PUTENV */
+
+
+#endif /* JINCLUDE_H */
diff --git a/third_party/libjpeg-turbo/jmemmgr.c b/third_party/libjpeg-turbo/jmemmgr.c
index 508ca74..a40446f 100644
--- a/third_party/libjpeg-turbo/jmemmgr.c
+++ b/third_party/libjpeg-turbo/jmemmgr.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2016, D. R. Commander.
+ * Copyright (C) 2016, 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -37,12 +37,6 @@
 #endif
 #include <limits.h>
 
-#ifndef NO_GETENV
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare getenv() */
-extern char *getenv(const char *name);
-#endif
-#endif
-
 
 LOCAL(size_t)
 round_up_pow2(size_t a, size_t b)
@@ -74,10 +68,13 @@
  * There isn't any really portable way to determine the worst-case alignment
  * requirement.  This module assumes that the alignment requirement is
  * multiples of ALIGN_SIZE.
- * By default, we define ALIGN_SIZE as sizeof(double).  This is necessary on
- * some workstations (where doubles really do need 8-byte alignment) and will
- * work fine on nearly everything.  If your machine has lesser alignment needs,
- * you can save a few bytes by making ALIGN_SIZE smaller.
+ * By default, we define ALIGN_SIZE as the maximum of sizeof(double) and
+ * sizeof(void *).  This is necessary on some workstations (where doubles
+ * really do need 8-byte alignment) and will work fine on nearly everything.
+ * We use the maximum of sizeof(double) and sizeof(void *) since sizeof(double)
+ * may be insufficient, for example, on CHERI-enabled platforms with 16-byte
+ * pointers and a 16-byte alignment requirement.  If your machine has lesser
+ * alignment needs, you can save a few bytes by making ALIGN_SIZE smaller.
  * The only place I know of where this will NOT work is certain Macintosh
  * 680x0 compilers that define double as a 10-byte IEEE extended float.
  * Doing 10-byte alignment is counterproductive because longwords won't be
@@ -87,7 +84,7 @@
 
 #ifndef ALIGN_SIZE              /* so can override from jconfig.h */
 #ifndef WITH_SIMD
-#define ALIGN_SIZE  sizeof(double)
+#define ALIGN_SIZE  MAX(sizeof(void *), sizeof(double))
 #else
 #define ALIGN_SIZE  32 /* Most of the SIMD instructions we support require
                           16-byte (128-bit) alignment, but AVX2 requires
@@ -1032,7 +1029,7 @@
     large_pool_ptr next_lhdr_ptr = lhdr_ptr->next;
     space_freed = lhdr_ptr->bytes_used +
                   lhdr_ptr->bytes_left +
-                  sizeof(large_pool_hdr);
+                  sizeof(large_pool_hdr) + ALIGN_SIZE - 1;
     jpeg_free_large(cinfo, (void *)lhdr_ptr, space_freed);
     mem->total_space_allocated -= space_freed;
     lhdr_ptr = next_lhdr_ptr;
@@ -1045,7 +1042,7 @@
   while (shdr_ptr != NULL) {
     small_pool_ptr next_shdr_ptr = shdr_ptr->next;
     space_freed = shdr_ptr->bytes_used + shdr_ptr->bytes_left +
-                  sizeof(small_pool_hdr);
+                  sizeof(small_pool_hdr) + ALIGN_SIZE - 1;
     jpeg_free_small(cinfo, (void *)shdr_ptr, space_freed);
     mem->total_space_allocated -= space_freed;
     shdr_ptr = next_shdr_ptr;
@@ -1162,12 +1159,16 @@
    */
 #ifndef NO_GETENV
   {
-    char *memenv;
+    char memenv[30] = { 0 };
 
-    if ((memenv = getenv("JPEGMEM")) != NULL) {
+    if (!GETENV_S(memenv, 30, "JPEGMEM") && strlen(memenv) > 0) {
       char ch = 'x';
 
+#ifdef _MSC_VER
+      if (sscanf_s(memenv, "%ld%c", &max_to_use, &ch, 1) > 0) {
+#else
       if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) {
+#endif
         if (ch == 'm' || ch == 'M')
           max_to_use *= 1000L;
         mem->pub.max_memory_to_use = max_to_use * 1000L;
diff --git a/third_party/libjpeg-turbo/jmemnobs.c b/third_party/libjpeg-turbo/jmemnobs.c
index a7f2674..cd6571b 100644
--- a/third_party/libjpeg-turbo/jmemnobs.c
+++ b/third_party/libjpeg-turbo/jmemnobs.c
@@ -22,15 +22,6 @@
 #include "jpeglib.h"
 #include "jmemsys.h"            /* import the system-dependent declarations */
 
-#if defined(NEED_STARBOARD_MEMORY)
-#include "starboard/client_porting/poem/stdio_poem.h"
-#else
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc(),free() */
-extern void *malloc(size_t size);
-extern void free(void *ptr);
-#endif
-#endif
-
 
 /*
  * Memory allocation and freeing are controlled by the regular library
diff --git a/third_party/libjpeg-turbo/jmorecfg.h b/third_party/libjpeg-turbo/jmorecfg.h
index fb3a9cf..b33a991 100644
--- a/third_party/libjpeg-turbo/jmorecfg.h
+++ b/third_party/libjpeg-turbo/jmorecfg.h
@@ -100,11 +100,7 @@
 
 /* UINT16 must hold at least the values 0..65535. */
 
-#ifdef HAVE_UNSIGNED_SHORT
 typedef unsigned short UINT16;
-#else /* not HAVE_UNSIGNED_SHORT */
-typedef unsigned int UINT16;
-#endif /* HAVE_UNSIGNED_SHORT */
 
 /* INT16 must hold at least the values -32768..32767. */
 
diff --git a/third_party/libjpeg-turbo/jpegint.h b/third_party/libjpeg-turbo/jpegint.h
index 195fbcb..6af9e2a 100644
--- a/third_party/libjpeg-turbo/jpegint.h
+++ b/third_party/libjpeg-turbo/jpegint.h
@@ -5,8 +5,9 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 1997-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015-2016, 2019, D. R. Commander.
+ * Copyright (C) 2015-2016, 2019, 2021, D. R. Commander.
  * Copyright (C) 2015, Google, Inc.
+ * Copyright (C) 2021, Alex Richardson.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -47,6 +48,18 @@
 /* JLONG must hold at least signed 32-bit values. */
 typedef long JLONG;
 
+/* JUINTPTR must hold pointer values. */
+#ifdef __UINTPTR_TYPE__
+/*
+ * __UINTPTR_TYPE__ is GNU-specific and available in GCC 4.6+ and Clang 3.0+.
+ * Fortunately, that is sufficient to support the few architectures for which
+ * sizeof(void *) != sizeof(size_t).  The only other options would require C99
+ * or Clang-specific builtins.
+ */
+typedef __UINTPTR_TYPE__ JUINTPTR;
+#else
+typedef size_t JUINTPTR;
+#endif
 
 /*
  * Left shift macro that handles a negative operand without causing any
@@ -360,12 +373,3 @@
 
 /* Arithmetic coding probability estimation tables in jaricom.c */
 extern const JLONG jpeg_aritab[];
-
-/* Suppress undefined-structure complaints if necessary. */
-
-#ifdef INCOMPLETE_TYPES_BROKEN
-#ifndef AM_MEMORY_MANAGER       /* only jmemmgr.c defines these */
-struct jvirt_sarray_control { long dummy; };
-struct jvirt_barray_control { long dummy; };
-#endif
-#endif /* INCOMPLETE_TYPES_BROKEN */
diff --git a/third_party/libjpeg-turbo/jpegtran.1 b/third_party/libjpeg-turbo/jpegtran.1
deleted file mode 100644
index da7a266..0000000
--- a/third_party/libjpeg-turbo/jpegtran.1
+++ /dev/null
@@ -1,358 +0,0 @@
-.TH JPEGTRAN 1 "26 October 2020"
-.SH NAME
-jpegtran \- lossless transformation of JPEG files
-.SH SYNOPSIS
-.B jpegtran
-[
-.I options
-]
-[
-.I filename
-]
-.LP
-.SH DESCRIPTION
-.LP
-.B jpegtran
-performs various useful transformations of JPEG files.
-It can translate the coded representation from one variant of JPEG to another,
-for example from baseline JPEG to progressive JPEG or vice versa.  It can also
-perform some rearrangements of the image data, for example turning an image
-from landscape to portrait format by rotation.
-.PP
-For EXIF files and JPEG files containing Exif data, you may prefer to use
-.B exiftran
-instead.
-.PP
-.B jpegtran
-works by rearranging the compressed data (DCT coefficients), without
-ever fully decoding the image.  Therefore, its transformations are lossless:
-there is no image degradation at all, which would not be true if you used
-.B djpeg
-followed by
-.B cjpeg
-to accomplish the same conversion.  But by the same token,
-.B jpegtran
-cannot perform lossy operations such as changing the image quality.  However,
-while the image data is losslessly transformed, metadata can be removed.  See
-the
-.B \-copy
-option for specifics.
-.PP
-.B jpegtran
-reads the named JPEG/JFIF file, or the standard input if no file is
-named, and produces a JPEG/JFIF file on the standard output.
-.SH OPTIONS
-All switch names may be abbreviated; for example,
-.B \-optimize
-may be written
-.B \-opt
-or
-.BR \-o .
-Upper and lower case are equivalent.
-British spellings are also accepted (e.g.,
-.BR \-optimise ),
-though for brevity these are not mentioned below.
-.PP
-To specify the coded JPEG representation used in the output file,
-.B jpegtran
-accepts a subset of the switches recognized by
-.BR cjpeg :
-.TP
-.B \-optimize
-Perform optimization of entropy encoding parameters.
-.TP
-.B \-progressive
-Create progressive JPEG file.
-.TP
-.BI \-restart " N"
-Emit a JPEG restart marker every N MCU rows, or every N MCU blocks if "B" is
-attached to the number.
-.TP
-.B \-arithmetic
-Use arithmetic coding.
-.TP
-.BI \-scans " file"
-Use the scan script given in the specified text file.
-.PP
-See
-.BR cjpeg (1)
-for more details about these switches.
-If you specify none of these switches, you get a plain baseline-JPEG output
-file.  The quality setting and so forth are determined by the input file.
-.PP
-The image can be losslessly transformed by giving one of these switches:
-.TP
-.B \-flip horizontal
-Mirror image horizontally (left-right).
-.TP
-.B \-flip vertical
-Mirror image vertically (top-bottom).
-.TP
-.B \-rotate 90
-Rotate image 90 degrees clockwise.
-.TP
-.B \-rotate 180
-Rotate image 180 degrees.
-.TP
-.B \-rotate 270
-Rotate image 270 degrees clockwise (or 90 ccw).
-.TP
-.B \-transpose
-Transpose image (across UL-to-LR axis).
-.TP
-.B \-transverse
-Transverse transpose (across UR-to-LL axis).
-.PP
-The transpose transformation has no restrictions regarding image dimensions.
-The other transformations operate rather oddly if the image dimensions are not
-a multiple of the iMCU size (usually 8 or 16 pixels), because they can only
-transform complete blocks of DCT coefficient data in the desired way.
-.PP
-.BR jpegtran 's
-default behavior when transforming an odd-size image is designed
-to preserve exact reversibility and mathematical consistency of the
-transformation set.  As stated, transpose is able to flip the entire image
-area.  Horizontal mirroring leaves any partial iMCU column at the right edge
-untouched, but is able to flip all rows of the image.  Similarly, vertical
-mirroring leaves any partial iMCU row at the bottom edge untouched, but is
-able to flip all columns.  The other transforms can be built up as sequences
-of transpose and flip operations; for consistency, their actions on edge
-pixels are defined to be the same as the end result of the corresponding
-transpose-and-flip sequence.
-.PP
-For practical use, you may prefer to discard any untransformable edge pixels
-rather than having a strange-looking strip along the right and/or bottom edges
-of a transformed image.  To do this, add the
-.B \-trim
-switch:
-.TP
-.B \-trim
-Drop non-transformable edge blocks.
-.IP
-Obviously, a transformation with
-.B \-trim
-is not reversible, so strictly speaking
-.B jpegtran
-with this switch is not lossless.  Also, the expected mathematical
-equivalences between the transformations no longer hold.  For example,
-.B \-rot 270 -trim
-trims only the bottom edge, but
-.B \-rot 90 -trim
-followed by
-.B \-rot 180 -trim
-trims both edges.
-.TP
-.B \-perfect
-If you are only interested in perfect transformations, add the
-.B \-perfect
-switch.  This causes
-.B jpegtran
-to fail with an error if the transformation is not perfect.
-.IP
-For example, you may want to do
-.IP
-.B (jpegtran \-rot 90 -perfect
-.I foo.jpg
-.B || djpeg
-.I foo.jpg
-.B | pnmflip \-r90 | cjpeg)
-.IP
-to do a perfect rotation, if available, or an approximated one if not.
-.PP
-This version of \fBjpegtran\fR also offers a lossless crop option, which
-discards data outside of a given image region but losslessly preserves what is
-inside.  Like the rotate and flip transforms, lossless crop is restricted by
-the current JPEG format; the upper left corner of the selected region must fall
-on an iMCU boundary.  If it doesn't, then it is silently moved up and/or left
-to the nearest iMCU boundary (the lower right corner is unchanged.)  Thus, the
-output image covers at least the requested region, but it may cover more.  The
-adjustment of the region dimensions may be optionally disabled by attaching an
-'f' character ("force") to the width or height number.
-
-The image can be losslessly cropped by giving the switch:
-.TP
-.B \-crop WxH+X+Y
-Crop the image to a rectangular region of width W and height H, starting at
-point X,Y.  The lossless crop feature discards data outside of a given image
-region but losslessly preserves what is inside.  Like the rotate and flip
-transforms, lossless crop is restricted by the current JPEG format; the upper
-left corner of the selected region must fall on an iMCU boundary.  If it
-doesn't, then it is silently moved up and/or left to the nearest iMCU boundary
-(the lower right corner is unchanged.)
-.PP
-If W or H is larger than the width/height of the input image, then the output
-image is expanded in size, and the expanded region is filled in with zeros
-(neutral gray).  Attaching an 'f' character ("flatten") to the width number
-will cause each block in the expanded region to be filled in with the DC
-coefficient of the nearest block in the input image rather than grayed out.
-Attaching an 'r' character ("reflect") to the width number will cause the
-expanded region to be filled in with repeated reflections of the input image
-rather than grayed out.
-.PP
-A complementary lossless wipe option is provided to discard (gray out) data
-inside a given image region while losslessly preserving what is outside:
-.TP
-.B \-wipe WxH+X+Y
-Wipe (gray out) a rectangular region of width W and height H from the input
-image, starting at point X,Y.
-.PP
-Attaching an 'f' character ("flatten") to the width number will cause the
-region to be filled with the average of adjacent blocks rather than grayed out.
-If the wipe region and the region outside the wipe region, when adjusted to the
-nearest iMCU boundary, form two horizontally adjacent rectangles, then
-attaching an 'r' character ("reflect") to the width number will cause the wipe
-region to be filled with repeated reflections of the outside region rather than
-grayed out.
-.PP
-A lossless drop option is also provided, which allows another JPEG image to be
-inserted ("dropped") into the input image data at a given position, replacing
-the existing image data at that position:
-.TP
-.B \-drop +X+Y filename
-Drop (insert) another image at point X,Y
-.PP
-Both the input image and the drop image must have the same subsampling level.
-It is best if they also have the same quantization (quality.)  Otherwise, the
-quantization of the output image will be adapted to accommodate the higher of
-the input image quality and the drop image quality.  The trim option can be
-used with the drop option to requantize the drop image to match the input
-image.  Note that a grayscale image can be dropped into a full-color image or
-vice versa, as long as the full-color image has no vertical subsampling.  If
-the input image is grayscale and the drop image is full-color, then the
-chrominance channels from the drop image will be discarded.
-.PP
-Other not-strictly-lossless transformation switches are:
-.TP
-.B \-grayscale
-Force grayscale output.
-.IP
-This option discards the chrominance channels if the input image is YCbCr
-(ie, a standard color JPEG), resulting in a grayscale JPEG file.  The
-luminance channel is preserved exactly, so this is a better method of reducing
-to grayscale than decompression, conversion, and recompression.  This switch
-is particularly handy for fixing a monochrome picture that was mistakenly
-encoded as a color JPEG.  (In such a case, the space savings from getting rid
-of the near-empty chroma channels won't be large; but the decoding time for
-a grayscale JPEG is substantially less than that for a color JPEG.)
-.PP
-.B jpegtran
-also recognizes these switches that control what to do with "extra" markers,
-such as comment blocks:
-.TP
-.B \-copy none
-Copy no extra markers from source file.  This setting suppresses all
-comments and other metadata in the source file.
-.TP
-.B \-copy comments
-Copy only comment markers.  This setting copies comments from the source file
-but discards any other metadata.
-.TP
-.B \-copy all
-Copy all extra markers.  This setting preserves miscellaneous markers
-found in the source file, such as JFIF thumbnails, Exif data, and Photoshop
-settings.  In some files, these extra markers can be sizable.  Note that this
-option will copy thumbnails as-is; they will not be transformed.
-.PP
-The default behavior is \fB-copy comments\fR.  (Note: in IJG releases v6 and
-v6a, \fBjpegtran\fR always did the equivalent of \fB-copy none\fR.)
-.PP
-Additional switches recognized by jpegtran are:
-.TP
-.BI \-icc " file"
-Embed ICC color management profile contained in the specified file.  Note that
-this will cause \fBjpegtran\fR to ignore any APP2 markers in the input file,
-even if \fB-copy all\fR is specified.
-.TP
-.BI \-maxmemory " N"
-Set limit for amount of memory to use in processing large images.  Value is
-in thousands of bytes, or millions of bytes if "M" is attached to the
-number.  For example,
-.B \-max 4m
-selects 4000000 bytes.  If more space is needed, an error will occur.
-.TP
-.BI \-maxscans " N"
-Abort if the input image contains more than
-.I N
-scans.  This feature demonstrates a method by which applications can guard
-against denial-of-service attacks instigated by specially-crafted malformed
-JPEG images containing numerous scans with missing image data or image data
-consisting only of "EOB runs" (a feature of progressive JPEG images that allows
-potentially hundreds of thousands of adjoining zero-value pixels to be
-represented using only a few bytes.)  Attempting to transform such malformed
-JPEG images can cause excessive CPU activity, since the decompressor must fully
-process each scan (even if the scan is corrupt) before it can proceed to the
-next scan.
-.TP
-.BI \-outfile " name"
-Send output image to the named file, not to standard output.
-.TP
-.BI \-report
-Report transformation progress.
-.TP
-.BI \-strict
-Treat all warnings as fatal.  This feature also demonstrates a method by which
-applications can guard against attacks instigated by specially-crafted
-malformed JPEG images.  Enabling this option will cause the decompressor to
-abort if the input image contains incomplete or corrupt image data.
-.TP
-.B \-verbose
-Enable debug printout.  More
-.BR \-v 's
-give more output.  Also, version information is printed at startup.
-.TP
-.B \-debug
-Same as
-.BR \-verbose .
-.TP
-.B \-version
-Print version information and exit.
-.SH EXAMPLES
-.LP
-This example converts a baseline JPEG file to progressive form:
-.IP
-.B jpegtran \-progressive
-.I foo.jpg
-.B >
-.I fooprog.jpg
-.PP
-This example rotates an image 90 degrees clockwise, discarding any
-unrotatable edge pixels:
-.IP
-.B jpegtran \-rot 90 -trim
-.I foo.jpg
-.B >
-.I foo90.jpg
-.SH ENVIRONMENT
-.TP
-.B JPEGMEM
-If this environment variable is set, its value is the default memory limit.
-The value is specified as described for the
-.B \-maxmemory
-switch.
-.B JPEGMEM
-overrides the default value specified when the program was compiled, and
-itself is overridden by an explicit
-.BR \-maxmemory .
-.SH SEE ALSO
-.BR cjpeg (1),
-.BR djpeg (1),
-.BR rdjpgcom (1),
-.BR wrjpgcom (1)
-.br
-Wallace, Gregory K.  "The JPEG Still Picture Compression Standard",
-Communications of the ACM, April 1991 (vol. 34, no. 4), pp. 30-44.
-.SH AUTHOR
-Independent JPEG Group
-.PP
-This file was modified by The libjpeg-turbo Project to include only information
-relevant to libjpeg-turbo and to wordsmith certain sections.
-.SH BUGS
-The transform options can't transform odd-size images perfectly.  Use
-.B \-trim
-or
-.B \-perfect
-if you don't like the results.
-.PP
-The entire image is read into memory and then written out again, even in
-cases where this isn't really necessary.  Expect swapping on large images,
-especially when using the more complex transform options.
diff --git a/third_party/libjpeg-turbo/jpegtran.c b/third_party/libjpeg-turbo/jpegtran.c
index 90fda7d..c7bee83 100644
--- a/third_party/libjpeg-turbo/jpegtran.c
+++ b/third_party/libjpeg-turbo/jpegtran.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1995-2019, Thomas G. Lane, Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2014, 2017, 2019-2020, D. R. Commander.
+ * Copyright (C) 2010, 2014, 2017, 2019-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -14,21 +14,15 @@
  * provides some lossless and sort-of-lossless transformations of JPEG data.
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #include "cdjpeg.h"             /* Common decls for cjpeg/djpeg applications */
 #include "transupp.h"           /* Support routines for jpegtran */
 #include "jversion.h"           /* for version message */
 #include "jconfigint.h"
 
-#ifdef USE_CCOMMAND             /* command-line reader for Macintosh */
-#ifdef __MWERKS__
-#include <SIOUX.h>              /* Metrowerks needs this */
-#include <console.h>            /* ... and this */
-#endif
-#ifdef THINK_C
-#include <console.h>            /* Think declares it here */
-#endif
-#endif
-
 
 /*
  * Argument-parsing code.
@@ -64,6 +58,7 @@
   fprintf(stderr, "Switches (names may be abbreviated):\n");
   fprintf(stderr, "  -copy none     Copy no extra markers from source file\n");
   fprintf(stderr, "  -copy comments Copy only comment markers (default)\n");
+  fprintf(stderr, "  -copy icc      Copy only ICC profile markers\n");
   fprintf(stderr, "  -copy all      Copy all extra markers\n");
 #ifdef ENTROPY_OPT_SUPPORTED
   fprintf(stderr, "  -optimize      Optimize Huffman table (smaller file, but slow compression)\n");
@@ -196,6 +191,8 @@
         copyoption = JCOPYOPT_NONE;
       } else if (keymatch(argv[argn], "comments", 1)) {
         copyoption = JCOPYOPT_COMMENTS;
+      } else if (keymatch(argv[argn], "icc", 1)) {
+        copyoption = JCOPYOPT_ICC;
       } else if (keymatch(argv[argn], "all", 1)) {
         copyoption = JCOPYOPT_ALL;
       } else
@@ -480,11 +477,6 @@
   JOCTET *icc_profile = NULL;
   long icc_len = 0;
 
-  /* On Mac, fetch a command line. */
-#ifdef USE_CCOMMAND
-  argc = ccommand(&argv);
-#endif
-
   progname = argv[0];
   if (progname == NULL || progname[0] == 0)
     progname = "jpegtran";      /* in case C library doesn't provide it */
@@ -574,6 +566,8 @@
     fclose(icc_file);
     if (copyoption == JCOPYOPT_ALL)
       copyoption = JCOPYOPT_ALL_EXCEPT_ICC;
+    if (copyoption == JCOPYOPT_ICC)
+      copyoption = JCOPYOPT_NONE;
   }
 
   if (report) {
diff --git a/third_party/libjpeg-turbo/jstdhuff.c b/third_party/libjpeg-turbo/jstdhuff.c
index 036d649..345b513 100644
--- a/third_party/libjpeg-turbo/jstdhuff.c
+++ b/third_party/libjpeg-turbo/jstdhuff.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1998, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2013, D. R. Commander.
+ * Copyright (C) 2013, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -29,7 +29,7 @@
     return;
 
   /* Copy the number-of-symbols-of-each-code-length counts */
-  MEMCOPY((*htblptr)->bits, bits, sizeof((*htblptr)->bits));
+  memcpy((*htblptr)->bits, bits, sizeof((*htblptr)->bits));
 
   /* Validate the counts.  We do this here mainly so we can copy the right
    * number of symbols from the val[] array, without risking marching off
@@ -41,8 +41,9 @@
   if (nsymbols < 1 || nsymbols > 256)
     ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
 
-  MEMCOPY((*htblptr)->huffval, val, nsymbols * sizeof(UINT8));
-  MEMZERO(&((*htblptr)->huffval[nsymbols]), (256 - nsymbols) * sizeof(UINT8));
+  memcpy((*htblptr)->huffval, val, nsymbols * sizeof(UINT8));
+  memset(&((*htblptr)->huffval[nsymbols]), 0,
+         (256 - nsymbols) * sizeof(UINT8));
 
   /* Initialize sent_table FALSE so table will be written to JPEG file. */
   (*htblptr)->sent_table = FALSE;
diff --git a/third_party/libjpeg-turbo/jutils.c b/third_party/libjpeg-turbo/jutils.c
index 5c5bb17..d862716 100644
--- a/third_party/libjpeg-turbo/jutils.c
+++ b/third_party/libjpeg-turbo/jutils.c
@@ -3,8 +3,8 @@
  *
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1996, Thomas G. Lane.
- * It was modified by The libjpeg-turbo Project to include only code
- * relevant to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -110,7 +110,7 @@
   for (row = num_rows; row > 0; row--) {
     inptr = *input_array++;
     outptr = *output_array++;
-    MEMCOPY(outptr, inptr, count);
+    memcpy(outptr, inptr, count);
   }
 }
 
@@ -120,7 +120,7 @@
                 JDIMENSION num_blocks)
 /* Copy a row of coefficient blocks from one place to another. */
 {
-  MEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * sizeof(JCOEF)));
+  memcpy(output_row, input_row, num_blocks * (DCTSIZE2 * sizeof(JCOEF)));
 }
 
 
@@ -129,5 +129,5 @@
 /* Zero out a chunk of memory. */
 /* This might be sample-array data, block-array data, or alloc_large data. */
 {
-  MEMZERO(target, bytestozero);
+  memset(target, 0, bytestozero);
 }
diff --git a/third_party/libjpeg-turbo/jversion.h b/third_party/libjpeg-turbo/jversion.h
index 2ab534a..63db95b 100644
--- a/third_party/libjpeg-turbo/jversion.h
+++ b/third_party/libjpeg-turbo/jversion.h
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2012-2021, D. R. Commander.
+ * Copyright (C) 2010, 2012-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -37,7 +37,7 @@
  */
 
 #define JCOPYRIGHT \
-  "Copyright (C) 2009-2021 D. R. Commander\n" \
+  "Copyright (C) 2009-2022 D. R. Commander\n" \
   "Copyright (C) 2015, 2020 Google, Inc.\n" \
   "Copyright (C) 2019-2020 Arm Limited\n" \
   "Copyright (C) 2015-2016, 2018 Matthieu Darbois\n" \
@@ -51,4 +51,4 @@
   "Copyright (C) 1991-2020 Thomas G. Lane, Guido Vollbeding"
 
 #define JCOPYRIGHT_SHORT \
-  "Copyright (C) 1991-2021 The libjpeg-turbo Project and many others"
+  "Copyright (C) 1991-2022 The libjpeg-turbo Project and many others"
diff --git a/third_party/libjpeg-turbo/libjpeg.txt b/third_party/libjpeg-turbo/libjpeg.txt
index 3c680b5..309f9d3 100644
--- a/third_party/libjpeg-turbo/libjpeg.txt
+++ b/third_party/libjpeg-turbo/libjpeg.txt
@@ -3,7 +3,7 @@
 This file was part of the Independent JPEG Group's software:
 Copyright (C) 1994-2013, Thomas G. Lane, Guido Vollbeding.
 libjpeg-turbo Modifications:
-Copyright (C) 2010, 2014-2018, 2020, D. R. Commander.
+Copyright (C) 2010, 2014-2018, 2020, 2022, D. R. Commander.
 Copyright (C) 2015, Google, Inc.
 For conditions of distribution and use, see the accompanying README.ijg file.
 
@@ -840,18 +840,7 @@
 machines) and reference it at your link step.  If you use only half of the
 library (only compression or only decompression), only that much code will be
 included from the library, unless your linker is hopelessly brain-damaged.
-The supplied makefiles build libjpeg.a automatically (see install.txt).
-
-While you can build the JPEG library as a shared library if the whim strikes
-you, we don't really recommend it.  The trouble with shared libraries is that
-at some point you'll probably try to substitute a new version of the library
-without recompiling the calling applications.  That generally doesn't work
-because the parameter struct declarations usually change with each new
-version.  In other words, the library's API is *not* guaranteed binary
-compatible across versions; we only try to ensure source-code compatibility.
-(In hindsight, it might have been smarter to hide the parameter structs from
-applications and introduce a ton of access functions instead.  Too late now,
-however.)
+The supplied build system builds libjpeg.a automatically.
 
 It may be worth pointing out that the core JPEG library does not actually
 require the stdio library: only the default source/destination managers and
@@ -3075,9 +3064,8 @@
 larger than a char, so it affects the surrounding application's image data.
 The sample applications cjpeg and djpeg can support 12-bit mode only for PPM
 and GIF file formats; you must disable the other file formats to compile a
-12-bit cjpeg or djpeg.  (install.txt has more information about that.)
-At present, a 12-bit library can handle *only* 12-bit images, not both
-precisions.
+12-bit cjpeg or djpeg.  At present, a 12-bit library can handle *only* 12-bit
+images, not both precisions.
 
 Note that a 12-bit library always compresses in Huffman optimization mode,
 in order to generate valid Huffman tables.  This is necessary because our
diff --git a/third_party/libjpeg-turbo/md5/md5hl.c b/third_party/libjpeg-turbo/md5/md5hl.c
index 8a4a762..849a136 100644
--- a/third_party/libjpeg-turbo/md5/md5hl.c
+++ b/third_party/libjpeg-turbo/md5/md5hl.c
@@ -6,7 +6,7 @@
  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
  * ----------------------------------------------------------------------------
  * libjpeg-turbo Modifications:
- * Copyright (C)2016, 2018-2019 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2016, 2018-2019, 2022 D. R. Commander.  All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -34,6 +34,10 @@
  * ----------------------------------------------------------------------------
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
diff --git a/third_party/libjpeg-turbo/rdbmp.c b/third_party/libjpeg-turbo/rdbmp.c
index 358a026..433ebe2 100644
--- a/third_party/libjpeg-turbo/rdbmp.c
+++ b/third_party/libjpeg-turbo/rdbmp.c
@@ -6,7 +6,7 @@
  * Modified 2009-2017 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
  * Modified 2011 by Siarhei Siamashka.
- * Copyright (C) 2015, 2017-2018, 2021, D. R. Commander.
+ * Copyright (C) 2015, 2017-2018, 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -39,7 +39,7 @@
 
 
 #define ReadOK(file, buffer, len) \
-  (JFREAD(file, buffer, len) == ((size_t)(len)))
+  (fread(buffer, 1, len, file) == ((size_t)(len)))
 
 static int alpha_index[JPEG_NUMCS] = {
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 0, 0, -1
@@ -125,7 +125,8 @@
     break;
   }
 
-  if (sinfo->cinfo->in_color_space == JCS_UNKNOWN && gray)
+  if ((sinfo->cinfo->in_color_space == JCS_UNKNOWN ||
+       sinfo->cinfo->in_color_space == JCS_RGB) && gray)
     sinfo->cinfo->in_color_space = JCS_GRAYSCALE;
 
   if (sinfo->cinfo->in_color_space == JCS_GRAYSCALE && !gray)
@@ -245,7 +246,7 @@
    */
   outptr = source->pub.buffer[0];
   if (cinfo->in_color_space == JCS_EXT_BGR) {
-    MEMCOPY(outptr, inptr, source->row_width);
+    memcpy(outptr, inptr, source->row_width);
   } else if (cinfo->in_color_space == JCS_CMYK) {
     for (col = cinfo->image_width; col > 0; col--) {
       JSAMPLE b = *inptr++, g = *inptr++, r = *inptr++;
@@ -309,7 +310,7 @@
   outptr = source->pub.buffer[0];
   if (cinfo->in_color_space == JCS_EXT_BGRX ||
       cinfo->in_color_space == JCS_EXT_BGRA) {
-    MEMCOPY(outptr, inptr, source->row_width);
+    memcpy(outptr, inptr, source->row_width);
   } else if (cinfo->in_color_space == JCS_CMYK) {
     for (col = cinfo->image_width; col > 0; col--) {
       JSAMPLE b = *inptr++, g = *inptr++, r = *inptr++;
diff --git a/third_party/libjpeg-turbo/rdgif.c b/third_party/libjpeg-turbo/rdgif.c
index c814c6b..bdf7401 100644
--- a/third_party/libjpeg-turbo/rdgif.c
+++ b/third_party/libjpeg-turbo/rdgif.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2019 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2021, D. R. Commander.
+ * Copyright (C) 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -45,7 +45,7 @@
 
 
 #define ReadOK(file, buffer, len) \
-  (JFREAD(file, buffer, len) == ((size_t)(len)))
+  (fread(buffer, 1, len, file) == ((size_t)(len)))
 
 
 #define MAXCOLORMAPSIZE  256    /* max # of colors in a GIF colormap */
@@ -345,7 +345,7 @@
 ReadColorMap(gif_source_ptr sinfo, int cmaplen, JSAMPARRAY cmap)
 /* Read a GIF colormap */
 {
-  int i;
+  int i, gray = 1;
 
   for (i = 0; i < cmaplen; i++) {
 #if BITS_IN_JSAMPLE == 8
@@ -356,6 +356,14 @@
     cmap[CM_RED][i]   = (JSAMPLE)UPSCALE(ReadByte(sinfo));
     cmap[CM_GREEN][i] = (JSAMPLE)UPSCALE(ReadByte(sinfo));
     cmap[CM_BLUE][i]  = (JSAMPLE)UPSCALE(ReadByte(sinfo));
+    if (cmap[CM_RED][i] != cmap[CM_GREEN][i] ||
+        cmap[CM_GREEN][i] != cmap[CM_BLUE][i])
+      gray = 0;
+  }
+
+  if (sinfo->cinfo->in_color_space == JCS_RGB && gray) {
+    sinfo->cinfo->in_color_space = JCS_GRAYSCALE;
+    sinfo->cinfo->input_components = 1;
   }
 }
 
@@ -516,10 +524,15 @@
     source->pub.get_pixel_rows = get_pixel_rows;
   }
 
+  if (cinfo->in_color_space != JCS_GRAYSCALE) {
+    cinfo->in_color_space = JCS_RGB;
+    cinfo->input_components = NUMCOLORS;
+  }
+
   /* Create compressor input buffer. */
   source->pub.buffer = (*cinfo->mem->alloc_sarray)
-    ((j_common_ptr)cinfo, JPOOL_IMAGE, (JDIMENSION)width * NUMCOLORS,
-     (JDIMENSION)1);
+    ((j_common_ptr)cinfo, JPOOL_IMAGE,
+     (JDIMENSION)width * cinfo->input_components, (JDIMENSION)1);
   source->pub.buffer_height = 1;
 
   /* Pad colormap for safety. */
@@ -530,8 +543,6 @@
   }
 
   /* Return info about the image. */
-  cinfo->in_color_space = JCS_RGB;
-  cinfo->input_components = NUMCOLORS;
   cinfo->data_precision = BITS_IN_JSAMPLE; /* we always rescale data to this */
   cinfo->image_width = width;
   cinfo->image_height = height;
@@ -556,11 +567,18 @@
   register JSAMPARRAY colormap = source->colormap;
 
   ptr = source->pub.buffer[0];
-  for (col = cinfo->image_width; col > 0; col--) {
-    c = LZWReadByte(source);
-    *ptr++ = colormap[CM_RED][c];
-    *ptr++ = colormap[CM_GREEN][c];
-    *ptr++ = colormap[CM_BLUE][c];
+  if (cinfo->in_color_space == JCS_GRAYSCALE) {
+    for (col = cinfo->image_width; col > 0; col--) {
+      c = LZWReadByte(source);
+      *ptr++ = colormap[CM_RED][c];
+    }
+  } else {
+    for (col = cinfo->image_width; col > 0; col--) {
+      c = LZWReadByte(source);
+      *ptr++ = colormap[CM_RED][c];
+      *ptr++ = colormap[CM_GREEN][c];
+      *ptr++ = colormap[CM_BLUE][c];
+    }
   }
   return 1;
 }
@@ -646,11 +664,18 @@
      FALSE);
   /* Scan the row, expand colormap, and output */
   ptr = source->pub.buffer[0];
-  for (col = cinfo->image_width; col > 0; col--) {
-    c = *sptr++;
-    *ptr++ = colormap[CM_RED][c];
-    *ptr++ = colormap[CM_GREEN][c];
-    *ptr++ = colormap[CM_BLUE][c];
+  if (cinfo->in_color_space == JCS_GRAYSCALE) {
+    for (col = cinfo->image_width; col > 0; col--) {
+      c = *sptr++;
+      *ptr++ = colormap[CM_RED][c];
+    }
+  } else {
+    for (col = cinfo->image_width; col > 0; col--) {
+      c = *sptr++;
+      *ptr++ = colormap[CM_RED][c];
+      *ptr++ = colormap[CM_GREEN][c];
+      *ptr++ = colormap[CM_BLUE][c];
+    }
   }
   source->cur_row_number++;     /* for next time */
   return 1;
diff --git a/third_party/libjpeg-turbo/rdjpgcom.1 b/third_party/libjpeg-turbo/rdjpgcom.1
deleted file mode 100644
index 97611df..0000000
--- a/third_party/libjpeg-turbo/rdjpgcom.1
+++ /dev/null
@@ -1,63 +0,0 @@
-.TH RDJPGCOM 1 "02 April 2009"
-.SH NAME
-rdjpgcom \- display text comments from a JPEG file
-.SH SYNOPSIS
-.B rdjpgcom
-[
-.B \-raw
-]
-[
-.B \-verbose
-]
-[
-.I filename
-]
-.LP
-.SH DESCRIPTION
-.LP
-.B rdjpgcom
-reads the named JPEG/JFIF file, or the standard input if no file is named,
-and prints any text comments found in the file on the standard output.
-.PP
-The JPEG standard allows "comment" (COM) blocks to occur within a JPEG file.
-Although the standard doesn't actually define what COM blocks are for, they
-are widely used to hold user-supplied text strings.  This lets you add
-annotations, titles, index terms, etc to your JPEG files, and later retrieve
-them as text.  COM blocks do not interfere with the image stored in the JPEG
-file.  The maximum size of a COM block is 64K, but you can have as many of
-them as you like in one JPEG file.
-.SH OPTIONS
-.TP
-.B \-raw
-Normally
-.B rdjpgcom
-escapes non-printable characters in comments, for security reasons.
-This option avoids that.
-.PP
-.B \-verbose
-Causes
-.B rdjpgcom
-to also display the JPEG image dimensions.
-.PP
-Switch names may be abbreviated, and are not case sensitive.
-.SH HINTS
-.B rdjpgcom
-does not depend on the IJG JPEG library.  Its source code is intended as an
-illustration of the minimum amount of code required to parse a JPEG file
-header correctly.
-.PP
-In
-.B \-verbose
-mode,
-.B rdjpgcom
-will also attempt to print the contents of any "APP12" markers as text.
-Some digital cameras produce APP12 markers containing useful textual
-information.  If you like, you can modify the source code to print
-other APPn marker types as well.
-.SH SEE ALSO
-.BR cjpeg (1),
-.BR djpeg (1),
-.BR jpegtran (1),
-.BR wrjpgcom (1)
-.SH AUTHOR
-Independent JPEG Group
diff --git a/third_party/libjpeg-turbo/rdjpgcom.c b/third_party/libjpeg-turbo/rdjpgcom.c
index 620270e..d9a6f85 100644
--- a/third_party/libjpeg-turbo/rdjpgcom.c
+++ b/third_party/libjpeg-turbo/rdjpgcom.c
@@ -4,8 +4,8 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1997, Thomas G. Lane.
  * Modified 2009 by Bill Allombert, Guido Vollbeding.
- * It was modified by The libjpeg-turbo Project to include only code relevant
- * to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -15,12 +15,14 @@
  * JPEG markers.
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #define JPEG_CJPEG_DJPEG        /* to get the command-line config symbols */
 #include "jinclude.h"           /* get auto-config symbols, <stdio.h> */
 
-#ifdef HAVE_LOCALE_H
 #include <locale.h>             /* Bill Allombert: use locale for isprint */
-#endif
 #include <ctype.h>              /* to declare isupper(), tolower() */
 #ifdef USE_SETMODE
 #include <fcntl.h>              /* to declare setmode()'s parameter macros */
@@ -28,16 +30,6 @@
 #include <io.h>                 /* to declare setmode() */
 #endif
 
-#ifdef USE_CCOMMAND             /* command-line reader for Macintosh */
-#ifdef __MWERKS__
-#include <SIOUX.h>              /* Metrowerks needs this */
-#include <console.h>            /* ... and this */
-#endif
-#ifdef THINK_C
-#include <console.h>            /* Think declares it here */
-#endif
-#endif
-
 #ifdef DONT_USE_B_MODE          /* define mode parameters for fopen() */
 #define READ_BINARY     "r"
 #else
@@ -223,9 +215,7 @@
   int lastch = 0;
 
   /* Bill Allombert: set locale properly for isprint */
-#ifdef HAVE_LOCALE_H
   setlocale(LC_CTYPE, "");
-#endif
 
   /* Get the marker parameter length count */
   length = read_2_bytes();
@@ -253,7 +243,7 @@
     } else if (isprint(ch)) {
       putc(ch, stdout);
     } else {
-      printf("\\%03o", ch);
+      printf("\\%03o", (unsigned int)ch);
     }
     lastch = ch;
     length--;
@@ -261,9 +251,7 @@
   printf("\n");
 
   /* Bill Allombert: revert to C locale */
-#ifdef HAVE_LOCALE_H
   setlocale(LC_CTYPE, "C");
-#endif
 }
 
 
@@ -452,11 +440,6 @@
   char *arg;
   int verbose = 0, raw = 0;
 
-  /* On Mac, fetch a command line. */
-#ifdef USE_CCOMMAND
-  argc = ccommand(&argv);
-#endif
-
   progname = argv[0];
   if (progname == NULL || progname[0] == 0)
     progname = "rdjpgcom";      /* in case C library doesn't provide it */
diff --git a/third_party/libjpeg-turbo/rdppm.c b/third_party/libjpeg-turbo/rdppm.c
index 9699ca5..294749a 100644
--- a/third_party/libjpeg-turbo/rdppm.c
+++ b/third_party/libjpeg-turbo/rdppm.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2009 by Bill Allombert, Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015-2017, 2020-2021, D. R. Commander.
+ * Copyright (C) 2015-2017, 2020-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -48,7 +48,7 @@
 
 
 #define ReadOK(file, buffer, len) \
-  (JFREAD(file, buffer, len) == ((size_t)(len)))
+  (fread(buffer, 1, len, file) == ((size_t)(len)))
 
 static int alpha_index[JPEG_NUMCS] = {
   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 0, 0, -1
@@ -178,16 +178,16 @@
   ptr = source->pub.buffer[0];
   if (maxval == MAXJSAMPLE) {
     if (aindex >= 0)
-      GRAY_RGB_READ_LOOP(read_pbm_integer(cinfo, infile, maxval),
+      GRAY_RGB_READ_LOOP((JSAMPLE)read_pbm_integer(cinfo, infile, maxval),
                          ptr[aindex] = 0xFF;)
     else
-      GRAY_RGB_READ_LOOP(read_pbm_integer(cinfo, infile, maxval),)
+      GRAY_RGB_READ_LOOP((JSAMPLE)read_pbm_integer(cinfo, infile, maxval), {})
   } else {
     if (aindex >= 0)
       GRAY_RGB_READ_LOOP(rescale[read_pbm_integer(cinfo, infile, maxval)],
                          ptr[aindex] = 0xFF;)
     else
-      GRAY_RGB_READ_LOOP(rescale[read_pbm_integer(cinfo, infile, maxval)],)
+      GRAY_RGB_READ_LOOP(rescale[read_pbm_integer(cinfo, infile, maxval)], {})
   }
   return 1;
 }
@@ -208,7 +208,7 @@
   ptr = source->pub.buffer[0];
   if (maxval == MAXJSAMPLE) {
     for (col = cinfo->image_width; col > 0; col--) {
-      JSAMPLE gray = read_pbm_integer(cinfo, infile, maxval);
+      JSAMPLE gray = (JSAMPLE)read_pbm_integer(cinfo, infile, maxval);
       rgb_to_cmyk(gray, gray, gray, ptr, ptr + 1, ptr + 2, ptr + 3);
       ptr += 4;
     }
@@ -252,16 +252,16 @@
   ptr = source->pub.buffer[0];
   if (maxval == MAXJSAMPLE) {
     if (aindex >= 0)
-      RGB_READ_LOOP(read_pbm_integer(cinfo, infile, maxval),
+      RGB_READ_LOOP((JSAMPLE)read_pbm_integer(cinfo, infile, maxval),
                     ptr[aindex] = 0xFF;)
     else
-      RGB_READ_LOOP(read_pbm_integer(cinfo, infile, maxval),)
+      RGB_READ_LOOP((JSAMPLE)read_pbm_integer(cinfo, infile, maxval), {})
   } else {
     if (aindex >= 0)
       RGB_READ_LOOP(rescale[read_pbm_integer(cinfo, infile, maxval)],
                     ptr[aindex] = 0xFF;)
     else
-      RGB_READ_LOOP(rescale[read_pbm_integer(cinfo, infile, maxval)],)
+      RGB_READ_LOOP(rescale[read_pbm_integer(cinfo, infile, maxval)], {})
   }
   return 1;
 }
@@ -282,9 +282,9 @@
   ptr = source->pub.buffer[0];
   if (maxval == MAXJSAMPLE) {
     for (col = cinfo->image_width; col > 0; col--) {
-      JSAMPLE r = read_pbm_integer(cinfo, infile, maxval);
-      JSAMPLE g = read_pbm_integer(cinfo, infile, maxval);
-      JSAMPLE b = read_pbm_integer(cinfo, infile, maxval);
+      JSAMPLE r = (JSAMPLE)read_pbm_integer(cinfo, infile, maxval);
+      JSAMPLE g = (JSAMPLE)read_pbm_integer(cinfo, infile, maxval);
+      JSAMPLE b = (JSAMPLE)read_pbm_integer(cinfo, infile, maxval);
       rgb_to_cmyk(r, g, b, ptr, ptr + 1, ptr + 2, ptr + 3);
       ptr += 4;
     }
@@ -347,12 +347,12 @@
     if (aindex >= 0)
       GRAY_RGB_READ_LOOP(*bufferptr++, ptr[aindex] = 0xFF;)
     else
-      GRAY_RGB_READ_LOOP(*bufferptr++,)
+      GRAY_RGB_READ_LOOP(*bufferptr++, {})
   } else {
     if (aindex >= 0)
       GRAY_RGB_READ_LOOP(rescale[UCH(*bufferptr++)], ptr[aindex] = 0xFF;)
     else
-      GRAY_RGB_READ_LOOP(rescale[UCH(*bufferptr++)],)
+      GRAY_RGB_READ_LOOP(rescale[UCH(*bufferptr++)], {})
   }
   return 1;
 }
@@ -415,12 +415,12 @@
     if (aindex >= 0)
       RGB_READ_LOOP(*bufferptr++, ptr[aindex] = 0xFF;)
     else
-      RGB_READ_LOOP(*bufferptr++,)
+      RGB_READ_LOOP(*bufferptr++, {})
   } else {
     if (aindex >= 0)
       RGB_READ_LOOP(rescale[UCH(*bufferptr++)], ptr[aindex] = 0xFF;)
     else
-      RGB_READ_LOOP(rescale[UCH(*bufferptr++)],)
+      RGB_READ_LOOP(rescale[UCH(*bufferptr++)], {})
   }
   return 1;
 }
@@ -603,7 +603,8 @@
 
   switch (c) {
   case '2':                     /* it's a text-format PGM file */
-    if (cinfo->in_color_space == JCS_UNKNOWN)
+    if (cinfo->in_color_space == JCS_UNKNOWN ||
+        cinfo->in_color_space == JCS_RGB)
       cinfo->in_color_space = JCS_GRAYSCALE;
     TRACEMS2(cinfo, 1, JTRC_PGM_TEXT, w, h);
     if (cinfo->in_color_space == JCS_GRAYSCALE)
@@ -631,7 +632,8 @@
     break;
 
   case '5':                     /* it's a raw-format PGM file */
-    if (cinfo->in_color_space == JCS_UNKNOWN)
+    if (cinfo->in_color_space == JCS_UNKNOWN ||
+        cinfo->in_color_space == JCS_RGB)
       cinfo->in_color_space = JCS_GRAYSCALE;
     TRACEMS2(cinfo, 1, JTRC_PGM, w, h);
     if (maxval > 255) {
@@ -730,8 +732,8 @@
       (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                   (size_t)(((long)MAX(maxval, 255) + 1L) *
                                            sizeof(JSAMPLE)));
-    MEMZERO(source->rescale, (size_t)(((long)MAX(maxval, 255) + 1L) *
-                                      sizeof(JSAMPLE)));
+    memset(source->rescale, 0, (size_t)(((long)MAX(maxval, 255) + 1L) *
+                                        sizeof(JSAMPLE)));
     half_maxval = maxval / 2;
     for (val = 0; val <= (long)maxval; val++) {
       /* The multiplication here must be done in 32 bits to avoid overflow */
diff --git a/third_party/libjpeg-turbo/rdswitch.c b/third_party/libjpeg-turbo/rdswitch.c
index 886fec3..33449c8 100644
--- a/third_party/libjpeg-turbo/rdswitch.c
+++ b/third_party/libjpeg-turbo/rdswitch.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1996, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2018, D. R. Commander.
+ * Copyright (C) 2010, 2018, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -17,6 +17,10 @@
  *      -sample HxV[,HxV,...]   Set component sampling factors
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #include "cdjpeg.h"             /* Common decls for cjpeg/djpeg applications */
 #include <ctype.h>              /* to declare isdigit(), isspace() */
 
@@ -263,7 +267,7 @@
     scanptr = (jpeg_scan_info *)
       (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
                                   scanno * sizeof(jpeg_scan_info));
-    MEMCOPY(scanptr, scans, scanno * sizeof(jpeg_scan_info));
+    memcpy(scanptr, scans, scanno * sizeof(jpeg_scan_info));
     cinfo->scan_info = scanptr;
     cinfo->num_scans = scanno;
   }
diff --git a/third_party/libjpeg-turbo/rdtarga.c b/third_party/libjpeg-turbo/rdtarga.c
index 8f2d031..3ed7eb3 100644
--- a/third_party/libjpeg-turbo/rdtarga.c
+++ b/third_party/libjpeg-turbo/rdtarga.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1996, Thomas G. Lane.
  * Modified 2017 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2018, 2021, D. R. Commander.
+ * Copyright (C) 2018, 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -33,7 +33,7 @@
 
 
 #define ReadOK(file, buffer, len) \
-  (JFREAD(file, buffer, len) == ((size_t)(len)))
+  (fread(buffer, 1, len, file) == ((size_t)(len)))
 
 
 /* Private version of data source object */
diff --git a/third_party/libjpeg-turbo/simd/arm/aarch32/jsimd.c b/third_party/libjpeg-turbo/simd/arm/aarch32/jsimd.c
index c286994..51ea665 100644
--- a/third_party/libjpeg-turbo/simd/arm/aarch32/jsimd.c
+++ b/third_party/libjpeg-turbo/simd/arm/aarch32/jsimd.c
@@ -3,7 +3,7 @@
  *
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
  * Copyright (C) 2011, Nokia Corporation and/or its subsidiary(-ies).
- * Copyright (C) 2009-2011, 2013-2014, 2016, 2018, D. R. Commander.
+ * Copyright (C) 2009-2011, 2013-2014, 2016, 2018, 2022, D. R. Commander.
  * Copyright (C) 2015-2016, 2018, Matthieu Darbois.
  * Copyright (C) 2019, Google LLC.
  * Copyright (C) 2020, Arm Limited.
@@ -26,14 +26,13 @@
 #include "../../jsimd.h"
 
 #ifdef STARBOARD
-#include "starboard/client_porting/poem/strings_poem.h"
-#include "starboard/client_porting/poem/string_poem.h"
-#include "starboard/configuration.h"
-#include "starboard/client_porting/poem/stdio_poem.h"
 #include "starboard/character.h"
-#else
 #include "starboard/client_porting/poem/stdio_poem.h"
+#include "starboard/client_porting/poem/string_poem.h"
+#include "starboard/client_porting/poem/strings_poem.h"
+#include "starboard/configuration.h"
 #endif
+#include <ctype.h>
 
 static unsigned int simd_support = ~0;
 static unsigned int simd_huffman = 1;
@@ -113,7 +112,7 @@
 init_simd(void)
 {
 #ifndef NO_GETENV
-  char *env = NULL;
+  char env[2] = { 0 };
 #endif
 #if !defined(__ARM_NEON__) && (defined(__linux__) || defined(ANDROID) || defined(__ANDROID__))
 #if !defined(STARBOARD)
@@ -143,14 +142,11 @@
 
 #ifndef NO_GETENV
   /* Force different settings through environment variables */
-  env = getenv("JSIMD_FORCENEON");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCENEON") && !strcmp(env, "1"))
     simd_support = JSIMD_NEON;
-  env = getenv("JSIMD_FORCENONE");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCENONE") && !strcmp(env, "1"))
     simd_support = 0;
-  env = getenv("JSIMD_NOHUFFENC");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_NOHUFFENC") && !strcmp(env, "1"))
     simd_huffman = 0;
 #endif
 }
diff --git a/third_party/libjpeg-turbo/simd/arm/aarch64/jchuff-neon.c b/third_party/libjpeg-turbo/simd/arm/aarch64/jchuff-neon.c
index f13fd1b..607a116 100644
--- a/third_party/libjpeg-turbo/simd/arm/aarch64/jchuff-neon.c
+++ b/third_party/libjpeg-turbo/simd/arm/aarch64/jchuff-neon.c
@@ -2,7 +2,7 @@
  * jchuff-neon.c - Huffman entropy encoding (64-bit Arm Neon)
  *
  * Copyright (C) 2020-2021, Arm Limited.  All Rights Reserved.
- * Copyright (C) 2020, D. R. Commander.  All Rights Reserved.
+ * Copyright (C) 2020, 2022, D. R. Commander.  All Rights Reserved.
  *
  * This software is provided 'as-is', without any express or implied
  * warranty.  In no event will the authors be held liable for any damages
@@ -59,6 +59,17 @@
    14,  15,  30,  31,  44,  45,  46,  47
 };
 
+/* The AArch64 implementation of the FLUSH() macro triggers a UBSan misaligned
+ * address warning because the macro sometimes writes a 64-bit value to a
+ * non-64-bit-aligned address.  That behavior is technically undefined per
+ * the C specification, but it is supported by the AArch64 architecture and
+ * compilers.
+ */
+#if defined(__has_feature)
+#if __has_feature(undefined_behavior_sanitizer)
+__attribute__((no_sanitize("alignment")))
+#endif
+#endif
 JOCTET *jsimd_huff_encode_one_block_neon(void *state, JOCTET *buffer,
                                          JCOEFPTR block, int last_dc_val,
                                          c_derived_tbl *dctbl,
@@ -158,73 +169,43 @@
                                   7), row6, 5);
 
   /* DCT block is now in zig-zag order; start Huffman encoding process. */
-  int16x8_t abs_row0 = vabsq_s16(row0);
-  int16x8_t abs_row1 = vabsq_s16(row1);
-  int16x8_t abs_row2 = vabsq_s16(row2);
-  int16x8_t abs_row3 = vabsq_s16(row3);
-  int16x8_t abs_row4 = vabsq_s16(row4);
-  int16x8_t abs_row5 = vabsq_s16(row5);
-  int16x8_t abs_row6 = vabsq_s16(row6);
-  int16x8_t abs_row7 = vabsq_s16(row7);
-
-  /* For negative coeffs: diff = abs(coeff) -1 = ~abs(coeff) */
-  uint16x8_t row0_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row0, vshrq_n_s16(row0, 15)));
-  uint16x8_t row1_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row1, vshrq_n_s16(row1, 15)));
-  uint16x8_t row2_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row2, vshrq_n_s16(row2, 15)));
-  uint16x8_t row3_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row3, vshrq_n_s16(row3, 15)));
-  uint16x8_t row4_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row4, vshrq_n_s16(row4, 15)));
-  uint16x8_t row5_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row5, vshrq_n_s16(row5, 15)));
-  uint16x8_t row6_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row6, vshrq_n_s16(row6, 15)));
-  uint16x8_t row7_diff =
-    vreinterpretq_u16_s16(veorq_s16(abs_row7, vshrq_n_s16(row7, 15)));
 
   /* Construct bitmap to accelerate encoding of AC coefficients.  A set bit
    * means that the corresponding coefficient != 0.
    */
-  uint8x8_t abs_row0_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row0),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row1_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row1),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row2_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row2),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row3_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row3),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row4_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row4),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row5_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row5),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row6_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row6),
-                                               vdupq_n_u16(0)));
-  uint8x8_t abs_row7_gt0 = vmovn_u16(vcgtq_u16(vreinterpretq_u16_s16(abs_row7),
-                                               vdupq_n_u16(0)));
+  uint16x8_t row0_ne_0 = vtstq_s16(row0, row0);
+  uint16x8_t row1_ne_0 = vtstq_s16(row1, row1);
+  uint16x8_t row2_ne_0 = vtstq_s16(row2, row2);
+  uint16x8_t row3_ne_0 = vtstq_s16(row3, row3);
+  uint16x8_t row4_ne_0 = vtstq_s16(row4, row4);
+  uint16x8_t row5_ne_0 = vtstq_s16(row5, row5);
+  uint16x8_t row6_ne_0 = vtstq_s16(row6, row6);
+  uint16x8_t row7_ne_0 = vtstq_s16(row7, row7);
+
+  uint8x16_t row10_ne_0 = vuzp1q_u8(vreinterpretq_u8_u16(row1_ne_0),
+                                    vreinterpretq_u8_u16(row0_ne_0));
+  uint8x16_t row32_ne_0 = vuzp1q_u8(vreinterpretq_u8_u16(row3_ne_0),
+                                    vreinterpretq_u8_u16(row2_ne_0));
+  uint8x16_t row54_ne_0 = vuzp1q_u8(vreinterpretq_u8_u16(row5_ne_0),
+                                    vreinterpretq_u8_u16(row4_ne_0));
+  uint8x16_t row76_ne_0 = vuzp1q_u8(vreinterpretq_u8_u16(row7_ne_0),
+                                    vreinterpretq_u8_u16(row6_ne_0));
 
   /* { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 } */
-  const uint8x8_t bitmap_mask =
-    vreinterpret_u8_u64(vmov_n_u64(0x0102040810204080));
+  const uint8x16_t bitmap_mask =
+    vreinterpretq_u8_u64(vdupq_n_u64(0x0102040810204080));
 
-  abs_row0_gt0 = vand_u8(abs_row0_gt0, bitmap_mask);
-  abs_row1_gt0 = vand_u8(abs_row1_gt0, bitmap_mask);
-  abs_row2_gt0 = vand_u8(abs_row2_gt0, bitmap_mask);
-  abs_row3_gt0 = vand_u8(abs_row3_gt0, bitmap_mask);
-  abs_row4_gt0 = vand_u8(abs_row4_gt0, bitmap_mask);
-  abs_row5_gt0 = vand_u8(abs_row5_gt0, bitmap_mask);
-  abs_row6_gt0 = vand_u8(abs_row6_gt0, bitmap_mask);
-  abs_row7_gt0 = vand_u8(abs_row7_gt0, bitmap_mask);
+  uint8x16_t bitmap_rows_10 = vandq_u8(row10_ne_0, bitmap_mask);
+  uint8x16_t bitmap_rows_32 = vandq_u8(row32_ne_0, bitmap_mask);
+  uint8x16_t bitmap_rows_54 = vandq_u8(row54_ne_0, bitmap_mask);
+  uint8x16_t bitmap_rows_76 = vandq_u8(row76_ne_0, bitmap_mask);
 
-  uint8x8_t bitmap_rows_10 = vpadd_u8(abs_row1_gt0, abs_row0_gt0);
-  uint8x8_t bitmap_rows_32 = vpadd_u8(abs_row3_gt0, abs_row2_gt0);
-  uint8x8_t bitmap_rows_54 = vpadd_u8(abs_row5_gt0, abs_row4_gt0);
-  uint8x8_t bitmap_rows_76 = vpadd_u8(abs_row7_gt0, abs_row6_gt0);
-  uint8x8_t bitmap_rows_3210 = vpadd_u8(bitmap_rows_32, bitmap_rows_10);
-  uint8x8_t bitmap_rows_7654 = vpadd_u8(bitmap_rows_76, bitmap_rows_54);
-  uint8x8_t bitmap_all = vpadd_u8(bitmap_rows_7654, bitmap_rows_3210);
+  uint8x16_t bitmap_rows_3210 = vpaddq_u8(bitmap_rows_32, bitmap_rows_10);
+  uint8x16_t bitmap_rows_7654 = vpaddq_u8(bitmap_rows_76, bitmap_rows_54);
+  uint8x16_t bitmap_rows_76543210 = vpaddq_u8(bitmap_rows_7654,
+                                              bitmap_rows_3210);
+  uint8x8_t bitmap_all = vpadd_u8(vget_low_u8(bitmap_rows_76543210),
+                                  vget_high_u8(bitmap_rows_76543210));
 
   /* Shift left to remove DC bit. */
   bitmap_all =
@@ -241,16 +222,16 @@
 
   /* Encode DC coefficient. */
 
+  /* For negative coeffs: diff = abs(coeff) -1 = ~abs(coeff) */
+  int16x8_t abs_row0 = vabsq_s16(row0);
+  int16x8_t row0_lz = vclzq_s16(abs_row0);
+  uint16x8_t row0_mask = vshlq_u16(vcltzq_s16(row0), vnegq_s16(row0_lz));
+  uint16x8_t row0_diff = veorq_u16(vreinterpretq_u16_s16(abs_row0), row0_mask);
   /* Find nbits required to specify sign and amplitude of coefficient. */
-#if defined(_MSC_VER) && !defined(__clang__)
-  unsigned int lz = BUILTIN_CLZ(vgetq_lane_s16(abs_row0, 0));
-#else
-  unsigned int lz;
-  __asm__("clz %w0, %w1" : "=r"(lz) : "r"(vgetq_lane_s16(abs_row0, 0)));
-#endif
-  unsigned int nbits = 32 - lz;
+  unsigned int lz = vgetq_lane_u16(vreinterpretq_u16_s16(row0_lz), 0);
+  unsigned int nbits = 16 - lz;
   /* Emit Huffman-coded symbol and additional diff bits. */
-  unsigned int diff = (unsigned int)(vgetq_lane_u16(row0_diff, 0) << lz) >> lz;
+  unsigned int diff = vgetq_lane_u16(row0_diff, 0);
   PUT_CODE(dctbl->ehufco[nbits], dctbl->ehufsi[nbits], diff)
 
   /* Encode AC coefficients. */
@@ -263,13 +244,20 @@
 
   /* The most efficient method of computing nbits and diff depends on the
    * number of non-zero coefficients.  If the bitmap is not too sparse (> 8
-   * non-zero AC coefficients), it is beneficial to use Neon; else we compute
-   * nbits and diff on demand using scalar code.
+   * non-zero AC coefficients), it is beneficial to do all of the work using
+   * Neon; else we do some of the work using Neon and the rest on demand using
+   * scalar code.
    */
   if (non_zero_coefficients > 8) {
     uint8_t block_nbits[DCTSIZE2];
 
-    int16x8_t row0_lz = vclzq_s16(abs_row0);
+    int16x8_t abs_row1 = vabsq_s16(row1);
+    int16x8_t abs_row2 = vabsq_s16(row2);
+    int16x8_t abs_row3 = vabsq_s16(row3);
+    int16x8_t abs_row4 = vabsq_s16(row4);
+    int16x8_t abs_row5 = vabsq_s16(row5);
+    int16x8_t abs_row6 = vabsq_s16(row6);
+    int16x8_t abs_row7 = vabsq_s16(row7);
     int16x8_t row1_lz = vclzq_s16(abs_row1);
     int16x8_t row2_lz = vclzq_s16(abs_row2);
     int16x8_t row3_lz = vclzq_s16(abs_row3);
@@ -277,49 +265,48 @@
     int16x8_t row5_lz = vclzq_s16(abs_row5);
     int16x8_t row6_lz = vclzq_s16(abs_row6);
     int16x8_t row7_lz = vclzq_s16(abs_row7);
+    /* Narrow leading zero count to 8 bits. */
+    uint8x16_t row01_lz = vuzp1q_u8(vreinterpretq_u8_s16(row0_lz),
+                                    vreinterpretq_u8_s16(row1_lz));
+    uint8x16_t row23_lz = vuzp1q_u8(vreinterpretq_u8_s16(row2_lz),
+                                    vreinterpretq_u8_s16(row3_lz));
+    uint8x16_t row45_lz = vuzp1q_u8(vreinterpretq_u8_s16(row4_lz),
+                                    vreinterpretq_u8_s16(row5_lz));
+    uint8x16_t row67_lz = vuzp1q_u8(vreinterpretq_u8_s16(row6_lz),
+                                    vreinterpretq_u8_s16(row7_lz));
     /* Compute nbits needed to specify magnitude of each coefficient. */
-    uint8x8_t row0_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row0_lz)));
-    uint8x8_t row1_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row1_lz)));
-    uint8x8_t row2_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row2_lz)));
-    uint8x8_t row3_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row3_lz)));
-    uint8x8_t row4_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row4_lz)));
-    uint8x8_t row5_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row5_lz)));
-    uint8x8_t row6_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row6_lz)));
-    uint8x8_t row7_nbits = vsub_u8(vdup_n_u8(16),
-                                   vmovn_u16(vreinterpretq_u16_s16(row7_lz)));
+    uint8x16_t row01_nbits = vsubq_u8(vdupq_n_u8(16), row01_lz);
+    uint8x16_t row23_nbits = vsubq_u8(vdupq_n_u8(16), row23_lz);
+    uint8x16_t row45_nbits = vsubq_u8(vdupq_n_u8(16), row45_lz);
+    uint8x16_t row67_nbits = vsubq_u8(vdupq_n_u8(16), row67_lz);
     /* Store nbits. */
-    vst1_u8(block_nbits + 0 * DCTSIZE, row0_nbits);
-    vst1_u8(block_nbits + 1 * DCTSIZE, row1_nbits);
-    vst1_u8(block_nbits + 2 * DCTSIZE, row2_nbits);
-    vst1_u8(block_nbits + 3 * DCTSIZE, row3_nbits);
-    vst1_u8(block_nbits + 4 * DCTSIZE, row4_nbits);
-    vst1_u8(block_nbits + 5 * DCTSIZE, row5_nbits);
-    vst1_u8(block_nbits + 6 * DCTSIZE, row6_nbits);
-    vst1_u8(block_nbits + 7 * DCTSIZE, row7_nbits);
+    vst1q_u8(block_nbits + 0 * DCTSIZE, row01_nbits);
+    vst1q_u8(block_nbits + 2 * DCTSIZE, row23_nbits);
+    vst1q_u8(block_nbits + 4 * DCTSIZE, row45_nbits);
+    vst1q_u8(block_nbits + 6 * DCTSIZE, row67_nbits);
     /* Mask bits not required to specify sign and amplitude of diff. */
-    row0_diff = vshlq_u16(row0_diff, row0_lz);
-    row1_diff = vshlq_u16(row1_diff, row1_lz);
-    row2_diff = vshlq_u16(row2_diff, row2_lz);
-    row3_diff = vshlq_u16(row3_diff, row3_lz);
-    row4_diff = vshlq_u16(row4_diff, row4_lz);
-    row5_diff = vshlq_u16(row5_diff, row5_lz);
-    row6_diff = vshlq_u16(row6_diff, row6_lz);
-    row7_diff = vshlq_u16(row7_diff, row7_lz);
-    row0_diff = vshlq_u16(row0_diff, vnegq_s16(row0_lz));
-    row1_diff = vshlq_u16(row1_diff, vnegq_s16(row1_lz));
-    row2_diff = vshlq_u16(row2_diff, vnegq_s16(row2_lz));
-    row3_diff = vshlq_u16(row3_diff, vnegq_s16(row3_lz));
-    row4_diff = vshlq_u16(row4_diff, vnegq_s16(row4_lz));
-    row5_diff = vshlq_u16(row5_diff, vnegq_s16(row5_lz));
-    row6_diff = vshlq_u16(row6_diff, vnegq_s16(row6_lz));
-    row7_diff = vshlq_u16(row7_diff, vnegq_s16(row7_lz));
+    uint16x8_t row1_mask = vshlq_u16(vcltzq_s16(row1), vnegq_s16(row1_lz));
+    uint16x8_t row2_mask = vshlq_u16(vcltzq_s16(row2), vnegq_s16(row2_lz));
+    uint16x8_t row3_mask = vshlq_u16(vcltzq_s16(row3), vnegq_s16(row3_lz));
+    uint16x8_t row4_mask = vshlq_u16(vcltzq_s16(row4), vnegq_s16(row4_lz));
+    uint16x8_t row5_mask = vshlq_u16(vcltzq_s16(row5), vnegq_s16(row5_lz));
+    uint16x8_t row6_mask = vshlq_u16(vcltzq_s16(row6), vnegq_s16(row6_lz));
+    uint16x8_t row7_mask = vshlq_u16(vcltzq_s16(row7), vnegq_s16(row7_lz));
+    /* diff = abs(coeff) ^ sign(coeff) [no-op for positive coefficients] */
+    uint16x8_t row1_diff = veorq_u16(vreinterpretq_u16_s16(abs_row1),
+                                     row1_mask);
+    uint16x8_t row2_diff = veorq_u16(vreinterpretq_u16_s16(abs_row2),
+                                     row2_mask);
+    uint16x8_t row3_diff = veorq_u16(vreinterpretq_u16_s16(abs_row3),
+                                     row3_mask);
+    uint16x8_t row4_diff = veorq_u16(vreinterpretq_u16_s16(abs_row4),
+                                     row4_mask);
+    uint16x8_t row5_diff = veorq_u16(vreinterpretq_u16_s16(abs_row5),
+                                     row5_mask);
+    uint16x8_t row6_diff = veorq_u16(vreinterpretq_u16_s16(abs_row6),
+                                     row6_mask);
+    uint16x8_t row7_diff = veorq_u16(vreinterpretq_u16_s16(abs_row7),
+                                     row7_mask);
     /* Store diff bits. */
     vst1q_u16(block_diff + 0 * DCTSIZE, row0_diff);
     vst1q_u16(block_diff + 1 * DCTSIZE, row1_diff);
@@ -349,7 +336,14 @@
     }
   } else if (bitmap != 0) {
     uint16_t block_abs[DCTSIZE2];
-    /* Store absolute value of coefficients. */
+    /* Compute and store absolute value of coefficients. */
+    int16x8_t abs_row1 = vabsq_s16(row1);
+    int16x8_t abs_row2 = vabsq_s16(row2);
+    int16x8_t abs_row3 = vabsq_s16(row3);
+    int16x8_t abs_row4 = vabsq_s16(row4);
+    int16x8_t abs_row5 = vabsq_s16(row5);
+    int16x8_t abs_row6 = vabsq_s16(row6);
+    int16x8_t abs_row7 = vabsq_s16(row7);
     vst1q_u16(block_abs + 0 * DCTSIZE, vreinterpretq_u16_s16(abs_row0));
     vst1q_u16(block_abs + 1 * DCTSIZE, vreinterpretq_u16_s16(abs_row1));
     vst1q_u16(block_abs + 2 * DCTSIZE, vreinterpretq_u16_s16(abs_row2));
@@ -358,7 +352,21 @@
     vst1q_u16(block_abs + 5 * DCTSIZE, vreinterpretq_u16_s16(abs_row5));
     vst1q_u16(block_abs + 6 * DCTSIZE, vreinterpretq_u16_s16(abs_row6));
     vst1q_u16(block_abs + 7 * DCTSIZE, vreinterpretq_u16_s16(abs_row7));
-    /* Store diff bits. */
+    /* Compute diff bits (without nbits mask) and store. */
+    uint16x8_t row1_diff = veorq_u16(vreinterpretq_u16_s16(abs_row1),
+                                     vcltzq_s16(row1));
+    uint16x8_t row2_diff = veorq_u16(vreinterpretq_u16_s16(abs_row2),
+                                     vcltzq_s16(row2));
+    uint16x8_t row3_diff = veorq_u16(vreinterpretq_u16_s16(abs_row3),
+                                     vcltzq_s16(row3));
+    uint16x8_t row4_diff = veorq_u16(vreinterpretq_u16_s16(abs_row4),
+                                     vcltzq_s16(row4));
+    uint16x8_t row5_diff = veorq_u16(vreinterpretq_u16_s16(abs_row5),
+                                     vcltzq_s16(row5));
+    uint16x8_t row6_diff = veorq_u16(vreinterpretq_u16_s16(abs_row6),
+                                     vcltzq_s16(row6));
+    uint16x8_t row7_diff = veorq_u16(vreinterpretq_u16_s16(abs_row7),
+                                     vcltzq_s16(row7));
     vst1q_u16(block_diff + 0 * DCTSIZE, row0_diff);
     vst1q_u16(block_diff + 1 * DCTSIZE, row1_diff);
     vst1q_u16(block_diff + 2 * DCTSIZE, row2_diff);
@@ -375,7 +383,7 @@
       bitmap <<= r;
       lz = BUILTIN_CLZ(block_abs[i]);
       nbits = 32 - lz;
-      diff = (unsigned int)(block_diff[i] << lz) >> lz;
+      diff = ((unsigned int)block_diff[i] << lz) >> lz;
       while (r > 15) {
         /* If run length > 15, emit special run-length-16 codes. */
         PUT_BITS(code_0xf0, size_0xf0)
diff --git a/third_party/libjpeg-turbo/simd/arm/aarch64/jsimd.c b/third_party/libjpeg-turbo/simd/arm/aarch64/jsimd.c
index 7e0c2e0..0acbc2a 100644
--- a/third_party/libjpeg-turbo/simd/arm/aarch64/jsimd.c
+++ b/third_party/libjpeg-turbo/simd/arm/aarch64/jsimd.c
@@ -3,7 +3,7 @@
  *
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
  * Copyright (C) 2011, Nokia Corporation and/or its subsidiary(-ies).
- * Copyright (C) 2009-2011, 2013-2014, 2016, 2018, 2020, D. R. Commander.
+ * Copyright (C) 2009-2011, 2013-2014, 2016, 2018, 2020, 2022, D. R. Commander.
  * Copyright (C) 2015-2016, 2018, Matthieu Darbois.
  * Copyright (C) 2020, Arm Limited.
  *
@@ -26,14 +26,13 @@
 #include "jconfigint.h"
 
 #ifdef STARBOARD
-#include "starboard/client_porting/poem/strings_poem.h"
-#include "starboard/client_porting/poem/string_poem.h"
-#include "starboard/configuration.h"
-#include "starboard/client_porting/poem/stdio_poem.h"
 #include "starboard/character.h"
-#else
 #include "starboard/client_porting/poem/stdio_poem.h"
+#include "starboard/client_porting/poem/string_poem.h"
+#include "starboard/client_porting/poem/strings_poem.h"
+#include "starboard/configuration.h"
 #endif
+#include <ctype.h>
 
 #define JSIMD_FASTLD3  1
 #define JSIMD_FASTST3  2
@@ -133,7 +132,7 @@
 init_simd(void)
 {
 #ifndef NO_GETENV
-  char *env = NULL;
+  char env[2] = { 0 };
 #endif
 #if defined(__linux__) || defined(ANDROID) || defined(__ANDROID__)
 #if !defined(STARBOARD)
@@ -159,24 +158,19 @@
 
 #ifndef NO_GETENV
   /* Force different settings through environment variables */
-  env = getenv("JSIMD_FORCENEON");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCENEON") && !strcmp(env, "1"))
     simd_support = JSIMD_NEON;
-  env = getenv("JSIMD_FORCENONE");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCENONE") && !strcmp(env, "1"))
     simd_support = 0;
-  env = getenv("JSIMD_NOHUFFENC");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_NOHUFFENC") && !strcmp(env, "1"))
     simd_huffman = 0;
-  env = getenv("JSIMD_FASTLD3");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FASTLD3") && !strcmp(env, "1"))
     simd_features |= JSIMD_FASTLD3;
-  if ((env != NULL) && (strcmp(env, "0") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FASTLD3") && !strcmp(env, "0"))
     simd_features &= ~JSIMD_FASTLD3;
-  env = getenv("JSIMD_FASTST3");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FASTST3") && !strcmp(env, "1"))
     simd_features |= JSIMD_FASTST3;
-  if ((env != NULL) && (strcmp(env, "0") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FASTST3") && !strcmp(env, "0"))
     simd_features &= ~JSIMD_FASTST3;
 #endif
 }
diff --git a/third_party/libjpeg-turbo/simd/arm/jquanti-neon.c b/third_party/libjpeg-turbo/simd/arm/jquanti-neon.c
index a7eb6f1..d5d95d8 100644
--- a/third_party/libjpeg-turbo/simd/arm/jquanti-neon.c
+++ b/third_party/libjpeg-turbo/simd/arm/jquanti-neon.c
@@ -1,7 +1,7 @@
 /*
  * jquanti-neon.c - sample data conversion and quantization (Arm Neon)
  *
- * Copyright (C) 2020, Arm Limited.  All Rights Reserved.
+ * Copyright (C) 2020-2021, Arm Limited.  All Rights Reserved.
  *
  * This software is provided 'as-is', without any express or implied
  * warranty.  In no event will the authors be held liable for any damages
@@ -100,6 +100,9 @@
   DCTELEM *shift_ptr = divisors + 3 * DCTSIZE2;
   int i;
 
+#if defined(__clang__) && (defined(__aarch64__) || defined(_M_ARM64))
+#pragma unroll
+#endif
   for (i = 0; i < DCTSIZE; i += DCTSIZE / 2) {
     /* Load DCT coefficients. */
     int16x8_t row0 = vld1q_s16(workspace + (i + 0) * DCTSIZE);
diff --git a/third_party/libjpeg-turbo/simd/i386/jsimd.c b/third_party/libjpeg-turbo/simd/i386/jsimd.c
index 563949a..80bc821 100644
--- a/third_party/libjpeg-turbo/simd/i386/jsimd.c
+++ b/third_party/libjpeg-turbo/simd/i386/jsimd.c
@@ -2,7 +2,7 @@
  * jsimd_i386.c
  *
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
- * Copyright (C) 2009-2011, 2013-2014, 2016, 2018, D. R. Commander.
+ * Copyright (C) 2009-2011, 2013-2014, 2016, 2018, 2022, D. R. Commander.
  * Copyright (C) 2015-2016, 2018, Matthieu Darbois.
  *
  * Based on the x86 SIMD extension for IJG JPEG library,
@@ -44,7 +44,7 @@
 init_simd(void)
 {
 #ifndef NO_GETENV
-  char *env = NULL;
+  char env[2] = { 0 };
 #endif
 
   if (simd_support != ~0U)
@@ -54,26 +54,19 @@
 
 #ifndef NO_GETENV
   /* Force different settings through environment variables */
-  env = getenv("JSIMD_FORCEMMX");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCEMMX") && !strcmp(env, "1"))
     simd_support &= JSIMD_MMX;
-  env = getenv("JSIMD_FORCE3DNOW");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCE3DNOW") && !strcmp(env, "1"))
     simd_support &= JSIMD_3DNOW | JSIMD_MMX;
-  env = getenv("JSIMD_FORCESSE");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCESSE") && !strcmp(env, "1"))
     simd_support &= JSIMD_SSE | JSIMD_MMX;
-  env = getenv("JSIMD_FORCESSE2");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCESSE2") && !strcmp(env, "1"))
     simd_support &= JSIMD_SSE2;
-  env = getenv("JSIMD_FORCEAVX2");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCEAVX2") && !strcmp(env, "1"))
     simd_support &= JSIMD_AVX2;
-  env = getenv("JSIMD_FORCENONE");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCENONE") && !strcmp(env, "1"))
     simd_support = 0;
-  env = getenv("JSIMD_NOHUFFENC");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_NOHUFFENC") && !strcmp(env, "1"))
     simd_huffman = 0;
 #endif
 }
diff --git a/third_party/libjpeg-turbo/simd/x86_64/jchuff-sse2.asm b/third_party/libjpeg-turbo/simd/x86_64/jchuff-sse2.asm
index 0072028..9ea6df9 100644
--- a/third_party/libjpeg-turbo/simd/x86_64/jchuff-sse2.asm
+++ b/third_party/libjpeg-turbo/simd/x86_64/jchuff-sse2.asm
@@ -1,7 +1,7 @@
 ;
 ; jchuff-sse2.asm - Huffman entropy encoding (64-bit SSE2)
 ;
-; Copyright (C) 2009-2011, 2014-2016, 2019, D. R. Commander.
+; Copyright (C) 2009-2011, 2014-2016, 2019, 2021, D. R. Commander.
 ; Copyright (C) 2015, Matthieu Darbois.
 ; Copyright (C) 2018, Matthias Räncker.
 ;
@@ -83,6 +83,7 @@
 times 1 << 12 db 13
 times 1 << 13 db 14
 times 1 << 14 db 15
+times 1 << 15 db 16
 
     alignz      32
 
diff --git a/third_party/libjpeg-turbo/simd/x86_64/jsimd.c b/third_party/libjpeg-turbo/simd/x86_64/jsimd.c
index eb76679..584a010 100644
--- a/third_party/libjpeg-turbo/simd/x86_64/jsimd.c
+++ b/third_party/libjpeg-turbo/simd/x86_64/jsimd.c
@@ -2,7 +2,7 @@
  * jsimd_x86_64.c
  *
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
- * Copyright (C) 2009-2011, 2014, 2016, 2018, D. R. Commander.
+ * Copyright (C) 2009-2011, 2014, 2016, 2018, 2022, D. R. Commander.
  * Copyright (C) 2015-2016, 2018, Matthieu Darbois.
  *
  * Based on the x86 SIMD extension for IJG JPEG library,
@@ -44,7 +44,7 @@
 init_simd(void)
 {
 #ifndef NO_GETENV
-  char *env = NULL;
+  char env[2] = { 0 };
 #endif
 
   if (simd_support != ~0U)
@@ -54,17 +54,13 @@
 
 #ifndef NO_GETENV
   /* Force different settings through environment variables */
-  env = getenv("JSIMD_FORCESSE2");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCESSE2") && !strcmp(env, "1"))
     simd_support &= JSIMD_SSE2;
-  env = getenv("JSIMD_FORCEAVX2");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCEAVX2") && !strcmp(env, "1"))
     simd_support &= JSIMD_AVX2;
-  env = getenv("JSIMD_FORCENONE");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_FORCENONE") && !strcmp(env, "1"))
     simd_support = 0;
-  env = getenv("JSIMD_NOHUFFENC");
-  if ((env != NULL) && (strcmp(env, "1") == 0))
+  if (!GETENV_S(env, 2, "JSIMD_NOHUFFENC") && !strcmp(env, "1"))
     simd_huffman = 0;
 #endif
 }
diff --git a/third_party/libjpeg-turbo/tjbench.c b/third_party/libjpeg-turbo/tjbench.c
index 97475ec..b8d41c6 100644
--- a/third_party/libjpeg-turbo/tjbench.c
+++ b/third_party/libjpeg-turbo/tjbench.c
@@ -26,6 +26,10 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -62,8 +66,10 @@
     if (strncmp(tjErrorStr, _tjErrorStr, JMSG_LENGTH_MAX) || \
         strncmp(tjErrorMsg, m, JMSG_LENGTH_MAX) || \
         tjErrorCode != _tjErrorCode || tjErrorLine != __LINE__) { \
-      strncpy(tjErrorStr, _tjErrorStr, JMSG_LENGTH_MAX - 1); \
-      strncpy(tjErrorMsg, m, JMSG_LENGTH_MAX - 1); \
+      strncpy(tjErrorStr, _tjErrorStr, JMSG_LENGTH_MAX); \
+      tjErrorStr[JMSG_LENGTH_MAX - 1] = '\0'; \
+      strncpy(tjErrorMsg, m, JMSG_LENGTH_MAX); \
+      tjErrorMsg[JMSG_LENGTH_MAX - 1] = '\0'; \
       tjErrorCode = _tjErrorCode; \
       tjErrorLine = __LINE__; \
       fprintf(stderr, "WARNING in line %d while %s:\n%s\n", __LINE__, m, \
@@ -104,7 +110,7 @@
   if (cs == TJCS_YCbCr)
     return (char *)subNameLong[subsamp];
   else if (cs == TJCS_YCCK || cs == TJCS_CMYK) {
-    snprintf(buf, 80, "%s %s", csName[cs], subNameLong[subsamp]);
+    SNPRINTF(buf, 80, "%s %s", csName[cs], subNameLong[subsamp]);
     return buf;
   } else
     return (char *)csName[cs];
@@ -117,10 +123,10 @@
   int digitsAfterDecimal = figs - (int)ceil(log10(fabs(val)));
 
   if (digitsAfterDecimal < 1)
-    snprintf(format, 80, "%%.0f");
+    SNPRINTF(format, 80, "%%.0f");
   else
-    snprintf(format, 80, "%%.%df", digitsAfterDecimal);
-  snprintf(buf, len, format, val);
+    SNPRINTF(format, 80, "%%.%df", digitsAfterDecimal);
+  SNPRINTF(buf, len, format, val);
   return buf;
 }
 
@@ -157,7 +163,7 @@
   unsigned char *dstPtr, *dstPtr2, *yuvBuf = NULL;
 
   if (jpegQual > 0) {
-    snprintf(qualStr, 13, "_Q%d", jpegQual);
+    SNPRINTF(qualStr, 13, "_Q%d", jpegQual);
     qualStr[12] = 0;
   }
 
@@ -261,20 +267,20 @@
   if (!doWrite) goto bailout;
 
   if (sf.num != 1 || sf.denom != 1)
-    snprintf(sizeStr, 24, "%d_%d", sf.num, sf.denom);
+    SNPRINTF(sizeStr, 24, "%d_%d", sf.num, sf.denom);
   else if (tilew != w || tileh != h)
-    snprintf(sizeStr, 24, "%dx%d", tilew, tileh);
-  else snprintf(sizeStr, 24, "full");
+    SNPRINTF(sizeStr, 24, "%dx%d", tilew, tileh);
+  else SNPRINTF(sizeStr, 24, "full");
   if (decompOnly)
-    snprintf(tempStr, 1024, "%s_%s.%s", fileName, sizeStr, ext);
+    SNPRINTF(tempStr, 1024, "%s_%s.%s", fileName, sizeStr, ext);
   else
-    snprintf(tempStr, 1024, "%s_%s%s_%s.%s", fileName, subName[subsamp],
+    SNPRINTF(tempStr, 1024, "%s_%s%s_%s.%s", fileName, subName[subsamp],
              qualStr, sizeStr, ext);
 
   if (tjSaveImage(tempStr, dstBuf, scaledw, 0, scaledh, pf, flags) == -1)
     THROW_TJG("saving bitmap");
   ptr = strrchr(tempStr, '.');
-  snprintf(ptr, 1024 - (ptr - tempStr), "-err.%s", ext);
+  SNPRINTF(ptr, 1024 - (ptr - tempStr), "-err.%s", ext);
   if (srcBuf && sf.num == 1 && sf.denom == 1) {
     if (!quiet) fprintf(stderr, "Compression error written to %s.\n", tempStr);
     if (subsamp == TJ_GRAYSCALE) {
@@ -291,16 +297,17 @@
 
           if (y > 255) y = 255;
           if (y < 0) y = 0;
-          dstBuf[rindex] = abs(dstBuf[rindex] - y);
-          dstBuf[gindex] = abs(dstBuf[gindex] - y);
-          dstBuf[bindex] = abs(dstBuf[bindex] - y);
+          dstBuf[rindex] = (unsigned char)abs(dstBuf[rindex] - y);
+          dstBuf[gindex] = (unsigned char)abs(dstBuf[gindex] - y);
+          dstBuf[bindex] = (unsigned char)abs(dstBuf[bindex] - y);
         }
       }
     } else {
       for (row = 0; row < h; row++)
         for (col = 0; col < w * ps; col++)
           dstBuf[pitch * row + col] =
-            abs(dstBuf[pitch * row + col] - srcBuf[pitch * row + col]);
+            (unsigned char)abs(dstBuf[pitch * row + col] -
+                               srcBuf[pitch * row + col]);
     }
     if (tjSaveImage(tempStr, dstBuf, w, 0, h, pf, flags) == -1)
       THROW_TJG("saving bitmap");
@@ -482,7 +489,7 @@
               (double)totalJpegSize * 8. / 1000000. * (double)iter / elapsed);
     }
     if (tilew == w && tileh == h && doWrite) {
-      snprintf(tempStr, 1024, "%s_%s_Q%d.jpg", fileName, subName[subsamp],
+      SNPRINTF(tempStr, 1024, "%s_%s_Q%d.jpg", fileName, subName[subsamp],
                jpegQual);
       if ((file = fopen(tempStr, "wb")) == NULL)
         THROW_UNIX("opening reference image");
@@ -699,7 +706,7 @@
                 sigfig((double)(w * h * ps) / (double)totalJpegSize, 4,
                        tempStr2, 80),
                 quiet == 2 ? "\n" : "  ");
-      } else if (!quiet) {
+      } else {
         fprintf(stderr, "Transform     --> Frame rate:         %f fps\n",
                 1.0 / elapsed);
         fprintf(stderr, "                  Output image size:  %lu bytes\n",
diff --git a/third_party/libjpeg-turbo/tjbenchtest.in b/third_party/libjpeg-turbo/tjbenchtest.in
deleted file mode 100644
index 1c08b37..0000000
--- a/third_party/libjpeg-turbo/tjbenchtest.in
+++ /dev/null
@@ -1,256 +0,0 @@
-#!/bin/bash
-
-set -u
-set -e
-trap onexit INT
-trap onexit TERM
-trap onexit EXIT
-
-onexit()
-{
-	if [ -d $OUTDIR ]; then
-		rm -rf $OUTDIR
-	fi
-}
-
-runme()
-{
-	echo \*\*\* $*
-	$*
-}
-
-EXT=bmp
-IMAGES="vgl_5674_0098.${EXT} vgl_6434_0018a.${EXT} vgl_6548_0026a.${EXT} nightshot_iso_100.${EXT}"
-IMGDIR=@CMAKE_CURRENT_SOURCE_DIR@/testimages
-OUTDIR=`mktemp -d /tmp/__tjbenchtest_output.XXXXXX`
-EXEDIR=@CMAKE_CURRENT_BINARY_DIR@
-BMPARG=
-NSARG=
-YUVARG=
-ALLOC=0
-ALLOCARG=
-PROGARG=
-if [ "$EXT" = "bmp" ]; then BMPARG=-bmp; fi
-
-if [ -d $OUTDIR ]; then
-	rm -rf $OUTDIR
-fi
-mkdir -p $OUTDIR
-
-while [ $# -gt 0 ]; do
-	case "$1" in
-	-yuv)
-		NSARG=-nosmooth
-		YUVARG=-yuv
-
-# NOTE: The combination of tjEncodeYUV*() and tjCompressFromYUV*() does not
-# always produce bitwise-identical results to tjCompress*() if subsampling is
-# enabled.  In both cases, if the image width or height are not evenly
-# divisible by the MCU width/height, then the bottom and/or right edge are
-# expanded.  However, the libjpeg code performs this expansion prior to
-# downsampling, and TurboJPEG performs it in tjCompressFromYUV*(), which is
-# after downsampling.  Thus, the two will agree only if the width/height along
-# each downsampled dimension is an odd number or is evenly divisible by the MCU
-# width/height.  This disagreement basically amounts to a round-off error, but
-# there is no easy way around it, so for now, we just test the only image that
-# works.  (NOTE: nightshot_iso_100 does not suffer from the above issue, but
-# it suffers from an unrelated problem whereby the combination of
-# tjDecompressToYUV*() and tjDecodeYUV*() do not produce bitwise-identical
-# results to tjDecompress*() if decompression scaling is enabled.  This latter
-# phenomenon is not yet fully understood but is also believed to be some sort
-# of round-off error.)
-		IMAGES="vgl_6548_0026a.${EXT}"
-		;;
-	-alloc)
-		ALLOCARG=-alloc
-		ALLOC=1
-		;;
-	-progressive)
-		PROGARG=-progressive
-		;;
-	esac
-	shift
-done
-
-exec >$EXEDIR/tjbenchtest$YUVARG$ALLOCARG$PROGARG.log
-
-# Standard tests
-for image in $IMAGES; do
-
-	cp $IMGDIR/$image $OUTDIR
-	basename=`basename $image .${EXT}`
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -grayscale -outfile $OUTDIR/${basename}_GRAY_fast_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -sample 2x2 -outfile $OUTDIR/${basename}_420_fast_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -sample 2x1 -outfile $OUTDIR/${basename}_422_fast_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -sample 1x1 -outfile $OUTDIR/${basename}_444_fast_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -grayscale -outfile $OUTDIR/${basename}_GRAY_accurate_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -sample 2x2 -outfile $OUTDIR/${basename}_420_accurate_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -sample 2x1 -outfile $OUTDIR/${basename}_422_accurate_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -sample 1x1 -outfile $OUTDIR/${basename}_444_accurate_cjpeg.jpg $IMGDIR/${basename}.${EXT}
-	for samp in GRAY 420 422 444; do
-		runme $EXEDIR/djpeg -rgb $NSARG $BMPARG -outfile $OUTDIR/${basename}_${samp}_default_djpeg.${EXT} $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct fast -rgb $NSARG $BMPARG -outfile $OUTDIR/${basename}_${samp}_fast_djpeg.${EXT} $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct int -rgb $NSARG $BMPARG -outfile $OUTDIR/${basename}_${samp}_accurate_djpeg.${EXT} $OUTDIR/${basename}_${samp}_accurate_cjpeg.jpg
-	done
-	for samp in 420 422; do
-		runme $EXEDIR/djpeg -nosmooth $BMPARG -outfile $OUTDIR/${basename}_${samp}_default_nosmooth_djpeg.${EXT} $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct fast -nosmooth $BMPARG -outfile $OUTDIR/${basename}_${samp}_fast_nosmooth_djpeg.${EXT} $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct int -nosmooth $BMPARG -outfile $OUTDIR/${basename}_${samp}_accurate_nosmooth_djpeg.${EXT} $OUTDIR/${basename}_${samp}_accurate_cjpeg.jpg
-	done
-
-	# Compression
-	for dct in accurate fast; do
-		runme $EXEDIR/tjbench $OUTDIR/$image 95 -rgb -quiet -benchtime 0.01 -warmup 0 -${dct}dct $YUVARG $ALLOCARG $PROGARG
-		for samp in GRAY 420 422 444; do
-			runme cmp $OUTDIR/${basename}_${samp}_Q95.jpg $OUTDIR/${basename}_${samp}_${dct}_cjpeg.jpg
-		done
-	done
-
-	for dct in fast accurate default; do
-		dctarg=-${dct}dct
-		if [ "${dct}" = "default" ]; then
-			dctarg=
-		fi
-
-		# Tiled compression & decompression
-		runme $EXEDIR/tjbench $OUTDIR/$image 95 -rgb -tile -quiet -benchtime 0.01 -warmup 0 ${dctarg} $YUVARG $ALLOCARG $PROGARG
-		for samp in GRAY 444; do
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_${samp}_${dct}_djpeg.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_${samp}_${dct}_djpeg.${EXT}
-					rm $i
-				done
-			fi
-		done
-		runme $EXEDIR/tjbench $OUTDIR/$image 95 -rgb -tile -quiet -benchtime 0.01 -warmup 0 -fastupsample ${dctarg} $YUVARG $ALLOCARG $PROGARG
-		for samp in 420 422; do
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.${EXT}
-					rm $i
-				done
-			fi
-		done
-
-		# Tiled decompression
-		for samp in GRAY 444; do
-			runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -tile -quiet -benchtime 0.01 -warmup 0 ${dctarg} $YUVARG $ALLOCARG $PROGARG
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_${samp}_${dct}_djpeg.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_${samp}_${dct}_djpeg.${EXT}
-					rm $i
-				done
-			fi
-		done
-		for samp in 420 422; do
-			runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -tile -quiet -benchtime 0.01 -warmup 0 -fastupsample ${dctarg} $YUVARG $ALLOCARG $PROGARG
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.${EXT}
-					rm $i
-				done
-			fi
-		done
-	done
-
-	# Scaled decompression
-	for scale in 2_1 15_8 7_4 13_8 3_2 11_8 5_4 9_8 7_8 3_4 5_8 1_2 3_8 1_4 1_8; do
-		scalearg=`echo $scale | sed 's/\_/\//g'`
-		for samp in GRAY 420 422 444; do
-			runme $EXEDIR/djpeg -rgb -scale ${scalearg} $NSARG $BMPARG -outfile $OUTDIR/${basename}_${samp}_${scale}_djpeg.${EXT} $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-			runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -scale ${scalearg} -quiet -benchtime 0.01 -warmup 0 $YUVARG $ALLOCARG $PROGARG
-			runme cmp $OUTDIR/${basename}_${samp}_Q95_${scale}.${EXT} $OUTDIR/${basename}_${samp}_${scale}_djpeg.${EXT}
-			rm $OUTDIR/${basename}_${samp}_Q95_${scale}.${EXT}
-		done
-	done
-
-	# Transforms
-	for samp in GRAY 420 422 444; do
-		runme $EXEDIR/jpegtran -flip horizontal -trim -outfile $OUTDIR/${basename}_${samp}_hflip_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -flip vertical -trim -outfile $OUTDIR/${basename}_${samp}_vflip_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -transpose -trim -outfile $OUTDIR/${basename}_${samp}_transpose_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -transverse -trim -outfile $OUTDIR/${basename}_${samp}_transverse_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -rotate 90 -trim -outfile $OUTDIR/${basename}_${samp}_rot90_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -rotate 180 -trim -outfile $OUTDIR/${basename}_${samp}_rot180_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -rotate 270 -trim -outfile $OUTDIR/${basename}_${samp}_rot270_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-	done
-	for xform in hflip vflip transpose transverse rot90 rot180 rot270; do
-		for samp in GRAY 444; do
-			runme $EXEDIR/djpeg -rgb $BMPARG -outfile $OUTDIR/${basename}_${samp}_${xform}_jpegtran.${EXT} $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg
-			runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -$xform -tile -quiet -benchtime 0.01 -warmup 0 $YUVARG $ALLOCARG $PROGARG
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_${samp}_${xform}_jpegtran.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_${samp}_${xform}_jpegtran.${EXT}
-					rm $i
-				done
-			fi
-		done
-		for samp in 420 422; do
-			runme $EXEDIR/djpeg -nosmooth -rgb $BMPARG -outfile $OUTDIR/${basename}_${samp}_${xform}_jpegtran.${EXT} $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg
-			runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -$xform -tile -quiet -benchtime 0.01 -warmup 0 -fastupsample $YUVARG $ALLOCARG $PROGARG
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_${samp}_${xform}_jpegtran.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_${samp}_${xform}_jpegtran.${EXT}
-					rm $i
-				done
-			fi
-		done
-	done
-
-	# Grayscale transform
-	for xform in hflip vflip transpose transverse rot90 rot180 rot270; do
-		for samp in GRAY 444 422 420; do
-			runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -$xform -tile -quiet -benchtime 0.01 -warmup 0 -grayscale $YUVARG $ALLOCARG $PROGARG
-			if [ $ALLOC = 1 ]; then
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_full.${EXT} $OUTDIR/${basename}_GRAY_${xform}_jpegtran.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_full.${EXT}
-			else
-				for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].${EXT} \
-					$OUTDIR/${basename}_${samp}_Q95_full.${EXT}; do
-					runme cmp $i $OUTDIR/${basename}_GRAY_${xform}_jpegtran.${EXT}
-					rm $i
-				done
-			fi
-		done
-	done
-
-	# Transforms with scaling
-	for xform in hflip vflip transpose transverse rot90 rot180 rot270; do
-		for samp in GRAY 444 422 420; do
-			for scale in 2_1 15_8 7_4 13_8 3_2 11_8 5_4 9_8 7_8 3_4 5_8 1_2 3_8 1_4 1_8; do
-				scalearg=`echo $scale | sed 's/\_/\//g'`
-				runme $EXEDIR/djpeg -rgb -scale ${scalearg} $NSARG $BMPARG -outfile $OUTDIR/${basename}_${samp}_${xform}_${scale}_jpegtran.${EXT} $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg
-				runme $EXEDIR/tjbench $OUTDIR/${basename}_${samp}_Q95.jpg $BMPARG -$xform -scale ${scalearg} -quiet -benchtime 0.01 -warmup 0 $YUVARG $ALLOCARG $PROGARG
-				runme cmp $OUTDIR/${basename}_${samp}_Q95_${scale}.${EXT} $OUTDIR/${basename}_${samp}_${xform}_${scale}_jpegtran.${EXT}
-				rm $OUTDIR/${basename}_${samp}_Q95_${scale}.${EXT}
-			done
-		done
-	done
-
-done
-
-echo SUCCESS!
diff --git a/third_party/libjpeg-turbo/tjbenchtest.java.in b/third_party/libjpeg-turbo/tjbenchtest.java.in
deleted file mode 100644
index 689561d..0000000
--- a/third_party/libjpeg-turbo/tjbenchtest.java.in
+++ /dev/null
@@ -1,215 +0,0 @@
-#!/bin/bash
-
-set -u
-set -e
-trap onexit INT
-trap onexit TERM
-trap onexit EXIT
-
-onexit()
-{
-	if [ -d $OUTDIR ]; then
-		rm -rf $OUTDIR
-	fi
-}
-
-runme()
-{
-	echo \*\*\* $*
-	"$@"
-}
-
-IMAGES="vgl_5674_0098.bmp vgl_6434_0018a.bmp vgl_6548_0026a.bmp nightshot_iso_100.bmp"
-IMGDIR=@CMAKE_CURRENT_SOURCE_DIR@/testimages
-OUTDIR=`mktemp -d /tmp/__tjbenchtest_java_output.XXXXXX`
-EXEDIR=@CMAKE_CURRENT_BINARY_DIR@
-JAVA="@Java_JAVA_EXECUTABLE@"
-JAVAARGS="-cp $EXEDIR/java/turbojpeg.jar -Djava.library.path=$EXEDIR"
-BMPARG=
-NSARG=
-YUVARG=
-PROGARG=
-
-if [ -d $OUTDIR ]; then
-	rm -rf $OUTDIR
-fi
-mkdir -p $OUTDIR
-
-while [ $# -gt 0 ]; do
-	case "$1" in
-	-yuv)
-		NSARG=-nosmooth
-		YUVARG=-yuv
-
-# NOTE: The combination of tjEncodeYUV*() and tjCompressFromYUV*() does not
-# always produce bitwise-identical results to tjCompress*() if subsampling is
-# enabled.  In both cases, if the image width or height are not evenly
-# divisible by the MCU width/height, then the bottom and/or right edge are
-# expanded.  However, the libjpeg code performs this expansion prior to
-# downsampling, and TurboJPEG performs it in tjCompressFromYUV*(), which is
-# after downsampling.  Thus, the two will agree only if the width/height along
-# each downsampled dimension is an odd number or is evenly divisible by the MCU
-# width/height.  This disagreement basically amounts to a round-off error, but
-# there is no easy way around it, so for now, we just test the only image that
-# works.  (NOTE: nightshot_iso_100 does not suffer from the above issue, but
-# it suffers from an unrelated problem whereby the combination of
-# tjDecompressToYUV*() and tjDecodeYUV*() do not produce bitwise-identical
-# results to tjDecompress*() if decompression scaling is enabled.  This latter
-# phenomenon is not yet fully understood but is also believed to be some sort
-# of round-off error.)
-		IMAGES="vgl_6548_0026a.bmp"
-		;;
-	-progressive)
-		PROGARG=-progressive
-		;;
-	esac
-	shift
-done
-
-exec >$EXEDIR/tjbenchtest-java$YUVARG$PROGARG.log
-
-# Standard tests
-for image in $IMAGES; do
-
-	cp $IMGDIR/$image $OUTDIR
-	basename=`basename $image .bmp`
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -grayscale -outfile $OUTDIR/${basename}_GRAY_fast_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -sample 2x2 -outfile $OUTDIR/${basename}_420_fast_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -sample 2x1 -outfile $OUTDIR/${basename}_422_fast_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct fast $PROGARG -sample 1x1 -outfile $OUTDIR/${basename}_444_fast_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -grayscale -outfile $OUTDIR/${basename}_GRAY_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -sample 2x2 -outfile $OUTDIR/${basename}_420_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -sample 2x1 -outfile $OUTDIR/${basename}_422_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp
-	runme $EXEDIR/cjpeg -quality 95 -dct int $PROGARG -sample 1x1 -outfile $OUTDIR/${basename}_444_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp
-	for samp in GRAY 420 422 444; do
-		runme $EXEDIR/djpeg -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_default_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct fast -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_fast_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct int -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_accurate_djpeg.bmp $OUTDIR/${basename}_${samp}_accurate_cjpeg.jpg
-	done
-	for samp in 420 422; do
-		runme $EXEDIR/djpeg -nosmooth -bmp -outfile $OUTDIR/${basename}_${samp}_default_nosmooth_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct fast -nosmooth -bmp -outfile $OUTDIR/${basename}_${samp}_fast_nosmooth_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-		runme $EXEDIR/djpeg -dct int -nosmooth -bmp -outfile $OUTDIR/${basename}_${samp}_accurate_nosmooth_djpeg.bmp $OUTDIR/${basename}_${samp}_accurate_cjpeg.jpg
-	done
-
-	# Compression
-	for dct in accurate fast; do
-		runme "$JAVA" $JAVAARGS TJBench $OUTDIR/$image 95 -rgb -quiet -benchtime 0.01 -warmup 0 -${dct}dct $YUVARG $PROGARG
-		for samp in GRAY 420 422 444; do
-			runme cmp $OUTDIR/${basename}_${samp}_Q95.jpg $OUTDIR/${basename}_${samp}_${dct}_cjpeg.jpg
-		done
-	done
-
-	for dct in fast accurate default; do
-		dctarg=-${dct}dct
-		if [ "${dct}" = "default" ]; then
-			dctarg=
-		fi
-
-		# Tiled compression & decompression
-		runme "$JAVA" $JAVAARGS TJBench $OUTDIR/$image 95 -rgb -tile -quiet -benchtime 0.01 -warmup 0 ${dctarg} $YUVARG $PROGARG
-		for samp in GRAY 444; do
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp -i 54:54 $i $OUTDIR/${basename}_${samp}_${dct}_djpeg.bmp
-				rm $i
-			done
-		done
-		runme "$JAVA" $JAVAARGS TJBench $OUTDIR/$image 95 -rgb -tile -quiet -benchtime 0.01 -warmup 0 -fastupsample ${dctarg} $YUVARG $PROGARG
-		for samp in 420 422; do
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp -i 54:54 $i $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.bmp
-				rm $i
-			done
-		done
-
-		# Tiled decompression
-		for samp in GRAY 444; do
-			runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -tile -quiet -benchtime 0.01 -warmup 0 ${dctarg} $YUVARG $PROGARG
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp -i 54:54 $i $OUTDIR/${basename}_${samp}_${dct}_djpeg.bmp
-				rm $i
-			done
-		done
-		for samp in 420 422; do
-			runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -tile -quiet -benchtime 0.01 -warmup 0 -fastupsample ${dctarg} $YUVARG $PROGARG
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp $i -i 54:54 $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.bmp
-				rm $i
-			done
-		done
-	done
-
-	# Scaled decompression
-	for scale in 2_1 15_8 7_4 13_8 3_2 11_8 5_4 9_8 7_8 3_4 5_8 1_2 3_8 1_4 1_8; do
-		scalearg=`echo $scale | sed 's/\_/\//g'`
-		for samp in GRAY 420 422 444; do
-			runme $EXEDIR/djpeg -rgb -scale ${scalearg} $NSARG -bmp -outfile $OUTDIR/${basename}_${samp}_${scale}_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg
-			runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -scale ${scalearg} -quiet -benchtime 0.01 -warmup 0 $YUVARG $PROGARG
-			runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_Q95_${scale}.bmp $OUTDIR/${basename}_${samp}_${scale}_djpeg.bmp
-			rm $OUTDIR/${basename}_${samp}_Q95_${scale}.bmp
-		done
-	done
-
-	# Transforms
-	for samp in GRAY 420 422 444; do
-		runme $EXEDIR/jpegtran -flip horizontal -trim -outfile $OUTDIR/${basename}_${samp}_hflip_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -flip vertical -trim -outfile $OUTDIR/${basename}_${samp}_vflip_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -transpose -trim -outfile $OUTDIR/${basename}_${samp}_transpose_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -transverse -trim -outfile $OUTDIR/${basename}_${samp}_transverse_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -rotate 90 -trim -outfile $OUTDIR/${basename}_${samp}_rot90_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -rotate 180 -trim -outfile $OUTDIR/${basename}_${samp}_rot180_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-		runme $EXEDIR/jpegtran -rotate 270 -trim -outfile $OUTDIR/${basename}_${samp}_rot270_jpegtran.jpg $OUTDIR/${basename}_${samp}_Q95.jpg
-	done
-	for xform in hflip vflip transpose transverse rot90 rot180 rot270; do
-		for samp in GRAY 444; do
-			runme $EXEDIR/djpeg -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg
-			runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -$xform -tile -quiet -benchtime 0.01 -warmup 0 $YUVARG $PROGARG
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp -i 54:54 $i $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp
-				rm $i
-			done
-		done
-		for samp in 420 422; do
-			runme $EXEDIR/djpeg -nosmooth -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg
-			runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -$xform -tile -quiet -benchtime 0.01 -warmup 0 -fastupsample $YUVARG $PROGARG
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp -i 54:54 $i $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp
-				rm $i
-			done
-		done
-	done
-
-	# Grayscale transform
-	for xform in hflip vflip transpose transverse rot90 rot180 rot270; do
-		for samp in GRAY 444 422 420; do
-			runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -$xform -tile -quiet -benchtime 0.01 -warmup 0 -grayscale $YUVARG $PROGARG
-			for i in $OUTDIR/${basename}_${samp}_Q95_[0-9]*[0-9]x[0-9]*[0-9].bmp \
-				$OUTDIR/${basename}_${samp}_Q95_full.bmp; do
-				runme cmp -i 54:54 $i $OUTDIR/${basename}_GRAY_${xform}_jpegtran.bmp
-				rm $i
-			done
-		done
-	done
-
-	# Transforms with scaling
-	for xform in hflip vflip transpose transverse rot90 rot180 rot270; do
-		for samp in GRAY 444 422 420; do
-			for scale in 2_1 15_8 7_4 13_8 3_2 11_8 5_4 9_8 7_8 3_4 5_8 1_2 3_8 1_4 1_8; do
-				scalearg=`echo $scale | sed 's/\_/\//g'`
-				runme $EXEDIR/djpeg -rgb -scale ${scalearg} $NSARG -bmp -outfile $OUTDIR/${basename}_${samp}_${xform}_${scale}_jpegtran.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg
-				runme "$JAVA" $JAVAARGS TJBench $OUTDIR/${basename}_${samp}_Q95.jpg -$xform -scale ${scalearg} -quiet -benchtime 0.01 -warmup 0 $YUVARG $PROGARG
-				runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_Q95_${scale}.bmp $OUTDIR/${basename}_${samp}_${xform}_${scale}_jpegtran.bmp
-				rm $OUTDIR/${basename}_${samp}_Q95_${scale}.bmp
-			done
-		done
-	done
-
-done
-
-echo SUCCESS!
diff --git a/third_party/libjpeg-turbo/tjunittest.c b/third_party/libjpeg-turbo/tjunittest.c
index af409a5..04be30d 100644
--- a/third_party/libjpeg-turbo/tjunittest.c
+++ b/third_party/libjpeg-turbo/tjunittest.c
@@ -1,5 +1,6 @@
 /*
- * Copyright (C)2009-2014, 2017-2019 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2009-2014, 2017-2019, 2022 D. R. Commander.
+ *                                         All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +31,10 @@
  * This program tests the various code paths in the TurboJPEG C Wrapper
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -354,7 +359,7 @@
 {
 #if defined(ANDROID) && defined(GTEST)
   char path[filePathSize];
-  snprintf(path, filePathSize, "/sdcard/%s", filename);
+  SNPRINTF(path, filePathSize, "/sdcard/%s", filename);
   FILE *file = fopen(path, "wb");
 #else
   FILE *file = fopen(filename, "wb");
@@ -418,7 +423,7 @@
                        jpegQual, flags));
   }
 
-  snprintf(tempStr, filePathSize, "%s_enc_%s_%s_%s_Q%d.jpg", basename, pfStr,
+  SNPRINTF(tempStr, filePathSize, "%s_enc_%s_%s_%s_Q%d.jpg", basename, pfStr,
            buStr, subName[subsamp], jpegQual);
   writeJPEG(*dstBuf, *dstSize, tempStr);
   fprintf(stderr, "Done.\n  Result in %s\n", tempStr);
@@ -797,11 +802,11 @@
   initBitmap(buf, width, pitch, height, pf, flags);
 
 #if defined(ANDROID) && defined(GTEST)
-  snprintf(filename, filenameSize, "/sdcard/test_bmp_%s_%d_%s.%s",
+  SNPRINTF(filename, filenameSize, "/sdcard/test_bmp_%s_%d_%s.%s",
            pixFormatStr[pf], align, (flags & TJFLAG_BOTTOMUP) ? "bu" : "td",
            ext);
 #else
-  snprintf(filename, filenameSize, "test_bmp_%s_%d_%s.%s", pixFormatStr[pf],
+  SNPRINTF(filename, filenameSize, "test_bmp_%s_%d_%s.%s", pixFormatStr[pf],
            align, (flags & TJFLAG_BOTTOMUP) ? "bu" : "td", ext);
 #endif
   TRY_TJ(tjSaveImage(filename, buf, width, pitch, height, pf, flags));
diff --git a/third_party/libjpeg-turbo/tjutil.h b/third_party/libjpeg-turbo/tjutil.h
index 34a9e37..55fb2a6 100644
--- a/third_party/libjpeg-turbo/tjutil.h
+++ b/third_party/libjpeg-turbo/tjutil.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C)2011 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2011, 2022 D. R. Commander.  All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,27 +26,28 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifdef _WIN32
-#ifndef __MINGW32__
-
 #ifdef STARBOARD
 #include "starboard/client_porting/poem/stdio_poem.h"
-#else
-#include <stdio.h>
-#define snprintf(str, n, format, ...) \
-  _snprintf_s(str, n, _TRUNCATE, format, ##__VA_ARGS__)
-#endif
-
-#endif
-
-#ifdef STARBOARD
 #include "starboard/client_porting/poem/strings_poem.h"
+#define SNPRINTF snprintf
 #else
+#ifdef _WIN32
+#ifndef strcasecmp
 #define strcasecmp  stricmp
+#endif
+#ifndef strncasecmp
 #define strncasecmp  strnicmp
 #endif
 #endif
 
+#ifdef _MSC_VER
+#define SNPRINTF(str, n, format, ...) \
+  _snprintf_s(str, n, _TRUNCATE, format, ##__VA_ARGS__)
+#else
+#define SNPRINTF  snprintf
+#endif
+#endif
+
 #ifndef min
 #define min(a, b)  ((a) < (b) ? (a) : (b))
 #endif
diff --git a/third_party/libjpeg-turbo/transupp.c b/third_party/libjpeg-turbo/transupp.c
index 6e86077..a3d878c 100644
--- a/third_party/libjpeg-turbo/transupp.c
+++ b/third_party/libjpeg-turbo/transupp.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1997-2019, Thomas G. Lane, Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, 2017, D. R. Commander.
+ * Copyright (C) 2010, 2017, 2021-2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -262,8 +262,8 @@
         }
       } else {
         for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
-          MEMZERO(dst_buffer[offset_y] + x_drop_blocks,
-                  comp_width * sizeof(JBLOCK));
+          memset(dst_buffer[offset_y] + x_drop_blocks, 0,
+                 comp_width * sizeof(JBLOCK));
         }
       }
     }
@@ -345,8 +345,8 @@
         if (dst_blk_y < y_crop_blocks ||
             dst_blk_y >= y_crop_blocks + comp_height) {
           for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
-            MEMZERO(dst_buffer[offset_y],
-                    compptr->width_in_blocks * sizeof(JBLOCK));
+            memset(dst_buffer[offset_y], 0,
+                   compptr->width_in_blocks * sizeof(JBLOCK));
           }
           continue;
         }
@@ -363,14 +363,14 @@
       for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
         if (dstinfo->_jpeg_width > srcinfo->output_width) {
           if (x_crop_blocks > 0) {
-            MEMZERO(dst_buffer[offset_y], x_crop_blocks * sizeof(JBLOCK));
+            memset(dst_buffer[offset_y], 0, x_crop_blocks * sizeof(JBLOCK));
           }
           jcopy_block_row(src_buffer[offset_y],
                           dst_buffer[offset_y] + x_crop_blocks, comp_width);
           if (compptr->width_in_blocks > x_crop_blocks + comp_width) {
-            MEMZERO(dst_buffer[offset_y] + x_crop_blocks + comp_width,
-                    (compptr->width_in_blocks - x_crop_blocks - comp_width) *
-                    sizeof(JBLOCK));
+            memset(dst_buffer[offset_y] + x_crop_blocks + comp_width, 0,
+                   (compptr->width_in_blocks - x_crop_blocks - comp_width) *
+                   sizeof(JBLOCK));
           }
         } else {
           jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
@@ -421,8 +421,8 @@
         if (dst_blk_y < y_crop_blocks ||
             dst_blk_y >= y_crop_blocks + comp_height) {
           for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
-            MEMZERO(dst_buffer[offset_y],
-                    compptr->width_in_blocks * sizeof(JBLOCK));
+            memset(dst_buffer[offset_y], 0,
+                   compptr->width_in_blocks * sizeof(JBLOCK));
           }
           continue;
         }
@@ -438,7 +438,7 @@
       }
       for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
         if (x_crop_blocks > 0) {
-          MEMZERO(dst_buffer[offset_y], x_crop_blocks * sizeof(JBLOCK));
+          memset(dst_buffer[offset_y], 0, x_crop_blocks * sizeof(JBLOCK));
           dc = src_buffer[offset_y][0][0];
           for (dst_blk_x = 0; dst_blk_x < x_crop_blocks; dst_blk_x++) {
             dst_buffer[offset_y][dst_blk_x][0] = dc;
@@ -447,9 +447,9 @@
         jcopy_block_row(src_buffer[offset_y],
                         dst_buffer[offset_y] + x_crop_blocks, comp_width);
         if (compptr->width_in_blocks > x_crop_blocks + comp_width) {
-          MEMZERO(dst_buffer[offset_y] + x_crop_blocks + comp_width,
-                  (compptr->width_in_blocks - x_crop_blocks - comp_width) *
-                  sizeof(JBLOCK));
+          memset(dst_buffer[offset_y] + x_crop_blocks + comp_width, 0,
+                 (compptr->width_in_blocks - x_crop_blocks - comp_width) *
+                 sizeof(JBLOCK));
           dc = src_buffer[offset_y][comp_width - 1][0];
           for (dst_blk_x = x_crop_blocks + comp_width;
                dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
@@ -502,8 +502,8 @@
         if (dst_blk_y < y_crop_blocks ||
             dst_blk_y >= y_crop_blocks + comp_height) {
           for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
-            MEMZERO(dst_buffer[offset_y],
-                    compptr->width_in_blocks * sizeof(JBLOCK));
+            memset(dst_buffer[offset_y], 0,
+                   compptr->width_in_blocks * sizeof(JBLOCK));
           }
           continue;
         }
@@ -591,7 +591,8 @@
         ((j_common_ptr)srcinfo, src_coef_arrays[ci], y_wipe_blocks,
          (JDIMENSION)compptr->v_samp_factor, TRUE);
       for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
-        MEMZERO(buffer[offset_y] + x_wipe_blocks, wipe_width * sizeof(JBLOCK));
+        memset(buffer[offset_y] + x_wipe_blocks, 0,
+               wipe_width * sizeof(JBLOCK));
       }
     }
   }
@@ -626,7 +627,8 @@
         ((j_common_ptr)srcinfo, src_coef_arrays[ci], y_wipe_blocks,
          (JDIMENSION)compptr->v_samp_factor, TRUE);
       for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
-        MEMZERO(buffer[offset_y] + x_wipe_blocks, wipe_width * sizeof(JBLOCK));
+        memset(buffer[offset_y] + x_wipe_blocks, 0,
+               wipe_width * sizeof(JBLOCK));
         if (x_wipe_blocks > 0) {
           dc_left_value = buffer[offset_y][x_wipe_blocks - 1][0];
           if (wipe_right < compptr->width_in_blocks) {
@@ -709,8 +711,8 @@
             }
           }
         } else {
-          MEMZERO(buffer[offset_y] + x_wipe_blocks,
-                  wipe_width * sizeof(JBLOCK));
+          memset(buffer[offset_y] + x_wipe_blocks, 0,
+                 wipe_width * sizeof(JBLOCK));
         }
       }
     }
@@ -2310,7 +2312,7 @@
   int m;
 
   /* Save comments except under NONE option */
-  if (option != JCOPYOPT_NONE) {
+  if (option != JCOPYOPT_NONE && option != JCOPYOPT_ICC) {
     jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
   }
   /* Save all types of APPn markers iff ALL option */
@@ -2321,6 +2323,10 @@
       jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
     }
   }
+  /* Save only APP2 markers if ICC option selected */
+  if (option == JCOPYOPT_ICC) {
+    jpeg_save_markers(srcinfo, JPEG_APP0 + 2, 0xFFFF);
+  }
 #endif /* SAVE_MARKERS_SUPPORTED */
 }
 
diff --git a/third_party/libjpeg-turbo/transupp.h b/third_party/libjpeg-turbo/transupp.h
index ea6be1f..cea1f40 100644
--- a/third_party/libjpeg-turbo/transupp.h
+++ b/third_party/libjpeg-turbo/transupp.h
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1997-2019, Thomas G. Lane, Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2017, D. R. Commander.
+ * Copyright (C) 2017, 2021, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -213,10 +213,11 @@
  */
 
 typedef enum {
-  JCOPYOPT_NONE,          /* copy no optional markers */
-  JCOPYOPT_COMMENTS,      /* copy only comment (COM) markers */
-  JCOPYOPT_ALL,           /* copy all optional markers */
-  JCOPYOPT_ALL_EXCEPT_ICC /* copy all optional markers except APP2 */
+  JCOPYOPT_NONE,           /* copy no optional markers */
+  JCOPYOPT_COMMENTS,       /* copy only comment (COM) markers */
+  JCOPYOPT_ALL,            /* copy all optional markers */
+  JCOPYOPT_ALL_EXCEPT_ICC, /* copy all optional markers except APP2 */
+  JCOPYOPT_ICC             /* copy only ICC profile (APP2) markers */
 } JCOPY_OPTION;
 
 #define JCOPYOPT_DEFAULT  JCOPYOPT_COMMENTS     /* recommended default */
diff --git a/third_party/libjpeg-turbo/turbojpeg-jni.c b/third_party/libjpeg-turbo/turbojpeg-jni.c
index 1b728e3..0cf5f70 100644
--- a/third_party/libjpeg-turbo/turbojpeg-jni.c
+++ b/third_party/libjpeg-turbo/turbojpeg-jni.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C)2011-2020 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2011-2022 D. R. Commander.  All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,12 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <stdlib.h>
-#include <string.h>
 #include "turbojpeg.h"
-#ifdef WIN32
-#include "tjutil.h"
-#endif
+#include "jinclude.h"
 #include <jni.h>
 #include "java/org_libjpegturbo_turbojpeg_TJCompressor.h"
 #include "java/org_libjpegturbo_turbojpeg_TJDecompressor.h"
@@ -44,6 +40,12 @@
   } \
 }
 
+#define BAILIF0NOEC(f) { \
+  if (!(f)) { \
+    goto bailout; \
+  } \
+}
+
 #define THROW(msg, exceptionClass) { \
   jclass _exccls = (*env)->FindClass(env, exceptionClass); \
   \
@@ -82,20 +84,20 @@
   BAILIF0(_fid = (*env)->GetFieldID(env, _cls, "handle", "J")); \
   handle = (tjhandle)(size_t)(*env)->GetLongField(env, obj, _fid);
 
-#ifdef _WIN32
-#define setenv(envvar, value, dummy)  _putenv_s(envvar, value)
-#endif
-
+#ifndef NO_PUTENV
 #define PROP2ENV(property, envvar) { \
-  if ((jName = (*env)->NewStringUTF(env, property)) != NULL && \
-      (jValue = (*env)->CallStaticObjectMethod(env, cls, mid, \
-                                               jName)) != NULL) { \
-    if ((value = (*env)->GetStringUTFChars(env, jValue, 0)) != NULL) { \
-      setenv(envvar, value, 1); \
+  if ((jName = (*env)->NewStringUTF(env, property)) != NULL) { \
+    jboolean exception; \
+    jValue = (*env)->CallStaticObjectMethod(env, cls, mid, jName); \
+    exception = (*env)->ExceptionCheck(env); \
+    if (jValue && !exception && \
+        (value = (*env)->GetStringUTFChars(env, jValue, 0)) != NULL) { \
+      PUTENV_S(envvar, value); \
       (*env)->ReleaseStringUTFChars(env, jValue, value); \
     } \
   } \
 }
+#endif
 
 #define SAFE_RELEASE(javaArray, cArray) { \
   if (javaArray && cArray) \
@@ -114,10 +116,12 @@
   BAILIF0(mid = (*env)->GetStaticMethodID(env, cls, "getProperty",
     "(Ljava/lang/String;)Ljava/lang/String;"));
 
+#ifndef NO_PUTENV
   PROP2ENV("turbojpeg.optimize", "TJ_OPTIMIZE");
   PROP2ENV("turbojpeg.arithmetic", "TJ_ARITHMETIC");
   PROP2ENV("turbojpeg.restart", "TJ_RESTART");
   PROP2ENV("turbojpeg.progressive", "TJ_PROGRESSIVE");
+#endif
   return 0;
 
 bailout:
@@ -242,8 +246,8 @@
 
   if (ProcessSystemProperties(env) < 0) goto bailout;
 
-  BAILIF0(srcBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
+  BAILIF0NOEC(srcBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
 
   if (tjCompress2(handle, &srcBuf[y * actualPitch + x * tjPixelSize[pf]],
                   width, pitch, height, pf, &jpegBuf, &jpegSize, jpegSubsamp,
@@ -328,8 +332,8 @@
   jbyteArray jSrcPlanes[3] = { NULL, NULL, NULL };
   const unsigned char *srcPlanesTmp[3] = { NULL, NULL, NULL };
   const unsigned char *srcPlanes[3] = { NULL, NULL, NULL };
-  int *srcOffsetsTmp = NULL, srcOffsets[3] = { 0, 0, 0 };
-  int *srcStridesTmp = NULL, srcStrides[3] = { 0, 0, 0 };
+  jint srcOffsetsTmp[3] = { 0, 0, 0 }, srcStridesTmp[3] = { 0, 0, 0 };
+  int srcOffsets[3] = { 0, 0, 0 }, srcStrides[3] = { 0, 0, 0 };
   unsigned char *jpegBuf = NULL;
   int nc = (subsamp == org_libjpegturbo_turbojpeg_TJ_SAMP_GRAY ? 1 : 3), i;
 
@@ -353,15 +357,15 @@
 
   if (ProcessSystemProperties(env) < 0) goto bailout;
 
-  BAILIF0(srcOffsetsTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jSrcOffsets, 0));
-  for (i = 0; i < nc; i++) srcOffsets[i] = srcOffsetsTmp[i];
-  SAFE_RELEASE(jSrcOffsets, srcOffsetsTmp);
+  (*env)->GetIntArrayRegion(env, jSrcOffsets, 0, nc, srcOffsetsTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    srcOffsets[i] = srcOffsetsTmp[i];
 
-  BAILIF0(srcStridesTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jSrcStrides, 0));
-  for (i = 0; i < nc; i++) srcStrides[i] = srcStridesTmp[i];
-  SAFE_RELEASE(jSrcStrides, srcStridesTmp);
+  (*env)->GetIntArrayRegion(env, jSrcStrides, 0, nc, srcStridesTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    srcStrides[i] = srcStridesTmp[i];
 
   for (i = 0; i < nc; i++) {
     int planeSize = tjPlaneSizeYUV(i, width, srcStrides[i], height, subsamp);
@@ -379,23 +383,27 @@
     if ((*env)->GetArrayLength(env, jSrcPlanes[i]) <
         srcOffsets[i] + planeSize)
       THROW_ARG("Source plane is not large enough");
-
-    BAILIF0(srcPlanesTmp[i] =
-            (*env)->GetPrimitiveArrayCritical(env, jSrcPlanes[i], 0));
-    srcPlanes[i] = &srcPlanesTmp[i][srcOffsets[i]];
-    SAFE_RELEASE(jSrcPlanes[i], srcPlanesTmp[i]);
   }
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
+  for (i = 0; i < nc; i++) {
+    BAILIF0NOEC(srcPlanesTmp[i] =
+                (*env)->GetPrimitiveArrayCritical(env, jSrcPlanes[i], 0));
+    srcPlanes[i] = &srcPlanesTmp[i][srcOffsets[i]];
+  }
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
 
   if (tjCompressFromYUVPlanes(handle, srcPlanes, width, srcStrides, height,
                               subsamp, &jpegBuf, &jpegSize, jpegQual,
                               flags | TJFLAG_NOREALLOC) == -1) {
     SAFE_RELEASE(dst, jpegBuf);
+    for (i = 0; i < nc; i++)
+      SAFE_RELEASE(jSrcPlanes[i], srcPlanesTmp[i]);
     THROW_TJ();
   }
 
 bailout:
   SAFE_RELEASE(dst, jpegBuf);
+  for (i = 0; i < nc; i++)
+    SAFE_RELEASE(jSrcPlanes[i], srcPlanesTmp[i]);
   return (jint)jpegSize;
 }
 
@@ -410,8 +418,8 @@
   jbyteArray jDstPlanes[3] = { NULL, NULL, NULL };
   unsigned char *dstPlanesTmp[3] = { NULL, NULL, NULL };
   unsigned char *dstPlanes[3] = { NULL, NULL, NULL };
-  int *dstOffsetsTmp = NULL, dstOffsets[3] = { 0, 0, 0 };
-  int *dstStridesTmp = NULL, dstStrides[3] = { 0, 0, 0 };
+  jint dstOffsetsTmp[3] = { 0, 0, 0 }, dstStridesTmp[3] = { 0, 0, 0 };
+  int dstOffsets[3] = { 0, 0, 0 }, dstStrides[3] = { 0, 0, 0 };
   int nc = (subsamp == org_libjpegturbo_turbojpeg_TJ_SAMP_GRAY ? 1 : 3), i;
 
   GET_HANDLE();
@@ -436,15 +444,15 @@
   if ((*env)->GetArrayLength(env, src) * srcElementSize < arraySize)
     THROW_ARG("Source buffer is not large enough");
 
-  BAILIF0(dstOffsetsTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jDstOffsets, 0));
-  for (i = 0; i < nc; i++) dstOffsets[i] = dstOffsetsTmp[i];
-  SAFE_RELEASE(jDstOffsets, dstOffsetsTmp);
+  (*env)->GetIntArrayRegion(env, jDstOffsets, 0, nc, dstOffsetsTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    dstOffsets[i] = dstOffsetsTmp[i];
 
-  BAILIF0(dstStridesTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jDstStrides, 0));
-  for (i = 0; i < nc; i++) dstStrides[i] = dstStridesTmp[i];
-  SAFE_RELEASE(jDstStrides, dstStridesTmp);
+  (*env)->GetIntArrayRegion(env, jDstStrides, 0, nc, dstStridesTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    dstStrides[i] = dstStridesTmp[i];
 
   for (i = 0; i < nc; i++) {
     int planeSize = tjPlaneSizeYUV(i, width, dstStrides[i], height, subsamp);
@@ -462,23 +470,27 @@
     if ((*env)->GetArrayLength(env, jDstPlanes[i]) <
         dstOffsets[i] + planeSize)
       THROW_ARG("Destination plane is not large enough");
-
-    BAILIF0(dstPlanesTmp[i] =
-            (*env)->GetPrimitiveArrayCritical(env, jDstPlanes[i], 0));
-    dstPlanes[i] = &dstPlanesTmp[i][dstOffsets[i]];
-    SAFE_RELEASE(jDstPlanes[i], dstPlanesTmp[i]);
   }
-  BAILIF0(srcBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  for (i = 0; i < nc; i++) {
+    BAILIF0NOEC(dstPlanesTmp[i] =
+                (*env)->GetPrimitiveArrayCritical(env, jDstPlanes[i], 0));
+    dstPlanes[i] = &dstPlanesTmp[i][dstOffsets[i]];
+  }
+  BAILIF0NOEC(srcBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
 
   if (tjEncodeYUVPlanes(handle, &srcBuf[y * actualPitch + x * tjPixelSize[pf]],
                         width, pitch, height, pf, dstPlanes, dstStrides,
                         subsamp, flags) == -1) {
     SAFE_RELEASE(src, srcBuf);
+    for (i = 0; i < nc; i++)
+      SAFE_RELEASE(jDstPlanes[i], dstPlanesTmp[i]);
     THROW_TJ();
   }
 
 bailout:
   SAFE_RELEASE(src, srcBuf);
+  for (i = 0; i < nc; i++)
+    SAFE_RELEASE(jDstPlanes[i], dstPlanesTmp[i]);
 }
 
 /* TurboJPEG 1.4.x: TJCompressor::encodeYUV() byte source */
@@ -533,8 +545,8 @@
       (jsize)tjBufSizeYUV(width, height, subsamp))
     THROW_ARG("Destination buffer is not large enough");
 
-  BAILIF0(srcBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
-  BAILIF0(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
+  BAILIF0NOEC(srcBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  BAILIF0NOEC(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
 
   if (tjEncodeYUV2(handle, srcBuf, width, pitch, height, pf, dstBuf, subsamp,
                    flags) == -1) {
@@ -653,7 +665,7 @@
   if ((*env)->GetArrayLength(env, src) < jpegSize)
     THROW_ARG("Source buffer is not large enough");
 
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
 
   if (tjDecompressHeader3(handle, jpegBuf, (unsigned long)jpegSize, &width,
                           &height, &jpegSubsamp, &jpegColorspace) == -1) {
@@ -701,8 +713,8 @@
   if ((*env)->GetArrayLength(env, dst) * dstElementSize < arraySize)
     THROW_ARG("Destination buffer is not large enough");
 
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
-  BAILIF0(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  BAILIF0NOEC(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
 
   if (tjDecompress2(handle, jpegBuf, (unsigned long)jpegSize,
                     &dstBuf[y * actualPitch + x * tjPixelSize[pf]], width,
@@ -780,8 +792,8 @@
   jbyteArray jDstPlanes[3] = { NULL, NULL, NULL };
   unsigned char *dstPlanesTmp[3] = { NULL, NULL, NULL };
   unsigned char *dstPlanes[3] = { NULL, NULL, NULL };
-  int *dstOffsetsTmp = NULL, dstOffsets[3] = { 0, 0, 0 };
-  int *dstStridesTmp = NULL, dstStrides[3] = { 0, 0, 0 };
+  jint dstOffsetsTmp[3] = { 0, 0, 0 }, dstStridesTmp[3] = { 0, 0, 0 };
+  int dstOffsets[3] = { 0, 0, 0 }, dstStrides[3] = { 0, 0, 0 };
   int jpegSubsamp = -1, jpegWidth = 0, jpegHeight = 0;
   int nc = 0, i, width, height, scaledWidth, scaledHeight, nsf = 0;
   tjscalingfactor *sf;
@@ -815,15 +827,15 @@
   if (i >= nsf)
     THROW_ARG("Could not scale down to desired image dimensions");
 
-  BAILIF0(dstOffsetsTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jDstOffsets, 0));
-  for (i = 0; i < nc; i++) dstOffsets[i] = dstOffsetsTmp[i];
-  SAFE_RELEASE(jDstOffsets, dstOffsetsTmp);
+  (*env)->GetIntArrayRegion(env, jDstOffsets, 0, nc, dstOffsetsTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    dstOffsets[i] = dstOffsetsTmp[i];
 
-  BAILIF0(dstStridesTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jDstStrides, 0));
-  for (i = 0; i < nc; i++) dstStrides[i] = dstStridesTmp[i];
-  SAFE_RELEASE(jDstStrides, dstStridesTmp);
+  (*env)->GetIntArrayRegion(env, jDstStrides, 0, nc, dstStridesTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    dstStrides[i] = dstStridesTmp[i];
 
   for (i = 0; i < nc; i++) {
     int planeSize = tjPlaneSizeYUV(i, scaledWidth, dstStrides[i], scaledHeight,
@@ -842,23 +854,27 @@
     if ((*env)->GetArrayLength(env, jDstPlanes[i]) <
         dstOffsets[i] + planeSize)
       THROW_ARG("Destination plane is not large enough");
-
-    BAILIF0(dstPlanesTmp[i] =
-            (*env)->GetPrimitiveArrayCritical(env, jDstPlanes[i], 0));
-    dstPlanes[i] = &dstPlanesTmp[i][dstOffsets[i]];
-    SAFE_RELEASE(jDstPlanes[i], dstPlanesTmp[i]);
   }
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  for (i = 0; i < nc; i++) {
+    BAILIF0NOEC(dstPlanesTmp[i] =
+                (*env)->GetPrimitiveArrayCritical(env, jDstPlanes[i], 0));
+    dstPlanes[i] = &dstPlanesTmp[i][dstOffsets[i]];
+  }
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
 
   if (tjDecompressToYUVPlanes(handle, jpegBuf, (unsigned long)jpegSize,
                               dstPlanes, desiredWidth, dstStrides,
                               desiredHeight, flags) == -1) {
     SAFE_RELEASE(src, jpegBuf);
+    for (i = 0; i < nc; i++)
+      SAFE_RELEASE(jDstPlanes[i], dstPlanesTmp[i]);
     THROW_TJ();
   }
 
 bailout:
   SAFE_RELEASE(src, jpegBuf);
+  for (i = 0; i < nc; i++)
+    SAFE_RELEASE(jDstPlanes[i], dstPlanesTmp[i]);
 }
 
 /* TurboJPEG 1.2.x: TJDecompressor::decompressToYUV() */
@@ -884,8 +900,8 @@
       (jsize)tjBufSizeYUV(jpegWidth, jpegHeight, jpegSubsamp))
     THROW_ARG("Destination buffer is not large enough");
 
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
-  BAILIF0(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, src, 0));
+  BAILIF0NOEC(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
 
   if (tjDecompressToYUV(handle, jpegBuf, (unsigned long)jpegSize, dstBuf,
                         flags) == -1) {
@@ -909,8 +925,8 @@
   jbyteArray jSrcPlanes[3] = { NULL, NULL, NULL };
   const unsigned char *srcPlanesTmp[3] = { NULL, NULL, NULL };
   const unsigned char *srcPlanes[3] = { NULL, NULL, NULL };
-  int *srcOffsetsTmp = NULL, srcOffsets[3] = { 0, 0, 0 };
-  int *srcStridesTmp = NULL, srcStrides[3] = { 0, 0, 0 };
+  jint srcOffsetsTmp[3] = { 0, 0, 0 }, srcStridesTmp[3] = { 0, 0, 0 };
+  int srcOffsets[3] = { 0, 0, 0 }, srcStrides[3] = { 0, 0, 0 };
   unsigned char *dstBuf = NULL;
   int nc = (subsamp == org_libjpegturbo_turbojpeg_TJ_SAMP_GRAY ? 1 : 3), i;
 
@@ -935,15 +951,15 @@
   if ((*env)->GetArrayLength(env, dst) * dstElementSize < arraySize)
     THROW_ARG("Destination buffer is not large enough");
 
-  BAILIF0(srcOffsetsTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jSrcOffsets, 0));
-  for (i = 0; i < nc; i++) srcOffsets[i] = srcOffsetsTmp[i];
-  SAFE_RELEASE(jSrcOffsets, srcOffsetsTmp);
+  (*env)->GetIntArrayRegion(env, jSrcOffsets, 0, nc, srcOffsetsTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    srcOffsets[i] = srcOffsetsTmp[i];
 
-  BAILIF0(srcStridesTmp =
-          (*env)->GetPrimitiveArrayCritical(env, jSrcStrides, 0));
-  for (i = 0; i < nc; i++) srcStrides[i] = srcStridesTmp[i];
-  SAFE_RELEASE(jSrcStrides, srcStridesTmp);
+  (*env)->GetIntArrayRegion(env, jSrcStrides, 0, nc, srcStridesTmp);
+  if ((*env)->ExceptionCheck(env)) goto bailout;
+  for (i = 0; i < 3; i++)
+    srcStrides[i] = srcStridesTmp[i];
 
   for (i = 0; i < nc; i++) {
     int planeSize = tjPlaneSizeYUV(i, width, srcStrides[i], height, subsamp);
@@ -961,23 +977,27 @@
     if ((*env)->GetArrayLength(env, jSrcPlanes[i]) <
         srcOffsets[i] + planeSize)
       THROW_ARG("Source plane is not large enough");
-
-    BAILIF0(srcPlanesTmp[i] =
-            (*env)->GetPrimitiveArrayCritical(env, jSrcPlanes[i], 0));
-    srcPlanes[i] = &srcPlanesTmp[i][srcOffsets[i]];
-    SAFE_RELEASE(jSrcPlanes[i], srcPlanesTmp[i]);
   }
-  BAILIF0(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
+  for (i = 0; i < nc; i++) {
+    BAILIF0NOEC(srcPlanesTmp[i] =
+                (*env)->GetPrimitiveArrayCritical(env, jSrcPlanes[i], 0));
+    srcPlanes[i] = &srcPlanesTmp[i][srcOffsets[i]];
+  }
+  BAILIF0NOEC(dstBuf = (*env)->GetPrimitiveArrayCritical(env, dst, 0));
 
   if (tjDecodeYUVPlanes(handle, srcPlanes, srcStrides, subsamp,
                         &dstBuf[y * actualPitch + x * tjPixelSize[pf]], width,
                         pitch, height, pf, flags) == -1) {
     SAFE_RELEASE(dst, dstBuf);
+    for (i = 0; i < nc; i++)
+      SAFE_RELEASE(jSrcPlanes[i], srcPlanesTmp[i]);
     THROW_TJ();
   }
 
 bailout:
   SAFE_RELEASE(dst, dstBuf);
+  for (i = 0; i < nc; i++)
+    SAFE_RELEASE(jSrcPlanes[i], srcPlanesTmp[i]);
 }
 
 /* TurboJPEG 1.4.x: TJDecompressor::decodeYUV() byte destination */
@@ -1183,10 +1203,10 @@
         tjBufSize(w, h, jpegSubsamp))
       THROW_ARG("Destination buffer is not large enough");
   }
-  BAILIF0(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, jsrcBuf, 0));
+  BAILIF0NOEC(jpegBuf = (*env)->GetPrimitiveArrayCritical(env, jsrcBuf, 0));
   for (i = 0; i < n; i++)
-    BAILIF0(dstBufs[i] =
-            (*env)->GetPrimitiveArrayCritical(env, jdstBufs[i], 0));
+    BAILIF0NOEC(dstBufs[i] =
+                (*env)->GetPrimitiveArrayCritical(env, jdstBufs[i], 0));
 
   if (tjTransform(handle, jpegBuf, jpegSize, n, dstBufs, dstSizes, t,
                   flags | TJFLAG_NOREALLOC) == -1) {
diff --git a/third_party/libjpeg-turbo/turbojpeg.c b/third_party/libjpeg-turbo/turbojpeg.c
index 72a2b90..0f3acf6 100644
--- a/third_party/libjpeg-turbo/turbojpeg.c
+++ b/third_party/libjpeg-turbo/turbojpeg.c
@@ -1,5 +1,6 @@
 /*
- * Copyright (C)2009-2021 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2009-2022 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2021 Alex Richardson.  All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -29,9 +30,7 @@
 /* TurboJPEG/LJT:  this implements the TurboJPEG API using libjpeg or
    libjpeg-turbo */
 
-#include <stdlib.h>
-#include <string.h>
-
+#include <ctype.h>
 #include <jinclude.h>
 
 #include <setjmp.h>
@@ -176,9 +175,9 @@
     int scan_no = ((j_decompress_ptr)dinfo)->input_scan_number;
 
     if (scan_no > 500) {
-      snprintf(myprog->this->errStr, JMSG_LENGTH_MAX,
+      SNPRINTF(myprog->this->errStr, JMSG_LENGTH_MAX,
                "Progressive JPEG image has more than 500 scans");
-      snprintf(GET_ERRSTR, JMSG_LENGTH_MAX,
+      SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX,
                "Progressive JPEG image has more than 500 scans");
       myprog->this->isInstanceError = TRUE;
       myerr->warning = FALSE;
@@ -241,17 +240,24 @@
 };
 
 #define THROWG(m) { \
-  snprintf(GET_ERRSTR, JMSG_LENGTH_MAX, "%s", m); \
+  SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX, "%s", m); \
   retval = -1;  goto bailout; \
 }
+#ifdef _MSC_VER
 #define THROW_UNIX(m) { \
-  char message[512]; \
+  char message[512] = { 0 }; \
   SbSystemGetErrorString(errno, message, SB_ARRAY_SIZE_INT(message)); \
-  snprintf(GET_ERRSTR, JMSG_LENGTH_MAX, "%s\n%s", m, message); \
+  SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX, "%s\n%s", m, strerrorBuf); \
   retval = -1;  goto bailout; \
 }
+#else
+#define THROW_UNIX(m) { \
+  SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX, "%s\n%s", m, strerror(errno)); \
+  retval = -1;  goto bailout; \
+}
+#endif
 #define THROW(m) { \
-  snprintf(this->errStr, JMSG_LENGTH_MAX, "%s", m); \
+  SNPRINTF(this->errStr, JMSG_LENGTH_MAX, "%s", m); \
   this->isInstanceError = TRUE;  THROWG(m) \
 }
 
@@ -266,7 +272,7 @@
   j_decompress_ptr dinfo = NULL; \
   \
   if (!this) { \
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX, "Invalid handle"); \
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX, "Invalid handle"); \
     return -1; \
   } \
   cinfo = &this->cinfo;  dinfo = &this->dinfo; \
@@ -278,7 +284,7 @@
   j_compress_ptr cinfo = NULL; \
   \
   if (!this) { \
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX, "Invalid handle"); \
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX, "Invalid handle"); \
     return -1; \
   } \
   cinfo = &this->cinfo; \
@@ -290,7 +296,7 @@
   j_decompress_ptr dinfo = NULL; \
   \
   if (!this) { \
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX, "Invalid handle"); \
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX, "Invalid handle"); \
     return -1; \
   } \
   dinfo = &this->dinfo; \
@@ -321,7 +327,7 @@
                             int flags)
 {
 #ifndef NO_GETENV
-  char *env = NULL;
+  char env[7] = { 0 };
 #endif
 
   cinfo->in_color_space = pf2cs[pixelFormat];
@@ -329,18 +335,21 @@
   jpeg_set_defaults(cinfo);
 
 #ifndef NO_GETENV
-  if ((env = getenv("TJ_OPTIMIZE")) != NULL && strlen(env) > 0 &&
-      !strcmp(env, "1"))
+  if (!GETENV_S(env, 7, "TJ_OPTIMIZE") && !strcmp(env, "1"))
     cinfo->optimize_coding = TRUE;
-  if ((env = getenv("TJ_ARITHMETIC")) != NULL && strlen(env) > 0 &&
-      !strcmp(env, "1"))
+  if (!GETENV_S(env, 7, "TJ_ARITHMETIC") && !strcmp(env, "1"))
     cinfo->arith_code = TRUE;
-  if ((env = getenv("TJ_RESTART")) != NULL && strlen(env) > 0) {
+  if (!GETENV_S(env, 7, "TJ_RESTART") && strlen(env) > 0) {
     int temp = -1;
     char tempc = 0;
 
+#ifdef _MSC_VER
+    if (sscanf_s(env, "%d%c", &temp, &tempc, 1) >= 1 && temp >= 0 &&
+        temp <= 65535) {
+#else
     if (sscanf(env, "%d%c", &temp, &tempc) >= 1 && temp >= 0 &&
         temp <= 65535) {
+#endif
       if (toupper(tempc) == 'B') {
         cinfo->restart_interval = temp;
         cinfo->restart_in_rows = 0;
@@ -367,8 +376,7 @@
   if (flags & TJFLAG_PROGRESSIVE)
     jpeg_simple_progression(cinfo);
 #ifndef NO_GETENV
-  else if ((env = getenv("TJ_PROGRESSIVE")) != NULL && strlen(env) > 0 &&
-           !strcmp(env, "1"))
+  else if (!GETENV_S(env, 7, "TJ_PROGRESSIVE") && !strcmp(env, "1"))
     jpeg_simple_progression(cinfo);
 #endif
 
@@ -560,12 +568,12 @@
   tjinstance *this = NULL;
 
   if ((this = (tjinstance *)malloc(sizeof(tjinstance))) == NULL) {
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX,
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX,
              "tjInitCompress(): Memory allocation failure");
     return NULL;
   }
-  MEMZERO(this, sizeof(tjinstance));
-  snprintf(this->errStr, JMSG_LENGTH_MAX, "No error");
+  memset(this, 0, sizeof(tjinstance));
+  SNPRINTF(this->errStr, JMSG_LENGTH_MAX, "No error");
   return _tjInitCompress(this);
 }
 
@@ -719,7 +727,8 @@
                           unsigned char **jpegBuf, unsigned long *jpegSize,
                           int jpegSubsamp, int jpegQual, int flags)
 {
-  int i, retval = 0, alloc = 1;
+  int i, retval = 0;
+  boolean alloc = TRUE;
   JSAMPROW *row_pointer = NULL;
 
   GET_CINSTANCE(handle)
@@ -747,13 +756,13 @@
   cinfo->image_height = height;
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   if (flags & TJFLAG_NOREALLOC) {
-    alloc = 0;  *jpegSize = tjBufSize(width, height, jpegSubsamp);
+    alloc = FALSE;  *jpegSize = tjBufSize(width, height, jpegSubsamp);
   }
   jpeg_mem_dest_tj(cinfo, jpegBuf, jpegSize, alloc);
   setCompDefaults(cinfo, pixelFormat, jpegSubsamp, jpegQual, flags);
@@ -849,9 +858,9 @@
   cinfo->image_height = height;
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   setCompDefaults(cinfo, pixelFormat, subsamp, -1, flags);
@@ -896,7 +905,7 @@
       THROW("tjEncodeYUVPlanes(): Memory allocation failure");
     for (row = 0; row < cinfo->max_v_samp_factor; row++) {
       unsigned char *_tmpbuf_aligned =
-        (unsigned char *)PAD((size_t)_tmpbuf[i], 32);
+        (unsigned char *)PAD((JUINTPTR)_tmpbuf[i], 32);
 
       tmpbuf[i][row] = &_tmpbuf_aligned[
         PAD((compptr->width_in_blocks * cinfo->max_h_samp_factor * DCTSIZE) /
@@ -912,7 +921,7 @@
       THROW("tjEncodeYUVPlanes(): Memory allocation failure");
     for (row = 0; row < compptr->v_samp_factor; row++) {
       unsigned char *_tmpbuf2_aligned =
-        (unsigned char *)PAD((size_t)_tmpbuf2[i], 32);
+        (unsigned char *)PAD((JUINTPTR)_tmpbuf2[i], 32);
 
       tmpbuf2[i][row] =
         &_tmpbuf2_aligned[PAD(compptr->width_in_blocks * DCTSIZE, 32) * row];
@@ -1027,7 +1036,8 @@
                                       unsigned long *jpegSize, int jpegQual,
                                       int flags)
 {
-  int i, row, retval = 0, alloc = 1;
+  int i, row, retval = 0;
+  boolean alloc = TRUE;
   int pw[MAX_COMPONENTS], ph[MAX_COMPONENTS], iw[MAX_COMPONENTS],
     tmpbufsize = 0, usetmpbuf = 0, th[MAX_COMPONENTS];
   JSAMPLE *_tmpbuf = NULL, *ptr;
@@ -1059,13 +1069,13 @@
   cinfo->image_height = height;
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   if (flags & TJFLAG_NOREALLOC) {
-    alloc = 0;  *jpegSize = tjBufSize(width, height, subsamp);
+    alloc = FALSE;  *jpegSize = tjBufSize(width, height, subsamp);
   }
   jpeg_mem_dest_tj(cinfo, jpegBuf, jpegSize, alloc);
   setCompDefaults(cinfo, TJPF_RGB, subsamp, jpegQual, flags);
@@ -1232,12 +1242,12 @@
   tjinstance *this;
 
   if ((this = (tjinstance *)malloc(sizeof(tjinstance))) == NULL) {
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX,
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX,
              "tjInitDecompress(): Memory allocation failure");
     return NULL;
   }
-  MEMZERO(this, sizeof(tjinstance));
-  snprintf(this->errStr, JMSG_LENGTH_MAX, "No error");
+  memset(this, 0, sizeof(tjinstance));
+  SNPRINTF(this->errStr, JMSG_LENGTH_MAX, "No error");
   return _tjInitDecompress(this);
 }
 
@@ -1264,7 +1274,13 @@
   }
 
   jpeg_mem_src_tj(dinfo, jpegBuf, jpegSize);
-  jpeg_read_header(dinfo, TRUE);
+
+  /* jpeg_read_header() calls jpeg_abort() and returns JPEG_HEADER_TABLES_ONLY
+     if the datastream is a tables-only datastream.  Since we aren't using a
+     suspending data source, the only other value it can return is
+     JPEG_HEADER_OK. */
+  if (jpeg_read_header(dinfo, FALSE) == JPEG_HEADER_TABLES_ONLY)
+    return 0;
 
   *width = dinfo->image_width;
   *height = dinfo->image_height;
@@ -1316,7 +1332,7 @@
 DLLEXPORT tjscalingfactor *tjGetScalingFactors(int *numscalingfactors)
 {
   if (numscalingfactors == NULL) {
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX,
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX,
              "tjGetScalingFactors(): Invalid argument");
     return NULL;
   }
@@ -1345,13 +1361,13 @@
     THROW("tjDecompress2(): Invalid argument");
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   if (flags & TJFLAG_LIMITSCANS) {
-    MEMZERO(&progress, sizeof(struct my_progress_mgr));
+    memset(&progress, 0, sizeof(struct my_progress_mgr));
     progress.pub.progress_monitor = my_progress_monitor;
     progress.this = this;
     dinfo->progress = &progress.pub;
@@ -1521,9 +1537,9 @@
   dinfo->image_height = height;
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   dinfo->progressive_mode = dinfo->inputctl->has_multiple_scans = FALSE;
@@ -1575,7 +1591,7 @@
       THROW("tjDecodeYUVPlanes(): Memory allocation failure");
     for (row = 0; row < compptr->v_samp_factor; row++) {
       unsigned char *_tmpbuf_aligned =
-        (unsigned char *)PAD((size_t)_tmpbuf[i], 32);
+        (unsigned char *)PAD((JUINTPTR)_tmpbuf[i], 32);
 
       tmpbuf[i][row] =
         &_tmpbuf_aligned[PAD(compptr->width_in_blocks * DCTSIZE, 32) * row];
@@ -1693,13 +1709,13 @@
     THROW("tjDecompressToYUVPlanes(): Invalid argument");
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   if (flags & TJFLAG_LIMITSCANS) {
-    MEMZERO(&progress, sizeof(struct my_progress_mgr));
+    memset(&progress, 0, sizeof(struct my_progress_mgr));
     progress.pub.progress_monitor = my_progress_monitor;
     progress.this = this;
     dinfo->progress = &progress.pub;
@@ -1923,12 +1939,12 @@
   tjhandle handle = NULL;
 
   if ((this = (tjinstance *)malloc(sizeof(tjinstance))) == NULL) {
-    snprintf(GET_ERRSTR, JMSG_LENGTH_MAX,
+    SNPRINTF(GET_ERRSTR, JMSG_LENGTH_MAX,
              "tjInitTransform(): Memory allocation failure");
     return NULL;
   }
-  MEMZERO(this, sizeof(tjinstance));
-  snprintf(this->errStr, JMSG_LENGTH_MAX, "No error");
+  memset(this, 0, sizeof(tjinstance));
+  SNPRINTF(this->errStr, JMSG_LENGTH_MAX, "No error");
   handle = _tjInitCompress(this);
   if (!handle) return NULL;
   handle = _tjInitDecompress(this);
@@ -1943,7 +1959,8 @@
 {
   jpeg_transform_info *xinfo = NULL;
   jvirt_barray_ptr *srccoefs, *dstcoefs;
-  int retval = 0, alloc = 1, i, jpegSubsamp, saveMarkers = 0;
+  int retval = 0, i, jpegSubsamp, saveMarkers = 0;
+  boolean alloc = TRUE;
   struct my_progress_mgr progress;
 
   GET_INSTANCE(handle);
@@ -1956,13 +1973,13 @@
     THROW("tjTransform(): Invalid argument");
 
 #ifndef NO_PUTENV
-  if (flags & TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
-  else if (flags & TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1");
-  else if (flags & TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+  if (flags & TJFLAG_FORCEMMX) PUTENV_S("JSIMD_FORCEMMX", "1");
+  else if (flags & TJFLAG_FORCESSE) PUTENV_S("JSIMD_FORCESSE", "1");
+  else if (flags & TJFLAG_FORCESSE2) PUTENV_S("JSIMD_FORCESSE2", "1");
 #endif
 
   if (flags & TJFLAG_LIMITSCANS) {
-    MEMZERO(&progress, sizeof(struct my_progress_mgr));
+    memset(&progress, 0, sizeof(struct my_progress_mgr));
     progress.pub.progress_monitor = my_progress_monitor;
     progress.this = this;
     dinfo->progress = &progress.pub;
@@ -1972,7 +1989,7 @@
   if ((xinfo =
        (jpeg_transform_info *)malloc(sizeof(jpeg_transform_info) * n)) == NULL)
     THROW("tjTransform(): Memory allocation failure");
-  MEMZERO(xinfo, sizeof(jpeg_transform_info) * n);
+  memset(xinfo, 0, sizeof(jpeg_transform_info) * n);
 
   if (setjmp(this->jerr.setjmp_buffer)) {
     /* If we get here, the JPEG code has signaled an error. */
@@ -2016,12 +2033,12 @@
       THROW("tjTransform(): Transform is not perfect");
 
     if (xinfo[i].crop) {
-      if ((t[i].r.x % xinfo[i].iMCU_sample_width) != 0 ||
-          (t[i].r.y % xinfo[i].iMCU_sample_height) != 0) {
-        snprintf(this->errStr, JMSG_LENGTH_MAX,
+      if ((t[i].r.x % tjMCUWidth[jpegSubsamp]) != 0 ||
+          (t[i].r.y % tjMCUHeight[jpegSubsamp]) != 0) {
+        SNPRINTF(this->errStr, JMSG_LENGTH_MAX,
                  "To crop this JPEG image, x must be a multiple of %d\n"
                  "and y must be a multiple of %d.\n",
-                 xinfo[i].iMCU_sample_width, xinfo[i].iMCU_sample_height);
+                 tjMCUWidth[jpegSubsamp], tjMCUHeight[jpegSubsamp]);
         this->isInstanceError = TRUE;
         retval = -1;  goto bailout;
       }
@@ -2039,7 +2056,7 @@
       w = xinfo[i].crop_width;  h = xinfo[i].crop_height;
     }
     if (flags & TJFLAG_NOREALLOC) {
-      alloc = 0;  dstSizes[i] = tjBufSize(w, h, jpegSubsamp);
+      alloc = FALSE;  dstSizes[i] = tjBufSize(w, h, jpegSubsamp);
     }
     if (!(t[i].options & TJXOPT_NOOUTPUT))
       jpeg_mem_dest_tj(cinfo, &dstBufs[i], &dstSizes[i], alloc);
@@ -2060,13 +2077,13 @@
 
       for (ci = 0; ci < cinfo->num_components; ci++) {
         jpeg_component_info *compptr = &cinfo->comp_info[ci];
-        tjregion arrayRegion = {
-          0, 0, compptr->width_in_blocks * DCTSIZE, DCTSIZE
-        };
-        tjregion planeRegion = {
-          0, 0, compptr->width_in_blocks * DCTSIZE,
-          compptr->height_in_blocks * DCTSIZE
-        };
+        tjregion arrayRegion = { 0, 0, 0, 0 };
+        tjregion planeRegion = { 0, 0, 0, 0 };
+
+        arrayRegion.w = compptr->width_in_blocks * DCTSIZE;
+        arrayRegion.h = DCTSIZE;
+        planeRegion.w = compptr->width_in_blocks * DCTSIZE;
+        planeRegion.h = compptr->height_in_blocks * DCTSIZE;
 
         for (by = 0; by < compptr->height_in_blocks;
              by += compptr->v_samp_factor) {
@@ -2126,7 +2143,11 @@
   this = (tjinstance *)handle;
   cinfo = &this->cinfo;
 
+#ifdef _MSC_VER
+  if (fopen_s(&file, filename, "rb") || file == NULL)
+#else
   if ((file = fopen(filename, "rb")) == NULL)
+#endif
     THROW_UNIX("tjLoadImage(): Cannot open input file");
 
   if ((tempc = getc(file)) < 0 || ungetc(tempc, file) == EOF)
@@ -2224,7 +2245,11 @@
   this = (tjinstance *)handle;
   dinfo = &this->dinfo;
 
+#ifdef _MSC_VER
+  if (fopen_s(&file, filename, "wb") || file == NULL)
+#else
   if ((file = fopen(filename, "wb")) == NULL)
+#endif
     THROW_UNIX("tjSaveImage(): Cannot open output file");
 
   if (setjmp(this->jerr.setjmp_buffer)) {
diff --git a/third_party/libjpeg-turbo/turbojpeg.h b/third_party/libjpeg-turbo/turbojpeg.h
index a7e120f..106284c 100644
--- a/third_party/libjpeg-turbo/turbojpeg.h
+++ b/third_party/libjpeg-turbo/turbojpeg.h
@@ -673,7 +673,7 @@
  * scalingFactor)</tt>.
  */
 #define TJSCALED(dimension, scalingFactor) \
-  ((dimension * scalingFactor.num + scalingFactor.denom - 1) / \
+  (((dimension) * scalingFactor.num + scalingFactor.denom - 1) / \
    scalingFactor.denom)
 
 
@@ -1129,27 +1129,38 @@
 
 
 /**
- * Retrieve information about a JPEG image without decompressing it.
+ * Retrieve information about a JPEG image without decompressing it, or prime
+ * the decompressor with quantization and Huffman tables.
  *
  * @param handle a handle to a TurboJPEG decompressor or transformer instance
  *
- * @param jpegBuf pointer to a buffer containing a JPEG image
+ * @param jpegBuf pointer to a buffer containing a JPEG image or an
+ * "abbreviated table specification" (AKA "tables-only") datastream.  Passing a
+ * tables-only datastream to this function primes the decompressor with
+ * quantization and Huffman tables that can be used when decompressing
+ * subsequent "abbreviated image" datastreams.  This is useful, for instance,
+ * when decompressing video streams in which all frames share the same
+ * quantization and Huffman tables.
  *
- * @param jpegSize size of the JPEG image (in bytes)
+ * @param jpegSize size of the JPEG image or tables-only datastream (in bytes)
  *
  * @param width pointer to an integer variable that will receive the width (in
- * pixels) of the JPEG image
+ * pixels) of the JPEG image.  If <tt>jpegBuf</tt> points to a tables-only
+ * datastream, then <tt>width</tt> is ignored.
  *
  * @param height pointer to an integer variable that will receive the height
- * (in pixels) of the JPEG image
+ * (in pixels) of the JPEG image.  If <tt>jpegBuf</tt> points to a tables-only
+ * datastream, then <tt>height</tt> is ignored.
  *
  * @param jpegSubsamp pointer to an integer variable that will receive the
  * level of chrominance subsampling used when the JPEG image was compressed
- * (see @ref TJSAMP "Chrominance subsampling options".)
+ * (see @ref TJSAMP "Chrominance subsampling options".)  If <tt>jpegBuf</tt>
+ * points to a tables-only datastream, then <tt>jpegSubsamp</tt> is ignored.
  *
  * @param jpegColorspace pointer to an integer variable that will receive one
  * of the JPEG colorspace constants, indicating the colorspace of the JPEG
- * image (see @ref TJCS "JPEG colorspaces".)
+ * image (see @ref TJCS "JPEG colorspaces".)  If <tt>jpegBuf</tt>
+ * points to a tables-only datastream, then <tt>jpegColorspace</tt> is ignored.
  *
  * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2()
  * and #tjGetErrorCode().)
diff --git a/third_party/libjpeg-turbo/usage.txt b/third_party/libjpeg-turbo/usage.txt
index f7fa3c0..0b6036a 100644
--- a/third_party/libjpeg-turbo/usage.txt
+++ b/third_party/libjpeg-turbo/usage.txt
@@ -25,7 +25,7 @@
 We provide two programs, cjpeg to compress an image file into JPEG format,
 and djpeg to decompress a JPEG file back into a conventional image format.
 
-On Unix-like systems, you say:
+On most systems, you say:
         cjpeg [switches] [imagefile] >jpegfile
 or
         djpeg [switches] [jpegfile]  >imagefile
@@ -34,19 +34,19 @@
 standard error).  These conventions are handy for piping images between
 programs.
 
-On most non-Unix systems, you say:
+If you defined TWO_FILE_COMMANDLINE when compiling the programs, you can
+instead say:
         cjpeg [switches] imagefile jpegfile
 or
         djpeg [switches] jpegfile  imagefile
 i.e., both the input and output files are named on the command line.  This
 style is a little more foolproof, and it loses no functionality if you don't
-have pipes.  (You can get this style on Unix too, if you prefer, by defining
-TWO_FILE_COMMANDLINE when you compile the programs; see install.txt.)
+have pipes.
 
 You can also say:
         cjpeg [switches] -outfile jpegfile  imagefile
 or
-        djpeg [switches] -outfile imagefile  jpegfile
+        djpeg [switches] -outfile imagefile jpegfile
 This syntax works on all systems, so it is useful for scripts.
 
 The currently supported image file formats are: PPM (PBMPLUS color format),
@@ -72,12 +72,9 @@
                           Quality is 0 (worst) to 100 (best); default is 75.
                           (See below for more info.)
 
-        -grayscale      Create monochrome JPEG file from color input.
-                        Be sure to use this switch when compressing a grayscale
-                        BMP or GIF file, because cjpeg isn't bright enough to
-                        notice whether a BMP or GIF file uses only shades of
-                        gray.  By saying -grayscale, you'll get a smaller JPEG
-                        file that takes less time to process.
+        -grayscale      Create monochrome JPEG file from color input.  By
+                        saying -grayscale, you'll get a smaller JPEG file that
+                        takes less time to process.
 
         -rgb            Create RGB JPEG file.
                         Using this switch suppresses the conversion from RGB
@@ -146,8 +143,8 @@
 assigned to components with the -qslots option (see the "wizard" switches
 below.)
 
-JPEG  files  generated  with separate luminance and chrominance quality are
-fully compliant with standard JPEG decoders.
+JPEG files generated with separate luminance and chrominance quality are fully
+compliant with standard JPEG decoders.
 
 CAUTION: For this setting to be useful, be sure to pass an argument of
 -sample 1x1 to cjpeg to disable chrominance subsampling.  Otherwise, the
@@ -221,7 +218,7 @@
                         space is needed, an error will occur.
 
         -verbose        Enable debug printout.  More -v's give more printout.
-        or  -debug      Also, version information is printed at startup.
+        or -debug       Also, version information is printed at startup.
 
 The -restart option inserts extra markers that allow a JPEG decoder to
 resynchronize after a transmission error.  Without restart markers, any damage
@@ -470,9 +467,10 @@
 can be removed.  See the -copy option for specifics.
 
 jpegtran uses a command line syntax similar to cjpeg or djpeg.
-On Unix-like systems, you say:
+On most systems, you say:
         jpegtran [switches] [inputfile] >outputfile
-On most non-Unix systems, you say:
+If you defined TWO_FILE_COMMANDLINE when compiling the program, you can instead
+say:
         jpegtran [switches] inputfile outputfile
 where both the input and output files are JPEG files.
 
@@ -601,6 +599,9 @@
         -copy comments  Copy only comment markers.  This setting copies
                         comments from the source file but discards any other
                         metadata.
+        -copy icc       Copy only ICC profile markers.  This setting copies the
+                        ICC profile from the source file but discards any other
+                        metadata.
         -copy all       Copy all extra markers.  This setting preserves
                         miscellaneous markers found in the source file, such
                         as JFIF thumbnails, Exif data, and Photoshop settings.
@@ -649,13 +650,13 @@
 file by directing wrjpgcom's output back into it; on most systems this will
 just destroy your file.
 
-The command line syntax for wrjpgcom is similar to cjpeg's.  On Unix-like
-systems, it is
+The command line syntax for wrjpgcom is similar to cjpeg's.  On most systems,
+it is
         wrjpgcom [switches] [inputfilename]
 The output file is written to standard output.  The input file comes from
 the named file, or from standard input if no input file is named.
 
-On most non-Unix systems, the syntax is
+If you defined TWO_FILE_COMMANDLINE when compiling the program, the syntax is:
         wrjpgcom [switches] inputfilename outputfilename
 where both input and output file names must be given explicitly.
 
diff --git a/third_party/libjpeg-turbo/wrbmp.c b/third_party/libjpeg-turbo/wrbmp.c
index 408a722..45fff68 100644
--- a/third_party/libjpeg-turbo/wrbmp.c
+++ b/third_party/libjpeg-turbo/wrbmp.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1994-1996, Thomas G. Lane.
  * libjpeg-turbo Modifications:
  * Copyright (C) 2013, Linaro Limited.
- * Copyright (C) 2014-2015, 2017, 2019, D. R. Commander.
+ * Copyright (C) 2014-2015, 2017, 2019, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -121,7 +121,7 @@
   inptr = dest->pub.buffer[0];
 
   if (cinfo->out_color_space == JCS_EXT_BGR) {
-    MEMCOPY(outptr, inptr, dest->row_width);
+    memcpy(outptr, inptr, dest->row_width);
     outptr += cinfo->output_width * 3;
   } else if (cinfo->out_color_space == JCS_RGB565) {
     boolean big_endian = is_big_endian();
@@ -165,7 +165,7 @@
     *outptr++ = 0;
 
   if (!dest->use_inversion_array)
-    (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->row_width);
+    fwrite(dest->iobuffer, 1, dest->row_width, dest->pub.output_file);
 }
 
 METHODDEF(void)
@@ -191,7 +191,7 @@
 
   /* Transfer data. */
   inptr = dest->pub.buffer[0];
-  MEMCOPY(outptr, inptr, cinfo->output_width);
+  memcpy(outptr, inptr, cinfo->output_width);
   outptr += cinfo->output_width;
 
   /* Zero out the pad bytes. */
@@ -200,7 +200,7 @@
     *outptr++ = 0;
 
   if (!dest->use_inversion_array)
-    (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->row_width);
+    fwrite(dest->iobuffer, 1, dest->row_width, dest->pub.output_file);
 }
 
 
@@ -256,8 +256,8 @@
   bfSize = headersize + (long)dest->row_width * (long)cinfo->output_height;
 
   /* Set unused fields of header to 0 */
-  MEMZERO(bmpfileheader, sizeof(bmpfileheader));
-  MEMZERO(bmpinfoheader, sizeof(bmpinfoheader));
+  memset(bmpfileheader, 0, sizeof(bmpfileheader));
+  memset(bmpinfoheader, 0, sizeof(bmpinfoheader));
 
   /* Fill the file header */
   bmpfileheader[0] = 0x42;      /* first 2 bytes are ASCII 'B', 'M' */
@@ -281,9 +281,9 @@
   PUT_2B(bmpinfoheader, 32, cmap_entries); /* biClrUsed */
   /* we leave biClrImportant = 0 */
 
-  if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t)14)
+  if (fwrite(bmpfileheader, 1, 14, dest->pub.output_file) != (size_t)14)
     ERREXIT(cinfo, JERR_FILE_WRITE);
-  if (JFWRITE(dest->pub.output_file, bmpinfoheader, 40) != (size_t)40)
+  if (fwrite(bmpinfoheader, 1, 40, dest->pub.output_file) != (size_t)40)
     ERREXIT(cinfo, JERR_FILE_WRITE);
 
   if (cmap_entries > 0)
@@ -325,8 +325,8 @@
   bfSize = headersize + (long)dest->row_width * (long)cinfo->output_height;
 
   /* Set unused fields of header to 0 */
-  MEMZERO(bmpfileheader, sizeof(bmpfileheader));
-  MEMZERO(bmpcoreheader, sizeof(bmpcoreheader));
+  memset(bmpfileheader, 0, sizeof(bmpfileheader));
+  memset(bmpcoreheader, 0, sizeof(bmpcoreheader));
 
   /* Fill the file header */
   bmpfileheader[0] = 0x42;      /* first 2 bytes are ASCII 'B', 'M' */
@@ -342,9 +342,9 @@
   PUT_2B(bmpcoreheader, 8, 1);  /* bcPlanes - must be 1 */
   PUT_2B(bmpcoreheader, 10, bits_per_pixel); /* bcBitCount */
 
-  if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t)14)
+  if (fwrite(bmpfileheader, 1, 14, dest->pub.output_file) != (size_t)14)
     ERREXIT(cinfo, JERR_FILE_WRITE);
-  if (JFWRITE(dest->pub.output_file, bmpcoreheader, 12) != (size_t)12)
+  if (fwrite(bmpcoreheader, 1, 12, dest->pub.output_file) != (size_t)12)
     ERREXIT(cinfo, JERR_FILE_WRITE);
 
   if (cmap_entries > 0)
@@ -456,7 +456,7 @@
         ((j_common_ptr)cinfo, dest->whole_image, row - 1, (JDIMENSION)1,
          FALSE);
       data_ptr = image_ptr[0];
-      (void)JFWRITE(outfile, data_ptr, dest->row_width);
+      fwrite(data_ptr, 1, dest->row_width, outfile);
     }
     if (progress != NULL)
       progress->completed_extra_passes++;
diff --git a/third_party/libjpeg-turbo/wrgif.c b/third_party/libjpeg-turbo/wrgif.c
index 82a2429..620a3ba 100644
--- a/third_party/libjpeg-turbo/wrgif.c
+++ b/third_party/libjpeg-turbo/wrgif.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2015-2019 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015, 2017, D. R. Commander.
+ * Copyright (C) 2015, 2017, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -114,8 +114,8 @@
 {
   if (dinfo->bytesinpkt > 0) {  /* never write zero-length packet */
     dinfo->packetbuf[0] = (char)dinfo->bytesinpkt++;
-    if (JFWRITE(dinfo->pub.output_file, dinfo->packetbuf, dinfo->bytesinpkt) !=
-        (size_t)dinfo->bytesinpkt)
+    if (fwrite(dinfo->packetbuf, 1, dinfo->bytesinpkt,
+               dinfo->pub.output_file) != (size_t)dinfo->bytesinpkt)
       ERREXIT(dinfo->cinfo, JERR_FILE_WRITE);
     dinfo->bytesinpkt = 0;
   }
@@ -169,7 +169,7 @@
 /* Fill the hash table with empty entries */
 {
   /* It's sufficient to zero hash_code[] */
-  MEMZERO(dinfo->hash_code, HSIZE * sizeof(code_int));
+  memset(dinfo->hash_code, 0, HSIZE * sizeof(code_int));
 }
 
 
diff --git a/third_party/libjpeg-turbo/wrjpgcom.1 b/third_party/libjpeg-turbo/wrjpgcom.1
deleted file mode 100644
index a255cab..0000000
--- a/third_party/libjpeg-turbo/wrjpgcom.1
+++ /dev/null
@@ -1,103 +0,0 @@
-.TH WRJPGCOM 1 "15 June 1995"
-.SH NAME
-wrjpgcom \- insert text comments into a JPEG file
-.SH SYNOPSIS
-.B wrjpgcom
-[
-.B \-replace
-]
-[
-.BI \-comment " text"
-]
-[
-.BI \-cfile " name"
-]
-[
-.I filename
-]
-.LP
-.SH DESCRIPTION
-.LP
-.B wrjpgcom
-reads the named JPEG/JFIF file, or the standard input if no file is named,
-and generates a new JPEG/JFIF file on standard output.  A comment block is
-added to the file.
-.PP
-The JPEG standard allows "comment" (COM) blocks to occur within a JPEG file.
-Although the standard doesn't actually define what COM blocks are for, they
-are widely used to hold user-supplied text strings.  This lets you add
-annotations, titles, index terms, etc to your JPEG files, and later retrieve
-them as text.  COM blocks do not interfere with the image stored in the JPEG
-file.  The maximum size of a COM block is 64K, but you can have as many of
-them as you like in one JPEG file.
-.PP
-.B wrjpgcom
-adds a COM block, containing text you provide, to a JPEG file.
-Ordinarily, the COM block is added after any existing COM blocks; but you
-can delete the old COM blocks if you wish.
-.SH OPTIONS
-Switch names may be abbreviated, and are not case sensitive.
-.TP
-.B \-replace
-Delete any existing COM blocks from the file.
-.TP
-.BI \-comment " text"
-Supply text for new COM block on command line.
-.TP
-.BI \-cfile " name"
-Read text for new COM block from named file.
-.PP
-If you have only one line of comment text to add, you can provide it on the
-command line with
-.BR \-comment .
-The comment text must be surrounded with quotes so that it is treated as a
-single argument.  Longer comments can be read from a text file.
-.PP
-If you give neither
-.B \-comment
-nor
-.BR \-cfile,
-then
-.B wrjpgcom
-will read the comment text from standard input.  (In this case an input image
-file name MUST be supplied, so that the source JPEG file comes from somewhere
-else.)  You can enter multiple lines, up to 64KB worth.  Type an end-of-file
-indicator (usually control-D) to terminate the comment text entry.
-.PP
-.B wrjpgcom
-will not add a COM block if the provided comment string is empty.  Therefore
-\fB\-replace \-comment ""\fR can be used to delete all COM blocks from a file.
-.SH EXAMPLES
-.LP
-Add a short comment to in.jpg, producing out.jpg:
-.IP
-.B wrjpgcom \-c
-\fI"View of my back yard" in.jpg
-.B >
-.I out.jpg
-.PP
-Attach a long comment previously stored in comment.txt:
-.IP
-.B wrjpgcom
-.I in.jpg
-.B <
-.I comment.txt
-.B >
-.I out.jpg
-.PP
-or equivalently
-.IP
-.B wrjpgcom
-.B -cfile
-.I comment.txt
-.B <
-.I in.jpg
-.B >
-.I out.jpg
-.SH SEE ALSO
-.BR cjpeg (1),
-.BR djpeg (1),
-.BR jpegtran (1),
-.BR rdjpgcom (1)
-.SH AUTHOR
-Independent JPEG Group
diff --git a/third_party/libjpeg-turbo/wrjpgcom.c b/third_party/libjpeg-turbo/wrjpgcom.c
index 8a4e741..060925f 100644
--- a/third_party/libjpeg-turbo/wrjpgcom.c
+++ b/third_party/libjpeg-turbo/wrjpgcom.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2014, D. R. Commander.
+ * Copyright (C) 2014, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -14,12 +14,13 @@
  * JPEG markers.
  */
 
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
 #define JPEG_CJPEG_DJPEG        /* to get the command-line config symbols */
 #include "jinclude.h"           /* get auto-config symbols, <stdio.h> */
 
-#ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc() */
-extern void *malloc();
-#endif
 #include <ctype.h>              /* to declare isupper(), tolower() */
 #ifdef USE_SETMODE
 #include <fcntl.h>              /* to declare setmode()'s parameter macros */
@@ -27,16 +28,6 @@
 #include <io.h>                 /* to declare setmode() */
 #endif
 
-#ifdef USE_CCOMMAND             /* command-line reader for Macintosh */
-#ifdef __MWERKS__
-#include <SIOUX.h>              /* Metrowerks needs this */
-#include <console.h>            /* ... and this */
-#endif
-#ifdef THINK_C
-#include <console.h>            /* Think declares it here */
-#endif
-#endif
-
 #ifdef DONT_USE_B_MODE          /* define mode parameters for fopen() */
 #define READ_BINARY     "r"
 #define WRITE_BINARY    "w"
@@ -414,11 +405,6 @@
   unsigned int comment_length = 0;
   int marker;
 
-  /* On Mac, fetch a command line. */
-#ifdef USE_CCOMMAND
-  argc = ccommand(&argv);
-#endif
-
   progname = argv[0];
   if (progname == NULL || progname[0] == 0)
     progname = "wrjpgcom";      /* in case C library doesn't provide it */
diff --git a/third_party/libjpeg-turbo/wrppm.c b/third_party/libjpeg-turbo/wrppm.c
index 3081ec3..57c8aaf 100644
--- a/third_party/libjpeg-turbo/wrppm.c
+++ b/third_party/libjpeg-turbo/wrppm.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1996, Thomas G. Lane.
  * Modified 2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2017, 2019-2020, D. R. Commander.
+ * Copyright (C) 2017, 2019-2020, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -92,7 +92,7 @@
 {
   ppm_dest_ptr dest = (ppm_dest_ptr)dinfo;
 
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
@@ -115,13 +115,13 @@
   ptr = dest->pub.buffer[0];
   bufferptr = dest->iobuffer;
 #if BITS_IN_JSAMPLE == 8
-  MEMCOPY(bufferptr, ptr, dest->samples_per_row);
+  memcpy(bufferptr, ptr, dest->samples_per_row);
 #else
   for (col = dest->samples_per_row; col > 0; col--) {
     PUTPPMSAMPLE(bufferptr, *ptr++);
   }
 #endif
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
@@ -149,7 +149,7 @@
     PUTPPMSAMPLE(bufferptr, ptr[bindex]);
     ptr += ps;
   }
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
@@ -175,7 +175,7 @@
     PUTPPMSAMPLE(bufferptr, g);
     PUTPPMSAMPLE(bufferptr, b);
   }
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
@@ -205,7 +205,7 @@
     PUTPPMSAMPLE(bufferptr, color_map1[pixval]);
     PUTPPMSAMPLE(bufferptr, color_map2[pixval]);
   }
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
@@ -224,7 +224,7 @@
   for (col = cinfo->output_width; col > 0; col--) {
     PUTPPMSAMPLE(bufferptr, color_map[*ptr++]);
   }
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
diff --git a/third_party/libjpeg-turbo/wrtarga.c b/third_party/libjpeg-turbo/wrtarga.c
index 7a654ff..67ca1f0 100644
--- a/third_party/libjpeg-turbo/wrtarga.c
+++ b/third_party/libjpeg-turbo/wrtarga.c
@@ -4,7 +4,7 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1996, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2017, 2019, D. R. Commander.
+ * Copyright (C) 2017, 2019, 2022, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -51,7 +51,7 @@
   char targaheader[18];
 
   /* Set unused fields of header to 0 */
-  MEMZERO(targaheader, sizeof(targaheader));
+  memset(targaheader, 0, sizeof(targaheader));
 
   if (num_colors > 0) {
     targaheader[1] = 1;         /* color map type 1 */
@@ -79,7 +79,7 @@
     }
   }
 
-  if (JFWRITE(dinfo->output_file, targaheader, 18) != (size_t)18)
+  if (fwrite(targaheader, 1, 18, dinfo->output_file) != (size_t)18)
     ERREXIT(cinfo, JERR_FILE_WRITE);
 }
 
@@ -107,7 +107,7 @@
     outptr[2] = inptr[0];
     inptr += 3, outptr += 3;
   }
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 METHODDEF(void)
@@ -121,8 +121,8 @@
 
   inptr = dest->pub.buffer[0];
   outptr = dest->iobuffer;
-  MEMCOPY(outptr, inptr, cinfo->output_width);
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  memcpy(outptr, inptr, cinfo->output_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
@@ -146,7 +146,7 @@
   for (col = cinfo->output_width; col > 0; col--) {
     *outptr++ = color_map0[*inptr++];
   }
-  (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->buffer_width);
+  fwrite(dest->iobuffer, 1, dest->buffer_width, dest->pub.output_file);
 }
 
 
diff --git a/third_party/libjpeg/BUILD.gn b/third_party/libjpeg/BUILD.gn
deleted file mode 100644
index 928ae35..0000000
--- a/third_party/libjpeg/BUILD.gn
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright 2021 The Cobalt Authors. 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.
-
-static_library("libjpeg") {
-  # We never use system libjpeg, so just make the non-system target.
-  sources = [
-    "jcapimin.c",
-    "jcapistd.c",
-    "jccoefct.c",
-    "jccolor.c",
-    "jcdctmgr.c",
-    "jchuff.c",
-    "jchuff.h",
-    "jcinit.c",
-    "jcmainct.c",
-    "jcmarker.c",
-    "jcmaster.c",
-    "jcomapi.c",
-    "jconfig.h",
-    "jcparam.c",
-    "jcphuff.c",
-    "jcprepct.c",
-    "jcsample.c",
-    "jdapimin.c",
-    "jdapistd.c",
-    "jdatadst.c",
-    "jdatasrc.c",
-    "jdcoefct.c",
-    "jdcolor.c",
-    "jdct.h",
-    "jddctmgr.c",
-    "jdhuff.c",
-    "jdhuff.h",
-    "jdinput.c",
-    "jdmainct.c",
-    "jdmarker.c",
-    "jdmaster.c",
-    "jdmerge.c",
-    "jdphuff.c",
-    "jdpostct.c",
-    "jdsample.c",
-    "jerror.c",
-    "jerror.h",
-    "jfdctflt.c",
-    "jfdctfst.c",
-    "jfdctint.c",
-    "jidctflt.c",
-    "jidctfst.c",
-    "jidctint.c",
-    "jinclude.h",
-    "jmemmgr.c",
-    "jmemnobs.c",
-    "jmemsys.h",
-    "jmorecfg.h",
-    "jpegint.h",
-    "jpeglib.h",
-    "jquant1.c",
-    "jquant2.c",
-    "jutils.c",
-    "jversion.h",
-  ]
-
-  if (is_starboard) {
-    sources -= [
-      # These are for decoding directly off of disk. They aren't
-      # currently used, and we probably should always be decoding from
-      # memory.
-      "jdatadst.c",
-      "jdatasrc.c",
-    ]
-    configs -= [ "//starboard/build/config:size" ]
-    configs += [ "//starboard/build/config:speed" ]
-  }
-
-  if (!is_win) {
-    output_name = "jpeg"
-  }
-
-  public_configs = [ ":libjpeg_config" ]
-
-  if (is_clang) {
-    cflags = [ "-Wno-format-security" ]
-  }
-
-  deps = [
-    "//starboard:starboard_headers_only",
-    "//starboard/common",
-  ]
-}
-
-config("libjpeg_config") {
-  include_dirs = [ "." ]
-}
diff --git a/third_party/libjpeg/LICENSE b/third_party/libjpeg/LICENSE
deleted file mode 100644
index 096becc..0000000
--- a/third_party/libjpeg/LICENSE
+++ /dev/null
@@ -1,75 +0,0 @@
-(Copied from the README.)
-
---------------------------------------------------------------------------------
-
-The authors make NO WARRANTY or representation, either express or implied,
-with respect to this software, its quality, accuracy, merchantability, or
-fitness for a particular purpose.  This software is provided "AS IS", and you,
-its user, assume the entire risk as to its quality and accuracy.
-
-This software is copyright (C) 1991-1998, Thomas G. Lane.
-All Rights Reserved except as specified below.
-
-Permission is hereby granted to use, copy, modify, and distribute this
-software (or portions thereof) for any purpose, without fee, subject to these
-conditions:
-(1) If any part of the source code for this software is distributed, then this
-README file must be included, with this copyright and no-warranty notice
-unaltered; and any additions, deletions, or changes to the original files
-must be clearly indicated in accompanying documentation.
-(2) If only executable code is distributed, then the accompanying
-documentation must state that "this software is based in part on the work of
-the Independent JPEG Group".
-(3) Permission for use of this software is granted only if the user accepts
-full responsibility for any undesirable consequences; the authors accept
-NO LIABILITY for damages of any kind.
-
-These conditions apply to any software derived from or based on the IJG code,
-not just to the unmodified library.  If you use our work, you ought to
-acknowledge us.
-
-Permission is NOT granted for the use of any IJG author's name or company name
-in advertising or publicity relating to this software or products derived from
-it.  This software may be referred to only as "the Independent JPEG Group's
-software".
-
-We specifically permit and encourage the use of this software as the basis of
-commercial products, provided that all warranty or liability claims are
-assumed by the product vendor.
-
-
-ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
-sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
-ansi2knr.c is NOT covered by the above copyright and conditions, but instead
-by the usual distribution terms of the Free Software Foundation; principally,
-that you must include source code if you redistribute it.  (See the file
-ansi2knr.c for full details.)  However, since ansi2knr.c is not needed as part
-of any program generated from the IJG code, this does not limit you more than
-the foregoing paragraphs do.
-
-The Unix configuration script "configure" was produced with GNU Autoconf.
-It is copyright by the Free Software Foundation but is freely distributable.
-The same holds for its supporting scripts (config.guess, config.sub,
-ltconfig, ltmain.sh).  Another support script, install-sh, is copyright
-by M.I.T. but is also freely distributable.
-
-It appears that the arithmetic coding option of the JPEG spec is covered by
-patents owned by IBM, AT&T, and Mitsubishi.  Hence arithmetic coding cannot
-legally be used without obtaining one or more licenses.  For this reason,
-support for arithmetic coding has been removed from the free JPEG software.
-(Since arithmetic coding provides only a marginal gain over the unpatented
-Huffman mode, it is unlikely that very many implementations will support it.)
-So far as we are aware, there are no patent restrictions on the remaining
-code.
-
-The IJG distribution formerly included code to read and write GIF files.
-To avoid entanglement with the Unisys LZW patent, GIF reading support has
-been removed altogether, and the GIF writer has been simplified to produce
-"uncompressed GIFs".  This technique does not use the LZW algorithm; the
-resulting GIF files are larger than usual, but are readable by all standard
-GIF decoders.
-
-We are required to state that
-    "The Graphics Interchange Format(c) is the Copyright property of
-    CompuServe Incorporated.  GIF(sm) is a Service Mark property of
-    CompuServe Incorporated."
diff --git a/third_party/libjpeg/METADATA b/third_party/libjpeg/METADATA
deleted file mode 100644
index c616de0..0000000
--- a/third_party/libjpeg/METADATA
+++ /dev/null
@@ -1,18 +0,0 @@
-name: "libjpeg"
-description:
-  "JPG decoder, used via Cobalt JPEG Image decoder and SbImageDecode SB API"
-
-third_party {
-  url {
-    type: ARCHIVE
-    value: "https://www.ijg.org/files/jpegsrc.v9b.tar.gz"
-  }
-  # NOTE: Listed version is 9B from 1998 ( ! ). Chromium changes on top
-  version: "v9b"
-  last_upgrade_date {
-    year: 1998
-    month: 03
-    day: 27
-  }
-  license_type: NOTICE
-}
diff --git a/third_party/libjpeg/README b/third_party/libjpeg/README
deleted file mode 100644
index 86cc206..0000000
--- a/third_party/libjpeg/README
+++ /dev/null
@@ -1,385 +0,0 @@
-The Independent JPEG Group's JPEG software
-==========================================
-
-README for release 6b of 27-Mar-1998
-====================================
-
-This distribution contains the sixth public release of the Independent JPEG
-Group's free JPEG software.  You are welcome to redistribute this software and
-to use it for any purpose, subject to the conditions under LEGAL ISSUES, below.
-
-Serious users of this software (particularly those incorporating it into
-larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to
-our electronic mailing list.  Mailing list members are notified of updates
-and have a chance to participate in technical discussions, etc.
-
-This software is the work of Tom Lane, Philip Gladstone, Jim Boucher,
-Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi,
-Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG
-Group.
-
-IJG is not affiliated with the official ISO JPEG standards committee.
-
-
-DOCUMENTATION ROADMAP
-=====================
-
-This file contains the following sections:
-
-OVERVIEW            General description of JPEG and the IJG software.
-LEGAL ISSUES        Copyright, lack of warranty, terms of distribution.
-REFERENCES          Where to learn more about JPEG.
-ARCHIVE LOCATIONS   Where to find newer versions of this software.
-RELATED SOFTWARE    Other stuff you should get.
-FILE FORMAT WARS    Software *not* to get.
-TO DO               Plans for future IJG releases.
-
-Other documentation files in the distribution are:
-
-User documentation:
-  install.doc       How to configure and install the IJG software.
-  usage.doc         Usage instructions for cjpeg, djpeg, jpegtran,
-                    rdjpgcom, and wrjpgcom.
-  *.1               Unix-style man pages for programs (same info as usage.doc).
-  wizard.doc        Advanced usage instructions for JPEG wizards only.
-  change.log        Version-to-version change highlights.
-Programmer and internal documentation:
-  libjpeg.doc       How to use the JPEG library in your own programs.
-  example.c         Sample code for calling the JPEG library.
-  structure.doc     Overview of the JPEG library's internal structure.
-  filelist.doc      Road map of IJG files.
-  coderules.doc     Coding style rules --- please read if you contribute code.
-
-Please read at least the files install.doc and usage.doc.  Useful information
-can also be found in the JPEG FAQ (Frequently Asked Questions) article.  See
-ARCHIVE LOCATIONS below to find out where to obtain the FAQ article.
-
-If you want to understand how the JPEG code works, we suggest reading one or
-more of the REFERENCES, then looking at the documentation files (in roughly
-the order listed) before diving into the code.
-
-
-OVERVIEW
-========
-
-This package contains C software to implement JPEG image compression and
-decompression.  JPEG (pronounced "jay-peg") is a standardized compression
-method for full-color and gray-scale images.  JPEG is intended for compressing
-"real-world" scenes; line drawings, cartoons and other non-realistic images
-are not its strong suit.  JPEG is lossy, meaning that the output image is not
-exactly identical to the input image.  Hence you must not use JPEG if you
-have to have identical output bits.  However, on typical photographic images,
-very good compression levels can be obtained with no visible change, and
-remarkably high compression levels are possible if you can tolerate a
-low-quality image.  For more details, see the references, or just experiment
-with various compression settings.
-
-This software implements JPEG baseline, extended-sequential, and progressive
-compression processes.  Provision is made for supporting all variants of these
-processes, although some uncommon parameter settings aren't implemented yet.
-For legal reasons, we are not distributing code for the arithmetic-coding
-variants of JPEG; see LEGAL ISSUES.  We have made no provision for supporting
-the hierarchical or lossless processes defined in the standard.
-
-We provide a set of library routines for reading and writing JPEG image files,
-plus two sample applications "cjpeg" and "djpeg", which use the library to
-perform conversion between JPEG and some other popular image file formats.
-The library is intended to be reused in other applications.
-
-In order to support file conversion and viewing software, we have included
-considerable functionality beyond the bare JPEG coding/decoding capability;
-for example, the color quantization modules are not strictly part of JPEG
-decoding, but they are essential for output to colormapped file formats or
-colormapped displays.  These extra functions can be compiled out of the
-library if not required for a particular application.  We have also included
-"jpegtran", a utility for lossless transcoding between different JPEG
-processes, and "rdjpgcom" and "wrjpgcom", two simple applications for
-inserting and extracting textual comments in JFIF files.
-
-The emphasis in designing this software has been on achieving portability and
-flexibility, while also making it fast enough to be useful.  In particular,
-the software is not intended to be read as a tutorial on JPEG.  (See the
-REFERENCES section for introductory material.)  Rather, it is intended to
-be reliable, portable, industrial-strength code.  We do not claim to have
-achieved that goal in every aspect of the software, but we strive for it.
-
-We welcome the use of this software as a component of commercial products.
-No royalty is required, but we do ask for an acknowledgement in product
-documentation, as described under LEGAL ISSUES.
-
-
-LEGAL ISSUES
-============
-
-In plain English:
-
-1. We don't promise that this software works.  (But if you find any bugs,
-   please let us know!)
-2. You can use this software for whatever you want.  You don't have to pay us.
-3. You may not pretend that you wrote this software.  If you use it in a
-   program, you must acknowledge somewhere in your documentation that
-   you've used the IJG code.
-
-In legalese:
-
-The authors make NO WARRANTY or representation, either express or implied,
-with respect to this software, its quality, accuracy, merchantability, or
-fitness for a particular purpose.  This software is provided "AS IS", and you,
-its user, assume the entire risk as to its quality and accuracy.
-
-This software is copyright (C) 1991-1998, Thomas G. Lane.
-All Rights Reserved except as specified below.
-
-Permission is hereby granted to use, copy, modify, and distribute this
-software (or portions thereof) for any purpose, without fee, subject to these
-conditions:
-(1) If any part of the source code for this software is distributed, then this
-README file must be included, with this copyright and no-warranty notice
-unaltered; and any additions, deletions, or changes to the original files
-must be clearly indicated in accompanying documentation.
-(2) If only executable code is distributed, then the accompanying
-documentation must state that "this software is based in part on the work of
-the Independent JPEG Group".
-(3) Permission for use of this software is granted only if the user accepts
-full responsibility for any undesirable consequences; the authors accept
-NO LIABILITY for damages of any kind.
-
-These conditions apply to any software derived from or based on the IJG code,
-not just to the unmodified library.  If you use our work, you ought to
-acknowledge us.
-
-Permission is NOT granted for the use of any IJG author's name or company name
-in advertising or publicity relating to this software or products derived from
-it.  This software may be referred to only as "the Independent JPEG Group's
-software".
-
-We specifically permit and encourage the use of this software as the basis of
-commercial products, provided that all warranty or liability claims are
-assumed by the product vendor.
-
-
-ansi2knr.c is included in this distribution by permission of L. Peter Deutsch,
-sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA.
-ansi2knr.c is NOT covered by the above copyright and conditions, but instead
-by the usual distribution terms of the Free Software Foundation; principally,
-that you must include source code if you redistribute it.  (See the file
-ansi2knr.c for full details.)  However, since ansi2knr.c is not needed as part
-of any program generated from the IJG code, this does not limit you more than
-the foregoing paragraphs do.
-
-The Unix configuration script "configure" was produced with GNU Autoconf.
-It is copyright by the Free Software Foundation but is freely distributable.
-The same holds for its supporting scripts (config.guess, config.sub,
-ltconfig, ltmain.sh).  Another support script, install-sh, is copyright
-by M.I.T. but is also freely distributable.
-
-It appears that the arithmetic coding option of the JPEG spec is covered by
-patents owned by IBM, AT&T, and Mitsubishi.  Hence arithmetic coding cannot
-legally be used without obtaining one or more licenses.  For this reason,
-support for arithmetic coding has been removed from the free JPEG software.
-(Since arithmetic coding provides only a marginal gain over the unpatented
-Huffman mode, it is unlikely that very many implementations will support it.)
-So far as we are aware, there are no patent restrictions on the remaining
-code.
-
-The IJG distribution formerly included code to read and write GIF files.
-To avoid entanglement with the Unisys LZW patent, GIF reading support has
-been removed altogether, and the GIF writer has been simplified to produce
-"uncompressed GIFs".  This technique does not use the LZW algorithm; the
-resulting GIF files are larger than usual, but are readable by all standard
-GIF decoders.
-
-We are required to state that
-    "The Graphics Interchange Format(c) is the Copyright property of
-    CompuServe Incorporated.  GIF(sm) is a Service Mark property of
-    CompuServe Incorporated."
-
-
-REFERENCES
-==========
-
-We highly recommend reading one or more of these references before trying to
-understand the innards of the JPEG software.
-
-The best short technical introduction to the JPEG compression algorithm is
-	Wallace, Gregory K.  "The JPEG Still Picture Compression Standard",
-	Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44.
-(Adjacent articles in that issue discuss MPEG motion picture compression,
-applications of JPEG, and related topics.)  If you don't have the CACM issue
-handy, a PostScript file containing a revised version of Wallace's article is
-available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz.  The file (actually
-a preprint for an article that appeared in IEEE Trans. Consumer Electronics)
-omits the sample images that appeared in CACM, but it includes corrections
-and some added material.  Note: the Wallace article is copyright ACM and IEEE,
-and it may not be used for commercial purposes.
-
-A somewhat less technical, more leisurely introduction to JPEG can be found in
-"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by
-M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1.  This book provides
-good explanations and example C code for a multitude of compression methods
-including JPEG.  It is an excellent source if you are comfortable reading C
-code but don't know much about data compression in general.  The book's JPEG
-sample code is far from industrial-strength, but when you are ready to look
-at a full implementation, you've got one here...
-
-The best full description of JPEG is the textbook "JPEG Still Image Data
-Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published
-by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1.  Price US$59.95, 638 pp.
-The book includes the complete text of the ISO JPEG standards (DIS 10918-1
-and draft DIS 10918-2).  This is by far the most complete exposition of JPEG
-in existence, and we highly recommend it.
-
-The JPEG standard itself is not available electronically; you must order a
-paper copy through ISO or ITU.  (Unless you feel a need to own a certified
-official copy, we recommend buying the Pennebaker and Mitchell book instead;
-it's much cheaper and includes a great deal of useful explanatory material.)
-In the USA, copies of the standard may be ordered from ANSI Sales at (212)
-642-4900, or from Global Engineering Documents at (800) 854-7179.  (ANSI
-doesn't take credit card orders, but Global does.)  It's not cheap: as of
-1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7%
-shipping/handling.  The standard is divided into two parts, Part 1 being the
-actual specification, while Part 2 covers compliance testing methods.  Part 1
-is titled "Digital Compression and Coding of Continuous-tone Still Images,
-Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS
-10918-1, ITU-T T.81.  Part 2 is titled "Digital Compression and Coding of
-Continuous-tone Still Images, Part 2: Compliance testing" and has document
-numbers ISO/IEC IS 10918-2, ITU-T T.83.
-
-Some extensions to the original JPEG standard are defined in JPEG Part 3,
-a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84.  IJG
-currently does not support any Part 3 extensions.
-
-The JPEG standard does not specify all details of an interchangeable file
-format.  For the omitted details we follow the "JFIF" conventions, revision
-1.02.  A copy of the JFIF spec is available from:
-	Literature Department
-	C-Cube Microsystems, Inc.
-	1778 McCarthy Blvd.
-	Milpitas, CA 95035
-	phone (408) 944-6300,  fax (408) 944-6314
-A PostScript version of this document is available by FTP at
-ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz.  There is also a plain text
-version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing
-the figures.
-
-The TIFF 6.0 file format specification can be obtained by FTP from
-ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz.  The JPEG incorporation scheme
-found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems.
-IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6).
-Instead, we recommend the JPEG design proposed by TIFF Technical Note #2
-(Compression tag 7).  Copies of this Note can be obtained from ftp.sgi.com or
-from ftp://ftp.uu.net/graphics/jpeg/.  It is expected that the next revision
-of the TIFF spec will replace the 6.0 JPEG design with the Note's design.
-Although IJG's own code does not support TIFF/JPEG, the free libtiff library
-uses our library to implement TIFF/JPEG per the Note.  libtiff is available
-from ftp://ftp.sgi.com/graphics/tiff/.
-
-
-ARCHIVE LOCATIONS
-=================
-
-The "official" archive site for this software is ftp.uu.net (Internet
-address 192.48.96.9).  The most recent released version can always be found
-there in directory graphics/jpeg.  This particular version will be archived
-as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz.  If you don't have
-direct Internet access, UUNET's archives are also available via UUCP; contact
-help@uunet.uu.net for information on retrieving files that way.
-
-Numerous Internet sites maintain copies of the UUNET files.  However, only
-ftp.uu.net is guaranteed to have the latest official version.
-
-You can also obtain this software in DOS-compatible "zip" archive format from
-the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or
-on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12
-"JPEG Tools".  Again, these versions may sometimes lag behind the ftp.uu.net
-release.
-
-The JPEG FAQ (Frequently Asked Questions) article is a useful source of
-general information about JPEG.  It is updated constantly and therefore is
-not included in this distribution.  The FAQ is posted every two weeks to
-Usenet newsgroups comp.graphics.misc, news.answers, and other groups.
-It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/
-and other news.answers archive sites, including the official news.answers
-archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/.
-If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu
-with body
-	send usenet/news.answers/jpeg-faq/part1
-	send usenet/news.answers/jpeg-faq/part2
-
-
-RELATED SOFTWARE
-================
-
-Numerous viewing and image manipulation programs now support JPEG.  (Quite a
-few of them use this library to do so.)  The JPEG FAQ described above lists
-some of the more popular free and shareware viewers, and tells where to
-obtain them on Internet.
-
-If you are on a Unix machine, we highly recommend Jef Poskanzer's free
-PBMPLUS software, which provides many useful operations on PPM-format image
-files.  In particular, it can convert PPM images to and from a wide range of
-other formats, thus making cjpeg/djpeg considerably more useful.  The latest
-version is distributed by the NetPBM group, and is available from numerous
-sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/.
-Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is;
-you are likely to have difficulty making it work on any non-Unix machine.
-
-A different free JPEG implementation, written by the PVRG group at Stanford,
-is available from ftp://havefun.stanford.edu/pub/jpeg/.  This program
-is designed for research and experimentation rather than production use;
-it is slower, harder to use, and less portable than the IJG code, but it
-is easier to read and modify.  Also, the PVRG code supports lossless JPEG,
-which we do not.  (On the other hand, it doesn't do progressive JPEG.)
-
-
-FILE FORMAT WARS
-================
-
-Some JPEG programs produce files that are not compatible with our library.
-The root of the problem is that the ISO JPEG committee failed to specify a
-concrete file format.  Some vendors "filled in the blanks" on their own,
-creating proprietary formats that no one else could read.  (For example, none
-of the early commercial JPEG implementations for the Macintosh were able to
-exchange compressed files.)
-
-The file format we have adopted is called JFIF (see REFERENCES).  This format
-has been agreed to by a number of major commercial JPEG vendors, and it has
-become the de facto standard.  JFIF is a minimal or "low end" representation.
-We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF
-Technical Note #2) for "high end" applications that need to record a lot of
-additional data about an image.  TIFF/JPEG is fairly new and not yet widely
-supported, unfortunately.
-
-The upcoming JPEG Part 3 standard defines a file format called SPIFF.
-SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should
-be able to read the most common variant of SPIFF.  SPIFF has some technical
-advantages over JFIF, but its major claim to fame is simply that it is an
-official standard rather than an informal one.  At this point it is unclear
-whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto
-standard.  IJG intends to support SPIFF once the standard is frozen, but we
-have not decided whether it should become our default output format or not.
-(In any case, our decoder will remain capable of reading JFIF indefinitely.)
-
-Various proprietary file formats incorporating JPEG compression also exist.
-We have little or no sympathy for the existence of these formats.  Indeed,
-one of the original reasons for developing this free software was to help
-force convergence on common, open format standards for JPEG files.  Don't
-use a proprietary file format!
-
-
-TO DO
-=====
-
-The major thrust for v7 will probably be improvement of visual quality.
-The current method for scaling the quantization tables is known not to be
-very good at low Q values.  We also intend to investigate block boundary
-smoothing, "poor man's variable quantization", and other means of improving
-quality-vs-file-size performance without sacrificing compatibility.
-
-In future versions, we are considering supporting some of the upcoming JPEG
-Part 3 extensions --- principally, variable quantization and the SPIFF file
-format.
-
-As always, speeding things up is of great interest.
-
-Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net.
diff --git a/third_party/libjpeg/README.chromium b/third_party/libjpeg/README.chromium
deleted file mode 100644
index 70ef475..0000000
--- a/third_party/libjpeg/README.chromium
+++ /dev/null
@@ -1,22 +0,0 @@
-Name: libjpeg
-URL: http://www.ijg.org/
-Version: 6b
-License: Custom license
-Security Critical: yes
-License Android Compatible: yes
-
-Description:
-This contains a copy of libjpeg-6b.
-
-The project files does not incldue from the distribution:
-  jidctred.c : downsampling
-  jdtrans.c : decoder transcoder
-
-Also not included are files obviously not needed:
-  jmemdos.c
-  jmemname.c
-along with all of the frontend files for doing utility programs.
-
-We added a new file jpeglibmangler.h and included it from jpeglib.h that changes
-the names of all externally visible functions to chromium_ijg_* so that we can
-avoid conflicts that arise when system libraries attempt to use our libjpeg.
diff --git a/third_party/libjpeg/jcapimin.c b/third_party/libjpeg/jcapimin.c
deleted file mode 100644
index 54fb8c5..0000000
--- a/third_party/libjpeg/jcapimin.c
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * jcapimin.c
- *
- * Copyright (C) 1994-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains application interface code for the compression half
- * of the JPEG library.  These are the "minimum" API routines that may be
- * needed in either the normal full-compression case or the transcoding-only
- * case.
- *
- * Most of the routines intended to be called directly by an application
- * are in this file or in jcapistd.c.  But also see jcparam.c for
- * parameter-setup helper routines, jcomapi.c for routines shared by
- * compression and decompression, and jctrans.c for the transcoding case.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * Initialization of a JPEG compression object.
- * The error manager must already be set up (in case memory manager fails).
- */
-
-GLOBAL(void)
-jpeg_CreateCompress (j_compress_ptr cinfo, int version, size_t structsize)
-{
-  int i;
-
-  /* Guard against version mismatches between library and caller. */
-  cinfo->mem = NULL;		/* so jpeg_destroy knows mem mgr not called */
-  if (version != JPEG_LIB_VERSION)
-    ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);
-  if (structsize != SIZEOF(struct jpeg_compress_struct))
-    ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, 
-	     (int) SIZEOF(struct jpeg_compress_struct), (int) structsize);
-
-  /* For debugging purposes, we zero the whole master structure.
-   * But the application has already set the err pointer, and may have set
-   * client_data, so we have to save and restore those fields.
-   * Note: if application hasn't set client_data, tools like Purify may
-   * complain here.
-   */
-  {
-    struct jpeg_error_mgr * err = cinfo->err;
-    void * client_data = cinfo->client_data; /* ignore Purify complaint here */
-    MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct));
-    cinfo->err = err;
-    cinfo->client_data = client_data;
-  }
-  cinfo->is_decompressor = FALSE;
-
-  /* Initialize a memory manager instance for this object */
-  jinit_memory_mgr((j_common_ptr) cinfo);
-
-  /* Zero out pointers to permanent structures. */
-  cinfo->progress = NULL;
-  cinfo->dest = NULL;
-
-  cinfo->comp_info = NULL;
-
-  for (i = 0; i < NUM_QUANT_TBLS; i++)
-    cinfo->quant_tbl_ptrs[i] = NULL;
-
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    cinfo->dc_huff_tbl_ptrs[i] = NULL;
-    cinfo->ac_huff_tbl_ptrs[i] = NULL;
-  }
-
-  cinfo->script_space = NULL;
-
-  cinfo->input_gamma = 1.0;	/* in case application forgets */
-
-  /* OK, I'm ready */
-  cinfo->global_state = CSTATE_START;
-}
-
-
-/*
- * Destruction of a JPEG compression object
- */
-
-GLOBAL(void)
-jpeg_destroy_compress (j_compress_ptr cinfo)
-{
-  jpeg_destroy((j_common_ptr) cinfo); /* use common routine */
-}
-
-
-/*
- * Abort processing of a JPEG compression operation,
- * but don't destroy the object itself.
- */
-
-GLOBAL(void)
-jpeg_abort_compress (j_compress_ptr cinfo)
-{
-  jpeg_abort((j_common_ptr) cinfo); /* use common routine */
-}
-
-
-/*
- * Forcibly suppress or un-suppress all quantization and Huffman tables.
- * Marks all currently defined tables as already written (if suppress)
- * or not written (if !suppress).  This will control whether they get emitted
- * by a subsequent jpeg_start_compress call.
- *
- * This routine is exported for use by applications that want to produce
- * abbreviated JPEG datastreams.  It logically belongs in jcparam.c, but
- * since it is called by jpeg_start_compress, we put it here --- otherwise
- * jcparam.o would be linked whether the application used it or not.
- */
-
-GLOBAL(void)
-jpeg_suppress_tables (j_compress_ptr cinfo, boolean suppress)
-{
-  int i;
-  JQUANT_TBL * qtbl;
-  JHUFF_TBL * htbl;
-
-  for (i = 0; i < NUM_QUANT_TBLS; i++) {
-    if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL)
-      qtbl->sent_table = suppress;
-  }
-
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL)
-      htbl->sent_table = suppress;
-    if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL)
-      htbl->sent_table = suppress;
-  }
-}
-
-
-/*
- * Finish JPEG compression.
- *
- * If a multipass operating mode was selected, this may do a great deal of
- * work including most of the actual output.
- */
-
-GLOBAL(void)
-jpeg_finish_compress (j_compress_ptr cinfo)
-{
-  JDIMENSION iMCU_row;
-
-  if (cinfo->global_state == CSTATE_SCANNING ||
-      cinfo->global_state == CSTATE_RAW_OK) {
-    /* Terminate first pass */
-    if (cinfo->next_scanline < cinfo->image_height)
-      ERREXIT(cinfo, JERR_TOO_LITTLE_DATA);
-    (*cinfo->master->finish_pass) (cinfo);
-  } else if (cinfo->global_state != CSTATE_WRCOEFS)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  /* Perform any remaining passes */
-  while (! cinfo->master->is_last_pass) {
-    (*cinfo->master->prepare_for_pass) (cinfo);
-    for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) {
-      if (cinfo->progress != NULL) {
-	cinfo->progress->pass_counter = (long) iMCU_row;
-	cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows;
-	(*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-      }
-      /* We bypass the main controller and invoke coef controller directly;
-       * all work is being done from the coefficient buffer.
-       */
-      if (! (*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE) NULL))
-	ERREXIT(cinfo, JERR_CANT_SUSPEND);
-    }
-    (*cinfo->master->finish_pass) (cinfo);
-  }
-  /* Write EOI, do final cleanup */
-  (*cinfo->marker->write_file_trailer) (cinfo);
-  (*cinfo->dest->term_destination) (cinfo);
-  /* We can use jpeg_abort to release memory and reset global_state */
-  jpeg_abort((j_common_ptr) cinfo);
-}
-
-
-/*
- * Write a special marker.
- * This is only recommended for writing COM or APPn markers.
- * Must be called after jpeg_start_compress() and before
- * first call to jpeg_write_scanlines() or jpeg_write_raw_data().
- */
-
-GLOBAL(void)
-jpeg_write_marker (j_compress_ptr cinfo, int marker,
-		   const JOCTET *dataptr, unsigned int datalen)
-{
-  JMETHOD(void, write_marker_byte, (j_compress_ptr info, int val));
-
-  if (cinfo->next_scanline != 0 ||
-      (cinfo->global_state != CSTATE_SCANNING &&
-       cinfo->global_state != CSTATE_RAW_OK &&
-       cinfo->global_state != CSTATE_WRCOEFS))
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  (*cinfo->marker->write_marker_header) (cinfo, marker, datalen);
-  write_marker_byte = cinfo->marker->write_marker_byte;	/* copy for speed */
-  while (datalen--) {
-    (*write_marker_byte) (cinfo, *dataptr);
-    dataptr++;
-  }
-}
-
-/* Same, but piecemeal. */
-
-GLOBAL(void)
-jpeg_write_m_header (j_compress_ptr cinfo, int marker, unsigned int datalen)
-{
-  if (cinfo->next_scanline != 0 ||
-      (cinfo->global_state != CSTATE_SCANNING &&
-       cinfo->global_state != CSTATE_RAW_OK &&
-       cinfo->global_state != CSTATE_WRCOEFS))
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  (*cinfo->marker->write_marker_header) (cinfo, marker, datalen);
-}
-
-GLOBAL(void)
-jpeg_write_m_byte (j_compress_ptr cinfo, int val)
-{
-  (*cinfo->marker->write_marker_byte) (cinfo, val);
-}
-
-
-/*
- * Alternate compression function: just write an abbreviated table file.
- * Before calling this, all parameters and a data destination must be set up.
- *
- * To produce a pair of files containing abbreviated tables and abbreviated
- * image data, one would proceed as follows:
- *
- *		initialize JPEG object
- *		set JPEG parameters
- *		set destination to table file
- *		jpeg_write_tables(cinfo);
- *		set destination to image file
- *		jpeg_start_compress(cinfo, FALSE);
- *		write data...
- *		jpeg_finish_compress(cinfo);
- *
- * jpeg_write_tables has the side effect of marking all tables written
- * (same as jpeg_suppress_tables(..., TRUE)).  Thus a subsequent start_compress
- * will not re-emit the tables unless it is passed write_all_tables=TRUE.
- */
-
-GLOBAL(void)
-jpeg_write_tables (j_compress_ptr cinfo)
-{
-  if (cinfo->global_state != CSTATE_START)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  /* (Re)initialize error mgr and destination modules */
-  (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo);
-  (*cinfo->dest->init_destination) (cinfo);
-  /* Initialize the marker writer ... bit of a crock to do it here. */
-  jinit_marker_writer(cinfo);
-  /* Write them tables! */
-  (*cinfo->marker->write_tables_only) (cinfo);
-  /* And clean up. */
-  (*cinfo->dest->term_destination) (cinfo);
-  /*
-   * In library releases up through v6a, we called jpeg_abort() here to free
-   * any working memory allocated by the destination manager and marker
-   * writer.  Some applications had a problem with that: they allocated space
-   * of their own from the library memory manager, and didn't want it to go
-   * away during write_tables.  So now we do nothing.  This will cause a
-   * memory leak if an app calls write_tables repeatedly without doing a full
-   * compression cycle or otherwise resetting the JPEG object.  However, that
-   * seems less bad than unexpectedly freeing memory in the normal case.
-   * An app that prefers the old behavior can call jpeg_abort for itself after
-   * each call to jpeg_write_tables().
-   */
-}
diff --git a/third_party/libjpeg/jcapistd.c b/third_party/libjpeg/jcapistd.c
deleted file mode 100644
index c0320b1..0000000
--- a/third_party/libjpeg/jcapistd.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * jcapistd.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains application interface code for the compression half
- * of the JPEG library.  These are the "standard" API routines that are
- * used in the normal full-compression case.  They are not used by a
- * transcoding-only application.  Note that if an application links in
- * jpeg_start_compress, it will end up linking in the entire compressor.
- * We thus must separate this file from jcapimin.c to avoid linking the
- * whole compression library into a transcoder.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * Compression initialization.
- * Before calling this, all parameters and a data destination must be set up.
- *
- * We require a write_all_tables parameter as a failsafe check when writing
- * multiple datastreams from the same compression object.  Since prior runs
- * will have left all the tables marked sent_table=TRUE, a subsequent run
- * would emit an abbreviated stream (no tables) by default.  This may be what
- * is wanted, but for safety's sake it should not be the default behavior:
- * programmers should have to make a deliberate choice to emit abbreviated
- * images.  Therefore the documentation and examples should encourage people
- * to pass write_all_tables=TRUE; then it will take active thought to do the
- * wrong thing.
- */
-
-GLOBAL(void)
-jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables)
-{
-  if (cinfo->global_state != CSTATE_START)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  if (write_all_tables)
-    jpeg_suppress_tables(cinfo, FALSE);	/* mark all tables to be written */
-
-  /* (Re)initialize error mgr and destination modules */
-  (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo);
-  (*cinfo->dest->init_destination) (cinfo);
-  /* Perform master selection of active modules */
-  jinit_compress_master(cinfo);
-  /* Set up for the first pass */
-  (*cinfo->master->prepare_for_pass) (cinfo);
-  /* Ready for application to drive first pass through jpeg_write_scanlines
-   * or jpeg_write_raw_data.
-   */
-  cinfo->next_scanline = 0;
-  cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING);
-}
-
-
-/*
- * Write some scanlines of data to the JPEG compressor.
- *
- * The return value will be the number of lines actually written.
- * This should be less than the supplied num_lines only in case that
- * the data destination module has requested suspension of the compressor,
- * or if more than image_height scanlines are passed in.
- *
- * Note: we warn about excess calls to jpeg_write_scanlines() since
- * this likely signals an application programmer error.  However,
- * excess scanlines passed in the last valid call are *silently* ignored,
- * so that the application need not adjust num_lines for end-of-image
- * when using a multiple-scanline buffer.
- */
-
-GLOBAL(JDIMENSION)
-jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines,
-		      JDIMENSION num_lines)
-{
-  JDIMENSION row_ctr, rows_left;
-
-  if (cinfo->global_state != CSTATE_SCANNING)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  if (cinfo->next_scanline >= cinfo->image_height)
-    WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
-
-  /* Call progress monitor hook if present */
-  if (cinfo->progress != NULL) {
-    cinfo->progress->pass_counter = (long) cinfo->next_scanline;
-    cinfo->progress->pass_limit = (long) cinfo->image_height;
-    (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-  }
-
-  /* Give master control module another chance if this is first call to
-   * jpeg_write_scanlines.  This lets output of the frame/scan headers be
-   * delayed so that application can write COM, etc, markers between
-   * jpeg_start_compress and jpeg_write_scanlines.
-   */
-  if (cinfo->master->call_pass_startup)
-    (*cinfo->master->pass_startup) (cinfo);
-
-  /* Ignore any extra scanlines at bottom of image. */
-  rows_left = cinfo->image_height - cinfo->next_scanline;
-  if (num_lines > rows_left)
-    num_lines = rows_left;
-
-  row_ctr = 0;
-  (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines);
-  cinfo->next_scanline += row_ctr;
-  return row_ctr;
-}
-
-
-/*
- * Alternate entry point to write raw data.
- * Processes exactly one iMCU row per call, unless suspended.
- */
-
-GLOBAL(JDIMENSION)
-jpeg_write_raw_data (j_compress_ptr cinfo, JSAMPIMAGE data,
-		     JDIMENSION num_lines)
-{
-  JDIMENSION lines_per_iMCU_row;
-
-  if (cinfo->global_state != CSTATE_RAW_OK)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  if (cinfo->next_scanline >= cinfo->image_height) {
-    WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
-    return 0;
-  }
-
-  /* Call progress monitor hook if present */
-  if (cinfo->progress != NULL) {
-    cinfo->progress->pass_counter = (long) cinfo->next_scanline;
-    cinfo->progress->pass_limit = (long) cinfo->image_height;
-    (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-  }
-
-  /* Give master control module another chance if this is first call to
-   * jpeg_write_raw_data.  This lets output of the frame/scan headers be
-   * delayed so that application can write COM, etc, markers between
-   * jpeg_start_compress and jpeg_write_raw_data.
-   */
-  if (cinfo->master->call_pass_startup)
-    (*cinfo->master->pass_startup) (cinfo);
-
-  /* Verify that at least one iMCU row has been passed. */
-  lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE;
-  if (num_lines < lines_per_iMCU_row)
-    ERREXIT(cinfo, JERR_BUFFER_SIZE);
-
-  /* Directly compress the row. */
-  if (! (*cinfo->coef->compress_data) (cinfo, data)) {
-    /* If compressor did not consume the whole row, suspend processing. */
-    return 0;
-  }
-
-  /* OK, we processed one iMCU row. */
-  cinfo->next_scanline += lines_per_iMCU_row;
-  return lines_per_iMCU_row;
-}
diff --git a/third_party/libjpeg/jccoefct.c b/third_party/libjpeg/jccoefct.c
deleted file mode 100644
index 1963ddb..0000000
--- a/third_party/libjpeg/jccoefct.c
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * jccoefct.c
- *
- * Copyright (C) 1994-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the coefficient buffer controller for compression.
- * This controller is the top level of the JPEG compressor proper.
- * The coefficient buffer lies between forward-DCT and entropy encoding steps.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* We use a full-image coefficient buffer when doing Huffman optimization,
- * and also for writing multiple-scan JPEG files.  In all cases, the DCT
- * step is run during the first pass, and subsequent passes need only read
- * the buffered coefficients.
- */
-#ifdef ENTROPY_OPT_SUPPORTED
-#define FULL_COEF_BUFFER_SUPPORTED
-#else
-#ifdef C_MULTISCAN_FILES_SUPPORTED
-#define FULL_COEF_BUFFER_SUPPORTED
-#endif
-#endif
-
-
-/* Private buffer controller object */
-
-typedef struct {
-  struct jpeg_c_coef_controller pub; /* public fields */
-
-  JDIMENSION iMCU_row_num;	/* iMCU row # within image */
-  JDIMENSION mcu_ctr;		/* counts MCUs processed in current row */
-  int MCU_vert_offset;		/* counts MCU rows within iMCU row */
-  int MCU_rows_per_iMCU_row;	/* number of such rows needed */
-
-  /* For single-pass compression, it's sufficient to buffer just one MCU
-   * (although this may prove a bit slow in practice).  We allocate a
-   * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each
-   * MCU constructed and sent.  (On 80x86, the workspace is FAR even though
-   * it's not really very big; this is to keep the module interfaces unchanged
-   * when a large coefficient buffer is necessary.)
-   * In multi-pass modes, this array points to the current MCU's blocks
-   * within the virtual arrays.
-   */
-  JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU];
-
-  /* In multi-pass modes, we need a virtual block array for each component. */
-  jvirt_barray_ptr whole_image[MAX_COMPONENTS];
-} my_coef_controller;
-
-typedef my_coef_controller * my_coef_ptr;
-
-
-/* Forward declarations */
-METHODDEF(boolean) compress_data
-    JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf));
-#ifdef FULL_COEF_BUFFER_SUPPORTED
-METHODDEF(boolean) compress_first_pass
-    JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf));
-METHODDEF(boolean) compress_output
-    JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf));
-#endif
-
-
-LOCAL(void)
-start_iMCU_row (j_compress_ptr cinfo)
-/* Reset within-iMCU-row counters for a new row */
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-
-  /* In an interleaved scan, an MCU row is the same as an iMCU row.
-   * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows.
-   * But at the bottom of the image, process only what's left.
-   */
-  if (cinfo->comps_in_scan > 1) {
-    coef->MCU_rows_per_iMCU_row = 1;
-  } else {
-    if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1))
-      coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor;
-    else
-      coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height;
-  }
-
-  coef->mcu_ctr = 0;
-  coef->MCU_vert_offset = 0;
-}
-
-
-/*
- * Initialize for a processing pass.
- */
-
-METHODDEF(void)
-start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-
-  coef->iMCU_row_num = 0;
-  start_iMCU_row(cinfo);
-
-  switch (pass_mode) {
-  case JBUF_PASS_THRU:
-    if (coef->whole_image[0] != NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    coef->pub.compress_data = compress_data;
-    break;
-#ifdef FULL_COEF_BUFFER_SUPPORTED
-  case JBUF_SAVE_AND_PASS:
-    if (coef->whole_image[0] == NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    coef->pub.compress_data = compress_first_pass;
-    break;
-  case JBUF_CRANK_DEST:
-    if (coef->whole_image[0] == NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    coef->pub.compress_data = compress_output;
-    break;
-#endif
-  default:
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    break;
-  }
-}
-
-
-/*
- * Process some data in the single-pass case.
- * We process the equivalent of one fully interleaved MCU row ("iMCU" row)
- * per call, ie, v_samp_factor block rows for each component in the image.
- * Returns TRUE if the iMCU row is completed, FALSE if suspended.
- *
- * NB: input_buf contains a plane for each component in image,
- * which we index according to the component's SOF position.
- */
-
-METHODDEF(boolean)
-compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION MCU_col_num;	/* index of current MCU within row */
-  JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1;
-  JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
-  int blkn, bi, ci, yindex, yoffset, blockcnt;
-  JDIMENSION ypos, xpos;
-  jpeg_component_info *compptr;
-
-  /* Loop to write as much as one whole iMCU row */
-  for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row;
-       yoffset++) {
-    for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col;
-	 MCU_col_num++) {
-      /* Determine where data comes from in input_buf and do the DCT thing.
-       * Each call on forward_DCT processes a horizontal row of DCT blocks
-       * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks
-       * sequentially.  Dummy blocks at the right or bottom edge are filled in
-       * specially.  The data in them does not matter for image reconstruction,
-       * so we fill them with values that will encode to the smallest amount of
-       * data, viz: all zeroes in the AC entries, DC entries equal to previous
-       * block's DC value.  (Thanks to Thomas Kinsman for this idea.)
-       */
-      blkn = 0;
-      for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-	compptr = cinfo->cur_comp_info[ci];
-	blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width
-						: compptr->last_col_width;
-	xpos = MCU_col_num * compptr->MCU_sample_width;
-	ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */
-	for (yindex = 0; yindex < compptr->MCU_height; yindex++) {
-	  if (coef->iMCU_row_num < last_iMCU_row ||
-	      yoffset+yindex < compptr->last_row_height) {
-	    (*cinfo->fdct->forward_DCT) (cinfo, compptr,
-					 input_buf[compptr->component_index],
-					 coef->MCU_buffer[blkn],
-					 ypos, xpos, (JDIMENSION) blockcnt);
-	    if (blockcnt < compptr->MCU_width) {
-	      /* Create some dummy blocks at the right edge of the image. */
-	      jzero_far((void FAR *) coef->MCU_buffer[blkn + blockcnt],
-			(compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK));
-	      for (bi = blockcnt; bi < compptr->MCU_width; bi++) {
-		coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0];
-	      }
-	    }
-	  } else {
-	    /* Create a row of dummy blocks at the bottom of the image. */
-	    jzero_far((void FAR *) coef->MCU_buffer[blkn],
-		      compptr->MCU_width * SIZEOF(JBLOCK));
-	    for (bi = 0; bi < compptr->MCU_width; bi++) {
-	      coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0];
-	    }
-	  }
-	  blkn += compptr->MCU_width;
-	  ypos += DCTSIZE;
-	}
-      }
-      /* Try to write the MCU.  In event of a suspension failure, we will
-       * re-DCT the MCU on restart (a bit inefficient, could be fixed...)
-       */
-      if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) {
-	/* Suspension forced; update state counters and exit */
-	coef->MCU_vert_offset = yoffset;
-	coef->mcu_ctr = MCU_col_num;
-	return FALSE;
-      }
-    }
-    /* Completed an MCU row, but perhaps not an iMCU row */
-    coef->mcu_ctr = 0;
-  }
-  /* Completed the iMCU row, advance counters for next one */
-  coef->iMCU_row_num++;
-  start_iMCU_row(cinfo);
-  return TRUE;
-}
-
-
-#ifdef FULL_COEF_BUFFER_SUPPORTED
-
-/*
- * Process some data in the first pass of a multi-pass case.
- * We process the equivalent of one fully interleaved MCU row ("iMCU" row)
- * per call, ie, v_samp_factor block rows for each component in the image.
- * This amount of data is read from the source buffer, DCT'd and quantized,
- * and saved into the virtual arrays.  We also generate suitable dummy blocks
- * as needed at the right and lower edges.  (The dummy blocks are constructed
- * in the virtual arrays, which have been padded appropriately.)  This makes
- * it possible for subsequent passes not to worry about real vs. dummy blocks.
- *
- * We must also emit the data to the entropy encoder.  This is conveniently
- * done by calling compress_output() after we've loaded the current strip
- * of the virtual arrays.
- *
- * NB: input_buf contains a plane for each component in image.  All
- * components are DCT'd and loaded into the virtual arrays in this pass.
- * However, it may be that only a subset of the components are emitted to
- * the entropy encoder during this first pass; be careful about looking
- * at the scan-dependent variables (MCU dimensions, etc).
- */
-
-METHODDEF(boolean)
-compress_first_pass (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
-  JDIMENSION blocks_across, MCUs_across, MCUindex;
-  int bi, ci, h_samp_factor, block_row, block_rows, ndummy;
-  JCOEF lastDC;
-  jpeg_component_info *compptr;
-  JBLOCKARRAY buffer;
-  JBLOCKROW thisblockrow, lastblockrow;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Align the virtual buffer for this component. */
-    buffer = (*cinfo->mem->access_virt_barray)
-      ((j_common_ptr) cinfo, coef->whole_image[ci],
-       coef->iMCU_row_num * compptr->v_samp_factor,
-       (JDIMENSION) compptr->v_samp_factor, TRUE);
-    /* Count non-dummy DCT block rows in this iMCU row. */
-    if (coef->iMCU_row_num < last_iMCU_row)
-      block_rows = compptr->v_samp_factor;
-    else {
-      /* NB: can't use last_row_height here, since may not be set! */
-      block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor);
-      if (block_rows == 0) block_rows = compptr->v_samp_factor;
-    }
-    blocks_across = compptr->width_in_blocks;
-    h_samp_factor = compptr->h_samp_factor;
-    /* Count number of dummy blocks to be added at the right margin. */
-    ndummy = (int) (blocks_across % h_samp_factor);
-    if (ndummy > 0)
-      ndummy = h_samp_factor - ndummy;
-    /* Perform DCT for all non-dummy blocks in this iMCU row.  Each call
-     * on forward_DCT processes a complete horizontal row of DCT blocks.
-     */
-    for (block_row = 0; block_row < block_rows; block_row++) {
-      thisblockrow = buffer[block_row];
-      (*cinfo->fdct->forward_DCT) (cinfo, compptr,
-				   input_buf[ci], thisblockrow,
-				   (JDIMENSION) (block_row * DCTSIZE),
-				   (JDIMENSION) 0, blocks_across);
-      if (ndummy > 0) {
-	/* Create dummy blocks at the right edge of the image. */
-	thisblockrow += blocks_across; /* => first dummy block */
-	jzero_far((void FAR *) thisblockrow, ndummy * SIZEOF(JBLOCK));
-	lastDC = thisblockrow[-1][0];
-	for (bi = 0; bi < ndummy; bi++) {
-	  thisblockrow[bi][0] = lastDC;
-	}
-      }
-    }
-    /* If at end of image, create dummy block rows as needed.
-     * The tricky part here is that within each MCU, we want the DC values
-     * of the dummy blocks to match the last real block's DC value.
-     * This squeezes a few more bytes out of the resulting file...
-     */
-    if (coef->iMCU_row_num == last_iMCU_row) {
-      blocks_across += ndummy;	/* include lower right corner */
-      MCUs_across = blocks_across / h_samp_factor;
-      for (block_row = block_rows; block_row < compptr->v_samp_factor;
-	   block_row++) {
-	thisblockrow = buffer[block_row];
-	lastblockrow = buffer[block_row-1];
-	jzero_far((void FAR *) thisblockrow,
-		  (size_t) (blocks_across * SIZEOF(JBLOCK)));
-	for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) {
-	  lastDC = lastblockrow[h_samp_factor-1][0];
-	  for (bi = 0; bi < h_samp_factor; bi++) {
-	    thisblockrow[bi][0] = lastDC;
-	  }
-	  thisblockrow += h_samp_factor; /* advance to next MCU in row */
-	  lastblockrow += h_samp_factor;
-	}
-      }
-    }
-  }
-  /* NB: compress_output will increment iMCU_row_num if successful.
-   * A suspension return will result in redoing all the work above next time.
-   */
-
-  /* Emit data to the entropy encoder, sharing code with subsequent passes */
-  return compress_output(cinfo, input_buf);
-}
-
-
-/*
- * Process some data in subsequent passes of a multi-pass case.
- * We process the equivalent of one fully interleaved MCU row ("iMCU" row)
- * per call, ie, v_samp_factor block rows for each component in the scan.
- * The data is obtained from the virtual arrays and fed to the entropy coder.
- * Returns TRUE if the iMCU row is completed, FALSE if suspended.
- *
- * NB: input_buf is ignored; it is likely to be a NULL pointer.
- */
-
-METHODDEF(boolean)
-compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION MCU_col_num;	/* index of current MCU within row */
-  int blkn, ci, xindex, yindex, yoffset;
-  JDIMENSION start_col;
-  JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN];
-  JBLOCKROW buffer_ptr;
-  jpeg_component_info *compptr;
-
-  /* Align the virtual buffers for the components used in this scan.
-   * NB: during first pass, this is safe only because the buffers will
-   * already be aligned properly, so jmemmgr.c won't need to do any I/O.
-   */
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    buffer[ci] = (*cinfo->mem->access_virt_barray)
-      ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index],
-       coef->iMCU_row_num * compptr->v_samp_factor,
-       (JDIMENSION) compptr->v_samp_factor, FALSE);
-  }
-
-  /* Loop to process one whole iMCU row */
-  for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row;
-       yoffset++) {
-    for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row;
-	 MCU_col_num++) {
-      /* Construct list of pointers to DCT blocks belonging to this MCU */
-      blkn = 0;			/* index of current DCT block within MCU */
-      for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-	compptr = cinfo->cur_comp_info[ci];
-	start_col = MCU_col_num * compptr->MCU_width;
-	for (yindex = 0; yindex < compptr->MCU_height; yindex++) {
-	  buffer_ptr = buffer[ci][yindex+yoffset] + start_col;
-	  for (xindex = 0; xindex < compptr->MCU_width; xindex++) {
-	    coef->MCU_buffer[blkn++] = buffer_ptr++;
-	  }
-	}
-      }
-      /* Try to write the MCU. */
-      if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) {
-	/* Suspension forced; update state counters and exit */
-	coef->MCU_vert_offset = yoffset;
-	coef->mcu_ctr = MCU_col_num;
-	return FALSE;
-      }
-    }
-    /* Completed an MCU row, but perhaps not an iMCU row */
-    coef->mcu_ctr = 0;
-  }
-  /* Completed the iMCU row, advance counters for next one */
-  coef->iMCU_row_num++;
-  start_iMCU_row(cinfo);
-  return TRUE;
-}
-
-#endif /* FULL_COEF_BUFFER_SUPPORTED */
-
-
-/*
- * Initialize coefficient buffer controller.
- */
-
-GLOBAL(void)
-jinit_c_coef_controller (j_compress_ptr cinfo, boolean need_full_buffer)
-{
-  my_coef_ptr coef;
-
-  coef = (my_coef_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_coef_controller));
-  cinfo->coef = (struct jpeg_c_coef_controller *) coef;
-  coef->pub.start_pass = start_pass_coef;
-
-  /* Create the coefficient buffer. */
-  if (need_full_buffer) {
-#ifdef FULL_COEF_BUFFER_SUPPORTED
-    /* Allocate a full-image virtual array for each component, */
-    /* padded to a multiple of samp_factor DCT blocks in each direction. */
-    int ci;
-    jpeg_component_info *compptr;
-
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      coef->whole_image[ci] = (*cinfo->mem->request_virt_barray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE,
-	 (JDIMENSION) jround_up((long) compptr->width_in_blocks,
-				(long) compptr->h_samp_factor),
-	 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
-				(long) compptr->v_samp_factor),
-	 (JDIMENSION) compptr->v_samp_factor);
-    }
-#else
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-#endif
-  } else {
-    /* We only need a single-MCU buffer. */
-    JBLOCKROW buffer;
-    int i;
-
-    buffer = (JBLOCKROW)
-      (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK));
-    for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) {
-      coef->MCU_buffer[i] = buffer + i;
-    }
-    coef->whole_image[0] = NULL; /* flag for no virtual arrays */
-  }
-}
diff --git a/third_party/libjpeg/jccolor.c b/third_party/libjpeg/jccolor.c
deleted file mode 100644
index 0a8a4b5..0000000
--- a/third_party/libjpeg/jccolor.c
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * jccolor.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains input colorspace conversion routines.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Private subobject */
-
-typedef struct {
-  struct jpeg_color_converter pub; /* public fields */
-
-  /* Private state for RGB->YCC conversion */
-  INT32 * rgb_ycc_tab;		/* => table for RGB to YCbCr conversion */
-} my_color_converter;
-
-typedef my_color_converter * my_cconvert_ptr;
-
-
-/**************** RGB -> YCbCr conversion: most common case **************/
-
-/*
- * YCbCr is defined per CCIR 601-1, except that Cb and Cr are
- * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5.
- * The conversion equations to be implemented are therefore
- *	Y  =  0.29900 * R + 0.58700 * G + 0.11400 * B
- *	Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B  + CENTERJSAMPLE
- *	Cr =  0.50000 * R - 0.41869 * G - 0.08131 * B  + CENTERJSAMPLE
- * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.)
- * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2,
- * rather than CENTERJSAMPLE, for Cb and Cr.  This gave equal positive and
- * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0)
- * were not represented exactly.  Now we sacrifice exact representation of
- * maximum red and maximum blue in order to get exact grayscales.
- *
- * To avoid floating-point arithmetic, we represent the fractional constants
- * as integers scaled up by 2^16 (about 4 digits precision); we have to divide
- * the products by 2^16, with appropriate rounding, to get the correct answer.
- *
- * For even more speed, we avoid doing any multiplications in the inner loop
- * by precalculating the constants times R,G,B for all possible values.
- * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table);
- * for 12-bit samples it is still acceptable.  It's not very reasonable for
- * 16-bit samples, but if you want lossless storage you shouldn't be changing
- * colorspace anyway.
- * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included
- * in the tables to save adding them separately in the inner loop.
- */
-
-#define SCALEBITS	16	/* speediest right-shift on some machines */
-#define CBCR_OFFSET	((INT32) CENTERJSAMPLE << SCALEBITS)
-#define ONE_HALF	((INT32) 1 << (SCALEBITS-1))
-#define FIX(x)		((INT32) ((x) * (1L<<SCALEBITS) + 0.5))
-
-/* We allocate one big table and divide it up into eight parts, instead of
- * doing eight alloc_small requests.  This lets us use a single table base
- * address, which can be held in a register in the inner loops on many
- * machines (more than can hold all eight addresses, anyway).
- */
-
-#define R_Y_OFF		0			/* offset to R => Y section */
-#define G_Y_OFF		(1*(MAXJSAMPLE+1))	/* offset to G => Y section */
-#define B_Y_OFF		(2*(MAXJSAMPLE+1))	/* etc. */
-#define R_CB_OFF	(3*(MAXJSAMPLE+1))
-#define G_CB_OFF	(4*(MAXJSAMPLE+1))
-#define B_CB_OFF	(5*(MAXJSAMPLE+1))
-#define R_CR_OFF	B_CB_OFF		/* B=>Cb, R=>Cr are the same */
-#define G_CR_OFF	(6*(MAXJSAMPLE+1))
-#define B_CR_OFF	(7*(MAXJSAMPLE+1))
-#define TABLE_SIZE	(8*(MAXJSAMPLE+1))
-
-
-/*
- * Initialize for RGB->YCC colorspace conversion.
- */
-
-METHODDEF(void)
-rgb_ycc_start (j_compress_ptr cinfo)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  INT32 * rgb_ycc_tab;
-  INT32 i;
-
-  /* Allocate and fill in the conversion tables. */
-  cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(TABLE_SIZE * SIZEOF(INT32)));
-
-  for (i = 0; i <= MAXJSAMPLE; i++) {
-    rgb_ycc_tab[i+R_Y_OFF] = FIX(0.29900) * i;
-    rgb_ycc_tab[i+G_Y_OFF] = FIX(0.58700) * i;
-    rgb_ycc_tab[i+B_Y_OFF] = FIX(0.11400) * i     + ONE_HALF;
-    rgb_ycc_tab[i+R_CB_OFF] = (-FIX(0.16874)) * i;
-    rgb_ycc_tab[i+G_CB_OFF] = (-FIX(0.33126)) * i;
-    /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr.
-     * This ensures that the maximum output will round to MAXJSAMPLE
-     * not MAXJSAMPLE+1, and thus that we don't have to range-limit.
-     */
-    rgb_ycc_tab[i+B_CB_OFF] = FIX(0.50000) * i    + CBCR_OFFSET + ONE_HALF-1;
-/*  B=>Cb and R=>Cr tables are the same
-    rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i    + CBCR_OFFSET + ONE_HALF-1;
-*/
-    rgb_ycc_tab[i+G_CR_OFF] = (-FIX(0.41869)) * i;
-    rgb_ycc_tab[i+B_CR_OFF] = (-FIX(0.08131)) * i;
-  }
-}
-
-
-/*
- * Convert some rows of samples to the JPEG colorspace.
- *
- * Note that we change from the application's interleaved-pixel format
- * to our internal noninterleaved, one-plane-per-component format.
- * The input buffer is therefore three times as wide as the output buffer.
- *
- * A starting row offset is provided only for the output buffer.  The caller
- * can easily adjust the passed input_buf value to accommodate any row
- * offset required on that side.
- */
-
-METHODDEF(void)
-rgb_ycc_convert (j_compress_ptr cinfo,
-		 JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
-		 JDIMENSION output_row, int num_rows)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  register int r, g, b;
-  register INT32 * ctab = cconvert->rgb_ycc_tab;
-  register JSAMPROW inptr;
-  register JSAMPROW outptr0, outptr1, outptr2;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->image_width;
-
-  while (--num_rows >= 0) {
-    inptr = *input_buf++;
-    outptr0 = output_buf[0][output_row];
-    outptr1 = output_buf[1][output_row];
-    outptr2 = output_buf[2][output_row];
-    output_row++;
-    for (col = 0; col < num_cols; col++) {
-      r = GETJSAMPLE(inptr[RGB_RED]);
-      g = GETJSAMPLE(inptr[RGB_GREEN]);
-      b = GETJSAMPLE(inptr[RGB_BLUE]);
-      inptr += RGB_PIXELSIZE;
-      /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations
-       * must be too; we do not need an explicit range-limiting operation.
-       * Hence the value being shifted is never negative, and we don't
-       * need the general RIGHT_SHIFT macro.
-       */
-      /* Y */
-      outptr0[col] = (JSAMPLE)
-		((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF])
-		 >> SCALEBITS);
-      /* Cb */
-      outptr1[col] = (JSAMPLE)
-		((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF])
-		 >> SCALEBITS);
-      /* Cr */
-      outptr2[col] = (JSAMPLE)
-		((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF])
-		 >> SCALEBITS);
-    }
-  }
-}
-
-
-/**************** Cases other than RGB -> YCbCr **************/
-
-
-/*
- * Convert some rows of samples to the JPEG colorspace.
- * This version handles RGB->grayscale conversion, which is the same
- * as the RGB->Y portion of RGB->YCbCr.
- * We assume rgb_ycc_start has been called (we only use the Y tables).
- */
-
-METHODDEF(void)
-rgb_gray_convert (j_compress_ptr cinfo,
-		  JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
-		  JDIMENSION output_row, int num_rows)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  register int r, g, b;
-  register INT32 * ctab = cconvert->rgb_ycc_tab;
-  register JSAMPROW inptr;
-  register JSAMPROW outptr;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->image_width;
-
-  while (--num_rows >= 0) {
-    inptr = *input_buf++;
-    outptr = output_buf[0][output_row];
-    output_row++;
-    for (col = 0; col < num_cols; col++) {
-      r = GETJSAMPLE(inptr[RGB_RED]);
-      g = GETJSAMPLE(inptr[RGB_GREEN]);
-      b = GETJSAMPLE(inptr[RGB_BLUE]);
-      inptr += RGB_PIXELSIZE;
-      /* Y */
-      outptr[col] = (JSAMPLE)
-		((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF])
-		 >> SCALEBITS);
-    }
-  }
-}
-
-
-/*
- * Convert some rows of samples to the JPEG colorspace.
- * This version handles Adobe-style CMYK->YCCK conversion,
- * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same
- * conversion as above, while passing K (black) unchanged.
- * We assume rgb_ycc_start has been called.
- */
-
-METHODDEF(void)
-cmyk_ycck_convert (j_compress_ptr cinfo,
-		   JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
-		   JDIMENSION output_row, int num_rows)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  register int r, g, b;
-  register INT32 * ctab = cconvert->rgb_ycc_tab;
-  register JSAMPROW inptr;
-  register JSAMPROW outptr0, outptr1, outptr2, outptr3;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->image_width;
-
-  while (--num_rows >= 0) {
-    inptr = *input_buf++;
-    outptr0 = output_buf[0][output_row];
-    outptr1 = output_buf[1][output_row];
-    outptr2 = output_buf[2][output_row];
-    outptr3 = output_buf[3][output_row];
-    output_row++;
-    for (col = 0; col < num_cols; col++) {
-      r = MAXJSAMPLE - GETJSAMPLE(inptr[0]);
-      g = MAXJSAMPLE - GETJSAMPLE(inptr[1]);
-      b = MAXJSAMPLE - GETJSAMPLE(inptr[2]);
-      /* K passes through as-is */
-      outptr3[col] = inptr[3];	/* don't need GETJSAMPLE here */
-      inptr += 4;
-      /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations
-       * must be too; we do not need an explicit range-limiting operation.
-       * Hence the value being shifted is never negative, and we don't
-       * need the general RIGHT_SHIFT macro.
-       */
-      /* Y */
-      outptr0[col] = (JSAMPLE)
-		((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF])
-		 >> SCALEBITS);
-      /* Cb */
-      outptr1[col] = (JSAMPLE)
-		((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF])
-		 >> SCALEBITS);
-      /* Cr */
-      outptr2[col] = (JSAMPLE)
-		((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF])
-		 >> SCALEBITS);
-    }
-  }
-}
-
-
-/*
- * Convert some rows of samples to the JPEG colorspace.
- * This version handles grayscale output with no conversion.
- * The source can be either plain grayscale or YCbCr (since Y == gray).
- */
-
-METHODDEF(void)
-grayscale_convert (j_compress_ptr cinfo,
-		   JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
-		   JDIMENSION output_row, int num_rows)
-{
-  register JSAMPROW inptr;
-  register JSAMPROW outptr;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->image_width;
-  int instride = cinfo->input_components;
-
-  while (--num_rows >= 0) {
-    inptr = *input_buf++;
-    outptr = output_buf[0][output_row];
-    output_row++;
-    for (col = 0; col < num_cols; col++) {
-      outptr[col] = inptr[0];	/* don't need GETJSAMPLE() here */
-      inptr += instride;
-    }
-  }
-}
-
-
-/*
- * Convert some rows of samples to the JPEG colorspace.
- * This version handles multi-component colorspaces without conversion.
- * We assume input_components == num_components.
- */
-
-METHODDEF(void)
-null_convert (j_compress_ptr cinfo,
-	      JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
-	      JDIMENSION output_row, int num_rows)
-{
-  register JSAMPROW inptr;
-  register JSAMPROW outptr;
-  register JDIMENSION col;
-  register int ci;
-  int nc = cinfo->num_components;
-  JDIMENSION num_cols = cinfo->image_width;
-
-  while (--num_rows >= 0) {
-    /* It seems fastest to make a separate pass for each component. */
-    for (ci = 0; ci < nc; ci++) {
-      inptr = *input_buf;
-      outptr = output_buf[ci][output_row];
-      for (col = 0; col < num_cols; col++) {
-	outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */
-	inptr += nc;
-      }
-    }
-    input_buf++;
-    output_row++;
-  }
-}
-
-
-/*
- * Empty method for start_pass.
- */
-
-METHODDEF(void)
-null_method (j_compress_ptr cinfo)
-{
-  /* no work needed */
-}
-
-
-/*
- * Module initialization routine for input colorspace conversion.
- */
-
-GLOBAL(void)
-jinit_color_converter (j_compress_ptr cinfo)
-{
-  my_cconvert_ptr cconvert;
-
-  cconvert = (my_cconvert_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_color_converter));
-  cinfo->cconvert = (struct jpeg_color_converter *) cconvert;
-  /* set start_pass to null method until we find out differently */
-  cconvert->pub.start_pass = null_method;
-
-  /* Make sure input_components agrees with in_color_space */
-  switch (cinfo->in_color_space) {
-  case JCS_GRAYSCALE:
-    if (cinfo->input_components != 1)
-      ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
-    break;
-
-  case JCS_RGB:
-#if RGB_PIXELSIZE != 3
-    if (cinfo->input_components != RGB_PIXELSIZE)
-      ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
-    break;
-#endif /* else share code with YCbCr */
-
-  case JCS_YCbCr:
-    if (cinfo->input_components != 3)
-      ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
-    break;
-
-  case JCS_CMYK:
-  case JCS_YCCK:
-    if (cinfo->input_components != 4)
-      ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
-    break;
-
-  default:			/* JCS_UNKNOWN can be anything */
-    if (cinfo->input_components < 1)
-      ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
-    break;
-  }
-
-  /* Check num_components, set conversion method based on requested space */
-  switch (cinfo->jpeg_color_space) {
-  case JCS_GRAYSCALE:
-    if (cinfo->num_components != 1)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    if (cinfo->in_color_space == JCS_GRAYSCALE)
-      cconvert->pub.color_convert = grayscale_convert;
-    else if (cinfo->in_color_space == JCS_RGB) {
-      cconvert->pub.start_pass = rgb_ycc_start;
-      cconvert->pub.color_convert = rgb_gray_convert;
-    } else if (cinfo->in_color_space == JCS_YCbCr)
-      cconvert->pub.color_convert = grayscale_convert;
-    else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  case JCS_RGB:
-    if (cinfo->num_components != 3)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3)
-      cconvert->pub.color_convert = null_convert;
-    else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  case JCS_YCbCr:
-    if (cinfo->num_components != 3)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    if (cinfo->in_color_space == JCS_RGB) {
-      cconvert->pub.start_pass = rgb_ycc_start;
-      cconvert->pub.color_convert = rgb_ycc_convert;
-    } else if (cinfo->in_color_space == JCS_YCbCr)
-      cconvert->pub.color_convert = null_convert;
-    else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  case JCS_CMYK:
-    if (cinfo->num_components != 4)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    if (cinfo->in_color_space == JCS_CMYK)
-      cconvert->pub.color_convert = null_convert;
-    else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  case JCS_YCCK:
-    if (cinfo->num_components != 4)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    if (cinfo->in_color_space == JCS_CMYK) {
-      cconvert->pub.start_pass = rgb_ycc_start;
-      cconvert->pub.color_convert = cmyk_ycck_convert;
-    } else if (cinfo->in_color_space == JCS_YCCK)
-      cconvert->pub.color_convert = null_convert;
-    else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  default:			/* allow null conversion of JCS_UNKNOWN */
-    if (cinfo->jpeg_color_space != cinfo->in_color_space ||
-	cinfo->num_components != cinfo->input_components)
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    cconvert->pub.color_convert = null_convert;
-    break;
-  }
-}
diff --git a/third_party/libjpeg/jcdctmgr.c b/third_party/libjpeg/jcdctmgr.c
deleted file mode 100644
index 61fa79b..0000000
--- a/third_party/libjpeg/jcdctmgr.c
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * jcdctmgr.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the forward-DCT management logic.
- * This code selects a particular DCT implementation to be used,
- * and it performs related housekeeping chores including coefficient
- * quantization.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-
-/* Private subobject for this module */
-
-typedef struct {
-  struct jpeg_forward_dct pub;	/* public fields */
-
-  /* Pointer to the DCT routine actually in use */
-  forward_DCT_method_ptr do_dct;
-
-  /* The actual post-DCT divisors --- not identical to the quant table
-   * entries, because of scaling (especially for an unnormalized DCT).
-   * Each table is given in normal array order.
-   */
-  DCTELEM * divisors[NUM_QUANT_TBLS];
-
-#ifdef DCT_FLOAT_SUPPORTED
-  /* Same as above for the floating-point case. */
-  float_DCT_method_ptr do_float_dct;
-  FAST_FLOAT * float_divisors[NUM_QUANT_TBLS];
-#endif
-} my_fdct_controller;
-
-typedef my_fdct_controller * my_fdct_ptr;
-
-
-/*
- * Initialize for a processing pass.
- * Verify that all referenced Q-tables are present, and set up
- * the divisor table for each one.
- * In the current implementation, DCT of all components is done during
- * the first pass, even if only some components will be output in the
- * first scan.  Hence all components should be examined here.
- */
-
-METHODDEF(void)
-start_pass_fdctmgr (j_compress_ptr cinfo)
-{
-  my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct;
-  int ci, qtblno, i;
-  jpeg_component_info *compptr;
-  JQUANT_TBL * qtbl;
-  DCTELEM * dtbl;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    qtblno = compptr->quant_tbl_no;
-    /* Make sure specified quantization table is present */
-    if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS ||
-	cinfo->quant_tbl_ptrs[qtblno] == NULL)
-      ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno);
-    qtbl = cinfo->quant_tbl_ptrs[qtblno];
-    /* Compute divisors for this quant table */
-    /* We may do this more than once for same table, but it's not a big deal */
-    switch (cinfo->dct_method) {
-#ifdef DCT_ISLOW_SUPPORTED
-    case JDCT_ISLOW:
-      /* For LL&M IDCT method, divisors are equal to raw quantization
-       * coefficients multiplied by 8 (to counteract scaling).
-       */
-      if (fdct->divisors[qtblno] == NULL) {
-	fdct->divisors[qtblno] = (DCTELEM *)
-	  (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				      DCTSIZE2 * SIZEOF(DCTELEM));
-      }
-      dtbl = fdct->divisors[qtblno];
-      for (i = 0; i < DCTSIZE2; i++) {
-	dtbl[i] = ((DCTELEM) qtbl->quantval[i]) << 3;
-      }
-      break;
-#endif
-#ifdef DCT_IFAST_SUPPORTED
-    case JDCT_IFAST:
-      {
-	/* For AA&N IDCT method, divisors are equal to quantization
-	 * coefficients scaled by scalefactor[row]*scalefactor[col], where
-	 *   scalefactor[0] = 1
-	 *   scalefactor[k] = cos(k*PI/16) * sqrt(2)    for k=1..7
-	 * We apply a further scale factor of 8.
-	 */
-#define CONST_BITS 14
-	static const INT16 aanscales[DCTSIZE2] = {
-	  /* precomputed values scaled up by 14 bits */
-	  16384, 22725, 21407, 19266, 16384, 12873,  8867,  4520,
-	  22725, 31521, 29692, 26722, 22725, 17855, 12299,  6270,
-	  21407, 29692, 27969, 25172, 21407, 16819, 11585,  5906,
-	  19266, 26722, 25172, 22654, 19266, 15137, 10426,  5315,
-	  16384, 22725, 21407, 19266, 16384, 12873,  8867,  4520,
-	  12873, 17855, 16819, 15137, 12873, 10114,  6967,  3552,
-	   8867, 12299, 11585, 10426,  8867,  6967,  4799,  2446,
-	   4520,  6270,  5906,  5315,  4520,  3552,  2446,  1247
-	};
-	SHIFT_TEMPS
-
-	if (fdct->divisors[qtblno] == NULL) {
-	  fdct->divisors[qtblno] = (DCTELEM *)
-	    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-					DCTSIZE2 * SIZEOF(DCTELEM));
-	}
-	dtbl = fdct->divisors[qtblno];
-	for (i = 0; i < DCTSIZE2; i++) {
-	  dtbl[i] = (DCTELEM)
-	    DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i],
-				  (INT32) aanscales[i]),
-		    CONST_BITS-3);
-	}
-      }
-      break;
-#endif
-#ifdef DCT_FLOAT_SUPPORTED
-    case JDCT_FLOAT:
-      {
-	/* For float AA&N IDCT method, divisors are equal to quantization
-	 * coefficients scaled by scalefactor[row]*scalefactor[col], where
-	 *   scalefactor[0] = 1
-	 *   scalefactor[k] = cos(k*PI/16) * sqrt(2)    for k=1..7
-	 * We apply a further scale factor of 8.
-	 * What's actually stored is 1/divisor so that the inner loop can
-	 * use a multiplication rather than a division.
-	 */
-	FAST_FLOAT * fdtbl;
-	int row, col;
-	static const double aanscalefactor[DCTSIZE] = {
-	  1.0, 1.387039845, 1.306562965, 1.175875602,
-	  1.0, 0.785694958, 0.541196100, 0.275899379
-	};
-
-	if (fdct->float_divisors[qtblno] == NULL) {
-	  fdct->float_divisors[qtblno] = (FAST_FLOAT *)
-	    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-					DCTSIZE2 * SIZEOF(FAST_FLOAT));
-	}
-	fdtbl = fdct->float_divisors[qtblno];
-	i = 0;
-	for (row = 0; row < DCTSIZE; row++) {
-	  for (col = 0; col < DCTSIZE; col++) {
-	    fdtbl[i] = (FAST_FLOAT)
-	      (1.0 / (((double) qtbl->quantval[i] *
-		       aanscalefactor[row] * aanscalefactor[col] * 8.0)));
-	    i++;
-	  }
-	}
-      }
-      break;
-#endif
-    default:
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-      break;
-    }
-  }
-}
-
-
-/*
- * Perform forward DCT on one or more blocks of a component.
- *
- * The input samples are taken from the sample_data[] array starting at
- * position start_row/start_col, and moving to the right for any additional
- * blocks. The quantized coefficients are returned in coef_blocks[].
- */
-
-METHODDEF(void)
-forward_DCT (j_compress_ptr cinfo, jpeg_component_info * compptr,
-	     JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
-	     JDIMENSION start_row, JDIMENSION start_col,
-	     JDIMENSION num_blocks)
-/* This version is used for integer DCT implementations. */
-{
-  /* This routine is heavily used, so it's worth coding it tightly. */
-  my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct;
-  forward_DCT_method_ptr do_dct = fdct->do_dct;
-  DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no];
-  DCTELEM workspace[DCTSIZE2];	/* work area for FDCT subroutine */
-  JDIMENSION bi;
-
-  sample_data += start_row;	/* fold in the vertical offset once */
-
-  for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) {
-    /* Load data into workspace, applying unsigned->signed conversion */
-    { register DCTELEM *workspaceptr;
-      register JSAMPROW elemptr;
-      register int elemr;
-
-      workspaceptr = workspace;
-      for (elemr = 0; elemr < DCTSIZE; elemr++) {
-	elemptr = sample_data[elemr] + start_col;
-#if DCTSIZE == 8		/* unroll the inner loop */
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	*workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-#else
-	{ register int elemc;
-	  for (elemc = DCTSIZE; elemc > 0; elemc--) {
-	    *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE;
-	  }
-	}
-#endif
-      }
-    }
-
-    /* Perform the DCT */
-    (*do_dct) (workspace);
-
-    /* Quantize/descale the coefficients, and store into coef_blocks[] */
-    { register DCTELEM temp, qval;
-      register int i;
-      register JCOEFPTR output_ptr = coef_blocks[bi];
-
-      for (i = 0; i < DCTSIZE2; i++) {
-	qval = divisors[i];
-	temp = workspace[i];
-	/* Divide the coefficient value by qval, ensuring proper rounding.
-	 * Since C does not specify the direction of rounding for negative
-	 * quotients, we have to force the dividend positive for portability.
-	 *
-	 * In most files, at least half of the output values will be zero
-	 * (at default quantization settings, more like three-quarters...)
-	 * so we should ensure that this case is fast.  On many machines,
-	 * a comparison is enough cheaper than a divide to make a special test
-	 * a win.  Since both inputs will be nonnegative, we need only test
-	 * for a < b to discover whether a/b is 0.
-	 * If your machine's division is fast enough, define FAST_DIVIDE.
-	 */
-#ifdef FAST_DIVIDE
-#define DIVIDE_BY(a,b)	a /= b
-#else
-#define DIVIDE_BY(a,b)	if (a >= b) a /= b; else a = 0
-#endif
-	if (temp < 0) {
-	  temp = -temp;
-	  temp += qval>>1;	/* for rounding */
-	  DIVIDE_BY(temp, qval);
-	  temp = -temp;
-	} else {
-	  temp += qval>>1;	/* for rounding */
-	  DIVIDE_BY(temp, qval);
-	}
-	output_ptr[i] = (JCOEF) temp;
-      }
-    }
-  }
-}
-
-
-#ifdef DCT_FLOAT_SUPPORTED
-
-METHODDEF(void)
-forward_DCT_float (j_compress_ptr cinfo, jpeg_component_info * compptr,
-		   JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
-		   JDIMENSION start_row, JDIMENSION start_col,
-		   JDIMENSION num_blocks)
-/* This version is used for floating-point DCT implementations. */
-{
-  /* This routine is heavily used, so it's worth coding it tightly. */
-  my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct;
-  float_DCT_method_ptr do_dct = fdct->do_float_dct;
-  FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no];
-  FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */
-  JDIMENSION bi;
-
-  sample_data += start_row;	/* fold in the vertical offset once */
-
-  for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) {
-    /* Load data into workspace, applying unsigned->signed conversion */
-    { register FAST_FLOAT *workspaceptr;
-      register JSAMPROW elemptr;
-      register int elemr;
-
-      workspaceptr = workspace;
-      for (elemr = 0; elemr < DCTSIZE; elemr++) {
-	elemptr = sample_data[elemr] + start_col;
-#if DCTSIZE == 8		/* unroll the inner loop */
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	*workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-#else
-	{ register int elemc;
-	  for (elemc = DCTSIZE; elemc > 0; elemc--) {
-	    *workspaceptr++ = (FAST_FLOAT)
-	      (GETJSAMPLE(*elemptr++) - CENTERJSAMPLE);
-	  }
-	}
-#endif
-      }
-    }
-
-    /* Perform the DCT */
-    (*do_dct) (workspace);
-
-    /* Quantize/descale the coefficients, and store into coef_blocks[] */
-    { register FAST_FLOAT temp;
-      register int i;
-      register JCOEFPTR output_ptr = coef_blocks[bi];
-
-      for (i = 0; i < DCTSIZE2; i++) {
-	/* Apply the quantization and scaling factor */
-	temp = workspace[i] * divisors[i];
-	/* Round to nearest integer.
-	 * Since C does not specify the direction of rounding for negative
-	 * quotients, we have to force the dividend positive for portability.
-	 * The maximum coefficient size is +-16K (for 12-bit data), so this
-	 * code should work for either 16-bit or 32-bit ints.
-	 */
-	output_ptr[i] = (JCOEF) ((int) (temp + (FAST_FLOAT) 16384.5) - 16384);
-      }
-    }
-  }
-}
-
-#endif /* DCT_FLOAT_SUPPORTED */
-
-
-/*
- * Initialize FDCT manager.
- */
-
-GLOBAL(void)
-jinit_forward_dct (j_compress_ptr cinfo)
-{
-  my_fdct_ptr fdct;
-  int i;
-
-  fdct = (my_fdct_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_fdct_controller));
-  cinfo->fdct = (struct jpeg_forward_dct *) fdct;
-  fdct->pub.start_pass = start_pass_fdctmgr;
-
-  switch (cinfo->dct_method) {
-#ifdef DCT_ISLOW_SUPPORTED
-  case JDCT_ISLOW:
-    fdct->pub.forward_DCT = forward_DCT;
-    fdct->do_dct = jpeg_fdct_islow;
-    break;
-#endif
-#ifdef DCT_IFAST_SUPPORTED
-  case JDCT_IFAST:
-    fdct->pub.forward_DCT = forward_DCT;
-    fdct->do_dct = jpeg_fdct_ifast;
-    break;
-#endif
-#ifdef DCT_FLOAT_SUPPORTED
-  case JDCT_FLOAT:
-    fdct->pub.forward_DCT = forward_DCT_float;
-    fdct->do_float_dct = jpeg_fdct_float;
-    break;
-#endif
-  default:
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-    break;
-  }
-
-  /* Mark divisor tables unallocated */
-  for (i = 0; i < NUM_QUANT_TBLS; i++) {
-    fdct->divisors[i] = NULL;
-#ifdef DCT_FLOAT_SUPPORTED
-    fdct->float_divisors[i] = NULL;
-#endif
-  }
-}
diff --git a/third_party/libjpeg/jchuff.c b/third_party/libjpeg/jchuff.c
deleted file mode 100644
index f235250..0000000
--- a/third_party/libjpeg/jchuff.c
+++ /dev/null
@@ -1,909 +0,0 @@
-/*
- * jchuff.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains Huffman entropy encoding routines.
- *
- * Much of the complexity here has to do with supporting output suspension.
- * If the data destination module demands suspension, we want to be able to
- * back up to the start of the current MCU.  To do this, we copy state
- * variables into local working storage, and update them back to the
- * permanent JPEG objects only upon successful completion of an MCU.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jchuff.h"		/* Declarations shared with jcphuff.c */
-
-
-/* Expanded entropy encoder object for Huffman encoding.
- *
- * The savable_state subrecord contains fields that change within an MCU,
- * but must not be updated permanently until we complete the MCU.
- */
-
-typedef struct {
-  INT32 put_buffer;		/* current bit-accumulation buffer */
-  int put_bits;			/* # of bits now in it */
-  int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */
-} savable_state;
-
-/* This macro is to work around compilers with missing or broken
- * structure assignment.  You'll need to fix this code if you have
- * such a compiler and you change MAX_COMPS_IN_SCAN.
- */
-
-#ifndef NO_STRUCT_ASSIGN
-#define ASSIGN_STATE(dest,src)  ((dest) = (src))
-#else
-#if MAX_COMPS_IN_SCAN == 4
-#define ASSIGN_STATE(dest,src)  \
-	((dest).put_buffer = (src).put_buffer, \
-	 (dest).put_bits = (src).put_bits, \
-	 (dest).last_dc_val[0] = (src).last_dc_val[0], \
-	 (dest).last_dc_val[1] = (src).last_dc_val[1], \
-	 (dest).last_dc_val[2] = (src).last_dc_val[2], \
-	 (dest).last_dc_val[3] = (src).last_dc_val[3])
-#endif
-#endif
-
-
-typedef struct {
-  struct jpeg_entropy_encoder pub; /* public fields */
-
-  savable_state saved;		/* Bit buffer & DC state at start of MCU */
-
-  /* These fields are NOT loaded into local working state. */
-  unsigned int restarts_to_go;	/* MCUs left in this restart interval */
-  int next_restart_num;		/* next restart number to write (0-7) */
-
-  /* Pointers to derived tables (these workspaces have image lifespan) */
-  c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS];
-  c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS];
-
-#ifdef ENTROPY_OPT_SUPPORTED	/* Statistics tables for optimization */
-  long * dc_count_ptrs[NUM_HUFF_TBLS];
-  long * ac_count_ptrs[NUM_HUFF_TBLS];
-#endif
-} huff_entropy_encoder;
-
-typedef huff_entropy_encoder * huff_entropy_ptr;
-
-/* Working state while writing an MCU.
- * This struct contains all the fields that are needed by subroutines.
- */
-
-typedef struct {
-  JOCTET * next_output_byte;	/* => next byte to write in buffer */
-  size_t free_in_buffer;	/* # of byte spaces remaining in buffer */
-  savable_state cur;		/* Current bit buffer & DC state */
-  j_compress_ptr cinfo;		/* dump_buffer needs access to this */
-} working_state;
-
-
-/* Forward declarations */
-METHODDEF(boolean) encode_mcu_huff JPP((j_compress_ptr cinfo,
-					JBLOCKROW *MCU_data));
-METHODDEF(void) finish_pass_huff JPP((j_compress_ptr cinfo));
-#ifdef ENTROPY_OPT_SUPPORTED
-METHODDEF(boolean) encode_mcu_gather JPP((j_compress_ptr cinfo,
-					  JBLOCKROW *MCU_data));
-METHODDEF(void) finish_pass_gather JPP((j_compress_ptr cinfo));
-#endif
-
-
-/*
- * Initialize for a Huffman-compressed scan.
- * If gather_statistics is TRUE, we do not output anything during the scan,
- * just count the Huffman symbols used and generate Huffman code tables.
- */
-
-METHODDEF(void)
-start_pass_huff (j_compress_ptr cinfo, boolean gather_statistics)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  int ci, dctbl, actbl;
-  jpeg_component_info * compptr;
-
-  if (gather_statistics) {
-#ifdef ENTROPY_OPT_SUPPORTED
-    entropy->pub.encode_mcu = encode_mcu_gather;
-    entropy->pub.finish_pass = finish_pass_gather;
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-  } else {
-    entropy->pub.encode_mcu = encode_mcu_huff;
-    entropy->pub.finish_pass = finish_pass_huff;
-  }
-
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    dctbl = compptr->dc_tbl_no;
-    actbl = compptr->ac_tbl_no;
-    if (gather_statistics) {
-#ifdef ENTROPY_OPT_SUPPORTED
-      /* Check for invalid table indexes */
-      /* (make_c_derived_tbl does this in the other path) */
-      if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS)
-	ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl);
-      if (actbl < 0 || actbl >= NUM_HUFF_TBLS)
-	ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl);
-      /* Allocate and zero the statistics tables */
-      /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */
-      if (entropy->dc_count_ptrs[dctbl] == NULL)
-	entropy->dc_count_ptrs[dctbl] = (long *)
-	  (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				      257 * SIZEOF(long));
-      MEMZERO(entropy->dc_count_ptrs[dctbl], 257 * SIZEOF(long));
-      if (entropy->ac_count_ptrs[actbl] == NULL)
-	entropy->ac_count_ptrs[actbl] = (long *)
-	  (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				      257 * SIZEOF(long));
-      MEMZERO(entropy->ac_count_ptrs[actbl], 257 * SIZEOF(long));
-#endif
-    } else {
-      /* Compute derived values for Huffman tables */
-      /* We may do this more than once for a table, but it's not expensive */
-      jpeg_make_c_derived_tbl(cinfo, TRUE, dctbl,
-			      & entropy->dc_derived_tbls[dctbl]);
-      jpeg_make_c_derived_tbl(cinfo, FALSE, actbl,
-			      & entropy->ac_derived_tbls[actbl]);
-    }
-    /* Initialize DC predictions to 0 */
-    entropy->saved.last_dc_val[ci] = 0;
-  }
-
-  /* Initialize bit buffer to empty */
-  entropy->saved.put_buffer = 0;
-  entropy->saved.put_bits = 0;
-
-  /* Initialize restart stuff */
-  entropy->restarts_to_go = cinfo->restart_interval;
-  entropy->next_restart_num = 0;
-}
-
-
-/*
- * Compute the derived values for a Huffman table.
- * This routine also performs some validation checks on the table.
- *
- * Note this is also used by jcphuff.c.
- */
-
-GLOBAL(void)
-jpeg_make_c_derived_tbl (j_compress_ptr cinfo, boolean isDC, int tblno,
-			 c_derived_tbl ** pdtbl)
-{
-  JHUFF_TBL *htbl;
-  c_derived_tbl *dtbl;
-  int p, i, l, lastp, si, maxsymbol;
-  char huffsize[257];
-  unsigned int huffcode[257];
-  unsigned int code;
-
-  /* Note that huffsize[] and huffcode[] are filled in code-length order,
-   * paralleling the order of the symbols themselves in htbl->huffval[].
-   */
-
-  /* Find the input Huffman table */
-  if (tblno < 0 || tblno >= NUM_HUFF_TBLS)
-    ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno);
-  htbl =
-    isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno];
-  if (htbl == NULL)
-    ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno);
-
-  /* Allocate a workspace if we haven't already done so. */
-  if (*pdtbl == NULL)
-    *pdtbl = (c_derived_tbl *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  SIZEOF(c_derived_tbl));
-  dtbl = *pdtbl;
-  
-  /* Figure C.1: make table of Huffman code length for each symbol */
-
-  p = 0;
-  for (l = 1; l <= 16; l++) {
-    i = (int) htbl->bits[l];
-    if (i < 0 || p + i > 256)	/* protect against table overrun */
-      ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-    while (i--)
-      huffsize[p++] = (char) l;
-  }
-  huffsize[p] = 0;
-  lastp = p;
-  
-  /* Figure C.2: generate the codes themselves */
-  /* We also validate that the counts represent a legal Huffman code tree. */
-
-  code = 0;
-  si = huffsize[0];
-  p = 0;
-  while (huffsize[p]) {
-    while (((int) huffsize[p]) == si) {
-      huffcode[p++] = code;
-      code++;
-    }
-    /* code is now 1 more than the last code used for codelength si; but
-     * it must still fit in si bits, since no code is allowed to be all ones.
-     */
-    if (((INT32) code) >= (((INT32) 1) << si))
-      ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-    code <<= 1;
-    si++;
-  }
-  
-  /* Figure C.3: generate encoding tables */
-  /* These are code and size indexed by symbol value */
-
-  /* Set all codeless symbols to have code length 0;
-   * this lets us detect duplicate VAL entries here, and later
-   * allows emit_bits to detect any attempt to emit such symbols.
-   */
-  MEMZERO(dtbl->ehufsi, SIZEOF(dtbl->ehufsi));
-
-  /* This is also a convenient place to check for out-of-range
-   * and duplicated VAL entries.  We allow 0..255 for AC symbols
-   * but only 0..15 for DC.  (We could constrain them further
-   * based on data depth and mode, but this seems enough.)
-   */
-  maxsymbol = isDC ? 15 : 255;
-
-  for (p = 0; p < lastp; p++) {
-    i = htbl->huffval[p];
-    if (i < 0 || i > maxsymbol || dtbl->ehufsi[i])
-      ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-    dtbl->ehufco[i] = huffcode[p];
-    dtbl->ehufsi[i] = huffsize[p];
-  }
-}
-
-
-/* Outputting bytes to the file */
-
-/* Emit a byte, taking 'action' if must suspend. */
-#define emit_byte(state,val,action)  \
-	{ *(state)->next_output_byte++ = (JOCTET) (val);  \
-	  if (--(state)->free_in_buffer == 0)  \
-	    if (! dump_buffer(state))  \
-	      { action; } }
-
-
-LOCAL(boolean)
-dump_buffer (working_state * state)
-/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */
-{
-  struct jpeg_destination_mgr * dest = state->cinfo->dest;
-
-  if (! (*dest->empty_output_buffer) (state->cinfo))
-    return FALSE;
-  /* After a successful buffer dump, must reset buffer pointers */
-  state->next_output_byte = dest->next_output_byte;
-  state->free_in_buffer = dest->free_in_buffer;
-  return TRUE;
-}
-
-
-/* Outputting bits to the file */
-
-/* Only the right 24 bits of put_buffer are used; the valid bits are
- * left-justified in this part.  At most 16 bits can be passed to emit_bits
- * in one call, and we never retain more than 7 bits in put_buffer
- * between calls, so 24 bits are sufficient.
- */
-
-INLINE
-LOCAL(boolean)
-emit_bits (working_state * state, unsigned int code, int size)
-/* Emit some bits; return TRUE if successful, FALSE if must suspend */
-{
-  /* This routine is heavily used, so it's worth coding tightly. */
-  register INT32 put_buffer = (INT32) code;
-  register int put_bits = state->cur.put_bits;
-
-  /* if size is 0, caller used an invalid Huffman table entry */
-  if (size == 0)
-    ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE);
-
-  put_buffer &= (((INT32) 1)<<size) - 1; /* mask off any extra bits in code */
-  
-  put_bits += size;		/* new number of bits in buffer */
-  
-  put_buffer <<= 24 - put_bits; /* align incoming bits */
-
-  put_buffer |= state->cur.put_buffer; /* and merge with old buffer contents */
-  
-  while (put_bits >= 8) {
-    int c = (int) ((put_buffer >> 16) & 0xFF);
-    
-    emit_byte(state, c, return FALSE);
-    if (c == 0xFF) {		/* need to stuff a zero byte? */
-      emit_byte(state, 0, return FALSE);
-    }
-    put_buffer <<= 8;
-    put_bits -= 8;
-  }
-
-  state->cur.put_buffer = put_buffer; /* update state variables */
-  state->cur.put_bits = put_bits;
-
-  return TRUE;
-}
-
-
-LOCAL(boolean)
-flush_bits (working_state * state)
-{
-  if (! emit_bits(state, 0x7F, 7)) /* fill any partial byte with ones */
-    return FALSE;
-  state->cur.put_buffer = 0;	/* and reset bit-buffer to empty */
-  state->cur.put_bits = 0;
-  return TRUE;
-}
-
-
-/* Encode a single block's worth of coefficients */
-
-LOCAL(boolean)
-encode_one_block (working_state * state, JCOEFPTR block, int last_dc_val,
-		  c_derived_tbl *dctbl, c_derived_tbl *actbl)
-{
-  register int temp, temp2;
-  register int nbits;
-  register int k, r, i;
-  
-  /* Encode the DC coefficient difference per section F.1.2.1 */
-  
-  temp = temp2 = block[0] - last_dc_val;
-
-  if (temp < 0) {
-    temp = -temp;		/* temp is abs value of input */
-    /* For a negative input, want temp2 = bitwise complement of abs(input) */
-    /* This code assumes we are on a two's complement machine */
-    temp2--;
-  }
-  
-  /* Find the number of bits needed for the magnitude of the coefficient */
-  nbits = 0;
-  while (temp) {
-    nbits++;
-    temp >>= 1;
-  }
-  /* Check for out-of-range coefficient values.
-   * Since we're encoding a difference, the range limit is twice as much.
-   */
-  if (nbits > MAX_COEF_BITS+1)
-    ERREXIT(state->cinfo, JERR_BAD_DCT_COEF);
-  
-  /* Emit the Huffman-coded symbol for the number of bits */
-  if (! emit_bits(state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits]))
-    return FALSE;
-
-  /* Emit that number of bits of the value, if positive, */
-  /* or the complement of its magnitude, if negative. */
-  if (nbits)			/* emit_bits rejects calls with size 0 */
-    if (! emit_bits(state, (unsigned int) temp2, nbits))
-      return FALSE;
-
-  /* Encode the AC coefficients per section F.1.2.2 */
-  
-  r = 0;			/* r = run length of zeros */
-  
-  for (k = 1; k < DCTSIZE2; k++) {
-    if ((temp = block[jpeg_natural_order[k]]) == 0) {
-      r++;
-    } else {
-      /* if run length > 15, must emit special run-length-16 codes (0xF0) */
-      while (r > 15) {
-	if (! emit_bits(state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0]))
-	  return FALSE;
-	r -= 16;
-      }
-
-      temp2 = temp;
-      if (temp < 0) {
-	temp = -temp;		/* temp is abs value of input */
-	/* This code assumes we are on a two's complement machine */
-	temp2--;
-      }
-      
-      /* Find the number of bits needed for the magnitude of the coefficient */
-      nbits = 1;		/* there must be at least one 1 bit */
-      while ((temp >>= 1))
-	nbits++;
-      /* Check for out-of-range coefficient values */
-      if (nbits > MAX_COEF_BITS)
-	ERREXIT(state->cinfo, JERR_BAD_DCT_COEF);
-      
-      /* Emit Huffman symbol for run length / number of bits */
-      i = (r << 4) + nbits;
-      if (! emit_bits(state, actbl->ehufco[i], actbl->ehufsi[i]))
-	return FALSE;
-
-      /* Emit that number of bits of the value, if positive, */
-      /* or the complement of its magnitude, if negative. */
-      if (! emit_bits(state, (unsigned int) temp2, nbits))
-	return FALSE;
-      
-      r = 0;
-    }
-  }
-
-  /* If the last coef(s) were zero, emit an end-of-block code */
-  if (r > 0)
-    if (! emit_bits(state, actbl->ehufco[0], actbl->ehufsi[0]))
-      return FALSE;
-
-  return TRUE;
-}
-
-
-/*
- * Emit a restart marker & resynchronize predictions.
- */
-
-LOCAL(boolean)
-emit_restart (working_state * state, int restart_num)
-{
-  int ci;
-
-  if (! flush_bits(state))
-    return FALSE;
-
-  emit_byte(state, 0xFF, return FALSE);
-  emit_byte(state, JPEG_RST0 + restart_num, return FALSE);
-
-  /* Re-initialize DC predictions to 0 */
-  for (ci = 0; ci < state->cinfo->comps_in_scan; ci++)
-    state->cur.last_dc_val[ci] = 0;
-
-  /* The restart counter is not updated until we successfully write the MCU. */
-
-  return TRUE;
-}
-
-
-/*
- * Encode and output one MCU's worth of Huffman-compressed coefficients.
- */
-
-METHODDEF(boolean)
-encode_mcu_huff (j_compress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  working_state state;
-  int blkn, ci;
-  jpeg_component_info * compptr;
-
-  /* Load up working state */
-  state.next_output_byte = cinfo->dest->next_output_byte;
-  state.free_in_buffer = cinfo->dest->free_in_buffer;
-  ASSIGN_STATE(state.cur, entropy->saved);
-  state.cinfo = cinfo;
-
-  /* Emit restart marker if needed */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0)
-      if (! emit_restart(&state, entropy->next_restart_num))
-	return FALSE;
-  }
-
-  /* Encode the MCU data blocks */
-  for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-    ci = cinfo->MCU_membership[blkn];
-    compptr = cinfo->cur_comp_info[ci];
-    if (! encode_one_block(&state,
-			   MCU_data[blkn][0], state.cur.last_dc_val[ci],
-			   entropy->dc_derived_tbls[compptr->dc_tbl_no],
-			   entropy->ac_derived_tbls[compptr->ac_tbl_no]))
-      return FALSE;
-    /* Update last_dc_val */
-    state.cur.last_dc_val[ci] = MCU_data[blkn][0][0];
-  }
-
-  /* Completed MCU, so update state */
-  cinfo->dest->next_output_byte = state.next_output_byte;
-  cinfo->dest->free_in_buffer = state.free_in_buffer;
-  ASSIGN_STATE(entropy->saved, state.cur);
-
-  /* Update restart-interval state too */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0) {
-      entropy->restarts_to_go = cinfo->restart_interval;
-      entropy->next_restart_num++;
-      entropy->next_restart_num &= 7;
-    }
-    entropy->restarts_to_go--;
-  }
-
-  return TRUE;
-}
-
-
-/*
- * Finish up at the end of a Huffman-compressed scan.
- */
-
-METHODDEF(void)
-finish_pass_huff (j_compress_ptr cinfo)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  working_state state;
-
-  /* Load up working state ... flush_bits needs it */
-  state.next_output_byte = cinfo->dest->next_output_byte;
-  state.free_in_buffer = cinfo->dest->free_in_buffer;
-  ASSIGN_STATE(state.cur, entropy->saved);
-  state.cinfo = cinfo;
-
-  /* Flush out the last data */
-  if (! flush_bits(&state))
-    ERREXIT(cinfo, JERR_CANT_SUSPEND);
-
-  /* Update state */
-  cinfo->dest->next_output_byte = state.next_output_byte;
-  cinfo->dest->free_in_buffer = state.free_in_buffer;
-  ASSIGN_STATE(entropy->saved, state.cur);
-}
-
-
-/*
- * Huffman coding optimization.
- *
- * We first scan the supplied data and count the number of uses of each symbol
- * that is to be Huffman-coded. (This process MUST agree with the code above.)
- * Then we build a Huffman coding tree for the observed counts.
- * Symbols which are not needed at all for the particular image are not
- * assigned any code, which saves space in the DHT marker as well as in
- * the compressed data.
- */
-
-#ifdef ENTROPY_OPT_SUPPORTED
-
-
-/* Process a single block's worth of coefficients */
-
-LOCAL(void)
-htest_one_block (j_compress_ptr cinfo, JCOEFPTR block, int last_dc_val,
-		 long dc_counts[], long ac_counts[])
-{
-  register int temp;
-  register int nbits;
-  register int k, r;
-  
-  /* Encode the DC coefficient difference per section F.1.2.1 */
-  
-  temp = block[0] - last_dc_val;
-  if (temp < 0)
-    temp = -temp;
-  
-  /* Find the number of bits needed for the magnitude of the coefficient */
-  nbits = 0;
-  while (temp) {
-    nbits++;
-    temp >>= 1;
-  }
-  /* Check for out-of-range coefficient values.
-   * Since we're encoding a difference, the range limit is twice as much.
-   */
-  if (nbits > MAX_COEF_BITS+1)
-    ERREXIT(cinfo, JERR_BAD_DCT_COEF);
-
-  /* Count the Huffman symbol for the number of bits */
-  dc_counts[nbits]++;
-  
-  /* Encode the AC coefficients per section F.1.2.2 */
-  
-  r = 0;			/* r = run length of zeros */
-  
-  for (k = 1; k < DCTSIZE2; k++) {
-    if ((temp = block[jpeg_natural_order[k]]) == 0) {
-      r++;
-    } else {
-      /* if run length > 15, must emit special run-length-16 codes (0xF0) */
-      while (r > 15) {
-	ac_counts[0xF0]++;
-	r -= 16;
-      }
-      
-      /* Find the number of bits needed for the magnitude of the coefficient */
-      if (temp < 0)
-	temp = -temp;
-      
-      /* Find the number of bits needed for the magnitude of the coefficient */
-      nbits = 1;		/* there must be at least one 1 bit */
-      while ((temp >>= 1))
-	nbits++;
-      /* Check for out-of-range coefficient values */
-      if (nbits > MAX_COEF_BITS)
-	ERREXIT(cinfo, JERR_BAD_DCT_COEF);
-      
-      /* Count Huffman symbol for run length / number of bits */
-      ac_counts[(r << 4) + nbits]++;
-      
-      r = 0;
-    }
-  }
-
-  /* If the last coef(s) were zero, emit an end-of-block code */
-  if (r > 0)
-    ac_counts[0]++;
-}
-
-
-/*
- * Trial-encode one MCU's worth of Huffman-compressed coefficients.
- * No data is actually output, so no suspension return is possible.
- */
-
-METHODDEF(boolean)
-encode_mcu_gather (j_compress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  int blkn, ci;
-  jpeg_component_info * compptr;
-
-  /* Take care of restart intervals if needed */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0) {
-      /* Re-initialize DC predictions to 0 */
-      for (ci = 0; ci < cinfo->comps_in_scan; ci++)
-	entropy->saved.last_dc_val[ci] = 0;
-      /* Update restart state */
-      entropy->restarts_to_go = cinfo->restart_interval;
-    }
-    entropy->restarts_to_go--;
-  }
-
-  for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-    ci = cinfo->MCU_membership[blkn];
-    compptr = cinfo->cur_comp_info[ci];
-    htest_one_block(cinfo, MCU_data[blkn][0], entropy->saved.last_dc_val[ci],
-		    entropy->dc_count_ptrs[compptr->dc_tbl_no],
-		    entropy->ac_count_ptrs[compptr->ac_tbl_no]);
-    entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0];
-  }
-
-  return TRUE;
-}
-
-
-/*
- * Generate the best Huffman code table for the given counts, fill htbl.
- * Note this is also used by jcphuff.c.
- *
- * The JPEG standard requires that no symbol be assigned a codeword of all
- * one bits (so that padding bits added at the end of a compressed segment
- * can't look like a valid code).  Because of the canonical ordering of
- * codewords, this just means that there must be an unused slot in the
- * longest codeword length category.  Section K.2 of the JPEG spec suggests
- * reserving such a slot by pretending that symbol 256 is a valid symbol
- * with count 1.  In theory that's not optimal; giving it count zero but
- * including it in the symbol set anyway should give a better Huffman code.
- * But the theoretically better code actually seems to come out worse in
- * practice, because it produces more all-ones bytes (which incur stuffed
- * zero bytes in the final file).  In any case the difference is tiny.
- *
- * The JPEG standard requires Huffman codes to be no more than 16 bits long.
- * If some symbols have a very small but nonzero probability, the Huffman tree
- * must be adjusted to meet the code length restriction.  We currently use
- * the adjustment method suggested in JPEG section K.2.  This method is *not*
- * optimal; it may not choose the best possible limited-length code.  But
- * typically only very-low-frequency symbols will be given less-than-optimal
- * lengths, so the code is almost optimal.  Experimental comparisons against
- * an optimal limited-length-code algorithm indicate that the difference is
- * microscopic --- usually less than a hundredth of a percent of total size.
- * So the extra complexity of an optimal algorithm doesn't seem worthwhile.
- */
-
-GLOBAL(void)
-jpeg_gen_optimal_table (j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[])
-{
-#define MAX_CLEN 32		/* assumed maximum initial code length */
-  UINT8 bits[MAX_CLEN+1];	/* bits[k] = # of symbols with code length k */
-  int codesize[257];		/* codesize[k] = code length of symbol k */
-  int others[257];		/* next symbol in current branch of tree */
-  int c1, c2;
-  int p, i, j;
-  long v;
-
-  /* This algorithm is explained in section K.2 of the JPEG standard */
-
-  MEMZERO(bits, SIZEOF(bits));
-  MEMZERO(codesize, SIZEOF(codesize));
-  for (i = 0; i < 257; i++)
-    others[i] = -1;		/* init links to empty */
-  
-  freq[256] = 1;		/* make sure 256 has a nonzero count */
-  /* Including the pseudo-symbol 256 in the Huffman procedure guarantees
-   * that no real symbol is given code-value of all ones, because 256
-   * will be placed last in the largest codeword category.
-   */
-
-  /* Huffman's basic algorithm to assign optimal code lengths to symbols */
-
-  for (;;) {
-    /* Find the smallest nonzero frequency, set c1 = its symbol */
-    /* In case of ties, take the larger symbol number */
-    c1 = -1;
-    v = 1000000000L;
-    for (i = 0; i <= 256; i++) {
-      if (freq[i] && freq[i] <= v) {
-	v = freq[i];
-	c1 = i;
-      }
-    }
-
-    /* Find the next smallest nonzero frequency, set c2 = its symbol */
-    /* In case of ties, take the larger symbol number */
-    c2 = -1;
-    v = 1000000000L;
-    for (i = 0; i <= 256; i++) {
-      if (freq[i] && freq[i] <= v && i != c1) {
-	v = freq[i];
-	c2 = i;
-      }
-    }
-
-    /* Done if we've merged everything into one frequency */
-    if (c2 < 0)
-      break;
-    
-    /* Else merge the two counts/trees */
-    freq[c1] += freq[c2];
-    freq[c2] = 0;
-
-    /* Increment the codesize of everything in c1's tree branch */
-    codesize[c1]++;
-    while (others[c1] >= 0) {
-      c1 = others[c1];
-      codesize[c1]++;
-    }
-    
-    others[c1] = c2;		/* chain c2 onto c1's tree branch */
-    
-    /* Increment the codesize of everything in c2's tree branch */
-    codesize[c2]++;
-    while (others[c2] >= 0) {
-      c2 = others[c2];
-      codesize[c2]++;
-    }
-  }
-
-  /* Now count the number of symbols of each code length */
-  for (i = 0; i <= 256; i++) {
-    if (codesize[i]) {
-      /* The JPEG standard seems to think that this can't happen, */
-      /* but I'm paranoid... */
-      if (codesize[i] > MAX_CLEN)
-	ERREXIT(cinfo, JERR_HUFF_CLEN_OVERFLOW);
-
-      bits[codesize[i]]++;
-    }
-  }
-
-  /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure
-   * Huffman procedure assigned any such lengths, we must adjust the coding.
-   * Here is what the JPEG spec says about how this next bit works:
-   * Since symbols are paired for the longest Huffman code, the symbols are
-   * removed from this length category two at a time.  The prefix for the pair
-   * (which is one bit shorter) is allocated to one of the pair; then,
-   * skipping the BITS entry for that prefix length, a code word from the next
-   * shortest nonzero BITS entry is converted into a prefix for two code words
-   * one bit longer.
-   */
-  
-  for (i = MAX_CLEN; i > 16; i--) {
-    while (bits[i] > 0) {
-      j = i - 2;		/* find length of new prefix to be used */
-      while (bits[j] == 0)
-	j--;
-      
-      bits[i] -= 2;		/* remove two symbols */
-      bits[i-1]++;		/* one goes in this length */
-      bits[j+1] += 2;		/* two new symbols in this length */
-      bits[j]--;		/* symbol of this length is now a prefix */
-    }
-  }
-
-  /* Remove the count for the pseudo-symbol 256 from the largest codelength */
-  while (bits[i] == 0)		/* find largest codelength still in use */
-    i--;
-  bits[i]--;
-  
-  /* Return final symbol counts (only for lengths 0..16) */
-  MEMCOPY(htbl->bits, bits, SIZEOF(htbl->bits));
-  
-  /* Return a list of the symbols sorted by code length */
-  /* It's not real clear to me why we don't need to consider the codelength
-   * changes made above, but the JPEG spec seems to think this works.
-   */
-  p = 0;
-  for (i = 1; i <= MAX_CLEN; i++) {
-    for (j = 0; j <= 255; j++) {
-      if (codesize[j] == i) {
-	htbl->huffval[p] = (UINT8) j;
-	p++;
-      }
-    }
-  }
-
-  /* Set sent_table FALSE so updated table will be written to JPEG file. */
-  htbl->sent_table = FALSE;
-}
-
-
-/*
- * Finish up a statistics-gathering pass and create the new Huffman tables.
- */
-
-METHODDEF(void)
-finish_pass_gather (j_compress_ptr cinfo)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  int ci, dctbl, actbl;
-  jpeg_component_info * compptr;
-  JHUFF_TBL **htblptr;
-  boolean did_dc[NUM_HUFF_TBLS];
-  boolean did_ac[NUM_HUFF_TBLS];
-
-  /* It's important not to apply jpeg_gen_optimal_table more than once
-   * per table, because it clobbers the input frequency counts!
-   */
-  MEMZERO(did_dc, SIZEOF(did_dc));
-  MEMZERO(did_ac, SIZEOF(did_ac));
-
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    dctbl = compptr->dc_tbl_no;
-    actbl = compptr->ac_tbl_no;
-    if (! did_dc[dctbl]) {
-      htblptr = & cinfo->dc_huff_tbl_ptrs[dctbl];
-      if (*htblptr == NULL)
-	*htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo);
-      jpeg_gen_optimal_table(cinfo, *htblptr, entropy->dc_count_ptrs[dctbl]);
-      did_dc[dctbl] = TRUE;
-    }
-    if (! did_ac[actbl]) {
-      htblptr = & cinfo->ac_huff_tbl_ptrs[actbl];
-      if (*htblptr == NULL)
-	*htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo);
-      jpeg_gen_optimal_table(cinfo, *htblptr, entropy->ac_count_ptrs[actbl]);
-      did_ac[actbl] = TRUE;
-    }
-  }
-}
-
-
-#endif /* ENTROPY_OPT_SUPPORTED */
-
-
-/*
- * Module initialization routine for Huffman entropy encoding.
- */
-
-GLOBAL(void)
-jinit_huff_encoder (j_compress_ptr cinfo)
-{
-  huff_entropy_ptr entropy;
-  int i;
-
-  entropy = (huff_entropy_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(huff_entropy_encoder));
-  cinfo->entropy = (struct jpeg_entropy_encoder *) entropy;
-  entropy->pub.start_pass = start_pass_huff;
-
-  /* Mark tables unallocated */
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL;
-#ifdef ENTROPY_OPT_SUPPORTED
-    entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL;
-#endif
-  }
-}
diff --git a/third_party/libjpeg/jchuff.h b/third_party/libjpeg/jchuff.h
deleted file mode 100644
index a9599fc..0000000
--- a/third_party/libjpeg/jchuff.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * jchuff.h
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains declarations for Huffman entropy encoding routines
- * that are shared between the sequential encoder (jchuff.c) and the
- * progressive encoder (jcphuff.c).  No other modules need to see these.
- */
-
-/* The legal range of a DCT coefficient is
- *  -1024 .. +1023  for 8-bit data;
- * -16384 .. +16383 for 12-bit data.
- * Hence the magnitude should always fit in 10 or 14 bits respectively.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define MAX_COEF_BITS 10
-#else
-#define MAX_COEF_BITS 14
-#endif
-
-/* Derived data constructed for each Huffman table */
-
-typedef struct {
-  unsigned int ehufco[256];	/* code for each symbol */
-  char ehufsi[256];		/* length of code for each symbol */
-  /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */
-} c_derived_tbl;
-
-/* Short forms of external names for systems with brain-damaged linkers. */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jpeg_make_c_derived_tbl	jMkCDerived
-#define jpeg_gen_optimal_table	jGenOptTbl
-#endif /* NEED_SHORT_EXTERNAL_NAMES */
-
-/* Expand a Huffman table definition into the derived format */
-EXTERN(void) jpeg_make_c_derived_tbl
-	JPP((j_compress_ptr cinfo, boolean isDC, int tblno,
-	     c_derived_tbl ** pdtbl));
-
-/* Generate an optimal table definition given the specified counts */
-EXTERN(void) jpeg_gen_optimal_table
-	JPP((j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[]));
diff --git a/third_party/libjpeg/jcinit.c b/third_party/libjpeg/jcinit.c
deleted file mode 100644
index 5efffe3..0000000
--- a/third_party/libjpeg/jcinit.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * jcinit.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains initialization logic for the JPEG compressor.
- * This routine is in charge of selecting the modules to be executed and
- * making an initialization call to each one.
- *
- * Logically, this code belongs in jcmaster.c.  It's split out because
- * linking this routine implies linking the entire compression library.
- * For a transcoding-only application, we want to be able to use jcmaster.c
- * without linking in the whole library.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * Master selection of compression modules.
- * This is done once at the start of processing an image.  We determine
- * which modules will be used and give them appropriate initialization calls.
- */
-
-GLOBAL(void)
-jinit_compress_master (j_compress_ptr cinfo)
-{
-  /* Initialize master control (includes parameter checking/processing) */
-  jinit_c_master_control(cinfo, FALSE /* full compression */);
-
-  /* Preprocessing */
-  if (! cinfo->raw_data_in) {
-    jinit_color_converter(cinfo);
-    jinit_downsampler(cinfo);
-    jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */);
-  }
-  /* Forward DCT */
-  jinit_forward_dct(cinfo);
-  /* Entropy encoding: either Huffman or arithmetic coding. */
-  if (cinfo->arith_code) {
-    ERREXIT(cinfo, JERR_ARITH_NOTIMPL);
-  } else {
-    if (cinfo->progressive_mode) {
-#ifdef C_PROGRESSIVE_SUPPORTED
-      jinit_phuff_encoder(cinfo);
-#else
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-    } else
-      jinit_huff_encoder(cinfo);
-  }
-
-  /* Need a full-image coefficient buffer in any multi-pass mode. */
-  jinit_c_coef_controller(cinfo,
-		(boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding));
-  jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */);
-
-  jinit_marker_writer(cinfo);
-
-  /* We can now tell the memory manager to allocate virtual arrays. */
-  (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo);
-
-  /* Write the datastream header (SOI) immediately.
-   * Frame and scan headers are postponed till later.
-   * This lets application insert special markers after the SOI.
-   */
-  (*cinfo->marker->write_file_header) (cinfo);
-}
diff --git a/third_party/libjpeg/jcmainct.c b/third_party/libjpeg/jcmainct.c
deleted file mode 100644
index e0279a7..0000000
--- a/third_party/libjpeg/jcmainct.c
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * jcmainct.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the main buffer controller for compression.
- * The main buffer lies between the pre-processor and the JPEG
- * compressor proper; it holds downsampled data in the JPEG colorspace.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Note: currently, there is no operating mode in which a full-image buffer
- * is needed at this step.  If there were, that mode could not be used with
- * "raw data" input, since this module is bypassed in that case.  However,
- * we've left the code here for possible use in special applications.
- */
-#undef FULL_MAIN_BUFFER_SUPPORTED
-
-
-/* Private buffer controller object */
-
-typedef struct {
-  struct jpeg_c_main_controller pub; /* public fields */
-
-  JDIMENSION cur_iMCU_row;	/* number of current iMCU row */
-  JDIMENSION rowgroup_ctr;	/* counts row groups received in iMCU row */
-  boolean suspended;		/* remember if we suspended output */
-  J_BUF_MODE pass_mode;		/* current operating mode */
-
-  /* If using just a strip buffer, this points to the entire set of buffers
-   * (we allocate one for each component).  In the full-image case, this
-   * points to the currently accessible strips of the virtual arrays.
-   */
-  JSAMPARRAY buffer[MAX_COMPONENTS];
-
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-  /* If using full-image storage, this array holds pointers to virtual-array
-   * control blocks for each component.  Unused if not full-image storage.
-   */
-  jvirt_sarray_ptr whole_image[MAX_COMPONENTS];
-#endif
-} my_main_controller;
-
-typedef my_main_controller * my_main_ptr;
-
-
-/* Forward declarations */
-METHODDEF(void) process_data_simple_main
-	JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf,
-	     JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail));
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-METHODDEF(void) process_data_buffer_main
-	JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf,
-	     JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail));
-#endif
-
-
-/*
- * Initialize for a processing pass.
- */
-
-METHODDEF(void)
-start_pass_main (j_compress_ptr cinfo, J_BUF_MODE pass_mode)
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-
-  /* Do nothing in raw-data mode. */
-  if (cinfo->raw_data_in)
-    return;
-
-  main->cur_iMCU_row = 0;	/* initialize counters */
-  main->rowgroup_ctr = 0;
-  main->suspended = FALSE;
-  main->pass_mode = pass_mode;	/* save mode for use by process_data */
-
-  switch (pass_mode) {
-  case JBUF_PASS_THRU:
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-    if (main->whole_image[0] != NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-#endif
-    main->pub.process_data = process_data_simple_main;
-    break;
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-  case JBUF_SAVE_SOURCE:
-  case JBUF_CRANK_DEST:
-  case JBUF_SAVE_AND_PASS:
-    if (main->whole_image[0] == NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    main->pub.process_data = process_data_buffer_main;
-    break;
-#endif
-  default:
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    break;
-  }
-}
-
-
-/*
- * Process some data.
- * This routine handles the simple pass-through mode,
- * where we have only a strip buffer.
- */
-
-METHODDEF(void)
-process_data_simple_main (j_compress_ptr cinfo,
-			  JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
-			  JDIMENSION in_rows_avail)
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-
-  while (main->cur_iMCU_row < cinfo->total_iMCU_rows) {
-    /* Read input data if we haven't filled the main buffer yet */
-    if (main->rowgroup_ctr < DCTSIZE)
-      (*cinfo->prep->pre_process_data) (cinfo,
-					input_buf, in_row_ctr, in_rows_avail,
-					main->buffer, &main->rowgroup_ctr,
-					(JDIMENSION) DCTSIZE);
-
-    /* If we don't have a full iMCU row buffered, return to application for
-     * more data.  Note that preprocessor will always pad to fill the iMCU row
-     * at the bottom of the image.
-     */
-    if (main->rowgroup_ctr != DCTSIZE)
-      return;
-
-    /* Send the completed row to the compressor */
-    if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) {
-      /* If compressor did not consume the whole row, then we must need to
-       * suspend processing and return to the application.  In this situation
-       * we pretend we didn't yet consume the last input row; otherwise, if
-       * it happened to be the last row of the image, the application would
-       * think we were done.
-       */
-      if (! main->suspended) {
-	(*in_row_ctr)--;
-	main->suspended = TRUE;
-      }
-      return;
-    }
-    /* We did finish the row.  Undo our little suspension hack if a previous
-     * call suspended; then mark the main buffer empty.
-     */
-    if (main->suspended) {
-      (*in_row_ctr)++;
-      main->suspended = FALSE;
-    }
-    main->rowgroup_ctr = 0;
-    main->cur_iMCU_row++;
-  }
-}
-
-
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-
-/*
- * Process some data.
- * This routine handles all of the modes that use a full-size buffer.
- */
-
-METHODDEF(void)
-process_data_buffer_main (j_compress_ptr cinfo,
-			  JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
-			  JDIMENSION in_rows_avail)
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-  int ci;
-  jpeg_component_info *compptr;
-  boolean writing = (main->pass_mode != JBUF_CRANK_DEST);
-
-  while (main->cur_iMCU_row < cinfo->total_iMCU_rows) {
-    /* Realign the virtual buffers if at the start of an iMCU row. */
-    if (main->rowgroup_ctr == 0) {
-      for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	   ci++, compptr++) {
-	main->buffer[ci] = (*cinfo->mem->access_virt_sarray)
-	  ((j_common_ptr) cinfo, main->whole_image[ci],
-	   main->cur_iMCU_row * (compptr->v_samp_factor * DCTSIZE),
-	   (JDIMENSION) (compptr->v_samp_factor * DCTSIZE), writing);
-      }
-      /* In a read pass, pretend we just read some source data. */
-      if (! writing) {
-	*in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE;
-	main->rowgroup_ctr = DCTSIZE;
-      }
-    }
-
-    /* If a write pass, read input data until the current iMCU row is full. */
-    /* Note: preprocessor will pad if necessary to fill the last iMCU row. */
-    if (writing) {
-      (*cinfo->prep->pre_process_data) (cinfo,
-					input_buf, in_row_ctr, in_rows_avail,
-					main->buffer, &main->rowgroup_ctr,
-					(JDIMENSION) DCTSIZE);
-      /* Return to application if we need more data to fill the iMCU row. */
-      if (main->rowgroup_ctr < DCTSIZE)
-	return;
-    }
-
-    /* Emit data, unless this is a sink-only pass. */
-    if (main->pass_mode != JBUF_SAVE_SOURCE) {
-      if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) {
-	/* If compressor did not consume the whole row, then we must need to
-	 * suspend processing and return to the application.  In this situation
-	 * we pretend we didn't yet consume the last input row; otherwise, if
-	 * it happened to be the last row of the image, the application would
-	 * think we were done.
-	 */
-	if (! main->suspended) {
-	  (*in_row_ctr)--;
-	  main->suspended = TRUE;
-	}
-	return;
-      }
-      /* We did finish the row.  Undo our little suspension hack if a previous
-       * call suspended; then mark the main buffer empty.
-       */
-      if (main->suspended) {
-	(*in_row_ctr)++;
-	main->suspended = FALSE;
-      }
-    }
-
-    /* If get here, we are done with this iMCU row.  Mark buffer empty. */
-    main->rowgroup_ctr = 0;
-    main->cur_iMCU_row++;
-  }
-}
-
-#endif /* FULL_MAIN_BUFFER_SUPPORTED */
-
-
-/*
- * Initialize main buffer controller.
- */
-
-GLOBAL(void)
-jinit_c_main_controller (j_compress_ptr cinfo, boolean need_full_buffer)
-{
-  my_main_ptr main;
-  int ci;
-  jpeg_component_info *compptr;
-
-  main = (my_main_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_main_controller));
-  cinfo->main = (struct jpeg_c_main_controller *) main;
-  main->pub.start_pass = start_pass_main;
-
-  /* We don't need to create a buffer in raw-data mode. */
-  if (cinfo->raw_data_in)
-    return;
-
-  /* Create the buffer.  It holds downsampled data, so each component
-   * may be of a different size.
-   */
-  if (need_full_buffer) {
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-    /* Allocate a full-image virtual array for each component */
-    /* Note we pad the bottom to a multiple of the iMCU height */
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      main->whole_image[ci] = (*cinfo->mem->request_virt_sarray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE,
-	 compptr->width_in_blocks * DCTSIZE,
-	 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
-				(long) compptr->v_samp_factor) * DCTSIZE,
-	 (JDIMENSION) (compptr->v_samp_factor * DCTSIZE));
-    }
-#else
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-#endif
-  } else {
-#ifdef FULL_MAIN_BUFFER_SUPPORTED
-    main->whole_image[0] = NULL; /* flag for no virtual arrays */
-#endif
-    /* Allocate a strip buffer for each component */
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      main->buffer[ci] = (*cinfo->mem->alloc_sarray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE,
-	 compptr->width_in_blocks * DCTSIZE,
-	 (JDIMENSION) (compptr->v_samp_factor * DCTSIZE));
-    }
-  }
-}
diff --git a/third_party/libjpeg/jcmarker.c b/third_party/libjpeg/jcmarker.c
deleted file mode 100644
index 3d1e6c6..0000000
--- a/third_party/libjpeg/jcmarker.c
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * jcmarker.c
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains routines to write JPEG datastream markers.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-typedef enum {			/* JPEG marker codes */
-  M_SOF0  = 0xc0,
-  M_SOF1  = 0xc1,
-  M_SOF2  = 0xc2,
-  M_SOF3  = 0xc3,
-  
-  M_SOF5  = 0xc5,
-  M_SOF6  = 0xc6,
-  M_SOF7  = 0xc7,
-  
-  M_JPG   = 0xc8,
-  M_SOF9  = 0xc9,
-  M_SOF10 = 0xca,
-  M_SOF11 = 0xcb,
-  
-  M_SOF13 = 0xcd,
-  M_SOF14 = 0xce,
-  M_SOF15 = 0xcf,
-  
-  M_DHT   = 0xc4,
-  
-  M_DAC   = 0xcc,
-  
-  M_RST0  = 0xd0,
-  M_RST1  = 0xd1,
-  M_RST2  = 0xd2,
-  M_RST3  = 0xd3,
-  M_RST4  = 0xd4,
-  M_RST5  = 0xd5,
-  M_RST6  = 0xd6,
-  M_RST7  = 0xd7,
-  
-  M_SOI   = 0xd8,
-  M_EOI   = 0xd9,
-  M_SOS   = 0xda,
-  M_DQT   = 0xdb,
-  M_DNL   = 0xdc,
-  M_DRI   = 0xdd,
-  M_DHP   = 0xde,
-  M_EXP   = 0xdf,
-  
-  M_APP0  = 0xe0,
-  M_APP1  = 0xe1,
-  M_APP2  = 0xe2,
-  M_APP3  = 0xe3,
-  M_APP4  = 0xe4,
-  M_APP5  = 0xe5,
-  M_APP6  = 0xe6,
-  M_APP7  = 0xe7,
-  M_APP8  = 0xe8,
-  M_APP9  = 0xe9,
-  M_APP10 = 0xea,
-  M_APP11 = 0xeb,
-  M_APP12 = 0xec,
-  M_APP13 = 0xed,
-  M_APP14 = 0xee,
-  M_APP15 = 0xef,
-  
-  M_JPG0  = 0xf0,
-  M_JPG13 = 0xfd,
-  M_COM   = 0xfe,
-  
-  M_TEM   = 0x01,
-  
-  M_ERROR = 0x100
-} JPEG_MARKER;
-
-
-/* Private state */
-
-typedef struct {
-  struct jpeg_marker_writer pub; /* public fields */
-
-  unsigned int last_restart_interval; /* last DRI value emitted; 0 after SOI */
-} my_marker_writer;
-
-typedef my_marker_writer * my_marker_ptr;
-
-
-/*
- * Basic output routines.
- *
- * Note that we do not support suspension while writing a marker.
- * Therefore, an application using suspension must ensure that there is
- * enough buffer space for the initial markers (typ. 600-700 bytes) before
- * calling jpeg_start_compress, and enough space to write the trailing EOI
- * (a few bytes) before calling jpeg_finish_compress.  Multipass compression
- * modes are not supported at all with suspension, so those two are the only
- * points where markers will be written.
- */
-
-LOCAL(void)
-emit_byte (j_compress_ptr cinfo, int val)
-/* Emit a byte */
-{
-  struct jpeg_destination_mgr * dest = cinfo->dest;
-
-  *(dest->next_output_byte)++ = (JOCTET) val;
-  if (--dest->free_in_buffer == 0) {
-    if (! (*dest->empty_output_buffer) (cinfo))
-      ERREXIT(cinfo, JERR_CANT_SUSPEND);
-  }
-}
-
-
-LOCAL(void)
-emit_marker (j_compress_ptr cinfo, JPEG_MARKER mark)
-/* Emit a marker code */
-{
-  emit_byte(cinfo, 0xFF);
-  emit_byte(cinfo, (int) mark);
-}
-
-
-LOCAL(void)
-emit_2bytes (j_compress_ptr cinfo, int value)
-/* Emit a 2-byte integer; these are always MSB first in JPEG files */
-{
-  emit_byte(cinfo, (value >> 8) & 0xFF);
-  emit_byte(cinfo, value & 0xFF);
-}
-
-
-/*
- * Routines to write specific marker types.
- */
-
-LOCAL(int)
-emit_dqt (j_compress_ptr cinfo, int index)
-/* Emit a DQT marker */
-/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */
-{
-  JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index];
-  int prec;
-  int i;
-
-  if (qtbl == NULL)
-    ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index);
-
-  prec = 0;
-  for (i = 0; i < DCTSIZE2; i++) {
-    if (qtbl->quantval[i] > 255)
-      prec = 1;
-  }
-
-  if (! qtbl->sent_table) {
-    emit_marker(cinfo, M_DQT);
-
-    emit_2bytes(cinfo, prec ? DCTSIZE2*2 + 1 + 2 : DCTSIZE2 + 1 + 2);
-
-    emit_byte(cinfo, index + (prec<<4));
-
-    for (i = 0; i < DCTSIZE2; i++) {
-      /* The table entries must be emitted in zigzag order. */
-      unsigned int qval = qtbl->quantval[jpeg_natural_order[i]];
-      if (prec)
-	emit_byte(cinfo, (int) (qval >> 8));
-      emit_byte(cinfo, (int) (qval & 0xFF));
-    }
-
-    qtbl->sent_table = TRUE;
-  }
-
-  return prec;
-}
-
-
-LOCAL(void)
-emit_dht (j_compress_ptr cinfo, int index, boolean is_ac)
-/* Emit a DHT marker */
-{
-  JHUFF_TBL * htbl;
-  int length, i;
-  
-  if (is_ac) {
-    htbl = cinfo->ac_huff_tbl_ptrs[index];
-    index += 0x10;		/* output index has AC bit set */
-  } else {
-    htbl = cinfo->dc_huff_tbl_ptrs[index];
-  }
-
-  if (htbl == NULL)
-    ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index);
-  
-  if (! htbl->sent_table) {
-    emit_marker(cinfo, M_DHT);
-    
-    length = 0;
-    for (i = 1; i <= 16; i++)
-      length += htbl->bits[i];
-    
-    emit_2bytes(cinfo, length + 2 + 1 + 16);
-    emit_byte(cinfo, index);
-    
-    for (i = 1; i <= 16; i++)
-      emit_byte(cinfo, htbl->bits[i]);
-    
-    for (i = 0; i < length; i++)
-      emit_byte(cinfo, htbl->huffval[i]);
-    
-    htbl->sent_table = TRUE;
-  }
-}
-
-
-LOCAL(void)
-emit_dac (j_compress_ptr cinfo)
-/* Emit a DAC marker */
-/* Since the useful info is so small, we want to emit all the tables in */
-/* one DAC marker.  Therefore this routine does its own scan of the table. */
-{
-#ifdef C_ARITH_CODING_SUPPORTED
-  char dc_in_use[NUM_ARITH_TBLS];
-  char ac_in_use[NUM_ARITH_TBLS];
-  int length, i;
-  jpeg_component_info *compptr;
-  
-  for (i = 0; i < NUM_ARITH_TBLS; i++)
-    dc_in_use[i] = ac_in_use[i] = 0;
-  
-  for (i = 0; i < cinfo->comps_in_scan; i++) {
-    compptr = cinfo->cur_comp_info[i];
-    dc_in_use[compptr->dc_tbl_no] = 1;
-    ac_in_use[compptr->ac_tbl_no] = 1;
-  }
-  
-  length = 0;
-  for (i = 0; i < NUM_ARITH_TBLS; i++)
-    length += dc_in_use[i] + ac_in_use[i];
-  
-  emit_marker(cinfo, M_DAC);
-  
-  emit_2bytes(cinfo, length*2 + 2);
-  
-  for (i = 0; i < NUM_ARITH_TBLS; i++) {
-    if (dc_in_use[i]) {
-      emit_byte(cinfo, i);
-      emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i]<<4));
-    }
-    if (ac_in_use[i]) {
-      emit_byte(cinfo, i + 0x10);
-      emit_byte(cinfo, cinfo->arith_ac_K[i]);
-    }
-  }
-#endif /* C_ARITH_CODING_SUPPORTED */
-}
-
-
-LOCAL(void)
-emit_dri (j_compress_ptr cinfo)
-/* Emit a DRI marker */
-{
-  emit_marker(cinfo, M_DRI);
-  
-  emit_2bytes(cinfo, 4);	/* fixed length */
-
-  emit_2bytes(cinfo, (int) cinfo->restart_interval);
-}
-
-
-LOCAL(void)
-emit_sof (j_compress_ptr cinfo, JPEG_MARKER code)
-/* Emit a SOF marker */
-{
-  int ci;
-  jpeg_component_info *compptr;
-  
-  emit_marker(cinfo, code);
-  
-  emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */
-
-  /* Make sure image isn't bigger than SOF field can handle */
-  if ((long) cinfo->image_height > 65535L ||
-      (long) cinfo->image_width > 65535L)
-    ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535);
-
-  emit_byte(cinfo, cinfo->data_precision);
-  emit_2bytes(cinfo, (int) cinfo->image_height);
-  emit_2bytes(cinfo, (int) cinfo->image_width);
-
-  emit_byte(cinfo, cinfo->num_components);
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    emit_byte(cinfo, compptr->component_id);
-    emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor);
-    emit_byte(cinfo, compptr->quant_tbl_no);
-  }
-}
-
-
-LOCAL(void)
-emit_sos (j_compress_ptr cinfo)
-/* Emit a SOS marker */
-{
-  int i, td, ta;
-  jpeg_component_info *compptr;
-  
-  emit_marker(cinfo, M_SOS);
-  
-  emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */
-  
-  emit_byte(cinfo, cinfo->comps_in_scan);
-  
-  for (i = 0; i < cinfo->comps_in_scan; i++) {
-    compptr = cinfo->cur_comp_info[i];
-    emit_byte(cinfo, compptr->component_id);
-    td = compptr->dc_tbl_no;
-    ta = compptr->ac_tbl_no;
-    if (cinfo->progressive_mode) {
-      /* Progressive mode: only DC or only AC tables are used in one scan;
-       * furthermore, Huffman coding of DC refinement uses no table at all.
-       * We emit 0 for unused field(s); this is recommended by the P&M text
-       * but does not seem to be specified in the standard.
-       */
-      if (cinfo->Ss == 0) {
-	ta = 0;			/* DC scan */
-	if (cinfo->Ah != 0 && !cinfo->arith_code)
-	  td = 0;		/* no DC table either */
-      } else {
-	td = 0;			/* AC scan */
-      }
-    }
-    emit_byte(cinfo, (td << 4) + ta);
-  }
-
-  emit_byte(cinfo, cinfo->Ss);
-  emit_byte(cinfo, cinfo->Se);
-  emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al);
-}
-
-
-LOCAL(void)
-emit_jfif_app0 (j_compress_ptr cinfo)
-/* Emit a JFIF-compliant APP0 marker */
-{
-  /*
-   * Length of APP0 block	(2 bytes)
-   * Block ID			(4 bytes - ASCII "JFIF")
-   * Zero byte			(1 byte to terminate the ID string)
-   * Version Major, Minor	(2 bytes - major first)
-   * Units			(1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm)
-   * Xdpu			(2 bytes - dots per unit horizontal)
-   * Ydpu			(2 bytes - dots per unit vertical)
-   * Thumbnail X size		(1 byte)
-   * Thumbnail Y size		(1 byte)
-   */
-  
-  emit_marker(cinfo, M_APP0);
-  
-  emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */
-
-  emit_byte(cinfo, 0x4A);	/* Identifier: ASCII "JFIF" */
-  emit_byte(cinfo, 0x46);
-  emit_byte(cinfo, 0x49);
-  emit_byte(cinfo, 0x46);
-  emit_byte(cinfo, 0);
-  emit_byte(cinfo, cinfo->JFIF_major_version); /* Version fields */
-  emit_byte(cinfo, cinfo->JFIF_minor_version);
-  emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */
-  emit_2bytes(cinfo, (int) cinfo->X_density);
-  emit_2bytes(cinfo, (int) cinfo->Y_density);
-  emit_byte(cinfo, 0);		/* No thumbnail image */
-  emit_byte(cinfo, 0);
-}
-
-
-LOCAL(void)
-emit_adobe_app14 (j_compress_ptr cinfo)
-/* Emit an Adobe APP14 marker */
-{
-  /*
-   * Length of APP14 block	(2 bytes)
-   * Block ID			(5 bytes - ASCII "Adobe")
-   * Version Number		(2 bytes - currently 100)
-   * Flags0			(2 bytes - currently 0)
-   * Flags1			(2 bytes - currently 0)
-   * Color transform		(1 byte)
-   *
-   * Although Adobe TN 5116 mentions Version = 101, all the Adobe files
-   * now in circulation seem to use Version = 100, so that's what we write.
-   *
-   * We write the color transform byte as 1 if the JPEG color space is
-   * YCbCr, 2 if it's YCCK, 0 otherwise.  Adobe's definition has to do with
-   * whether the encoder performed a transformation, which is pretty useless.
-   */
-  
-  emit_marker(cinfo, M_APP14);
-  
-  emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */
-
-  emit_byte(cinfo, 0x41);	/* Identifier: ASCII "Adobe" */
-  emit_byte(cinfo, 0x64);
-  emit_byte(cinfo, 0x6F);
-  emit_byte(cinfo, 0x62);
-  emit_byte(cinfo, 0x65);
-  emit_2bytes(cinfo, 100);	/* Version */
-  emit_2bytes(cinfo, 0);	/* Flags0 */
-  emit_2bytes(cinfo, 0);	/* Flags1 */
-  switch (cinfo->jpeg_color_space) {
-  case JCS_YCbCr:
-    emit_byte(cinfo, 1);	/* Color transform = 1 */
-    break;
-  case JCS_YCCK:
-    emit_byte(cinfo, 2);	/* Color transform = 2 */
-    break;
-  default:
-    emit_byte(cinfo, 0);	/* Color transform = 0 */
-    break;
-  }
-}
-
-
-/*
- * These routines allow writing an arbitrary marker with parameters.
- * The only intended use is to emit COM or APPn markers after calling
- * write_file_header and before calling write_frame_header.
- * Other uses are not guaranteed to produce desirable results.
- * Counting the parameter bytes properly is the caller's responsibility.
- */
-
-METHODDEF(void)
-write_marker_header (j_compress_ptr cinfo, int marker, unsigned int datalen)
-/* Emit an arbitrary marker header */
-{
-  if (datalen > (unsigned int) 65533)		/* safety check */
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  emit_marker(cinfo, (JPEG_MARKER) marker);
-
-  emit_2bytes(cinfo, (int) (datalen + 2));	/* total length */
-}
-
-METHODDEF(void)
-write_marker_byte (j_compress_ptr cinfo, int val)
-/* Emit one byte of marker parameters following write_marker_header */
-{
-  emit_byte(cinfo, val);
-}
-
-
-/*
- * Write datastream header.
- * This consists of an SOI and optional APPn markers.
- * We recommend use of the JFIF marker, but not the Adobe marker,
- * when using YCbCr or grayscale data.  The JFIF marker should NOT
- * be used for any other JPEG colorspace.  The Adobe marker is helpful
- * to distinguish RGB, CMYK, and YCCK colorspaces.
- * Note that an application can write additional header markers after
- * jpeg_start_compress returns.
- */
-
-METHODDEF(void)
-write_file_header (j_compress_ptr cinfo)
-{
-  my_marker_ptr marker = (my_marker_ptr) cinfo->marker;
-
-  emit_marker(cinfo, M_SOI);	/* first the SOI */
-
-  /* SOI is defined to reset restart interval to 0 */
-  marker->last_restart_interval = 0;
-
-  if (cinfo->write_JFIF_header)	/* next an optional JFIF APP0 */
-    emit_jfif_app0(cinfo);
-  if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */
-    emit_adobe_app14(cinfo);
-}
-
-
-/*
- * Write frame header.
- * This consists of DQT and SOFn markers.
- * Note that we do not emit the SOF until we have emitted the DQT(s).
- * This avoids compatibility problems with incorrect implementations that
- * try to error-check the quant table numbers as soon as they see the SOF.
- */
-
-METHODDEF(void)
-write_frame_header (j_compress_ptr cinfo)
-{
-  int ci, prec;
-  boolean is_baseline;
-  jpeg_component_info *compptr;
-  
-  /* Emit DQT for each quantization table.
-   * Note that emit_dqt() suppresses any duplicate tables.
-   */
-  prec = 0;
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    prec += emit_dqt(cinfo, compptr->quant_tbl_no);
-  }
-  /* now prec is nonzero iff there are any 16-bit quant tables. */
-
-  /* Check for a non-baseline specification.
-   * Note we assume that Huffman table numbers won't be changed later.
-   */
-  if (cinfo->arith_code || cinfo->progressive_mode ||
-      cinfo->data_precision != 8) {
-    is_baseline = FALSE;
-  } else {
-    is_baseline = TRUE;
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1)
-	is_baseline = FALSE;
-    }
-    if (prec && is_baseline) {
-      is_baseline = FALSE;
-      /* If it's baseline except for quantizer size, warn the user */
-      TRACEMS(cinfo, 0, JTRC_16BIT_TABLES);
-    }
-  }
-
-  /* Emit the proper SOF marker */
-  if (cinfo->arith_code) {
-    emit_sof(cinfo, M_SOF9);	/* SOF code for arithmetic coding */
-  } else {
-    if (cinfo->progressive_mode)
-      emit_sof(cinfo, M_SOF2);	/* SOF code for progressive Huffman */
-    else if (is_baseline)
-      emit_sof(cinfo, M_SOF0);	/* SOF code for baseline implementation */
-    else
-      emit_sof(cinfo, M_SOF1);	/* SOF code for non-baseline Huffman file */
-  }
-}
-
-
-/*
- * Write scan header.
- * This consists of DHT or DAC markers, optional DRI, and SOS.
- * Compressed data will be written following the SOS.
- */
-
-METHODDEF(void)
-write_scan_header (j_compress_ptr cinfo)
-{
-  my_marker_ptr marker = (my_marker_ptr) cinfo->marker;
-  int i;
-  jpeg_component_info *compptr;
-
-  if (cinfo->arith_code) {
-    /* Emit arith conditioning info.  We may have some duplication
-     * if the file has multiple scans, but it's so small it's hardly
-     * worth worrying about.
-     */
-    emit_dac(cinfo);
-  } else {
-    /* Emit Huffman tables.
-     * Note that emit_dht() suppresses any duplicate tables.
-     */
-    for (i = 0; i < cinfo->comps_in_scan; i++) {
-      compptr = cinfo->cur_comp_info[i];
-      if (cinfo->progressive_mode) {
-	/* Progressive mode: only DC or only AC tables are used in one scan */
-	if (cinfo->Ss == 0) {
-	  if (cinfo->Ah == 0)	/* DC needs no table for refinement scan */
-	    emit_dht(cinfo, compptr->dc_tbl_no, FALSE);
-	} else {
-	  emit_dht(cinfo, compptr->ac_tbl_no, TRUE);
-	}
-      } else {
-	/* Sequential mode: need both DC and AC tables */
-	emit_dht(cinfo, compptr->dc_tbl_no, FALSE);
-	emit_dht(cinfo, compptr->ac_tbl_no, TRUE);
-      }
-    }
-  }
-
-  /* Emit DRI if required --- note that DRI value could change for each scan.
-   * We avoid wasting space with unnecessary DRIs, however.
-   */
-  if (cinfo->restart_interval != marker->last_restart_interval) {
-    emit_dri(cinfo);
-    marker->last_restart_interval = cinfo->restart_interval;
-  }
-
-  emit_sos(cinfo);
-}
-
-
-/*
- * Write datastream trailer.
- */
-
-METHODDEF(void)
-write_file_trailer (j_compress_ptr cinfo)
-{
-  emit_marker(cinfo, M_EOI);
-}
-
-
-/*
- * Write an abbreviated table-specification datastream.
- * This consists of SOI, DQT and DHT tables, and EOI.
- * Any table that is defined and not marked sent_table = TRUE will be
- * emitted.  Note that all tables will be marked sent_table = TRUE at exit.
- */
-
-METHODDEF(void)
-write_tables_only (j_compress_ptr cinfo)
-{
-  int i;
-
-  emit_marker(cinfo, M_SOI);
-
-  for (i = 0; i < NUM_QUANT_TBLS; i++) {
-    if (cinfo->quant_tbl_ptrs[i] != NULL)
-      (void) emit_dqt(cinfo, i);
-  }
-
-  if (! cinfo->arith_code) {
-    for (i = 0; i < NUM_HUFF_TBLS; i++) {
-      if (cinfo->dc_huff_tbl_ptrs[i] != NULL)
-	emit_dht(cinfo, i, FALSE);
-      if (cinfo->ac_huff_tbl_ptrs[i] != NULL)
-	emit_dht(cinfo, i, TRUE);
-    }
-  }
-
-  emit_marker(cinfo, M_EOI);
-}
-
-
-/*
- * Initialize the marker writer module.
- */
-
-GLOBAL(void)
-jinit_marker_writer (j_compress_ptr cinfo)
-{
-  my_marker_ptr marker;
-
-  /* Create the subobject */
-  marker = (my_marker_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_marker_writer));
-  cinfo->marker = (struct jpeg_marker_writer *) marker;
-  /* Initialize method pointers */
-  marker->pub.write_file_header = write_file_header;
-  marker->pub.write_frame_header = write_frame_header;
-  marker->pub.write_scan_header = write_scan_header;
-  marker->pub.write_file_trailer = write_file_trailer;
-  marker->pub.write_tables_only = write_tables_only;
-  marker->pub.write_marker_header = write_marker_header;
-  marker->pub.write_marker_byte = write_marker_byte;
-  /* Initialize private state */
-  marker->last_restart_interval = 0;
-}
diff --git a/third_party/libjpeg/jcmaster.c b/third_party/libjpeg/jcmaster.c
deleted file mode 100644
index aab4020..0000000
--- a/third_party/libjpeg/jcmaster.c
+++ /dev/null
@@ -1,590 +0,0 @@
-/*
- * jcmaster.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains master control logic for the JPEG compressor.
- * These routines are concerned with parameter validation, initial setup,
- * and inter-pass control (determining the number of passes and the work 
- * to be done in each pass).
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Private state */
-
-typedef enum {
-	main_pass,		/* input data, also do first output step */
-	huff_opt_pass,		/* Huffman code optimization pass */
-	output_pass		/* data output pass */
-} c_pass_type;
-
-typedef struct {
-  struct jpeg_comp_master pub;	/* public fields */
-
-  c_pass_type pass_type;	/* the type of the current pass */
-
-  int pass_number;		/* # of passes completed */
-  int total_passes;		/* total # of passes needed */
-
-  int scan_number;		/* current index in scan_info[] */
-} my_comp_master;
-
-typedef my_comp_master * my_master_ptr;
-
-
-/*
- * Support routines that do various essential calculations.
- */
-
-LOCAL(void)
-initial_setup (j_compress_ptr cinfo)
-/* Do computations that are needed before master selection phase */
-{
-  int ci;
-  jpeg_component_info *compptr;
-  long samplesperrow;
-  JDIMENSION jd_samplesperrow;
-
-  /* Sanity check on image dimensions */
-  if (cinfo->image_height <= 0 || cinfo->image_width <= 0
-      || cinfo->num_components <= 0 || cinfo->input_components <= 0)
-    ERREXIT(cinfo, JERR_EMPTY_IMAGE);
-
-  /* Make sure image isn't bigger than I can handle */
-  if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION ||
-      (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION)
-    ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION);
-
-  /* Width of an input scanline must be representable as JDIMENSION. */
-  samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components;
-  jd_samplesperrow = (JDIMENSION) samplesperrow;
-  if ((long) jd_samplesperrow != samplesperrow)
-    ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
-
-  /* For now, precision must match compiled-in value... */
-  if (cinfo->data_precision != BITS_IN_JSAMPLE)
-    ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
-
-  /* Check that number of components won't exceed internal array sizes */
-  if (cinfo->num_components > MAX_COMPONENTS)
-    ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components,
-	     MAX_COMPONENTS);
-
-  /* Compute maximum sampling factors; check factor validity */
-  cinfo->max_h_samp_factor = 1;
-  cinfo->max_v_samp_factor = 1;
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR ||
-	compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR)
-      ERREXIT(cinfo, JERR_BAD_SAMPLING);
-    cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor,
-				   compptr->h_samp_factor);
-    cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor,
-				   compptr->v_samp_factor);
-  }
-
-  /* Compute dimensions of components */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Fill in the correct component_index value; don't rely on application */
-    compptr->component_index = ci;
-    /* For compression, we never do DCT scaling. */
-    compptr->DCT_scaled_size = DCTSIZE;
-    /* Size in DCT blocks */
-    compptr->width_in_blocks = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor,
-		    (long) (cinfo->max_h_samp_factor * DCTSIZE));
-    compptr->height_in_blocks = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor,
-		    (long) (cinfo->max_v_samp_factor * DCTSIZE));
-    /* Size in samples */
-    compptr->downsampled_width = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor,
-		    (long) cinfo->max_h_samp_factor);
-    compptr->downsampled_height = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor,
-		    (long) cinfo->max_v_samp_factor);
-    /* Mark component needed (this flag isn't actually used for compression) */
-    compptr->component_needed = TRUE;
-  }
-
-  /* Compute number of fully interleaved MCU rows (number of times that
-   * main controller will call coefficient controller).
-   */
-  cinfo->total_iMCU_rows = (JDIMENSION)
-    jdiv_round_up((long) cinfo->image_height,
-		  (long) (cinfo->max_v_samp_factor*DCTSIZE));
-}
-
-
-#ifdef C_MULTISCAN_FILES_SUPPORTED
-
-LOCAL(void)
-validate_script (j_compress_ptr cinfo)
-/* Verify that the scan script in cinfo->scan_info[] is valid; also
- * determine whether it uses progressive JPEG, and set cinfo->progressive_mode.
- */
-{
-  const jpeg_scan_info * scanptr;
-  int scanno, ncomps, ci, coefi, thisi;
-  int Ss, Se, Ah, Al;
-  boolean component_sent[MAX_COMPONENTS];
-#ifdef C_PROGRESSIVE_SUPPORTED
-  int * last_bitpos_ptr;
-  int last_bitpos[MAX_COMPONENTS][DCTSIZE2];
-  /* -1 until that coefficient has been seen; then last Al for it */
-#endif
-
-  if (cinfo->num_scans <= 0)
-    ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0);
-
-  /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1;
-   * for progressive JPEG, no scan can have this.
-   */
-  scanptr = cinfo->scan_info;
-  if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2-1) {
-#ifdef C_PROGRESSIVE_SUPPORTED
-    cinfo->progressive_mode = TRUE;
-    last_bitpos_ptr = & last_bitpos[0][0];
-    for (ci = 0; ci < cinfo->num_components; ci++) 
-      for (coefi = 0; coefi < DCTSIZE2; coefi++)
-	*last_bitpos_ptr++ = -1;
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-  } else {
-    cinfo->progressive_mode = FALSE;
-    for (ci = 0; ci < cinfo->num_components; ci++) 
-      component_sent[ci] = FALSE;
-  }
-
-  for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) {
-    /* Validate component indexes */
-    ncomps = scanptr->comps_in_scan;
-    if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN)
-      ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN);
-    for (ci = 0; ci < ncomps; ci++) {
-      thisi = scanptr->component_index[ci];
-      if (thisi < 0 || thisi >= cinfo->num_components)
-	ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno);
-      /* Components must appear in SOF order within each scan */
-      if (ci > 0 && thisi <= scanptr->component_index[ci-1])
-	ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno);
-    }
-    /* Validate progression parameters */
-    Ss = scanptr->Ss;
-    Se = scanptr->Se;
-    Ah = scanptr->Ah;
-    Al = scanptr->Al;
-    if (cinfo->progressive_mode) {
-#ifdef C_PROGRESSIVE_SUPPORTED
-      /* The JPEG spec simply gives the ranges 0..13 for Ah and Al, but that
-       * seems wrong: the upper bound ought to depend on data precision.
-       * Perhaps they really meant 0..N+1 for N-bit precision.
-       * Here we allow 0..10 for 8-bit data; Al larger than 10 results in
-       * out-of-range reconstructed DC values during the first DC scan,
-       * which might cause problems for some decoders.
-       */
-#if BITS_IN_JSAMPLE == 8
-#define MAX_AH_AL 10
-#else
-#define MAX_AH_AL 13
-#endif
-      if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 ||
-	  Ah < 0 || Ah > MAX_AH_AL || Al < 0 || Al > MAX_AH_AL)
-	ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-      if (Ss == 0) {
-	if (Se != 0)		/* DC and AC together not OK */
-	  ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-      } else {
-	if (ncomps != 1)	/* AC scans must be for only one component */
-	  ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-      }
-      for (ci = 0; ci < ncomps; ci++) {
-	last_bitpos_ptr = & last_bitpos[scanptr->component_index[ci]][0];
-	if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */
-	  ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-	for (coefi = Ss; coefi <= Se; coefi++) {
-	  if (last_bitpos_ptr[coefi] < 0) {
-	    /* first scan of this coefficient */
-	    if (Ah != 0)
-	      ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-	  } else {
-	    /* not first scan */
-	    if (Ah != last_bitpos_ptr[coefi] || Al != Ah-1)
-	      ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-	  }
-	  last_bitpos_ptr[coefi] = Al;
-	}
-      }
-#endif
-    } else {
-      /* For sequential JPEG, all progression parameters must be these: */
-      if (Ss != 0 || Se != DCTSIZE2-1 || Ah != 0 || Al != 0)
-	ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
-      /* Make sure components are not sent twice */
-      for (ci = 0; ci < ncomps; ci++) {
-	thisi = scanptr->component_index[ci];
-	if (component_sent[thisi])
-	  ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno);
-	component_sent[thisi] = TRUE;
-      }
-    }
-  }
-
-  /* Now verify that everything got sent. */
-  if (cinfo->progressive_mode) {
-#ifdef C_PROGRESSIVE_SUPPORTED
-    /* For progressive mode, we only check that at least some DC data
-     * got sent for each component; the spec does not require that all bits
-     * of all coefficients be transmitted.  Would it be wiser to enforce
-     * transmission of all coefficient bits??
-     */
-    for (ci = 0; ci < cinfo->num_components; ci++) {
-      if (last_bitpos[ci][0] < 0)
-	ERREXIT(cinfo, JERR_MISSING_DATA);
-    }
-#endif
-  } else {
-    for (ci = 0; ci < cinfo->num_components; ci++) {
-      if (! component_sent[ci])
-	ERREXIT(cinfo, JERR_MISSING_DATA);
-    }
-  }
-}
-
-#endif /* C_MULTISCAN_FILES_SUPPORTED */
-
-
-LOCAL(void)
-select_scan_parameters (j_compress_ptr cinfo)
-/* Set up the scan parameters for the current scan */
-{
-  int ci;
-
-#ifdef C_MULTISCAN_FILES_SUPPORTED
-  if (cinfo->scan_info != NULL) {
-    /* Prepare for current scan --- the script is already validated */
-    my_master_ptr master = (my_master_ptr) cinfo->master;
-    const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number;
-
-    cinfo->comps_in_scan = scanptr->comps_in_scan;
-    for (ci = 0; ci < scanptr->comps_in_scan; ci++) {
-      cinfo->cur_comp_info[ci] =
-	&cinfo->comp_info[scanptr->component_index[ci]];
-    }
-    cinfo->Ss = scanptr->Ss;
-    cinfo->Se = scanptr->Se;
-    cinfo->Ah = scanptr->Ah;
-    cinfo->Al = scanptr->Al;
-  }
-  else
-#endif
-  {
-    /* Prepare for single sequential-JPEG scan containing all components */
-    if (cinfo->num_components > MAX_COMPS_IN_SCAN)
-      ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components,
-	       MAX_COMPS_IN_SCAN);
-    cinfo->comps_in_scan = cinfo->num_components;
-    for (ci = 0; ci < cinfo->num_components; ci++) {
-      cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci];
-    }
-    cinfo->Ss = 0;
-    cinfo->Se = DCTSIZE2-1;
-    cinfo->Ah = 0;
-    cinfo->Al = 0;
-  }
-}
-
-
-LOCAL(void)
-per_scan_setup (j_compress_ptr cinfo)
-/* Do computations that are needed before processing a JPEG scan */
-/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */
-{
-  int ci, mcublks, tmp;
-  jpeg_component_info *compptr;
-  
-  if (cinfo->comps_in_scan == 1) {
-    
-    /* Noninterleaved (single-component) scan */
-    compptr = cinfo->cur_comp_info[0];
-    
-    /* Overall image size in MCUs */
-    cinfo->MCUs_per_row = compptr->width_in_blocks;
-    cinfo->MCU_rows_in_scan = compptr->height_in_blocks;
-    
-    /* For noninterleaved scan, always one block per MCU */
-    compptr->MCU_width = 1;
-    compptr->MCU_height = 1;
-    compptr->MCU_blocks = 1;
-    compptr->MCU_sample_width = DCTSIZE;
-    compptr->last_col_width = 1;
-    /* For noninterleaved scans, it is convenient to define last_row_height
-     * as the number of block rows present in the last iMCU row.
-     */
-    tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor);
-    if (tmp == 0) tmp = compptr->v_samp_factor;
-    compptr->last_row_height = tmp;
-    
-    /* Prepare array describing MCU composition */
-    cinfo->blocks_in_MCU = 1;
-    cinfo->MCU_membership[0] = 0;
-    
-  } else {
-    
-    /* Interleaved (multi-component) scan */
-    if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN)
-      ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan,
-	       MAX_COMPS_IN_SCAN);
-    
-    /* Overall image size in MCUs */
-    cinfo->MCUs_per_row = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width,
-		    (long) (cinfo->max_h_samp_factor*DCTSIZE));
-    cinfo->MCU_rows_in_scan = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height,
-		    (long) (cinfo->max_v_samp_factor*DCTSIZE));
-    
-    cinfo->blocks_in_MCU = 0;
-    
-    for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-      compptr = cinfo->cur_comp_info[ci];
-      /* Sampling factors give # of blocks of component in each MCU */
-      compptr->MCU_width = compptr->h_samp_factor;
-      compptr->MCU_height = compptr->v_samp_factor;
-      compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height;
-      compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE;
-      /* Figure number of non-dummy blocks in last MCU column & row */
-      tmp = (int) (compptr->width_in_blocks % compptr->MCU_width);
-      if (tmp == 0) tmp = compptr->MCU_width;
-      compptr->last_col_width = tmp;
-      tmp = (int) (compptr->height_in_blocks % compptr->MCU_height);
-      if (tmp == 0) tmp = compptr->MCU_height;
-      compptr->last_row_height = tmp;
-      /* Prepare array describing MCU composition */
-      mcublks = compptr->MCU_blocks;
-      if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU)
-	ERREXIT(cinfo, JERR_BAD_MCU_SIZE);
-      while (mcublks-- > 0) {
-	cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci;
-      }
-    }
-    
-  }
-
-  /* Convert restart specified in rows to actual MCU count. */
-  /* Note that count must fit in 16 bits, so we provide limiting. */
-  if (cinfo->restart_in_rows > 0) {
-    long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row;
-    cinfo->restart_interval = (unsigned int) MIN(nominal, 65535L);
-  }
-}
-
-
-/*
- * Per-pass setup.
- * This is called at the beginning of each pass.  We determine which modules
- * will be active during this pass and give them appropriate start_pass calls.
- * We also set is_last_pass to indicate whether any more passes will be
- * required.
- */
-
-METHODDEF(void)
-prepare_for_pass (j_compress_ptr cinfo)
-{
-  my_master_ptr master = (my_master_ptr) cinfo->master;
-
-  switch (master->pass_type) {
-  case main_pass:
-    /* Initial pass: will collect input data, and do either Huffman
-     * optimization or data output for the first scan.
-     */
-    select_scan_parameters(cinfo);
-    per_scan_setup(cinfo);
-    if (! cinfo->raw_data_in) {
-      (*cinfo->cconvert->start_pass) (cinfo);
-      (*cinfo->downsample->start_pass) (cinfo);
-      (*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU);
-    }
-    (*cinfo->fdct->start_pass) (cinfo);
-    (*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding);
-    (*cinfo->coef->start_pass) (cinfo,
-				(master->total_passes > 1 ?
-				 JBUF_SAVE_AND_PASS : JBUF_PASS_THRU));
-    (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU);
-    if (cinfo->optimize_coding) {
-      /* No immediate data output; postpone writing frame/scan headers */
-      master->pub.call_pass_startup = FALSE;
-    } else {
-      /* Will write frame/scan headers at first jpeg_write_scanlines call */
-      master->pub.call_pass_startup = TRUE;
-    }
-    break;
-#ifdef ENTROPY_OPT_SUPPORTED
-  case huff_opt_pass:
-    /* Do Huffman optimization for a scan after the first one. */
-    select_scan_parameters(cinfo);
-    per_scan_setup(cinfo);
-    if (cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code) {
-      (*cinfo->entropy->start_pass) (cinfo, TRUE);
-      (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST);
-      master->pub.call_pass_startup = FALSE;
-      break;
-    }
-    /* Special case: Huffman DC refinement scans need no Huffman table
-     * and therefore we can skip the optimization pass for them.
-     */
-    master->pass_type = output_pass;
-    master->pass_number++;
-    /*FALLTHROUGH*/
-#endif
-  case output_pass:
-    /* Do a data-output pass. */
-    /* We need not repeat per-scan setup if prior optimization pass did it. */
-    if (! cinfo->optimize_coding) {
-      select_scan_parameters(cinfo);
-      per_scan_setup(cinfo);
-    }
-    (*cinfo->entropy->start_pass) (cinfo, FALSE);
-    (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST);
-    /* We emit frame/scan headers now */
-    if (master->scan_number == 0)
-      (*cinfo->marker->write_frame_header) (cinfo);
-    (*cinfo->marker->write_scan_header) (cinfo);
-    master->pub.call_pass_startup = FALSE;
-    break;
-  default:
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-  }
-
-  master->pub.is_last_pass = (master->pass_number == master->total_passes-1);
-
-  /* Set up progress monitor's pass info if present */
-  if (cinfo->progress != NULL) {
-    cinfo->progress->completed_passes = master->pass_number;
-    cinfo->progress->total_passes = master->total_passes;
-  }
-}
-
-
-/*
- * Special start-of-pass hook.
- * This is called by jpeg_write_scanlines if call_pass_startup is TRUE.
- * In single-pass processing, we need this hook because we don't want to
- * write frame/scan headers during jpeg_start_compress; we want to let the
- * application write COM markers etc. between jpeg_start_compress and the
- * jpeg_write_scanlines loop.
- * In multi-pass processing, this routine is not used.
- */
-
-METHODDEF(void)
-pass_startup (j_compress_ptr cinfo)
-{
-  cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */
-
-  (*cinfo->marker->write_frame_header) (cinfo);
-  (*cinfo->marker->write_scan_header) (cinfo);
-}
-
-
-/*
- * Finish up at end of pass.
- */
-
-METHODDEF(void)
-finish_pass_master (j_compress_ptr cinfo)
-{
-  my_master_ptr master = (my_master_ptr) cinfo->master;
-
-  /* The entropy coder always needs an end-of-pass call,
-   * either to analyze statistics or to flush its output buffer.
-   */
-  (*cinfo->entropy->finish_pass) (cinfo);
-
-  /* Update state for next pass */
-  switch (master->pass_type) {
-  case main_pass:
-    /* next pass is either output of scan 0 (after optimization)
-     * or output of scan 1 (if no optimization).
-     */
-    master->pass_type = output_pass;
-    if (! cinfo->optimize_coding)
-      master->scan_number++;
-    break;
-  case huff_opt_pass:
-    /* next pass is always output of current scan */
-    master->pass_type = output_pass;
-    break;
-  case output_pass:
-    /* next pass is either optimization or output of next scan */
-    if (cinfo->optimize_coding)
-      master->pass_type = huff_opt_pass;
-    master->scan_number++;
-    break;
-  }
-
-  master->pass_number++;
-}
-
-
-/*
- * Initialize master compression control.
- */
-
-GLOBAL(void)
-jinit_c_master_control (j_compress_ptr cinfo, boolean transcode_only)
-{
-  my_master_ptr master;
-
-  master = (my_master_ptr)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  SIZEOF(my_comp_master));
-  cinfo->master = (struct jpeg_comp_master *) master;
-  master->pub.prepare_for_pass = prepare_for_pass;
-  master->pub.pass_startup = pass_startup;
-  master->pub.finish_pass = finish_pass_master;
-  master->pub.is_last_pass = FALSE;
-
-  /* Validate parameters, determine derived values */
-  initial_setup(cinfo);
-
-  if (cinfo->scan_info != NULL) {
-#ifdef C_MULTISCAN_FILES_SUPPORTED
-    validate_script(cinfo);
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-  } else {
-    cinfo->progressive_mode = FALSE;
-    cinfo->num_scans = 1;
-  }
-
-  if (cinfo->progressive_mode)	/*  TEMPORARY HACK ??? */
-    cinfo->optimize_coding = TRUE; /* assume default tables no good for progressive mode */
-
-  /* Initialize my private state */
-  if (transcode_only) {
-    /* no main pass in transcoding */
-    if (cinfo->optimize_coding)
-      master->pass_type = huff_opt_pass;
-    else
-      master->pass_type = output_pass;
-  } else {
-    /* for normal compression, first pass is always this type: */
-    master->pass_type = main_pass;
-  }
-  master->scan_number = 0;
-  master->pass_number = 0;
-  if (cinfo->optimize_coding)
-    master->total_passes = cinfo->num_scans * 2;
-  else
-    master->total_passes = cinfo->num_scans;
-}
diff --git a/third_party/libjpeg/jcomapi.c b/third_party/libjpeg/jcomapi.c
deleted file mode 100644
index 9b1fa75..0000000
--- a/third_party/libjpeg/jcomapi.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * jcomapi.c
- *
- * Copyright (C) 1994-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains application interface routines that are used for both
- * compression and decompression.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * Abort processing of a JPEG compression or decompression operation,
- * but don't destroy the object itself.
- *
- * For this, we merely clean up all the nonpermanent memory pools.
- * Note that temp files (virtual arrays) are not allowed to belong to
- * the permanent pool, so we will be able to close all temp files here.
- * Closing a data source or destination, if necessary, is the application's
- * responsibility.
- */
-
-GLOBAL(void)
-jpeg_abort (j_common_ptr cinfo)
-{
-  int pool;
-
-  /* Do nothing if called on a not-initialized or destroyed JPEG object. */
-  if (cinfo->mem == NULL)
-    return;
-
-  /* Releasing pools in reverse order might help avoid fragmentation
-   * with some (brain-damaged) malloc libraries.
-   */
-  for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) {
-    (*cinfo->mem->free_pool) (cinfo, pool);
-  }
-
-  /* Reset overall state for possible reuse of object */
-  if (cinfo->is_decompressor) {
-    cinfo->global_state = DSTATE_START;
-    /* Try to keep application from accessing now-deleted marker list.
-     * A bit kludgy to do it here, but this is the most central place.
-     */
-    ((j_decompress_ptr) cinfo)->marker_list = NULL;
-  } else {
-    cinfo->global_state = CSTATE_START;
-  }
-}
-
-
-/*
- * Destruction of a JPEG object.
- *
- * Everything gets deallocated except the master jpeg_compress_struct itself
- * and the error manager struct.  Both of these are supplied by the application
- * and must be freed, if necessary, by the application.  (Often they are on
- * the stack and so don't need to be freed anyway.)
- * Closing a data source or destination, if necessary, is the application's
- * responsibility.
- */
-
-GLOBAL(void)
-jpeg_destroy (j_common_ptr cinfo)
-{
-  /* We need only tell the memory manager to release everything. */
-  /* NB: mem pointer is NULL if memory mgr failed to initialize. */
-  if (cinfo->mem != NULL)
-    (*cinfo->mem->self_destruct) (cinfo);
-  cinfo->mem = NULL;		/* be safe if jpeg_destroy is called twice */
-  cinfo->global_state = 0;	/* mark it destroyed */
-}
-
-
-/*
- * Convenience routines for allocating quantization and Huffman tables.
- * (Would jutils.c be a more reasonable place to put these?)
- */
-
-GLOBAL(JQUANT_TBL *)
-jpeg_alloc_quant_table (j_common_ptr cinfo)
-{
-  JQUANT_TBL *tbl;
-
-  tbl = (JQUANT_TBL *)
-    (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL));
-  tbl->sent_table = FALSE;	/* make sure this is false in any new table */
-  return tbl;
-}
-
-
-GLOBAL(JHUFF_TBL *)
-jpeg_alloc_huff_table (j_common_ptr cinfo)
-{
-  JHUFF_TBL *tbl;
-
-  tbl = (JHUFF_TBL *)
-    (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL));
-  tbl->sent_table = FALSE;	/* make sure this is false in any new table */
-  return tbl;
-}
diff --git a/third_party/libjpeg/jconfig.h b/third_party/libjpeg/jconfig.h
deleted file mode 100644
index d9df654..0000000
--- a/third_party/libjpeg/jconfig.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (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.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/*
- * jconfig.h to configure the IJG JPEG library for the Mozilla/Netscape
- * environment.  Note that there are also Mozilla mods in jmorecfg.h.
- */
-
-/* We assume an ANSI C or C++ compilation environment */
-#define HAVE_PROTOTYPES 
-#define HAVE_UNSIGNED_CHAR 
-#define HAVE_UNSIGNED_SHORT 
-/* #define void char */
-/* #define const */
-#if defined(STARBOARD)
-#define NEED_STARBOARD_MEMORY
-#define NO_GETENV
-#define JPEG_NO_STDIO
-#else
-#ifndef HAVE_STDDEF_H 
-#define HAVE_STDDEF_H 
-#endif /* HAVE_STDDEF_H */
-#ifndef HAVE_STDLIB_H
-#define HAVE_STDLIB_H 
-#endif /* HAVE_STDLIB_H */
-#endif
-#undef NEED_BSD_STRINGS
-#undef NEED_SYS_TYPES_H
-#undef NEED_FAR_POINTERS
-#undef NEED_SHORT_EXTERNAL_NAMES
-/* Define this if you get warnings about undefined structures. */
-#undef INCOMPLETE_TYPES_BROKEN
-
-/* With this setting, the IJG code will work regardless of whether
- * type "char" is signed or unsigned.
- */
-#undef CHAR_IS_UNSIGNED
-
-
-/* defines that need not be visible to callers of the IJG library */
-
-#ifdef JPEG_INTERNALS
-
-/* If right shift of "long" quantities is unsigned on your machine,
- * you'll have to define this.  Fortunately few people should need it.
- */
-#undef RIGHT_SHIFT_IS_UNSIGNED
-
-#ifdef XP_MAC                   /* Macintosh */
-
-#define ALIGN_TYPE long         /* for sane memory alignment */
-#define NO_GETENV               /* we do have the function, but it's dead */
-
-#endif /* XP_MAC */
-
-#endif /* JPEG_INTERNALS */
-
-
-/* these defines are not interesting for building just the IJG library,
- * but we leave 'em here anyway.
- */
-#ifdef JPEG_CJPEG_DJPEG
-
-#define BMP_SUPPORTED		/* BMP image file format */
-#define GIF_SUPPORTED		/* GIF image file format */
-#define PPM_SUPPORTED		/* PBMPLUS PPM/PGM image file format */
-#undef RLE_SUPPORTED		/* Utah RLE image file format */
-#define TARGA_SUPPORTED		/* Targa image file format */
-
-#undef TWO_FILE_COMMANDLINE
-#undef NEED_SIGNAL_CATCHER
-#undef DONT_USE_B_MODE
-#undef PROGRESS_REPORT
-
-#endif /* JPEG_CJPEG_DJPEG */
diff --git a/third_party/libjpeg/jcparam.c b/third_party/libjpeg/jcparam.c
deleted file mode 100644
index 6fc48f5..0000000
--- a/third_party/libjpeg/jcparam.c
+++ /dev/null
@@ -1,610 +0,0 @@
-/*
- * jcparam.c
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains optional default-setting code for the JPEG compressor.
- * Applications do not have to use this file, but those that don't use it
- * must know a lot more about the innards of the JPEG code.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * Quantization table setup routines
- */
-
-GLOBAL(void)
-jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl,
-		      const unsigned int *basic_table,
-		      int scale_factor, boolean force_baseline)
-/* Define a quantization table equal to the basic_table times
- * a scale factor (given as a percentage).
- * If force_baseline is TRUE, the computed quantization table entries
- * are limited to 1..255 for JPEG baseline compatibility.
- */
-{
-  JQUANT_TBL ** qtblptr;
-  int i;
-  long temp;
-
-  /* Safety check to ensure start_compress not called yet. */
-  if (cinfo->global_state != CSTATE_START)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  if (which_tbl < 0 || which_tbl >= NUM_QUANT_TBLS)
-    ERREXIT1(cinfo, JERR_DQT_INDEX, which_tbl);
-
-  qtblptr = & cinfo->quant_tbl_ptrs[which_tbl];
-
-  if (*qtblptr == NULL)
-    *qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo);
-
-  for (i = 0; i < DCTSIZE2; i++) {
-    temp = ((long) basic_table[i] * scale_factor + 50L) / 100L;
-    /* limit the values to the valid range */
-    if (temp <= 0L) temp = 1L;
-    if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */
-    if (force_baseline && temp > 255L)
-      temp = 255L;		/* limit to baseline range if requested */
-    (*qtblptr)->quantval[i] = (UINT16) temp;
-  }
-
-  /* Initialize sent_table FALSE so table will be written to JPEG file. */
-  (*qtblptr)->sent_table = FALSE;
-}
-
-
-GLOBAL(void)
-jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor,
-			 boolean force_baseline)
-/* Set or change the 'quality' (quantization) setting, using default tables
- * and a straight percentage-scaling quality scale.  In most cases it's better
- * to use jpeg_set_quality (below); this entry point is provided for
- * applications that insist on a linear percentage scaling.
- */
-{
-  /* These are the sample quantization tables given in JPEG spec section K.1.
-   * The spec says that the values given produce "good" quality, and
-   * when divided by 2, "very good" quality.
-   */
-  static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = {
-    16,  11,  10,  16,  24,  40,  51,  61,
-    12,  12,  14,  19,  26,  58,  60,  55,
-    14,  13,  16,  24,  40,  57,  69,  56,
-    14,  17,  22,  29,  51,  87,  80,  62,
-    18,  22,  37,  56,  68, 109, 103,  77,
-    24,  35,  55,  64,  81, 104, 113,  92,
-    49,  64,  78,  87, 103, 121, 120, 101,
-    72,  92,  95,  98, 112, 100, 103,  99
-  };
-  static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = {
-    17,  18,  24,  47,  99,  99,  99,  99,
-    18,  21,  26,  66,  99,  99,  99,  99,
-    24,  26,  56,  99,  99,  99,  99,  99,
-    47,  66,  99,  99,  99,  99,  99,  99,
-    99,  99,  99,  99,  99,  99,  99,  99,
-    99,  99,  99,  99,  99,  99,  99,  99,
-    99,  99,  99,  99,  99,  99,  99,  99,
-    99,  99,  99,  99,  99,  99,  99,  99
-  };
-
-  /* Set up two quantization tables using the specified scaling */
-  jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl,
-		       scale_factor, force_baseline);
-  jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl,
-		       scale_factor, force_baseline);
-}
-
-
-GLOBAL(int)
-jpeg_quality_scaling (int quality)
-/* Convert a user-specified quality rating to a percentage scaling factor
- * for an underlying quantization table, using our recommended scaling curve.
- * The input 'quality' factor should be 0 (terrible) to 100 (very good).
- */
-{
-  /* Safety limit on quality factor.  Convert 0 to 1 to avoid zero divide. */
-  if (quality <= 0) quality = 1;
-  if (quality > 100) quality = 100;
-
-  /* The basic table is used as-is (scaling 100) for a quality of 50.
-   * Qualities 50..100 are converted to scaling percentage 200 - 2*Q;
-   * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table
-   * to make all the table entries 1 (hence, minimum quantization loss).
-   * Qualities 1..50 are converted to scaling percentage 5000/Q.
-   */
-  if (quality < 50)
-    quality = 5000 / quality;
-  else
-    quality = 200 - quality*2;
-
-  return quality;
-}
-
-
-GLOBAL(void)
-jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline)
-/* Set or change the 'quality' (quantization) setting, using default tables.
- * This is the standard quality-adjusting entry point for typical user
- * interfaces; only those who want detailed control over quantization tables
- * would use the preceding three routines directly.
- */
-{
-  /* Convert user 0-100 rating to percentage scaling */
-  quality = jpeg_quality_scaling(quality);
-
-  /* Set up standard quality tables */
-  jpeg_set_linear_quality(cinfo, quality, force_baseline);
-}
-
-
-/*
- * Huffman table setup routines
- */
-
-LOCAL(void)
-add_huff_table (j_compress_ptr cinfo,
-		JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val)
-/* Define a Huffman table */
-{
-  int nsymbols, len;
-
-  if (*htblptr == NULL)
-    *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo);
-
-  /* Copy the number-of-symbols-of-each-code-length counts */
-  MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits));
-
-  /* Validate the counts.  We do this here mainly so we can copy the right
-   * number of symbols from the val[] array, without risking marching off
-   * the end of memory.  jchuff.c will do a more thorough test later.
-   */
-  nsymbols = 0;
-  for (len = 1; len <= 16; len++)
-    nsymbols += bits[len];
-  if (nsymbols < 1 || nsymbols > 256)
-    ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-
-  MEMCOPY((*htblptr)->huffval, val, nsymbols * SIZEOF(UINT8));
-
-  /* Initialize sent_table FALSE so table will be written to JPEG file. */
-  (*htblptr)->sent_table = FALSE;
-}
-
-
-LOCAL(void)
-std_huff_tables (j_compress_ptr cinfo)
-/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */
-/* IMPORTANT: these are only valid for 8-bit data precision! */
-{
-  static const UINT8 bits_dc_luminance[17] =
-    { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 };
-  static const UINT8 val_dc_luminance[] =
-    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
-  
-  static const UINT8 bits_dc_chrominance[17] =
-    { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 };
-  static const UINT8 val_dc_chrominance[] =
-    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
-  
-  static const UINT8 bits_ac_luminance[17] =
-    { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d };
-  static const UINT8 val_ac_luminance[] =
-    { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
-      0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
-      0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
-      0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
-      0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
-      0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
-      0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-      0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
-      0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
-      0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
-      0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
-      0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
-      0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
-      0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
-      0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
-      0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
-      0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
-      0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
-      0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
-      0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
-      0xf9, 0xfa };
-  
-  static const UINT8 bits_ac_chrominance[17] =
-    { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 };
-  static const UINT8 val_ac_chrominance[] =
-    { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
-      0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
-      0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
-      0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
-      0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
-      0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
-      0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
-      0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
-      0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
-      0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
-      0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
-      0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-      0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
-      0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
-      0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
-      0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
-      0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
-      0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
-      0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
-      0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
-      0xf9, 0xfa };
-  
-  add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[0],
-		 bits_dc_luminance, val_dc_luminance);
-  add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[0],
-		 bits_ac_luminance, val_ac_luminance);
-  add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[1],
-		 bits_dc_chrominance, val_dc_chrominance);
-  add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[1],
-		 bits_ac_chrominance, val_ac_chrominance);
-}
-
-
-/*
- * Default parameter setup for compression.
- *
- * Applications that don't choose to use this routine must do their
- * own setup of all these parameters.  Alternately, you can call this
- * to establish defaults and then alter parameters selectively.  This
- * is the recommended approach since, if we add any new parameters,
- * your code will still work (they'll be set to reasonable defaults).
- */
-
-GLOBAL(void)
-jpeg_set_defaults (j_compress_ptr cinfo)
-{
-  int i;
-
-  /* Safety check to ensure start_compress not called yet. */
-  if (cinfo->global_state != CSTATE_START)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  /* Allocate comp_info array large enough for maximum component count.
-   * Array is made permanent in case application wants to compress
-   * multiple images at same param settings.
-   */
-  if (cinfo->comp_info == NULL)
-    cinfo->comp_info = (jpeg_component_info *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-				  MAX_COMPONENTS * SIZEOF(jpeg_component_info));
-
-  /* Initialize everything not dependent on the color space */
-
-  cinfo->data_precision = BITS_IN_JSAMPLE;
-  /* Set up two quantization tables using default quality of 75 */
-  jpeg_set_quality(cinfo, 75, TRUE);
-  /* Set up two Huffman tables */
-  std_huff_tables(cinfo);
-
-  /* Initialize default arithmetic coding conditioning */
-  for (i = 0; i < NUM_ARITH_TBLS; i++) {
-    cinfo->arith_dc_L[i] = 0;
-    cinfo->arith_dc_U[i] = 1;
-    cinfo->arith_ac_K[i] = 5;
-  }
-
-  /* Default is no multiple-scan output */
-  cinfo->scan_info = NULL;
-  cinfo->num_scans = 0;
-
-  /* Expect normal source image, not raw downsampled data */
-  cinfo->raw_data_in = FALSE;
-
-  /* Use Huffman coding, not arithmetic coding, by default */
-  cinfo->arith_code = FALSE;
-
-  /* By default, don't do extra passes to optimize entropy coding */
-  cinfo->optimize_coding = FALSE;
-  /* The standard Huffman tables are only valid for 8-bit data precision.
-   * If the precision is higher, force optimization on so that usable
-   * tables will be computed.  This test can be removed if default tables
-   * are supplied that are valid for the desired precision.
-   */
-  if (cinfo->data_precision > 8)
-    cinfo->optimize_coding = TRUE;
-
-  /* By default, use the simpler non-cosited sampling alignment */
-  cinfo->CCIR601_sampling = FALSE;
-
-  /* No input smoothing */
-  cinfo->smoothing_factor = 0;
-
-  /* DCT algorithm preference */
-  cinfo->dct_method = JDCT_DEFAULT;
-
-  /* No restart markers */
-  cinfo->restart_interval = 0;
-  cinfo->restart_in_rows = 0;
-
-  /* Fill in default JFIF marker parameters.  Note that whether the marker
-   * will actually be written is determined by jpeg_set_colorspace.
-   *
-   * By default, the library emits JFIF version code 1.01.
-   * An application that wants to emit JFIF 1.02 extension markers should set
-   * JFIF_minor_version to 2.  We could probably get away with just defaulting
-   * to 1.02, but there may still be some decoders in use that will complain
-   * about that; saying 1.01 should minimize compatibility problems.
-   */
-  cinfo->JFIF_major_version = 1; /* Default JFIF version = 1.01 */
-  cinfo->JFIF_minor_version = 1;
-  cinfo->density_unit = 0;	/* Pixel size is unknown by default */
-  cinfo->X_density = 1;		/* Pixel aspect ratio is square by default */
-  cinfo->Y_density = 1;
-
-  /* Choose JPEG colorspace based on input space, set defaults accordingly */
-
-  jpeg_default_colorspace(cinfo);
-}
-
-
-/*
- * Select an appropriate JPEG colorspace for in_color_space.
- */
-
-GLOBAL(void)
-jpeg_default_colorspace (j_compress_ptr cinfo)
-{
-  switch (cinfo->in_color_space) {
-  case JCS_GRAYSCALE:
-    jpeg_set_colorspace(cinfo, JCS_GRAYSCALE);
-    break;
-  case JCS_RGB:
-    jpeg_set_colorspace(cinfo, JCS_YCbCr);
-    break;
-  case JCS_YCbCr:
-    jpeg_set_colorspace(cinfo, JCS_YCbCr);
-    break;
-  case JCS_CMYK:
-    jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */
-    break;
-  case JCS_YCCK:
-    jpeg_set_colorspace(cinfo, JCS_YCCK);
-    break;
-  case JCS_UNKNOWN:
-    jpeg_set_colorspace(cinfo, JCS_UNKNOWN);
-    break;
-  default:
-    ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
-  }
-}
-
-
-/*
- * Set the JPEG colorspace, and choose colorspace-dependent default values.
- */
-
-GLOBAL(void)
-jpeg_set_colorspace (j_compress_ptr cinfo, J_COLOR_SPACE colorspace)
-{
-  jpeg_component_info * compptr;
-  int ci;
-
-#define SET_COMP(index,id,hsamp,vsamp,quant,dctbl,actbl)  \
-  (compptr = &cinfo->comp_info[index], \
-   compptr->component_id = (id), \
-   compptr->h_samp_factor = (hsamp), \
-   compptr->v_samp_factor = (vsamp), \
-   compptr->quant_tbl_no = (quant), \
-   compptr->dc_tbl_no = (dctbl), \
-   compptr->ac_tbl_no = (actbl) )
-
-  /* Safety check to ensure start_compress not called yet. */
-  if (cinfo->global_state != CSTATE_START)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  /* For all colorspaces, we use Q and Huff tables 0 for luminance components,
-   * tables 1 for chrominance components.
-   */
-
-  cinfo->jpeg_color_space = colorspace;
-
-  cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */
-  cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */
-
-  switch (colorspace) {
-  case JCS_GRAYSCALE:
-    cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */
-    cinfo->num_components = 1;
-    /* JFIF specifies component ID 1 */
-    SET_COMP(0, 1, 1,1, 0, 0,0);
-    break;
-  case JCS_RGB:
-    cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */
-    cinfo->num_components = 3;
-    SET_COMP(0, 0x52 /* 'R' */, 1,1, 0, 0,0);
-    SET_COMP(1, 0x47 /* 'G' */, 1,1, 0, 0,0);
-    SET_COMP(2, 0x42 /* 'B' */, 1,1, 0, 0,0);
-    break;
-  case JCS_YCbCr:
-    cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */
-    cinfo->num_components = 3;
-    /* JFIF specifies component IDs 1,2,3 */
-    /* We default to 2x2 subsamples of chrominance */
-    SET_COMP(0, 1, 2,2, 0, 0,0);
-    SET_COMP(1, 2, 1,1, 1, 1,1);
-    SET_COMP(2, 3, 1,1, 1, 1,1);
-    break;
-  case JCS_CMYK:
-    cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */
-    cinfo->num_components = 4;
-    SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0);
-    SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0);
-    SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0);
-    SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0);
-    break;
-  case JCS_YCCK:
-    cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */
-    cinfo->num_components = 4;
-    SET_COMP(0, 1, 2,2, 0, 0,0);
-    SET_COMP(1, 2, 1,1, 1, 1,1);
-    SET_COMP(2, 3, 1,1, 1, 1,1);
-    SET_COMP(3, 4, 2,2, 0, 0,0);
-    break;
-  case JCS_UNKNOWN:
-    cinfo->num_components = cinfo->input_components;
-    if (cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS)
-      ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components,
-	       MAX_COMPONENTS);
-    for (ci = 0; ci < cinfo->num_components; ci++) {
-      SET_COMP(ci, ci, 1,1, 0, 0,0);
-    }
-    break;
-  default:
-    ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-  }
-}
-
-
-#ifdef C_PROGRESSIVE_SUPPORTED
-
-LOCAL(jpeg_scan_info *)
-fill_a_scan (jpeg_scan_info * scanptr, int ci,
-	     int Ss, int Se, int Ah, int Al)
-/* Support routine: generate one scan for specified component */
-{
-  scanptr->comps_in_scan = 1;
-  scanptr->component_index[0] = ci;
-  scanptr->Ss = Ss;
-  scanptr->Se = Se;
-  scanptr->Ah = Ah;
-  scanptr->Al = Al;
-  scanptr++;
-  return scanptr;
-}
-
-LOCAL(jpeg_scan_info *)
-fill_scans (jpeg_scan_info * scanptr, int ncomps,
-	    int Ss, int Se, int Ah, int Al)
-/* Support routine: generate one scan for each component */
-{
-  int ci;
-
-  for (ci = 0; ci < ncomps; ci++) {
-    scanptr->comps_in_scan = 1;
-    scanptr->component_index[0] = ci;
-    scanptr->Ss = Ss;
-    scanptr->Se = Se;
-    scanptr->Ah = Ah;
-    scanptr->Al = Al;
-    scanptr++;
-  }
-  return scanptr;
-}
-
-LOCAL(jpeg_scan_info *)
-fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al)
-/* Support routine: generate interleaved DC scan if possible, else N scans */
-{
-  int ci;
-
-  if (ncomps <= MAX_COMPS_IN_SCAN) {
-    /* Single interleaved DC scan */
-    scanptr->comps_in_scan = ncomps;
-    for (ci = 0; ci < ncomps; ci++)
-      scanptr->component_index[ci] = ci;
-    scanptr->Ss = scanptr->Se = 0;
-    scanptr->Ah = Ah;
-    scanptr->Al = Al;
-    scanptr++;
-  } else {
-    /* Noninterleaved DC scan for each component */
-    scanptr = fill_scans(scanptr, ncomps, 0, 0, Ah, Al);
-  }
-  return scanptr;
-}
-
-
-/*
- * Create a recommended progressive-JPEG script.
- * cinfo->num_components and cinfo->jpeg_color_space must be correct.
- */
-
-GLOBAL(void)
-jpeg_simple_progression (j_compress_ptr cinfo)
-{
-  int ncomps = cinfo->num_components;
-  int nscans;
-  jpeg_scan_info * scanptr;
-
-  /* Safety check to ensure start_compress not called yet. */
-  if (cinfo->global_state != CSTATE_START)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  /* Figure space needed for script.  Calculation must match code below! */
-  if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) {
-    /* Custom script for YCbCr color images. */
-    nscans = 10;
-  } else {
-    /* All-purpose script for other color spaces. */
-    if (ncomps > MAX_COMPS_IN_SCAN)
-      nscans = 6 * ncomps;	/* 2 DC + 4 AC scans per component */
-    else
-      nscans = 2 + 4 * ncomps;	/* 2 DC scans; 4 AC scans per component */
-  }
-
-  /* Allocate space for script.
-   * We need to put it in the permanent pool in case the application performs
-   * multiple compressions without changing the settings.  To avoid a memory
-   * leak if jpeg_simple_progression is called repeatedly for the same JPEG
-   * object, we try to re-use previously allocated space, and we allocate
-   * enough space to handle YCbCr even if initially asked for grayscale.
-   */
-  if (cinfo->script_space == NULL || cinfo->script_space_size < nscans) {
-    cinfo->script_space_size = MAX(nscans, 10);
-    cinfo->script_space = (jpeg_scan_info *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-			cinfo->script_space_size * SIZEOF(jpeg_scan_info));
-  }
-  scanptr = cinfo->script_space;
-  cinfo->scan_info = scanptr;
-  cinfo->num_scans = nscans;
-
-  if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) {
-    /* Custom script for YCbCr color images. */
-    /* Initial DC scan */
-    scanptr = fill_dc_scans(scanptr, ncomps, 0, 1);
-    /* Initial AC scan: get some luma data out in a hurry */
-    scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 2);
-    /* Chroma data is too small to be worth expending many scans on */
-    scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 1);
-    scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 1);
-    /* Complete spectral selection for luma AC */
-    scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 2);
-    /* Refine next bit of luma AC */
-    scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1);
-    /* Finish DC successive approximation */
-    scanptr = fill_dc_scans(scanptr, ncomps, 1, 0);
-    /* Finish AC successive approximation */
-    scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0);
-    scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0);
-    /* Luma bottom bit comes last since it's usually largest scan */
-    scanptr = fill_a_scan(scanptr, 0, 1, 63, 1, 0);
-  } else {
-    /* All-purpose script for other color spaces. */
-    /* Successive approximation first pass */
-    scanptr = fill_dc_scans(scanptr, ncomps, 0, 1);
-    scanptr = fill_scans(scanptr, ncomps, 1, 5, 0, 2);
-    scanptr = fill_scans(scanptr, ncomps, 6, 63, 0, 2);
-    /* Successive approximation second pass */
-    scanptr = fill_scans(scanptr, ncomps, 1, 63, 2, 1);
-    /* Successive approximation final pass */
-    scanptr = fill_dc_scans(scanptr, ncomps, 1, 0);
-    scanptr = fill_scans(scanptr, ncomps, 1, 63, 1, 0);
-  }
-}
-
-#endif /* C_PROGRESSIVE_SUPPORTED */
diff --git a/third_party/libjpeg/jcphuff.c b/third_party/libjpeg/jcphuff.c
deleted file mode 100644
index 07f9178..0000000
--- a/third_party/libjpeg/jcphuff.c
+++ /dev/null
@@ -1,833 +0,0 @@
-/*
- * jcphuff.c
- *
- * Copyright (C) 1995-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains Huffman entropy encoding routines for progressive JPEG.
- *
- * We do not support output suspension in this module, since the library
- * currently does not allow multiple-scan files to be written with output
- * suspension.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jchuff.h"		/* Declarations shared with jchuff.c */
-
-#ifdef C_PROGRESSIVE_SUPPORTED
-
-/* Expanded entropy encoder object for progressive Huffman encoding. */
-
-typedef struct {
-  struct jpeg_entropy_encoder pub; /* public fields */
-
-  /* Mode flag: TRUE for optimization, FALSE for actual data output */
-  boolean gather_statistics;
-
-  /* Bit-level coding status.
-   * next_output_byte/free_in_buffer are local copies of cinfo->dest fields.
-   */
-  JOCTET * next_output_byte;	/* => next byte to write in buffer */
-  size_t free_in_buffer;	/* # of byte spaces remaining in buffer */
-  INT32 put_buffer;		/* current bit-accumulation buffer */
-  int put_bits;			/* # of bits now in it */
-  j_compress_ptr cinfo;		/* link to cinfo (needed for dump_buffer) */
-
-  /* Coding status for DC components */
-  int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */
-
-  /* Coding status for AC components */
-  int ac_tbl_no;		/* the table number of the single component */
-  unsigned int EOBRUN;		/* run length of EOBs */
-  unsigned int BE;		/* # of buffered correction bits before MCU */
-  char * bit_buffer;		/* buffer for correction bits (1 per char) */
-  /* packing correction bits tightly would save some space but cost time... */
-
-  unsigned int restarts_to_go;	/* MCUs left in this restart interval */
-  int next_restart_num;		/* next restart number to write (0-7) */
-
-  /* Pointers to derived tables (these workspaces have image lifespan).
-   * Since any one scan codes only DC or only AC, we only need one set
-   * of tables, not one for DC and one for AC.
-   */
-  c_derived_tbl * derived_tbls[NUM_HUFF_TBLS];
-
-  /* Statistics tables for optimization; again, one set is enough */
-  long * count_ptrs[NUM_HUFF_TBLS];
-} phuff_entropy_encoder;
-
-typedef phuff_entropy_encoder * phuff_entropy_ptr;
-
-/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit
- * buffer can hold.  Larger sizes may slightly improve compression, but
- * 1000 is already well into the realm of overkill.
- * The minimum safe size is 64 bits.
- */
-
-#define MAX_CORR_BITS  1000	/* Max # of correction bits I can buffer */
-
-/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32.
- * We assume that int right shift is unsigned if INT32 right shift is,
- * which should be safe.
- */
-
-#ifdef RIGHT_SHIFT_IS_UNSIGNED
-#define ISHIFT_TEMPS	int ishift_temp;
-#define IRIGHT_SHIFT(x,shft)  \
-	((ishift_temp = (x)) < 0 ? \
-	 (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \
-	 (ishift_temp >> (shft)))
-#else
-#define ISHIFT_TEMPS
-#define IRIGHT_SHIFT(x,shft)	((x) >> (shft))
-#endif
-
-/* Forward declarations */
-METHODDEF(boolean) encode_mcu_DC_first JPP((j_compress_ptr cinfo,
-					    JBLOCKROW *MCU_data));
-METHODDEF(boolean) encode_mcu_AC_first JPP((j_compress_ptr cinfo,
-					    JBLOCKROW *MCU_data));
-METHODDEF(boolean) encode_mcu_DC_refine JPP((j_compress_ptr cinfo,
-					     JBLOCKROW *MCU_data));
-METHODDEF(boolean) encode_mcu_AC_refine JPP((j_compress_ptr cinfo,
-					     JBLOCKROW *MCU_data));
-METHODDEF(void) finish_pass_phuff JPP((j_compress_ptr cinfo));
-METHODDEF(void) finish_pass_gather_phuff JPP((j_compress_ptr cinfo));
-
-
-/*
- * Initialize for a Huffman-compressed scan using progressive JPEG.
- */
-
-METHODDEF(void)
-start_pass_phuff (j_compress_ptr cinfo, boolean gather_statistics)
-{  
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  boolean is_DC_band;
-  int ci, tbl;
-  jpeg_component_info * compptr;
-
-  entropy->cinfo = cinfo;
-  entropy->gather_statistics = gather_statistics;
-
-  is_DC_band = (cinfo->Ss == 0);
-
-  /* We assume jcmaster.c already validated the scan parameters. */
-
-  /* Select execution routines */
-  if (cinfo->Ah == 0) {
-    if (is_DC_band)
-      entropy->pub.encode_mcu = encode_mcu_DC_first;
-    else
-      entropy->pub.encode_mcu = encode_mcu_AC_first;
-  } else {
-    if (is_DC_band)
-      entropy->pub.encode_mcu = encode_mcu_DC_refine;
-    else {
-      entropy->pub.encode_mcu = encode_mcu_AC_refine;
-      /* AC refinement needs a correction bit buffer */
-      if (entropy->bit_buffer == NULL)
-	entropy->bit_buffer = (char *)
-	  (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				      MAX_CORR_BITS * SIZEOF(char));
-    }
-  }
-  if (gather_statistics)
-    entropy->pub.finish_pass = finish_pass_gather_phuff;
-  else
-    entropy->pub.finish_pass = finish_pass_phuff;
-
-  /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1
-   * for AC coefficients.
-   */
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    /* Initialize DC predictions to 0 */
-    entropy->last_dc_val[ci] = 0;
-    /* Get table index */
-    if (is_DC_band) {
-      if (cinfo->Ah != 0)	/* DC refinement needs no table */
-	continue;
-      tbl = compptr->dc_tbl_no;
-    } else {
-      entropy->ac_tbl_no = tbl = compptr->ac_tbl_no;
-    }
-    if (gather_statistics) {
-      /* Check for invalid table index */
-      /* (make_c_derived_tbl does this in the other path) */
-      if (tbl < 0 || tbl >= NUM_HUFF_TBLS)
-        ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl);
-      /* Allocate and zero the statistics tables */
-      /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */
-      if (entropy->count_ptrs[tbl] == NULL)
-	entropy->count_ptrs[tbl] = (long *)
-	  (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				      257 * SIZEOF(long));
-      MEMZERO(entropy->count_ptrs[tbl], 257 * SIZEOF(long));
-    } else {
-      /* Compute derived values for Huffman table */
-      /* We may do this more than once for a table, but it's not expensive */
-      jpeg_make_c_derived_tbl(cinfo, is_DC_band, tbl,
-			      & entropy->derived_tbls[tbl]);
-    }
-  }
-
-  /* Initialize AC stuff */
-  entropy->EOBRUN = 0;
-  entropy->BE = 0;
-
-  /* Initialize bit buffer to empty */
-  entropy->put_buffer = 0;
-  entropy->put_bits = 0;
-
-  /* Initialize restart stuff */
-  entropy->restarts_to_go = cinfo->restart_interval;
-  entropy->next_restart_num = 0;
-}
-
-
-/* Outputting bytes to the file.
- * NB: these must be called only when actually outputting,
- * that is, entropy->gather_statistics == FALSE.
- */
-
-/* Emit a byte */
-#define emit_byte(entropy,val)  \
-	{ *(entropy)->next_output_byte++ = (JOCTET) (val);  \
-	  if (--(entropy)->free_in_buffer == 0)  \
-	    dump_buffer(entropy); }
-
-
-LOCAL(void)
-dump_buffer (phuff_entropy_ptr entropy)
-/* Empty the output buffer; we do not support suspension in this module. */
-{
-  struct jpeg_destination_mgr * dest = entropy->cinfo->dest;
-
-  if (! (*dest->empty_output_buffer) (entropy->cinfo))
-    ERREXIT(entropy->cinfo, JERR_CANT_SUSPEND);
-  /* After a successful buffer dump, must reset buffer pointers */
-  entropy->next_output_byte = dest->next_output_byte;
-  entropy->free_in_buffer = dest->free_in_buffer;
-}
-
-
-/* Outputting bits to the file */
-
-/* Only the right 24 bits of put_buffer are used; the valid bits are
- * left-justified in this part.  At most 16 bits can be passed to emit_bits
- * in one call, and we never retain more than 7 bits in put_buffer
- * between calls, so 24 bits are sufficient.
- */
-
-INLINE
-LOCAL(void)
-emit_bits (phuff_entropy_ptr entropy, unsigned int code, int size)
-/* Emit some bits, unless we are in gather mode */
-{
-  /* This routine is heavily used, so it's worth coding tightly. */
-  register INT32 put_buffer = (INT32) code;
-  register int put_bits = entropy->put_bits;
-
-  /* if size is 0, caller used an invalid Huffman table entry */
-  if (size == 0)
-    ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE);
-
-  if (entropy->gather_statistics)
-    return;			/* do nothing if we're only getting stats */
-
-  put_buffer &= (((INT32) 1)<<size) - 1; /* mask off any extra bits in code */
-  
-  put_bits += size;		/* new number of bits in buffer */
-  
-  put_buffer <<= 24 - put_bits; /* align incoming bits */
-
-  put_buffer |= entropy->put_buffer; /* and merge with old buffer contents */
-
-  while (put_bits >= 8) {
-    int c = (int) ((put_buffer >> 16) & 0xFF);
-    
-    emit_byte(entropy, c);
-    if (c == 0xFF) {		/* need to stuff a zero byte? */
-      emit_byte(entropy, 0);
-    }
-    put_buffer <<= 8;
-    put_bits -= 8;
-  }
-
-  entropy->put_buffer = put_buffer; /* update variables */
-  entropy->put_bits = put_bits;
-}
-
-
-LOCAL(void)
-flush_bits (phuff_entropy_ptr entropy)
-{
-  emit_bits(entropy, 0x7F, 7); /* fill any partial byte with ones */
-  entropy->put_buffer = 0;     /* and reset bit-buffer to empty */
-  entropy->put_bits = 0;
-}
-
-
-/*
- * Emit (or just count) a Huffman symbol.
- */
-
-INLINE
-LOCAL(void)
-emit_symbol (phuff_entropy_ptr entropy, int tbl_no, int symbol)
-{
-  if (entropy->gather_statistics)
-    entropy->count_ptrs[tbl_no][symbol]++;
-  else {
-    c_derived_tbl * tbl = entropy->derived_tbls[tbl_no];
-    emit_bits(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]);
-  }
-}
-
-
-/*
- * Emit bits from a correction bit buffer.
- */
-
-LOCAL(void)
-emit_buffered_bits (phuff_entropy_ptr entropy, char * bufstart,
-		    unsigned int nbits)
-{
-  if (entropy->gather_statistics)
-    return;			/* no real work */
-
-  while (nbits > 0) {
-    emit_bits(entropy, (unsigned int) (*bufstart), 1);
-    bufstart++;
-    nbits--;
-  }
-}
-
-
-/*
- * Emit any pending EOBRUN symbol.
- */
-
-LOCAL(void)
-emit_eobrun (phuff_entropy_ptr entropy)
-{
-  register int temp, nbits;
-
-  if (entropy->EOBRUN > 0) {	/* if there is any pending EOBRUN */
-    temp = entropy->EOBRUN;
-    nbits = 0;
-    while ((temp >>= 1))
-      nbits++;
-    /* safety check: shouldn't happen given limited correction-bit buffer */
-    if (nbits > 14)
-      ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE);
-
-    emit_symbol(entropy, entropy->ac_tbl_no, nbits << 4);
-    if (nbits)
-      emit_bits(entropy, entropy->EOBRUN, nbits);
-
-    entropy->EOBRUN = 0;
-
-    /* Emit any buffered correction bits */
-    emit_buffered_bits(entropy, entropy->bit_buffer, entropy->BE);
-    entropy->BE = 0;
-  }
-}
-
-
-/*
- * Emit a restart marker & resynchronize predictions.
- */
-
-LOCAL(void)
-emit_restart (phuff_entropy_ptr entropy, int restart_num)
-{
-  int ci;
-
-  emit_eobrun(entropy);
-
-  if (! entropy->gather_statistics) {
-    flush_bits(entropy);
-    emit_byte(entropy, 0xFF);
-    emit_byte(entropy, JPEG_RST0 + restart_num);
-  }
-
-  if (entropy->cinfo->Ss == 0) {
-    /* Re-initialize DC predictions to 0 */
-    for (ci = 0; ci < entropy->cinfo->comps_in_scan; ci++)
-      entropy->last_dc_val[ci] = 0;
-  } else {
-    /* Re-initialize all AC-related fields to 0 */
-    entropy->EOBRUN = 0;
-    entropy->BE = 0;
-  }
-}
-
-
-/*
- * MCU encoding for DC initial scan (either spectral selection,
- * or first pass of successive approximation).
- */
-
-METHODDEF(boolean)
-encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  register int temp, temp2;
-  register int nbits;
-  int blkn, ci;
-  int Al = cinfo->Al;
-  JBLOCKROW block;
-  jpeg_component_info * compptr;
-  ISHIFT_TEMPS
-
-  entropy->next_output_byte = cinfo->dest->next_output_byte;
-  entropy->free_in_buffer = cinfo->dest->free_in_buffer;
-
-  /* Emit restart marker if needed */
-  if (cinfo->restart_interval)
-    if (entropy->restarts_to_go == 0)
-      emit_restart(entropy, entropy->next_restart_num);
-
-  /* Encode the MCU data blocks */
-  for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-    block = MCU_data[blkn];
-    ci = cinfo->MCU_membership[blkn];
-    compptr = cinfo->cur_comp_info[ci];
-
-    /* Compute the DC value after the required point transform by Al.
-     * This is simply an arithmetic right shift.
-     */
-    temp2 = IRIGHT_SHIFT((int) ((*block)[0]), Al);
-
-    /* DC differences are figured on the point-transformed values. */
-    temp = temp2 - entropy->last_dc_val[ci];
-    entropy->last_dc_val[ci] = temp2;
-
-    /* Encode the DC coefficient difference per section G.1.2.1 */
-    temp2 = temp;
-    if (temp < 0) {
-      temp = -temp;		/* temp is abs value of input */
-      /* For a negative input, want temp2 = bitwise complement of abs(input) */
-      /* This code assumes we are on a two's complement machine */
-      temp2--;
-    }
-    
-    /* Find the number of bits needed for the magnitude of the coefficient */
-    nbits = 0;
-    while (temp) {
-      nbits++;
-      temp >>= 1;
-    }
-    /* Check for out-of-range coefficient values.
-     * Since we're encoding a difference, the range limit is twice as much.
-     */
-    if (nbits > MAX_COEF_BITS+1)
-      ERREXIT(cinfo, JERR_BAD_DCT_COEF);
-    
-    /* Count/emit the Huffman-coded symbol for the number of bits */
-    emit_symbol(entropy, compptr->dc_tbl_no, nbits);
-    
-    /* Emit that number of bits of the value, if positive, */
-    /* or the complement of its magnitude, if negative. */
-    if (nbits)			/* emit_bits rejects calls with size 0 */
-      emit_bits(entropy, (unsigned int) temp2, nbits);
-  }
-
-  cinfo->dest->next_output_byte = entropy->next_output_byte;
-  cinfo->dest->free_in_buffer = entropy->free_in_buffer;
-
-  /* Update restart-interval state too */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0) {
-      entropy->restarts_to_go = cinfo->restart_interval;
-      entropy->next_restart_num++;
-      entropy->next_restart_num &= 7;
-    }
-    entropy->restarts_to_go--;
-  }
-
-  return TRUE;
-}
-
-
-/*
- * MCU encoding for AC initial scan (either spectral selection,
- * or first pass of successive approximation).
- */
-
-METHODDEF(boolean)
-encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  register int temp, temp2;
-  register int nbits;
-  register int r, k;
-  int Se = cinfo->Se;
-  int Al = cinfo->Al;
-  JBLOCKROW block;
-
-  entropy->next_output_byte = cinfo->dest->next_output_byte;
-  entropy->free_in_buffer = cinfo->dest->free_in_buffer;
-
-  /* Emit restart marker if needed */
-  if (cinfo->restart_interval)
-    if (entropy->restarts_to_go == 0)
-      emit_restart(entropy, entropy->next_restart_num);
-
-  /* Encode the MCU data block */
-  block = MCU_data[0];
-
-  /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */
-  
-  r = 0;			/* r = run length of zeros */
-   
-  for (k = cinfo->Ss; k <= Se; k++) {
-    if ((temp = (*block)[jpeg_natural_order[k]]) == 0) {
-      r++;
-      continue;
-    }
-    /* We must apply the point transform by Al.  For AC coefficients this
-     * is an integer division with rounding towards 0.  To do this portably
-     * in C, we shift after obtaining the absolute value; so the code is
-     * interwoven with finding the abs value (temp) and output bits (temp2).
-     */
-    if (temp < 0) {
-      temp = -temp;		/* temp is abs value of input */
-      temp >>= Al;		/* apply the point transform */
-      /* For a negative coef, want temp2 = bitwise complement of abs(coef) */
-      temp2 = ~temp;
-    } else {
-      temp >>= Al;		/* apply the point transform */
-      temp2 = temp;
-    }
-    /* Watch out for case that nonzero coef is zero after point transform */
-    if (temp == 0) {
-      r++;
-      continue;
-    }
-
-    /* Emit any pending EOBRUN */
-    if (entropy->EOBRUN > 0)
-      emit_eobrun(entropy);
-    /* if run length > 15, must emit special run-length-16 codes (0xF0) */
-    while (r > 15) {
-      emit_symbol(entropy, entropy->ac_tbl_no, 0xF0);
-      r -= 16;
-    }
-
-    /* Find the number of bits needed for the magnitude of the coefficient */
-    nbits = 1;			/* there must be at least one 1 bit */
-    while ((temp >>= 1))
-      nbits++;
-    /* Check for out-of-range coefficient values */
-    if (nbits > MAX_COEF_BITS)
-      ERREXIT(cinfo, JERR_BAD_DCT_COEF);
-
-    /* Count/emit Huffman symbol for run length / number of bits */
-    emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + nbits);
-
-    /* Emit that number of bits of the value, if positive, */
-    /* or the complement of its magnitude, if negative. */
-    emit_bits(entropy, (unsigned int) temp2, nbits);
-
-    r = 0;			/* reset zero run length */
-  }
-
-  if (r > 0) {			/* If there are trailing zeroes, */
-    entropy->EOBRUN++;		/* count an EOB */
-    if (entropy->EOBRUN == 0x7FFF)
-      emit_eobrun(entropy);	/* force it out to avoid overflow */
-  }
-
-  cinfo->dest->next_output_byte = entropy->next_output_byte;
-  cinfo->dest->free_in_buffer = entropy->free_in_buffer;
-
-  /* Update restart-interval state too */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0) {
-      entropy->restarts_to_go = cinfo->restart_interval;
-      entropy->next_restart_num++;
-      entropy->next_restart_num &= 7;
-    }
-    entropy->restarts_to_go--;
-  }
-
-  return TRUE;
-}
-
-
-/*
- * MCU encoding for DC successive approximation refinement scan.
- * Note: we assume such scans can be multi-component, although the spec
- * is not very clear on the point.
- */
-
-METHODDEF(boolean)
-encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  register int temp;
-  int blkn;
-  int Al = cinfo->Al;
-  JBLOCKROW block;
-
-  entropy->next_output_byte = cinfo->dest->next_output_byte;
-  entropy->free_in_buffer = cinfo->dest->free_in_buffer;
-
-  /* Emit restart marker if needed */
-  if (cinfo->restart_interval)
-    if (entropy->restarts_to_go == 0)
-      emit_restart(entropy, entropy->next_restart_num);
-
-  /* Encode the MCU data blocks */
-  for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-    block = MCU_data[blkn];
-
-    /* We simply emit the Al'th bit of the DC coefficient value. */
-    temp = (*block)[0];
-    emit_bits(entropy, (unsigned int) (temp >> Al), 1);
-  }
-
-  cinfo->dest->next_output_byte = entropy->next_output_byte;
-  cinfo->dest->free_in_buffer = entropy->free_in_buffer;
-
-  /* Update restart-interval state too */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0) {
-      entropy->restarts_to_go = cinfo->restart_interval;
-      entropy->next_restart_num++;
-      entropy->next_restart_num &= 7;
-    }
-    entropy->restarts_to_go--;
-  }
-
-  return TRUE;
-}
-
-
-/*
- * MCU encoding for AC successive approximation refinement scan.
- */
-
-METHODDEF(boolean)
-encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  register int temp;
-  register int r, k;
-  int EOB;
-  char *BR_buffer;
-  unsigned int BR;
-  int Se = cinfo->Se;
-  int Al = cinfo->Al;
-  JBLOCKROW block;
-  int absvalues[DCTSIZE2];
-
-  entropy->next_output_byte = cinfo->dest->next_output_byte;
-  entropy->free_in_buffer = cinfo->dest->free_in_buffer;
-
-  /* Emit restart marker if needed */
-  if (cinfo->restart_interval)
-    if (entropy->restarts_to_go == 0)
-      emit_restart(entropy, entropy->next_restart_num);
-
-  /* Encode the MCU data block */
-  block = MCU_data[0];
-
-  /* It is convenient to make a pre-pass to determine the transformed
-   * coefficients' absolute values and the EOB position.
-   */
-  EOB = 0;
-  for (k = cinfo->Ss; k <= Se; k++) {
-    temp = (*block)[jpeg_natural_order[k]];
-    /* We must apply the point transform by Al.  For AC coefficients this
-     * is an integer division with rounding towards 0.  To do this portably
-     * in C, we shift after obtaining the absolute value.
-     */
-    if (temp < 0)
-      temp = -temp;		/* temp is abs value of input */
-    temp >>= Al;		/* apply the point transform */
-    absvalues[k] = temp;	/* save abs value for main pass */
-    if (temp == 1)
-      EOB = k;			/* EOB = index of last newly-nonzero coef */
-  }
-
-  /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */
-  
-  r = 0;			/* r = run length of zeros */
-  BR = 0;			/* BR = count of buffered bits added now */
-  BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */
-
-  for (k = cinfo->Ss; k <= Se; k++) {
-    if ((temp = absvalues[k]) == 0) {
-      r++;
-      continue;
-    }
-
-    /* Emit any required ZRLs, but not if they can be folded into EOB */
-    while (r > 15 && k <= EOB) {
-      /* emit any pending EOBRUN and the BE correction bits */
-      emit_eobrun(entropy);
-      /* Emit ZRL */
-      emit_symbol(entropy, entropy->ac_tbl_no, 0xF0);
-      r -= 16;
-      /* Emit buffered correction bits that must be associated with ZRL */
-      emit_buffered_bits(entropy, BR_buffer, BR);
-      BR_buffer = entropy->bit_buffer; /* BE bits are gone now */
-      BR = 0;
-    }
-
-    /* If the coef was previously nonzero, it only needs a correction bit.
-     * NOTE: a straight translation of the spec's figure G.7 would suggest
-     * that we also need to test r > 15.  But if r > 15, we can only get here
-     * if k > EOB, which implies that this coefficient is not 1.
-     */
-    if (temp > 1) {
-      /* The correction bit is the next bit of the absolute value. */
-      BR_buffer[BR++] = (char) (temp & 1);
-      continue;
-    }
-
-    /* Emit any pending EOBRUN and the BE correction bits */
-    emit_eobrun(entropy);
-
-    /* Count/emit Huffman symbol for run length / number of bits */
-    emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + 1);
-
-    /* Emit output bit for newly-nonzero coef */
-    temp = ((*block)[jpeg_natural_order[k]] < 0) ? 0 : 1;
-    emit_bits(entropy, (unsigned int) temp, 1);
-
-    /* Emit buffered correction bits that must be associated with this code */
-    emit_buffered_bits(entropy, BR_buffer, BR);
-    BR_buffer = entropy->bit_buffer; /* BE bits are gone now */
-    BR = 0;
-    r = 0;			/* reset zero run length */
-  }
-
-  if (r > 0 || BR > 0) {	/* If there are trailing zeroes, */
-    entropy->EOBRUN++;		/* count an EOB */
-    entropy->BE += BR;		/* concat my correction bits to older ones */
-    /* We force out the EOB if we risk either:
-     * 1. overflow of the EOB counter;
-     * 2. overflow of the correction bit buffer during the next MCU.
-     */
-    if (entropy->EOBRUN == 0x7FFF || entropy->BE > (MAX_CORR_BITS-DCTSIZE2+1))
-      emit_eobrun(entropy);
-  }
-
-  cinfo->dest->next_output_byte = entropy->next_output_byte;
-  cinfo->dest->free_in_buffer = entropy->free_in_buffer;
-
-  /* Update restart-interval state too */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0) {
-      entropy->restarts_to_go = cinfo->restart_interval;
-      entropy->next_restart_num++;
-      entropy->next_restart_num &= 7;
-    }
-    entropy->restarts_to_go--;
-  }
-
-  return TRUE;
-}
-
-
-/*
- * Finish up at the end of a Huffman-compressed progressive scan.
- */
-
-METHODDEF(void)
-finish_pass_phuff (j_compress_ptr cinfo)
-{   
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-
-  entropy->next_output_byte = cinfo->dest->next_output_byte;
-  entropy->free_in_buffer = cinfo->dest->free_in_buffer;
-
-  /* Flush out any buffered data */
-  emit_eobrun(entropy);
-  flush_bits(entropy);
-
-  cinfo->dest->next_output_byte = entropy->next_output_byte;
-  cinfo->dest->free_in_buffer = entropy->free_in_buffer;
-}
-
-
-/*
- * Finish up a statistics-gathering pass and create the new Huffman tables.
- */
-
-METHODDEF(void)
-finish_pass_gather_phuff (j_compress_ptr cinfo)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  boolean is_DC_band;
-  int ci, tbl;
-  jpeg_component_info * compptr;
-  JHUFF_TBL **htblptr;
-  boolean did[NUM_HUFF_TBLS];
-
-  /* Flush out buffered data (all we care about is counting the EOB symbol) */
-  emit_eobrun(entropy);
-
-  is_DC_band = (cinfo->Ss == 0);
-
-  /* It's important not to apply jpeg_gen_optimal_table more than once
-   * per table, because it clobbers the input frequency counts!
-   */
-  MEMZERO(did, SIZEOF(did));
-
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    if (is_DC_band) {
-      if (cinfo->Ah != 0)	/* DC refinement needs no table */
-	continue;
-      tbl = compptr->dc_tbl_no;
-    } else {
-      tbl = compptr->ac_tbl_no;
-    }
-    if (! did[tbl]) {
-      if (is_DC_band)
-        htblptr = & cinfo->dc_huff_tbl_ptrs[tbl];
-      else
-        htblptr = & cinfo->ac_huff_tbl_ptrs[tbl];
-      if (*htblptr == NULL)
-        *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo);
-      jpeg_gen_optimal_table(cinfo, *htblptr, entropy->count_ptrs[tbl]);
-      did[tbl] = TRUE;
-    }
-  }
-}
-
-
-/*
- * Module initialization routine for progressive Huffman entropy encoding.
- */
-
-GLOBAL(void)
-jinit_phuff_encoder (j_compress_ptr cinfo)
-{
-  phuff_entropy_ptr entropy;
-  int i;
-
-  entropy = (phuff_entropy_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(phuff_entropy_encoder));
-  cinfo->entropy = (struct jpeg_entropy_encoder *) entropy;
-  entropy->pub.start_pass = start_pass_phuff;
-
-  /* Mark tables unallocated */
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    entropy->derived_tbls[i] = NULL;
-    entropy->count_ptrs[i] = NULL;
-  }
-  entropy->bit_buffer = NULL;	/* needed only in AC refinement scan */
-}
-
-#endif /* C_PROGRESSIVE_SUPPORTED */
diff --git a/third_party/libjpeg/jcprepct.c b/third_party/libjpeg/jcprepct.c
deleted file mode 100644
index fa93333..0000000
--- a/third_party/libjpeg/jcprepct.c
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * jcprepct.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the compression preprocessing controller.
- * This controller manages the color conversion, downsampling,
- * and edge expansion steps.
- *
- * Most of the complexity here is associated with buffering input rows
- * as required by the downsampler.  See the comments at the head of
- * jcsample.c for the downsampler's needs.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* At present, jcsample.c can request context rows only for smoothing.
- * In the future, we might also need context rows for CCIR601 sampling
- * or other more-complex downsampling procedures.  The code to support
- * context rows should be compiled only if needed.
- */
-#ifdef INPUT_SMOOTHING_SUPPORTED
-#define CONTEXT_ROWS_SUPPORTED
-#endif
-
-
-/*
- * For the simple (no-context-row) case, we just need to buffer one
- * row group's worth of pixels for the downsampling step.  At the bottom of
- * the image, we pad to a full row group by replicating the last pixel row.
- * The downsampler's last output row is then replicated if needed to pad
- * out to a full iMCU row.
- *
- * When providing context rows, we must buffer three row groups' worth of
- * pixels.  Three row groups are physically allocated, but the row pointer
- * arrays are made five row groups high, with the extra pointers above and
- * below "wrapping around" to point to the last and first real row groups.
- * This allows the downsampler to access the proper context rows.
- * At the top and bottom of the image, we create dummy context rows by
- * copying the first or last real pixel row.  This copying could be avoided
- * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the
- * trouble on the compression side.
- */
-
-
-/* Private buffer controller object */
-
-typedef struct {
-  struct jpeg_c_prep_controller pub; /* public fields */
-
-  /* Downsampling input buffer.  This buffer holds color-converted data
-   * until we have enough to do a downsample step.
-   */
-  JSAMPARRAY color_buf[MAX_COMPONENTS];
-
-  JDIMENSION rows_to_go;	/* counts rows remaining in source image */
-  int next_buf_row;		/* index of next row to store in color_buf */
-
-#ifdef CONTEXT_ROWS_SUPPORTED	/* only needed for context case */
-  int this_row_group;		/* starting row index of group to process */
-  int next_buf_stop;		/* downsample when we reach this index */
-#endif
-} my_prep_controller;
-
-typedef my_prep_controller * my_prep_ptr;
-
-
-/*
- * Initialize for a processing pass.
- */
-
-METHODDEF(void)
-start_pass_prep (j_compress_ptr cinfo, J_BUF_MODE pass_mode)
-{
-  my_prep_ptr prep = (my_prep_ptr) cinfo->prep;
-
-  if (pass_mode != JBUF_PASS_THRU)
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-
-  /* Initialize total-height counter for detecting bottom of image */
-  prep->rows_to_go = cinfo->image_height;
-  /* Mark the conversion buffer empty */
-  prep->next_buf_row = 0;
-#ifdef CONTEXT_ROWS_SUPPORTED
-  /* Preset additional state variables for context mode.
-   * These aren't used in non-context mode, so we needn't test which mode.
-   */
-  prep->this_row_group = 0;
-  /* Set next_buf_stop to stop after two row groups have been read in. */
-  prep->next_buf_stop = 2 * cinfo->max_v_samp_factor;
-#endif
-}
-
-
-/*
- * Expand an image vertically from height input_rows to height output_rows,
- * by duplicating the bottom row.
- */
-
-LOCAL(void)
-expand_bottom_edge (JSAMPARRAY image_data, JDIMENSION num_cols,
-		    int input_rows, int output_rows)
-{
-  register int row;
-
-  for (row = input_rows; row < output_rows; row++) {
-    jcopy_sample_rows(image_data, input_rows-1, image_data, row,
-		      1, num_cols);
-  }
-}
-
-
-/*
- * Process some data in the simple no-context case.
- *
- * Preprocessor output data is counted in "row groups".  A row group
- * is defined to be v_samp_factor sample rows of each component.
- * Downsampling will produce this much data from each max_v_samp_factor
- * input rows.
- */
-
-METHODDEF(void)
-pre_process_data (j_compress_ptr cinfo,
-		  JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
-		  JDIMENSION in_rows_avail,
-		  JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr,
-		  JDIMENSION out_row_groups_avail)
-{
-  my_prep_ptr prep = (my_prep_ptr) cinfo->prep;
-  int numrows, ci;
-  JDIMENSION inrows;
-  jpeg_component_info * compptr;
-
-  while (*in_row_ctr < in_rows_avail &&
-	 *out_row_group_ctr < out_row_groups_avail) {
-    /* Do color conversion to fill the conversion buffer. */
-    inrows = in_rows_avail - *in_row_ctr;
-    numrows = cinfo->max_v_samp_factor - prep->next_buf_row;
-    numrows = (int) MIN((JDIMENSION) numrows, inrows);
-    (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr,
-				       prep->color_buf,
-				       (JDIMENSION) prep->next_buf_row,
-				       numrows);
-    *in_row_ctr += numrows;
-    prep->next_buf_row += numrows;
-    prep->rows_to_go -= numrows;
-    /* If at bottom of image, pad to fill the conversion buffer. */
-    if (prep->rows_to_go == 0 &&
-	prep->next_buf_row < cinfo->max_v_samp_factor) {
-      for (ci = 0; ci < cinfo->num_components; ci++) {
-	expand_bottom_edge(prep->color_buf[ci], cinfo->image_width,
-			   prep->next_buf_row, cinfo->max_v_samp_factor);
-      }
-      prep->next_buf_row = cinfo->max_v_samp_factor;
-    }
-    /* If we've filled the conversion buffer, empty it. */
-    if (prep->next_buf_row == cinfo->max_v_samp_factor) {
-      (*cinfo->downsample->downsample) (cinfo,
-					prep->color_buf, (JDIMENSION) 0,
-					output_buf, *out_row_group_ctr);
-      prep->next_buf_row = 0;
-      (*out_row_group_ctr)++;
-    }
-    /* If at bottom of image, pad the output to a full iMCU height.
-     * Note we assume the caller is providing a one-iMCU-height output buffer!
-     */
-    if (prep->rows_to_go == 0 &&
-	*out_row_group_ctr < out_row_groups_avail) {
-      for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	   ci++, compptr++) {
-	expand_bottom_edge(output_buf[ci],
-			   compptr->width_in_blocks * DCTSIZE,
-			   (int) (*out_row_group_ctr * compptr->v_samp_factor),
-			   (int) (out_row_groups_avail * compptr->v_samp_factor));
-      }
-      *out_row_group_ctr = out_row_groups_avail;
-      break;			/* can exit outer loop without test */
-    }
-  }
-}
-
-
-#ifdef CONTEXT_ROWS_SUPPORTED
-
-/*
- * Process some data in the context case.
- */
-
-METHODDEF(void)
-pre_process_context (j_compress_ptr cinfo,
-		     JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
-		     JDIMENSION in_rows_avail,
-		     JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr,
-		     JDIMENSION out_row_groups_avail)
-{
-  my_prep_ptr prep = (my_prep_ptr) cinfo->prep;
-  int numrows, ci;
-  int buf_height = cinfo->max_v_samp_factor * 3;
-  JDIMENSION inrows;
-
-  while (*out_row_group_ctr < out_row_groups_avail) {
-    if (*in_row_ctr < in_rows_avail) {
-      /* Do color conversion to fill the conversion buffer. */
-      inrows = in_rows_avail - *in_row_ctr;
-      numrows = prep->next_buf_stop - prep->next_buf_row;
-      numrows = (int) MIN((JDIMENSION) numrows, inrows);
-      (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr,
-					 prep->color_buf,
-					 (JDIMENSION) prep->next_buf_row,
-					 numrows);
-      /* Pad at top of image, if first time through */
-      if (prep->rows_to_go == cinfo->image_height) {
-	for (ci = 0; ci < cinfo->num_components; ci++) {
-	  int row;
-	  for (row = 1; row <= cinfo->max_v_samp_factor; row++) {
-	    jcopy_sample_rows(prep->color_buf[ci], 0,
-			      prep->color_buf[ci], -row,
-			      1, cinfo->image_width);
-	  }
-	}
-      }
-      *in_row_ctr += numrows;
-      prep->next_buf_row += numrows;
-      prep->rows_to_go -= numrows;
-    } else {
-      /* Return for more data, unless we are at the bottom of the image. */
-      if (prep->rows_to_go != 0)
-	break;
-      /* When at bottom of image, pad to fill the conversion buffer. */
-      if (prep->next_buf_row < prep->next_buf_stop) {
-	for (ci = 0; ci < cinfo->num_components; ci++) {
-	  expand_bottom_edge(prep->color_buf[ci], cinfo->image_width,
-			     prep->next_buf_row, prep->next_buf_stop);
-	}
-	prep->next_buf_row = prep->next_buf_stop;
-      }
-    }
-    /* If we've gotten enough data, downsample a row group. */
-    if (prep->next_buf_row == prep->next_buf_stop) {
-      (*cinfo->downsample->downsample) (cinfo,
-					prep->color_buf,
-					(JDIMENSION) prep->this_row_group,
-					output_buf, *out_row_group_ctr);
-      (*out_row_group_ctr)++;
-      /* Advance pointers with wraparound as necessary. */
-      prep->this_row_group += cinfo->max_v_samp_factor;
-      if (prep->this_row_group >= buf_height)
-	prep->this_row_group = 0;
-      if (prep->next_buf_row >= buf_height)
-	prep->next_buf_row = 0;
-      prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor;
-    }
-  }
-}
-
-
-/*
- * Create the wrapped-around downsampling input buffer needed for context mode.
- */
-
-LOCAL(void)
-create_context_buffer (j_compress_ptr cinfo)
-{
-  my_prep_ptr prep = (my_prep_ptr) cinfo->prep;
-  int rgroup_height = cinfo->max_v_samp_factor;
-  int ci, i;
-  jpeg_component_info * compptr;
-  JSAMPARRAY true_buffer, fake_buffer;
-
-  /* Grab enough space for fake row pointers for all the components;
-   * we need five row groups' worth of pointers for each component.
-   */
-  fake_buffer = (JSAMPARRAY)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(cinfo->num_components * 5 * rgroup_height) *
-				SIZEOF(JSAMPROW));
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Allocate the actual buffer space (3 row groups) for this component.
-     * We make the buffer wide enough to allow the downsampler to edge-expand
-     * horizontally within the buffer, if it so chooses.
-     */
-    true_buffer = (*cinfo->mem->alloc_sarray)
-      ((j_common_ptr) cinfo, JPOOL_IMAGE,
-       (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE *
-		      cinfo->max_h_samp_factor) / compptr->h_samp_factor),
-       (JDIMENSION) (3 * rgroup_height));
-    /* Copy true buffer row pointers into the middle of the fake row array */
-    MEMCOPY(fake_buffer + rgroup_height, true_buffer,
-	    3 * rgroup_height * SIZEOF(JSAMPROW));
-    /* Fill in the above and below wraparound pointers */
-    for (i = 0; i < rgroup_height; i++) {
-      fake_buffer[i] = true_buffer[2 * rgroup_height + i];
-      fake_buffer[4 * rgroup_height + i] = true_buffer[i];
-    }
-    prep->color_buf[ci] = fake_buffer + rgroup_height;
-    fake_buffer += 5 * rgroup_height; /* point to space for next component */
-  }
-}
-
-#endif /* CONTEXT_ROWS_SUPPORTED */
-
-
-/*
- * Initialize preprocessing controller.
- */
-
-GLOBAL(void)
-jinit_c_prep_controller (j_compress_ptr cinfo, boolean need_full_buffer)
-{
-  my_prep_ptr prep;
-  int ci;
-  jpeg_component_info * compptr;
-
-  if (need_full_buffer)		/* safety check */
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-
-  prep = (my_prep_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_prep_controller));
-  cinfo->prep = (struct jpeg_c_prep_controller *) prep;
-  prep->pub.start_pass = start_pass_prep;
-
-  /* Allocate the color conversion buffer.
-   * We make the buffer wide enough to allow the downsampler to edge-expand
-   * horizontally within the buffer, if it so chooses.
-   */
-  if (cinfo->downsample->need_context_rows) {
-    /* Set up to provide context rows */
-#ifdef CONTEXT_ROWS_SUPPORTED
-    prep->pub.pre_process_data = pre_process_context;
-    create_context_buffer(cinfo);
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-  } else {
-    /* No context, just make it tall enough for one row group */
-    prep->pub.pre_process_data = pre_process_data;
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      prep->color_buf[ci] = (*cinfo->mem->alloc_sarray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE,
-	 (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE *
-			cinfo->max_h_samp_factor) / compptr->h_samp_factor),
-	 (JDIMENSION) cinfo->max_v_samp_factor);
-    }
-  }
-}
diff --git a/third_party/libjpeg/jcsample.c b/third_party/libjpeg/jcsample.c
deleted file mode 100644
index 212ec87..0000000
--- a/third_party/libjpeg/jcsample.c
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * jcsample.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains downsampling routines.
- *
- * Downsampling input data is counted in "row groups".  A row group
- * is defined to be max_v_samp_factor pixel rows of each component,
- * from which the downsampler produces v_samp_factor sample rows.
- * A single row group is processed in each call to the downsampler module.
- *
- * The downsampler is responsible for edge-expansion of its output data
- * to fill an integral number of DCT blocks horizontally.  The source buffer
- * may be modified if it is helpful for this purpose (the source buffer is
- * allocated wide enough to correspond to the desired output width).
- * The caller (the prep controller) is responsible for vertical padding.
- *
- * The downsampler may request "context rows" by setting need_context_rows
- * during startup.  In this case, the input arrays will contain at least
- * one row group's worth of pixels above and below the passed-in data;
- * the caller will create dummy rows at image top and bottom by replicating
- * the first or last real pixel row.
- *
- * An excellent reference for image resampling is
- *   Digital Image Warping, George Wolberg, 1990.
- *   Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7.
- *
- * The downsampling algorithm used here is a simple average of the source
- * pixels covered by the output pixel.  The hi-falutin sampling literature
- * refers to this as a "box filter".  In general the characteristics of a box
- * filter are not very good, but for the specific cases we normally use (1:1
- * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not
- * nearly so bad.  If you intend to use other sampling ratios, you'd be well
- * advised to improve this code.
- *
- * A simple input-smoothing capability is provided.  This is mainly intended
- * for cleaning up color-dithered GIF input files (if you find it inadequate,
- * we suggest using an external filtering program such as pnmconvol).  When
- * enabled, each input pixel P is replaced by a weighted sum of itself and its
- * eight neighbors.  P's weight is 1-8*SF and each neighbor's weight is SF,
- * where SF = (smoothing_factor / 1024).
- * Currently, smoothing is only supported for 2h2v sampling factors.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Pointer to routine to downsample a single component */
-typedef JMETHOD(void, downsample1_ptr,
-		(j_compress_ptr cinfo, jpeg_component_info * compptr,
-		 JSAMPARRAY input_data, JSAMPARRAY output_data));
-
-/* Private subobject */
-
-typedef struct {
-  struct jpeg_downsampler pub;	/* public fields */
-
-  /* Downsampling method pointers, one per component */
-  downsample1_ptr methods[MAX_COMPONENTS];
-} my_downsampler;
-
-typedef my_downsampler * my_downsample_ptr;
-
-
-/*
- * Initialize for a downsampling pass.
- */
-
-METHODDEF(void)
-start_pass_downsample (j_compress_ptr cinfo)
-{
-  /* no work for now */
-}
-
-
-/*
- * Expand a component horizontally from width input_cols to width output_cols,
- * by duplicating the rightmost samples.
- */
-
-LOCAL(void)
-expand_right_edge (JSAMPARRAY image_data, int num_rows,
-		   JDIMENSION input_cols, JDIMENSION output_cols)
-{
-  register JSAMPROW ptr;
-  register JSAMPLE pixval;
-  register int count;
-  int row;
-  int numcols = (int) (output_cols - input_cols);
-
-  if (numcols > 0) {
-    for (row = 0; row < num_rows; row++) {
-      ptr = image_data[row] + input_cols;
-      pixval = ptr[-1];		/* don't need GETJSAMPLE() here */
-      for (count = numcols; count > 0; count--)
-	*ptr++ = pixval;
-    }
-  }
-}
-
-
-/*
- * Do downsampling for a whole row group (all components).
- *
- * In this version we simply downsample each component independently.
- */
-
-METHODDEF(void)
-sep_downsample (j_compress_ptr cinfo,
-		JSAMPIMAGE input_buf, JDIMENSION in_row_index,
-		JSAMPIMAGE output_buf, JDIMENSION out_row_group_index)
-{
-  my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample;
-  int ci;
-  jpeg_component_info * compptr;
-  JSAMPARRAY in_ptr, out_ptr;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    in_ptr = input_buf[ci] + in_row_index;
-    out_ptr = output_buf[ci] + (out_row_group_index * compptr->v_samp_factor);
-    (*downsample->methods[ci]) (cinfo, compptr, in_ptr, out_ptr);
-  }
-}
-
-
-/*
- * Downsample pixel values of a single component.
- * One row group is processed per call.
- * This version handles arbitrary integral sampling ratios, without smoothing.
- * Note that this version is not actually used for customary sampling ratios.
- */
-
-METHODDEF(void)
-int_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr,
-		JSAMPARRAY input_data, JSAMPARRAY output_data)
-{
-  int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v;
-  JDIMENSION outcol, outcol_h;	/* outcol_h == outcol*h_expand */
-  JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE;
-  JSAMPROW inptr, outptr;
-  INT32 outvalue;
-
-  h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor;
-  v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor;
-  numpix = h_expand * v_expand;
-  numpix2 = numpix/2;
-
-  /* Expand input data enough to let all the output samples be generated
-   * by the standard loop.  Special-casing padded output would be more
-   * efficient.
-   */
-  expand_right_edge(input_data, cinfo->max_v_samp_factor,
-		    cinfo->image_width, output_cols * h_expand);
-
-  inrow = 0;
-  for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) {
-    outptr = output_data[outrow];
-    for (outcol = 0, outcol_h = 0; outcol < output_cols;
-	 outcol++, outcol_h += h_expand) {
-      outvalue = 0;
-      for (v = 0; v < v_expand; v++) {
-	inptr = input_data[inrow+v] + outcol_h;
-	for (h = 0; h < h_expand; h++) {
-	  outvalue += (INT32) GETJSAMPLE(*inptr++);
-	}
-      }
-      *outptr++ = (JSAMPLE) ((outvalue + numpix2) / numpix);
-    }
-    inrow += v_expand;
-  }
-}
-
-
-/*
- * Downsample pixel values of a single component.
- * This version handles the special case of a full-size component,
- * without smoothing.
- */
-
-METHODDEF(void)
-fullsize_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr,
-		     JSAMPARRAY input_data, JSAMPARRAY output_data)
-{
-  /* Copy the data */
-  jcopy_sample_rows(input_data, 0, output_data, 0,
-		    cinfo->max_v_samp_factor, cinfo->image_width);
-  /* Edge-expand */
-  expand_right_edge(output_data, cinfo->max_v_samp_factor,
-		    cinfo->image_width, compptr->width_in_blocks * DCTSIZE);
-}
-
-
-/*
- * Downsample pixel values of a single component.
- * This version handles the common case of 2:1 horizontal and 1:1 vertical,
- * without smoothing.
- *
- * A note about the "bias" calculations: when rounding fractional values to
- * integer, we do not want to always round 0.5 up to the next integer.
- * If we did that, we'd introduce a noticeable bias towards larger values.
- * Instead, this code is arranged so that 0.5 will be rounded up or down at
- * alternate pixel locations (a simple ordered dither pattern).
- */
-
-METHODDEF(void)
-h2v1_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr,
-		 JSAMPARRAY input_data, JSAMPARRAY output_data)
-{
-  int outrow;
-  JDIMENSION outcol;
-  JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE;
-  register JSAMPROW inptr, outptr;
-  register int bias;
-
-  /* Expand input data enough to let all the output samples be generated
-   * by the standard loop.  Special-casing padded output would be more
-   * efficient.
-   */
-  expand_right_edge(input_data, cinfo->max_v_samp_factor,
-		    cinfo->image_width, output_cols * 2);
-
-  for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) {
-    outptr = output_data[outrow];
-    inptr = input_data[outrow];
-    bias = 0;			/* bias = 0,1,0,1,... for successive samples */
-    for (outcol = 0; outcol < output_cols; outcol++) {
-      *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr) + GETJSAMPLE(inptr[1])
-			      + bias) >> 1);
-      bias ^= 1;		/* 0=>1, 1=>0 */
-      inptr += 2;
-    }
-  }
-}
-
-
-/*
- * Downsample pixel values of a single component.
- * This version handles the standard case of 2:1 horizontal and 2:1 vertical,
- * without smoothing.
- */
-
-METHODDEF(void)
-h2v2_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr,
-		 JSAMPARRAY input_data, JSAMPARRAY output_data)
-{
-  int inrow, outrow;
-  JDIMENSION outcol;
-  JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE;
-  register JSAMPROW inptr0, inptr1, outptr;
-  register int bias;
-
-  /* Expand input data enough to let all the output samples be generated
-   * by the standard loop.  Special-casing padded output would be more
-   * efficient.
-   */
-  expand_right_edge(input_data, cinfo->max_v_samp_factor,
-		    cinfo->image_width, output_cols * 2);
-
-  inrow = 0;
-  for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) {
-    outptr = output_data[outrow];
-    inptr0 = input_data[inrow];
-    inptr1 = input_data[inrow+1];
-    bias = 1;			/* bias = 1,2,1,2,... for successive samples */
-    for (outcol = 0; outcol < output_cols; outcol++) {
-      *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) +
-			      GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1])
-			      + bias) >> 2);
-      bias ^= 3;		/* 1=>2, 2=>1 */
-      inptr0 += 2; inptr1 += 2;
-    }
-    inrow += 2;
-  }
-}
-
-
-#ifdef INPUT_SMOOTHING_SUPPORTED
-
-/*
- * Downsample pixel values of a single component.
- * This version handles the standard case of 2:1 horizontal and 2:1 vertical,
- * with smoothing.  One row of context is required.
- */
-
-METHODDEF(void)
-h2v2_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr,
-			JSAMPARRAY input_data, JSAMPARRAY output_data)
-{
-  int inrow, outrow;
-  JDIMENSION colctr;
-  JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE;
-  register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr;
-  INT32 membersum, neighsum, memberscale, neighscale;
-
-  /* Expand input data enough to let all the output samples be generated
-   * by the standard loop.  Special-casing padded output would be more
-   * efficient.
-   */
-  expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2,
-		    cinfo->image_width, output_cols * 2);
-
-  /* We don't bother to form the individual "smoothed" input pixel values;
-   * we can directly compute the output which is the average of the four
-   * smoothed values.  Each of the four member pixels contributes a fraction
-   * (1-8*SF) to its own smoothed image and a fraction SF to each of the three
-   * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final
-   * output.  The four corner-adjacent neighbor pixels contribute a fraction
-   * SF to just one smoothed pixel, or SF/4 to the final output; while the
-   * eight edge-adjacent neighbors contribute SF to each of two smoothed
-   * pixels, or SF/2 overall.  In order to use integer arithmetic, these
-   * factors are scaled by 2^16 = 65536.
-   * Also recall that SF = smoothing_factor / 1024.
-   */
-
-  memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */
-  neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */
-
-  inrow = 0;
-  for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) {
-    outptr = output_data[outrow];
-    inptr0 = input_data[inrow];
-    inptr1 = input_data[inrow+1];
-    above_ptr = input_data[inrow-1];
-    below_ptr = input_data[inrow+2];
-
-    /* Special case for first column: pretend column -1 is same as column 0 */
-    membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) +
-		GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]);
-    neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) +
-	       GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) +
-	       GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[2]) +
-	       GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[2]);
-    neighsum += neighsum;
-    neighsum += GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[2]) +
-		GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[2]);
-    membersum = membersum * memberscale + neighsum * neighscale;
-    *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16);
-    inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2;
-
-    for (colctr = output_cols - 2; colctr > 0; colctr--) {
-      /* sum of pixels directly mapped to this output element */
-      membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) +
-		  GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]);
-      /* sum of edge-neighbor pixels */
-      neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) +
-		 GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) +
-		 GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[2]) +
-		 GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[2]);
-      /* The edge-neighbors count twice as much as corner-neighbors */
-      neighsum += neighsum;
-      /* Add in the corner-neighbors */
-      neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[2]) +
-		  GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[2]);
-      /* form final output scaled up by 2^16 */
-      membersum = membersum * memberscale + neighsum * neighscale;
-      /* round, descale and output it */
-      *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16);
-      inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2;
-    }
-
-    /* Special case for last column */
-    membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) +
-		GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]);
-    neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) +
-	       GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) +
-	       GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[1]) +
-	       GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[1]);
-    neighsum += neighsum;
-    neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[1]) +
-		GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[1]);
-    membersum = membersum * memberscale + neighsum * neighscale;
-    *outptr = (JSAMPLE) ((membersum + 32768) >> 16);
-
-    inrow += 2;
-  }
-}
-
-
-/*
- * Downsample pixel values of a single component.
- * This version handles the special case of a full-size component,
- * with smoothing.  One row of context is required.
- */
-
-METHODDEF(void)
-fullsize_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info *compptr,
-			    JSAMPARRAY input_data, JSAMPARRAY output_data)
-{
-  int outrow;
-  JDIMENSION colctr;
-  JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE;
-  register JSAMPROW inptr, above_ptr, below_ptr, outptr;
-  INT32 membersum, neighsum, memberscale, neighscale;
-  int colsum, lastcolsum, nextcolsum;
-
-  /* Expand input data enough to let all the output samples be generated
-   * by the standard loop.  Special-casing padded output would be more
-   * efficient.
-   */
-  expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2,
-		    cinfo->image_width, output_cols);
-
-  /* Each of the eight neighbor pixels contributes a fraction SF to the
-   * smoothed pixel, while the main pixel contributes (1-8*SF).  In order
-   * to use integer arithmetic, these factors are multiplied by 2^16 = 65536.
-   * Also recall that SF = smoothing_factor / 1024.
-   */
-
-  memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */
-  neighscale = cinfo->smoothing_factor * 64; /* scaled SF */
-
-  for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) {
-    outptr = output_data[outrow];
-    inptr = input_data[outrow];
-    above_ptr = input_data[outrow-1];
-    below_ptr = input_data[outrow+1];
-
-    /* Special case for first column */
-    colsum = GETJSAMPLE(*above_ptr++) + GETJSAMPLE(*below_ptr++) +
-	     GETJSAMPLE(*inptr);
-    membersum = GETJSAMPLE(*inptr++);
-    nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) +
-		 GETJSAMPLE(*inptr);
-    neighsum = colsum + (colsum - membersum) + nextcolsum;
-    membersum = membersum * memberscale + neighsum * neighscale;
-    *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16);
-    lastcolsum = colsum; colsum = nextcolsum;
-
-    for (colctr = output_cols - 2; colctr > 0; colctr--) {
-      membersum = GETJSAMPLE(*inptr++);
-      above_ptr++; below_ptr++;
-      nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) +
-		   GETJSAMPLE(*inptr);
-      neighsum = lastcolsum + (colsum - membersum) + nextcolsum;
-      membersum = membersum * memberscale + neighsum * neighscale;
-      *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16);
-      lastcolsum = colsum; colsum = nextcolsum;
-    }
-
-    /* Special case for last column */
-    membersum = GETJSAMPLE(*inptr);
-    neighsum = lastcolsum + (colsum - membersum) + colsum;
-    membersum = membersum * memberscale + neighsum * neighscale;
-    *outptr = (JSAMPLE) ((membersum + 32768) >> 16);
-
-  }
-}
-
-#endif /* INPUT_SMOOTHING_SUPPORTED */
-
-
-/*
- * Module initialization routine for downsampling.
- * Note that we must select a routine for each component.
- */
-
-GLOBAL(void)
-jinit_downsampler (j_compress_ptr cinfo)
-{
-  my_downsample_ptr downsample;
-  int ci;
-  jpeg_component_info * compptr;
-  boolean smoothok = TRUE;
-
-  downsample = (my_downsample_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_downsampler));
-  cinfo->downsample = (struct jpeg_downsampler *) downsample;
-  downsample->pub.start_pass = start_pass_downsample;
-  downsample->pub.downsample = sep_downsample;
-  downsample->pub.need_context_rows = FALSE;
-
-  if (cinfo->CCIR601_sampling)
-    ERREXIT(cinfo, JERR_CCIR601_NOTIMPL);
-
-  /* Verify we can handle the sampling factors, and set up method pointers */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    if (compptr->h_samp_factor == cinfo->max_h_samp_factor &&
-	compptr->v_samp_factor == cinfo->max_v_samp_factor) {
-#ifdef INPUT_SMOOTHING_SUPPORTED
-      if (cinfo->smoothing_factor) {
-	downsample->methods[ci] = fullsize_smooth_downsample;
-	downsample->pub.need_context_rows = TRUE;
-      } else
-#endif
-	downsample->methods[ci] = fullsize_downsample;
-    } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor &&
-	       compptr->v_samp_factor == cinfo->max_v_samp_factor) {
-      smoothok = FALSE;
-      downsample->methods[ci] = h2v1_downsample;
-    } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor &&
-	       compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor) {
-#ifdef INPUT_SMOOTHING_SUPPORTED
-      if (cinfo->smoothing_factor) {
-	downsample->methods[ci] = h2v2_smooth_downsample;
-	downsample->pub.need_context_rows = TRUE;
-      } else
-#endif
-	downsample->methods[ci] = h2v2_downsample;
-    } else if ((cinfo->max_h_samp_factor % compptr->h_samp_factor) == 0 &&
-	       (cinfo->max_v_samp_factor % compptr->v_samp_factor) == 0) {
-      smoothok = FALSE;
-      downsample->methods[ci] = int_downsample;
-    } else
-      ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL);
-  }
-
-#ifdef INPUT_SMOOTHING_SUPPORTED
-  if (cinfo->smoothing_factor && !smoothok)
-    TRACEMS(cinfo, 0, JTRC_SMOOTH_NOTIMPL);
-#endif
-}
diff --git a/third_party/libjpeg/jdapimin.c b/third_party/libjpeg/jdapimin.c
deleted file mode 100644
index cadb59f..0000000
--- a/third_party/libjpeg/jdapimin.c
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * jdapimin.c
- *
- * Copyright (C) 1994-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains application interface code for the decompression half
- * of the JPEG library.  These are the "minimum" API routines that may be
- * needed in either the normal full-decompression case or the
- * transcoding-only case.
- *
- * Most of the routines intended to be called directly by an application
- * are in this file or in jdapistd.c.  But also see jcomapi.c for routines
- * shared by compression and decompression, and jdtrans.c for the transcoding
- * case.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * Initialization of a JPEG decompression object.
- * The error manager must already be set up (in case memory manager fails).
- */
-
-GLOBAL(void)
-jpeg_CreateDecompress (j_decompress_ptr cinfo, int version, size_t structsize)
-{
-  int i;
-
-  /* Guard against version mismatches between library and caller. */
-  cinfo->mem = NULL;		/* so jpeg_destroy knows mem mgr not called */
-  if (version != JPEG_LIB_VERSION)
-    ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);
-  if (structsize != SIZEOF(struct jpeg_decompress_struct))
-    ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, 
-	     (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize);
-
-  /* For debugging purposes, we zero the whole master structure.
-   * But the application has already set the err pointer, and may have set
-   * client_data, so we have to save and restore those fields.
-   * Note: if application hasn't set client_data, tools like Purify may
-   * complain here.
-   */
-  {
-    struct jpeg_error_mgr * err = cinfo->err;
-    void * client_data = cinfo->client_data; /* ignore Purify complaint here */
-    MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct));
-    cinfo->err = err;
-    cinfo->client_data = client_data;
-  }
-  cinfo->is_decompressor = TRUE;
-
-  /* Initialize a memory manager instance for this object */
-  jinit_memory_mgr((j_common_ptr) cinfo);
-
-  /* Zero out pointers to permanent structures. */
-  cinfo->progress = NULL;
-  cinfo->src = NULL;
-
-  for (i = 0; i < NUM_QUANT_TBLS; i++)
-    cinfo->quant_tbl_ptrs[i] = NULL;
-
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    cinfo->dc_huff_tbl_ptrs[i] = NULL;
-    cinfo->ac_huff_tbl_ptrs[i] = NULL;
-  }
-
-  /* Initialize marker processor so application can override methods
-   * for COM, APPn markers before calling jpeg_read_header.
-   */
-  cinfo->marker_list = NULL;
-  jinit_marker_reader(cinfo);
-
-  /* And initialize the overall input controller. */
-  jinit_input_controller(cinfo);
-
-  /* OK, I'm ready */
-  cinfo->global_state = DSTATE_START;
-}
-
-
-/*
- * Destruction of a JPEG decompression object
- */
-
-GLOBAL(void)
-jpeg_destroy_decompress (j_decompress_ptr cinfo)
-{
-  jpeg_destroy((j_common_ptr) cinfo); /* use common routine */
-}
-
-
-/*
- * Abort processing of a JPEG decompression operation,
- * but don't destroy the object itself.
- */
-
-GLOBAL(void)
-jpeg_abort_decompress (j_decompress_ptr cinfo)
-{
-  jpeg_abort((j_common_ptr) cinfo); /* use common routine */
-}
-
-
-/*
- * Set default decompression parameters.
- */
-
-LOCAL(void)
-default_decompress_parms (j_decompress_ptr cinfo)
-{
-  /* Guess the input colorspace, and set output colorspace accordingly. */
-  /* (Wish JPEG committee had provided a real way to specify this...) */
-  /* Note application may override our guesses. */
-  switch (cinfo->num_components) {
-  case 1:
-    cinfo->jpeg_color_space = JCS_GRAYSCALE;
-    cinfo->out_color_space = JCS_GRAYSCALE;
-    break;
-    
-  case 3:
-    if (cinfo->saw_JFIF_marker) {
-      cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */
-    } else if (cinfo->saw_Adobe_marker) {
-      switch (cinfo->Adobe_transform) {
-      case 0:
-	cinfo->jpeg_color_space = JCS_RGB;
-	break;
-      case 1:
-	cinfo->jpeg_color_space = JCS_YCbCr;
-	break;
-      default:
-	WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform);
-	cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */
-	break;
-      }
-    } else {
-      /* Saw no special markers, try to guess from the component IDs */
-      int cid0 = cinfo->comp_info[0].component_id;
-      int cid1 = cinfo->comp_info[1].component_id;
-      int cid2 = cinfo->comp_info[2].component_id;
-
-      if (cid0 == 1 && cid1 == 2 && cid2 == 3)
-	cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */
-      else if (cid0 == 82 && cid1 == 71 && cid2 == 66)
-	cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */
-      else {
-	TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2);
-	cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */
-      }
-    }
-    /* Always guess RGB is proper output colorspace. */
-    cinfo->out_color_space = JCS_RGB;
-    break;
-    
-  case 4:
-    if (cinfo->saw_Adobe_marker) {
-      switch (cinfo->Adobe_transform) {
-      case 0:
-	cinfo->jpeg_color_space = JCS_CMYK;
-	break;
-      case 2:
-	cinfo->jpeg_color_space = JCS_YCCK;
-	break;
-      default:
-	WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform);
-	cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */
-	break;
-      }
-    } else {
-      /* No special markers, assume straight CMYK. */
-      cinfo->jpeg_color_space = JCS_CMYK;
-    }
-    cinfo->out_color_space = JCS_CMYK;
-    break;
-    
-  default:
-    cinfo->jpeg_color_space = JCS_UNKNOWN;
-    cinfo->out_color_space = JCS_UNKNOWN;
-    break;
-  }
-
-  /* Set defaults for other decompression parameters. */
-  cinfo->scale_num = 1;		/* 1:1 scaling */
-  cinfo->scale_denom = 1;
-  cinfo->output_gamma = 1.0;
-  cinfo->buffered_image = FALSE;
-  cinfo->raw_data_out = FALSE;
-  cinfo->dct_method = JDCT_DEFAULT;
-  cinfo->do_fancy_upsampling = TRUE;
-  cinfo->do_block_smoothing = TRUE;
-  cinfo->quantize_colors = FALSE;
-  /* We set these in case application only sets quantize_colors. */
-  cinfo->dither_mode = JDITHER_FS;
-#ifdef QUANT_2PASS_SUPPORTED
-  cinfo->two_pass_quantize = TRUE;
-#else
-  cinfo->two_pass_quantize = FALSE;
-#endif
-  cinfo->desired_number_of_colors = 256;
-  cinfo->colormap = NULL;
-  /* Initialize for no mode change in buffered-image mode. */
-  cinfo->enable_1pass_quant = FALSE;
-  cinfo->enable_external_quant = FALSE;
-  cinfo->enable_2pass_quant = FALSE;
-}
-
-
-/*
- * Decompression startup: read start of JPEG datastream to see what's there.
- * Need only initialize JPEG object and supply a data source before calling.
- *
- * This routine will read as far as the first SOS marker (ie, actual start of
- * compressed data), and will save all tables and parameters in the JPEG
- * object.  It will also initialize the decompression parameters to default
- * values, and finally return JPEG_HEADER_OK.  On return, the application may
- * adjust the decompression parameters and then call jpeg_start_decompress.
- * (Or, if the application only wanted to determine the image parameters,
- * the data need not be decompressed.  In that case, call jpeg_abort or
- * jpeg_destroy to release any temporary space.)
- * If an abbreviated (tables only) datastream is presented, the routine will
- * return JPEG_HEADER_TABLES_ONLY upon reaching EOI.  The application may then
- * re-use the JPEG object to read the abbreviated image datastream(s).
- * It is unnecessary (but OK) to call jpeg_abort in this case.
- * The JPEG_SUSPENDED return code only occurs if the data source module
- * requests suspension of the decompressor.  In this case the application
- * should load more source data and then re-call jpeg_read_header to resume
- * processing.
- * If a non-suspending data source is used and require_image is TRUE, then the
- * return code need not be inspected since only JPEG_HEADER_OK is possible.
- *
- * This routine is now just a front end to jpeg_consume_input, with some
- * extra error checking.
- */
-
-GLOBAL(int)
-jpeg_read_header (j_decompress_ptr cinfo, boolean require_image)
-{
-  int retcode;
-
-  if (cinfo->global_state != DSTATE_START &&
-      cinfo->global_state != DSTATE_INHEADER)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  retcode = jpeg_consume_input(cinfo);
-
-  switch (retcode) {
-  case JPEG_REACHED_SOS:
-    retcode = JPEG_HEADER_OK;
-    break;
-  case JPEG_REACHED_EOI:
-    if (require_image)		/* Complain if application wanted an image */
-      ERREXIT(cinfo, JERR_NO_IMAGE);
-    /* Reset to start state; it would be safer to require the application to
-     * call jpeg_abort, but we can't change it now for compatibility reasons.
-     * A side effect is to free any temporary memory (there shouldn't be any).
-     */
-    jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */
-    retcode = JPEG_HEADER_TABLES_ONLY;
-    break;
-  case JPEG_SUSPENDED:
-    /* no work */
-    break;
-  }
-
-  return retcode;
-}
-
-
-/*
- * Consume data in advance of what the decompressor requires.
- * This can be called at any time once the decompressor object has
- * been created and a data source has been set up.
- *
- * This routine is essentially a state machine that handles a couple
- * of critical state-transition actions, namely initial setup and
- * transition from header scanning to ready-for-start_decompress.
- * All the actual input is done via the input controller's consume_input
- * method.
- */
-
-GLOBAL(int)
-jpeg_consume_input (j_decompress_ptr cinfo)
-{
-  int retcode = JPEG_SUSPENDED;
-
-  /* NB: every possible DSTATE value should be listed in this switch */
-  switch (cinfo->global_state) {
-  case DSTATE_START:
-    /* Start-of-datastream actions: reset appropriate modules */
-    (*cinfo->inputctl->reset_input_controller) (cinfo);
-    /* Initialize application's data source module */
-    (*cinfo->src->init_source) (cinfo);
-    cinfo->global_state = DSTATE_INHEADER;
-    /*FALLTHROUGH*/
-  case DSTATE_INHEADER:
-    retcode = (*cinfo->inputctl->consume_input) (cinfo);
-    if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */
-      /* Set up default parameters based on header data */
-      default_decompress_parms(cinfo);
-      /* Set global state: ready for start_decompress */
-      cinfo->global_state = DSTATE_READY;
-    }
-    break;
-  case DSTATE_READY:
-    /* Can't advance past first SOS until start_decompress is called */
-    retcode = JPEG_REACHED_SOS;
-    break;
-  case DSTATE_PRELOAD:
-  case DSTATE_PRESCAN:
-  case DSTATE_SCANNING:
-  case DSTATE_RAW_OK:
-  case DSTATE_BUFIMAGE:
-  case DSTATE_BUFPOST:
-  case DSTATE_STOPPING:
-    retcode = (*cinfo->inputctl->consume_input) (cinfo);
-    break;
-  default:
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  }
-  return retcode;
-}
-
-
-/*
- * Have we finished reading the input file?
- */
-
-GLOBAL(boolean)
-jpeg_input_complete (j_decompress_ptr cinfo)
-{
-  /* Check for valid jpeg object */
-  if (cinfo->global_state < DSTATE_START ||
-      cinfo->global_state > DSTATE_STOPPING)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  return cinfo->inputctl->eoi_reached;
-}
-
-
-/*
- * Is there more than one scan?
- */
-
-GLOBAL(boolean)
-jpeg_has_multiple_scans (j_decompress_ptr cinfo)
-{
-  /* Only valid after jpeg_read_header completes */
-  if (cinfo->global_state < DSTATE_READY ||
-      cinfo->global_state > DSTATE_STOPPING)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  return cinfo->inputctl->has_multiple_scans;
-}
-
-
-/*
- * Finish JPEG decompression.
- *
- * This will normally just verify the file trailer and release temp storage.
- *
- * Returns FALSE if suspended.  The return value need be inspected only if
- * a suspending data source is used.
- */
-
-GLOBAL(boolean)
-jpeg_finish_decompress (j_decompress_ptr cinfo)
-{
-  if ((cinfo->global_state == DSTATE_SCANNING ||
-       cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) {
-    /* Terminate final pass of non-buffered mode */
-    if (cinfo->output_scanline < cinfo->output_height)
-      ERREXIT(cinfo, JERR_TOO_LITTLE_DATA);
-    (*cinfo->master->finish_output_pass) (cinfo);
-    cinfo->global_state = DSTATE_STOPPING;
-  } else if (cinfo->global_state == DSTATE_BUFIMAGE) {
-    /* Finishing after a buffered-image operation */
-    cinfo->global_state = DSTATE_STOPPING;
-  } else if (cinfo->global_state != DSTATE_STOPPING) {
-    /* STOPPING = repeat call after a suspension, anything else is error */
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  }
-  /* Read until EOI */
-  while (! cinfo->inputctl->eoi_reached) {
-    if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED)
-      return FALSE;		/* Suspend, come back later */
-  }
-  /* Do final cleanup */
-  (*cinfo->src->term_source) (cinfo);
-  /* We can use jpeg_abort to release memory and reset global_state */
-  jpeg_abort((j_common_ptr) cinfo);
-  return TRUE;
-}
diff --git a/third_party/libjpeg/jdapistd.c b/third_party/libjpeg/jdapistd.c
deleted file mode 100644
index c8e3fa0..0000000
--- a/third_party/libjpeg/jdapistd.c
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * jdapistd.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains application interface code for the decompression half
- * of the JPEG library.  These are the "standard" API routines that are
- * used in the normal full-decompression case.  They are not used by a
- * transcoding-only application.  Note that if an application links in
- * jpeg_start_decompress, it will end up linking in the entire decompressor.
- * We thus must separate this file from jdapimin.c to avoid linking the
- * whole decompression library into a transcoder.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Forward declarations */
-LOCAL(boolean) output_pass_setup JPP((j_decompress_ptr cinfo));
-
-
-/*
- * Decompression initialization.
- * jpeg_read_header must be completed before calling this.
- *
- * If a multipass operating mode was selected, this will do all but the
- * last pass, and thus may take a great deal of time.
- *
- * Returns FALSE if suspended.  The return value need be inspected only if
- * a suspending data source is used.
- */
-
-GLOBAL(boolean)
-jpeg_start_decompress (j_decompress_ptr cinfo)
-{
-  if (cinfo->global_state == DSTATE_READY) {
-    /* First call: initialize master control, select active modules */
-    jinit_master_decompress(cinfo);
-    if (cinfo->buffered_image) {
-      /* No more work here; expecting jpeg_start_output next */
-      cinfo->global_state = DSTATE_BUFIMAGE;
-      return TRUE;
-    }
-    cinfo->global_state = DSTATE_PRELOAD;
-  }
-  if (cinfo->global_state == DSTATE_PRELOAD) {
-    /* If file has multiple scans, absorb them all into the coef buffer */
-    if (cinfo->inputctl->has_multiple_scans) {
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-      for (;;) {
-	int retcode;
-	/* Call progress monitor hook if present */
-	if (cinfo->progress != NULL)
-	  (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-	/* Absorb some more input */
-	retcode = (*cinfo->inputctl->consume_input) (cinfo);
-	if (retcode == JPEG_SUSPENDED)
-	  return FALSE;
-	if (retcode == JPEG_REACHED_EOI)
-	  break;
-	/* Advance progress counter if appropriate */
-	if (cinfo->progress != NULL &&
-	    (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) {
-	  if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) {
-	    /* jdmaster underestimated number of scans; ratchet up one scan */
-	    cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows;
-	  }
-	}
-      }
-#else
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif /* D_MULTISCAN_FILES_SUPPORTED */
-    }
-    cinfo->output_scan_number = cinfo->input_scan_number;
-  } else if (cinfo->global_state != DSTATE_PRESCAN)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  /* Perform any dummy output passes, and set up for the final pass */
-  return output_pass_setup(cinfo);
-}
-
-
-/*
- * Set up for an output pass, and perform any dummy pass(es) needed.
- * Common subroutine for jpeg_start_decompress and jpeg_start_output.
- * Entry: global_state = DSTATE_PRESCAN only if previously suspended.
- * Exit: If done, returns TRUE and sets global_state for proper output mode.
- *       If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN.
- */
-
-LOCAL(boolean)
-output_pass_setup (j_decompress_ptr cinfo)
-{
-  if (cinfo->global_state != DSTATE_PRESCAN) {
-    /* First call: do pass setup */
-    (*cinfo->master->prepare_for_output_pass) (cinfo);
-    cinfo->output_scanline = 0;
-    cinfo->global_state = DSTATE_PRESCAN;
-  }
-  /* Loop over any required dummy passes */
-  while (cinfo->master->is_dummy_pass) {
-#ifdef QUANT_2PASS_SUPPORTED
-    /* Crank through the dummy pass */
-    while (cinfo->output_scanline < cinfo->output_height) {
-      JDIMENSION last_scanline;
-      /* Call progress monitor hook if present */
-      if (cinfo->progress != NULL) {
-	cinfo->progress->pass_counter = (long) cinfo->output_scanline;
-	cinfo->progress->pass_limit = (long) cinfo->output_height;
-	(*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-      }
-      /* Process some data */
-      last_scanline = cinfo->output_scanline;
-      (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL,
-				    &cinfo->output_scanline, (JDIMENSION) 0);
-      if (cinfo->output_scanline == last_scanline)
-	return FALSE;		/* No progress made, must suspend */
-    }
-    /* Finish up dummy pass, and set up for another one */
-    (*cinfo->master->finish_output_pass) (cinfo);
-    (*cinfo->master->prepare_for_output_pass) (cinfo);
-    cinfo->output_scanline = 0;
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif /* QUANT_2PASS_SUPPORTED */
-  }
-  /* Ready for application to drive output pass through
-   * jpeg_read_scanlines or jpeg_read_raw_data.
-   */
-  cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING;
-  return TRUE;
-}
-
-
-/*
- * Read some scanlines of data from the JPEG decompressor.
- *
- * The return value will be the number of lines actually read.
- * This may be less than the number requested in several cases,
- * including bottom of image, data source suspension, and operating
- * modes that emit multiple scanlines at a time.
- *
- * Note: we warn about excess calls to jpeg_read_scanlines() since
- * this likely signals an application programmer error.  However,
- * an oversize buffer (max_lines > scanlines remaining) is not an error.
- */
-
-GLOBAL(JDIMENSION)
-jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines,
-		     JDIMENSION max_lines)
-{
-  JDIMENSION row_ctr;
-
-  if (cinfo->global_state != DSTATE_SCANNING)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  if (cinfo->output_scanline >= cinfo->output_height) {
-    WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
-    return 0;
-  }
-
-  /* Call progress monitor hook if present */
-  if (cinfo->progress != NULL) {
-    cinfo->progress->pass_counter = (long) cinfo->output_scanline;
-    cinfo->progress->pass_limit = (long) cinfo->output_height;
-    (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-  }
-
-  /* Process some data */
-  row_ctr = 0;
-  (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines);
-  cinfo->output_scanline += row_ctr;
-  return row_ctr;
-}
-
-
-/*
- * Alternate entry point to read raw data.
- * Processes exactly one iMCU row per call, unless suspended.
- */
-
-GLOBAL(JDIMENSION)
-jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data,
-		    JDIMENSION max_lines)
-{
-  JDIMENSION lines_per_iMCU_row;
-
-  if (cinfo->global_state != DSTATE_RAW_OK)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  if (cinfo->output_scanline >= cinfo->output_height) {
-    WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
-    return 0;
-  }
-
-  /* Call progress monitor hook if present */
-  if (cinfo->progress != NULL) {
-    cinfo->progress->pass_counter = (long) cinfo->output_scanline;
-    cinfo->progress->pass_limit = (long) cinfo->output_height;
-    (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
-  }
-
-  /* Verify that at least one iMCU row can be returned. */
-  lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size;
-  if (max_lines < lines_per_iMCU_row)
-    ERREXIT(cinfo, JERR_BUFFER_SIZE);
-
-  /* Decompress directly into user's buffer. */
-  if (! (*cinfo->coef->decompress_data) (cinfo, data))
-    return 0;			/* suspension forced, can do nothing more */
-
-  /* OK, we processed one iMCU row. */
-  cinfo->output_scanline += lines_per_iMCU_row;
-  return lines_per_iMCU_row;
-}
-
-
-/* Additional entry points for buffered-image mode. */
-
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-
-/*
- * Initialize for an output pass in buffered-image mode.
- */
-
-GLOBAL(boolean)
-jpeg_start_output (j_decompress_ptr cinfo, int scan_number)
-{
-  if (cinfo->global_state != DSTATE_BUFIMAGE &&
-      cinfo->global_state != DSTATE_PRESCAN)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  /* Limit scan number to valid range */
-  if (scan_number <= 0)
-    scan_number = 1;
-  if (cinfo->inputctl->eoi_reached &&
-      scan_number > cinfo->input_scan_number)
-    scan_number = cinfo->input_scan_number;
-  cinfo->output_scan_number = scan_number;
-  /* Perform any dummy output passes, and set up for the real pass */
-  return output_pass_setup(cinfo);
-}
-
-
-/*
- * Finish up after an output pass in buffered-image mode.
- *
- * Returns FALSE if suspended.  The return value need be inspected only if
- * a suspending data source is used.
- */
-
-GLOBAL(boolean)
-jpeg_finish_output (j_decompress_ptr cinfo)
-{
-  if ((cinfo->global_state == DSTATE_SCANNING ||
-       cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) {
-    /* Terminate this pass. */
-    /* We do not require the whole pass to have been completed. */
-    (*cinfo->master->finish_output_pass) (cinfo);
-    cinfo->global_state = DSTATE_BUFPOST;
-  } else if (cinfo->global_state != DSTATE_BUFPOST) {
-    /* BUFPOST = repeat call after a suspension, anything else is error */
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-  }
-  /* Read markers looking for SOS or EOI */
-  while (cinfo->input_scan_number <= cinfo->output_scan_number &&
-	 ! cinfo->inputctl->eoi_reached) {
-    if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED)
-      return FALSE;		/* Suspend, come back later */
-  }
-  cinfo->global_state = DSTATE_BUFIMAGE;
-  return TRUE;
-}
-
-#endif /* D_MULTISCAN_FILES_SUPPORTED */
diff --git a/third_party/libjpeg/jdatadst.c b/third_party/libjpeg/jdatadst.c
deleted file mode 100644
index a8f6fb0..0000000
--- a/third_party/libjpeg/jdatadst.c
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * jdatadst.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains compression data destination routines for the case of
- * emitting JPEG data to a file (or any stdio stream).  While these routines
- * are sufficient for most applications, some will want to use a different
- * destination manager.
- * IMPORTANT: we assume that fwrite() will correctly transcribe an array of
- * JOCTETs into 8-bit-wide elements on external storage.  If char is wider
- * than 8 bits on your machine, you may need to do some tweaking.
- */
-
-/* this is not a core library module, so it doesn't define JPEG_INTERNALS */
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jerror.h"
-
-
-/* Expanded data destination object for stdio output */
-
-typedef struct {
-  struct jpeg_destination_mgr pub; /* public fields */
-
-  FILE * outfile;		/* target stream */
-  JOCTET * buffer;		/* start of buffer */
-} my_destination_mgr;
-
-typedef my_destination_mgr * my_dest_ptr;
-
-#define OUTPUT_BUF_SIZE  4096	/* choose an efficiently fwrite'able size */
-
-
-/*
- * Initialize destination --- called by jpeg_start_compress
- * before any data is actually written.
- */
-
-METHODDEF(void)
-init_destination (j_compress_ptr cinfo)
-{
-  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
-
-  /* Allocate the output buffer --- it will be released when done with image */
-  dest->buffer = (JOCTET *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  OUTPUT_BUF_SIZE * SIZEOF(JOCTET));
-
-  dest->pub.next_output_byte = dest->buffer;
-  dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
-}
-
-
-/*
- * Empty the output buffer --- called whenever buffer fills up.
- *
- * In typical applications, this should write the entire output buffer
- * (ignoring the current state of next_output_byte & free_in_buffer),
- * reset the pointer & count to the start of the buffer, and return TRUE
- * indicating that the buffer has been dumped.
- *
- * In applications that need to be able to suspend compression due to output
- * overrun, a FALSE return indicates that the buffer cannot be emptied now.
- * In this situation, the compressor will return to its caller (possibly with
- * an indication that it has not accepted all the supplied scanlines).  The
- * application should resume compression after it has made more room in the
- * output buffer.  Note that there are substantial restrictions on the use of
- * suspension --- see the documentation.
- *
- * When suspending, the compressor will back up to a convenient restart point
- * (typically the start of the current MCU). next_output_byte & free_in_buffer
- * indicate where the restart point will be if the current call returns FALSE.
- * Data beyond this point will be regenerated after resumption, so do not
- * write it out when emptying the buffer externally.
- */
-
-METHODDEF(boolean)
-empty_output_buffer (j_compress_ptr cinfo)
-{
-  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
-
-  if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) !=
-      (size_t) OUTPUT_BUF_SIZE)
-    ERREXIT(cinfo, JERR_FILE_WRITE);
-
-  dest->pub.next_output_byte = dest->buffer;
-  dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
-
-  return TRUE;
-}
-
-
-/*
- * Terminate destination --- called by jpeg_finish_compress
- * after all data has been written.  Usually needs to flush buffer.
- *
- * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
- * application must deal with any cleanup that should happen even
- * for error exit.
- */
-
-METHODDEF(void)
-term_destination (j_compress_ptr cinfo)
-{
-  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
-  size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
-
-  /* Write any data remaining in the buffer */
-  if (datacount > 0) {
-    if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount)
-      ERREXIT(cinfo, JERR_FILE_WRITE);
-  }
-  fflush(dest->outfile);
-  /* Make sure we wrote the output file OK */
-  if (ferror(dest->outfile))
-    ERREXIT(cinfo, JERR_FILE_WRITE);
-}
-
-
-/*
- * Prepare for output to a stdio stream.
- * The caller must have already opened the stream, and is responsible
- * for closing it after finishing compression.
- */
-
-GLOBAL(void)
-jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile)
-{
-  my_dest_ptr dest;
-
-  /* The destination object is made permanent so that multiple JPEG images
-   * can be written to the same file without re-executing jpeg_stdio_dest.
-   * This makes it dangerous to use this manager and a different destination
-   * manager serially with the same JPEG object, because their private object
-   * sizes may be different.  Caveat programmer.
-   */
-  if (cinfo->dest == NULL) {	/* first time for this JPEG object? */
-    cinfo->dest = (struct jpeg_destination_mgr *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-				  SIZEOF(my_destination_mgr));
-  }
-
-  dest = (my_dest_ptr) cinfo->dest;
-  dest->pub.init_destination = init_destination;
-  dest->pub.empty_output_buffer = empty_output_buffer;
-  dest->pub.term_destination = term_destination;
-  dest->outfile = outfile;
-}
diff --git a/third_party/libjpeg/jdatasrc.c b/third_party/libjpeg/jdatasrc.c
deleted file mode 100644
index edc752b..0000000
--- a/third_party/libjpeg/jdatasrc.c
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * jdatasrc.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains decompression data source routines for the case of
- * reading JPEG data from a file (or any stdio stream).  While these routines
- * are sufficient for most applications, some will want to use a different
- * source manager.
- * IMPORTANT: we assume that fread() will correctly transcribe an array of
- * JOCTETs from 8-bit-wide elements on external storage.  If char is wider
- * than 8 bits on your machine, you may need to do some tweaking.
- */
-
-/* this is not a core library module, so it doesn't define JPEG_INTERNALS */
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jerror.h"
-
-
-/* Expanded data source object for stdio input */
-
-typedef struct {
-  struct jpeg_source_mgr pub;	/* public fields */
-
-  FILE * infile;		/* source stream */
-  JOCTET * buffer;		/* start of buffer */
-  boolean start_of_file;	/* have we gotten any data yet? */
-} my_source_mgr;
-
-typedef my_source_mgr * my_src_ptr;
-
-#define INPUT_BUF_SIZE  4096	/* choose an efficiently fread'able size */
-
-
-/*
- * Initialize source --- called by jpeg_read_header
- * before any data is actually read.
- */
-
-METHODDEF(void)
-init_source (j_decompress_ptr cinfo)
-{
-  my_src_ptr src = (my_src_ptr) cinfo->src;
-
-  /* We reset the empty-input-file flag for each image,
-   * but we don't clear the input buffer.
-   * This is correct behavior for reading a series of images from one source.
-   */
-  src->start_of_file = TRUE;
-}
-
-
-/*
- * Fill the input buffer --- called whenever buffer is emptied.
- *
- * In typical applications, this should read fresh data into the buffer
- * (ignoring the current state of next_input_byte & bytes_in_buffer),
- * reset the pointer & count to the start of the buffer, and return TRUE
- * indicating that the buffer has been reloaded.  It is not necessary to
- * fill the buffer entirely, only to obtain at least one more byte.
- *
- * There is no such thing as an EOF return.  If the end of the file has been
- * reached, the routine has a choice of ERREXIT() or inserting fake data into
- * the buffer.  In most cases, generating a warning message and inserting a
- * fake EOI marker is the best course of action --- this will allow the
- * decompressor to output however much of the image is there.  However,
- * the resulting error message is misleading if the real problem is an empty
- * input file, so we handle that case specially.
- *
- * In applications that need to be able to suspend compression due to input
- * not being available yet, a FALSE return indicates that no more data can be
- * obtained right now, but more may be forthcoming later.  In this situation,
- * the decompressor will return to its caller (with an indication of the
- * number of scanlines it has read, if any).  The application should resume
- * decompression after it has loaded more data into the input buffer.  Note
- * that there are substantial restrictions on the use of suspension --- see
- * the documentation.
- *
- * When suspending, the decompressor will back up to a convenient restart point
- * (typically the start of the current MCU). next_input_byte & bytes_in_buffer
- * indicate where the restart point will be if the current call returns FALSE.
- * Data beyond this point must be rescanned after resumption, so move it to
- * the front of the buffer rather than discarding it.
- */
-
-METHODDEF(boolean)
-fill_input_buffer (j_decompress_ptr cinfo)
-{
-  my_src_ptr src = (my_src_ptr) cinfo->src;
-  size_t nbytes;
-
-  nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE);
-
-  if (nbytes <= 0) {
-    if (src->start_of_file)	/* Treat empty input file as fatal error */
-      ERREXIT(cinfo, JERR_INPUT_EMPTY);
-    WARNMS(cinfo, JWRN_JPEG_EOF);
-    /* Insert a fake EOI marker */
-    src->buffer[0] = (JOCTET) 0xFF;
-    src->buffer[1] = (JOCTET) JPEG_EOI;
-    nbytes = 2;
-  }
-
-  src->pub.next_input_byte = src->buffer;
-  src->pub.bytes_in_buffer = nbytes;
-  src->start_of_file = FALSE;
-
-  return TRUE;
-}
-
-
-/*
- * Skip data --- used to skip over a potentially large amount of
- * uninteresting data (such as an APPn marker).
- *
- * Writers of suspendable-input applications must note that skip_input_data
- * is not granted the right to give a suspension return.  If the skip extends
- * beyond the data currently in the buffer, the buffer can be marked empty so
- * that the next read will cause a fill_input_buffer call that can suspend.
- * Arranging for additional bytes to be discarded before reloading the input
- * buffer is the application writer's problem.
- */
-
-METHODDEF(void)
-skip_input_data (j_decompress_ptr cinfo, long num_bytes)
-{
-  my_src_ptr src = (my_src_ptr) cinfo->src;
-
-  /* Just a dumb implementation for now.  Could use fseek() except
-   * it doesn't work on pipes.  Not clear that being smart is worth
-   * any trouble anyway --- large skips are infrequent.
-   */
-  if (num_bytes > 0) {
-    while (num_bytes > (long) src->pub.bytes_in_buffer) {
-      num_bytes -= (long) src->pub.bytes_in_buffer;
-      (void) fill_input_buffer(cinfo);
-      /* note we assume that fill_input_buffer will never return FALSE,
-       * so suspension need not be handled.
-       */
-    }
-    src->pub.next_input_byte += (size_t) num_bytes;
-    src->pub.bytes_in_buffer -= (size_t) num_bytes;
-  }
-}
-
-
-/*
- * An additional method that can be provided by data source modules is the
- * resync_to_restart method for error recovery in the presence of RST markers.
- * For the moment, this source module just uses the default resync method
- * provided by the JPEG library.  That method assumes that no backtracking
- * is possible.
- */
-
-
-/*
- * Terminate source --- called by jpeg_finish_decompress
- * after all data has been read.  Often a no-op.
- *
- * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
- * application must deal with any cleanup that should happen even
- * for error exit.
- */
-
-METHODDEF(void)
-term_source (j_decompress_ptr cinfo)
-{
-  /* no work necessary here */
-}
-
-
-/*
- * Prepare for input from a stdio stream.
- * The caller must have already opened the stream, and is responsible
- * for closing it after finishing decompression.
- */
-
-GLOBAL(void)
-jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile)
-{
-  my_src_ptr src;
-
-  /* The source object and input buffer are made permanent so that a series
-   * of JPEG images can be read from the same file by calling jpeg_stdio_src
-   * only before the first one.  (If we discarded the buffer at the end of
-   * one image, we'd likely lose the start of the next one.)
-   * This makes it unsafe to use this manager and a different source
-   * manager serially with the same JPEG object.  Caveat programmer.
-   */
-  if (cinfo->src == NULL) {	/* first time for this JPEG object? */
-    cinfo->src = (struct jpeg_source_mgr *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-				  SIZEOF(my_source_mgr));
-    src = (my_src_ptr) cinfo->src;
-    src->buffer = (JOCTET *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-				  INPUT_BUF_SIZE * SIZEOF(JOCTET));
-  }
-
-  src = (my_src_ptr) cinfo->src;
-  src->pub.init_source = init_source;
-  src->pub.fill_input_buffer = fill_input_buffer;
-  src->pub.skip_input_data = skip_input_data;
-  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
-  src->pub.term_source = term_source;
-  src->infile = infile;
-  src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
-  src->pub.next_input_byte = NULL; /* until buffer loaded */
-}
diff --git a/third_party/libjpeg/jdcoefct.c b/third_party/libjpeg/jdcoefct.c
deleted file mode 100644
index 4938d20..0000000
--- a/third_party/libjpeg/jdcoefct.c
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * jdcoefct.c
- *
- * Copyright (C) 1994-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the coefficient buffer controller for decompression.
- * This controller is the top level of the JPEG decompressor proper.
- * The coefficient buffer lies between entropy decoding and inverse-DCT steps.
- *
- * In buffered-image mode, this controller is the interface between
- * input-oriented processing and output-oriented processing.
- * Also, the input side (only) is used when reading a file for transcoding.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-/* Block smoothing is only applicable for progressive JPEG, so: */
-#ifndef D_PROGRESSIVE_SUPPORTED
-#undef BLOCK_SMOOTHING_SUPPORTED
-#endif
-
-/* Private buffer controller object */
-
-typedef struct {
-  struct jpeg_d_coef_controller pub; /* public fields */
-
-  /* These variables keep track of the current location of the input side. */
-  /* cinfo->input_iMCU_row is also used for this. */
-  JDIMENSION MCU_ctr;		/* counts MCUs processed in current row */
-  int MCU_vert_offset;		/* counts MCU rows within iMCU row */
-  int MCU_rows_per_iMCU_row;	/* number of such rows needed */
-
-  /* The output side's location is represented by cinfo->output_iMCU_row. */
-
-  /* In single-pass modes, it's sufficient to buffer just one MCU.
-   * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks,
-   * and let the entropy decoder write into that workspace each time.
-   * (On 80x86, the workspace is FAR even though it's not really very big;
-   * this is to keep the module interfaces unchanged when a large coefficient
-   * buffer is necessary.)
-   * In multi-pass modes, this array points to the current MCU's blocks
-   * within the virtual arrays; it is used only by the input side.
-   */
-  JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU];
-
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-  /* In multi-pass modes, we need a virtual block array for each component. */
-  jvirt_barray_ptr whole_image[MAX_COMPONENTS];
-#endif
-
-#ifdef BLOCK_SMOOTHING_SUPPORTED
-  /* When doing block smoothing, we latch coefficient Al values here */
-  int * coef_bits_latch;
-#define SAVED_COEFS  6		/* we save coef_bits[0..5] */
-#endif
-} my_coef_controller;
-
-typedef my_coef_controller * my_coef_ptr;
-
-/* Forward declarations */
-METHODDEF(int) decompress_onepass
-	JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf));
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-METHODDEF(int) decompress_data
-	JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf));
-#endif
-#ifdef BLOCK_SMOOTHING_SUPPORTED
-LOCAL(boolean) smoothing_ok JPP((j_decompress_ptr cinfo));
-METHODDEF(int) decompress_smooth_data
-	JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf));
-#endif
-
-
-LOCAL(void)
-start_iMCU_row (j_decompress_ptr cinfo)
-/* Reset within-iMCU-row counters for a new row (input side) */
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-
-  /* In an interleaved scan, an MCU row is the same as an iMCU row.
-   * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows.
-   * But at the bottom of the image, process only what's left.
-   */
-  if (cinfo->comps_in_scan > 1) {
-    coef->MCU_rows_per_iMCU_row = 1;
-  } else {
-    if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1))
-      coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor;
-    else
-      coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height;
-  }
-
-  coef->MCU_ctr = 0;
-  coef->MCU_vert_offset = 0;
-}
-
-
-/*
- * Initialize for an input processing pass.
- */
-
-METHODDEF(void)
-start_input_pass (j_decompress_ptr cinfo)
-{
-  cinfo->input_iMCU_row = 0;
-  start_iMCU_row(cinfo);
-}
-
-
-/*
- * Initialize for an output processing pass.
- */
-
-METHODDEF(void)
-start_output_pass (j_decompress_ptr cinfo)
-{
-#ifdef BLOCK_SMOOTHING_SUPPORTED
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-
-  /* If multipass, check to see whether to use block smoothing on this pass */
-  if (coef->pub.coef_arrays != NULL) {
-    if (cinfo->do_block_smoothing && smoothing_ok(cinfo))
-      coef->pub.decompress_data = decompress_smooth_data;
-    else
-      coef->pub.decompress_data = decompress_data;
-  }
-#endif
-  cinfo->output_iMCU_row = 0;
-}
-
-
-/*
- * Decompress and return some data in the single-pass case.
- * Always attempts to emit one fully interleaved MCU row ("iMCU" row).
- * Input and output must run in lockstep since we have only a one-MCU buffer.
- * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED.
- *
- * NB: output_buf contains a plane for each component in image,
- * which we index according to the component's SOF position.
- */
-
-METHODDEF(int)
-decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION MCU_col_num;	/* index of current MCU within row */
-  JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1;
-  JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
-  int blkn, ci, xindex, yindex, yoffset, useful_width;
-  JSAMPARRAY output_ptr;
-  JDIMENSION start_col, output_col;
-  jpeg_component_info *compptr;
-  inverse_DCT_method_ptr inverse_DCT;
-
-  /* Loop to process as much as one whole iMCU row */
-  for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row;
-       yoffset++) {
-    for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col;
-	 MCU_col_num++) {
-      /* Try to fetch an MCU.  Entropy decoder expects buffer to be zeroed. */
-      jzero_far((void FAR *) coef->MCU_buffer[0],
-		(size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK)));
-      if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) {
-	/* Suspension forced; update state counters and exit */
-	coef->MCU_vert_offset = yoffset;
-	coef->MCU_ctr = MCU_col_num;
-	return JPEG_SUSPENDED;
-      }
-      /* Determine where data should go in output_buf and do the IDCT thing.
-       * We skip dummy blocks at the right and bottom edges (but blkn gets
-       * incremented past them!).  Note the inner loop relies on having
-       * allocated the MCU_buffer[] blocks sequentially.
-       */
-      blkn = 0;			/* index of current DCT block within MCU */
-      for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-	compptr = cinfo->cur_comp_info[ci];
-	/* Don't bother to IDCT an uninteresting component. */
-	if (! compptr->component_needed) {
-	  blkn += compptr->MCU_blocks;
-	  continue;
-	}
-	inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index];
-	useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width
-						    : compptr->last_col_width;
-	output_ptr = output_buf[compptr->component_index] +
-	  yoffset * compptr->DCT_scaled_size;
-	start_col = MCU_col_num * compptr->MCU_sample_width;
-	for (yindex = 0; yindex < compptr->MCU_height; yindex++) {
-	  if (cinfo->input_iMCU_row < last_iMCU_row ||
-	      yoffset+yindex < compptr->last_row_height) {
-	    output_col = start_col;
-	    for (xindex = 0; xindex < useful_width; xindex++) {
-	      (*inverse_DCT) (cinfo, compptr,
-			      (JCOEFPTR) coef->MCU_buffer[blkn+xindex],
-			      output_ptr, output_col);
-	      output_col += compptr->DCT_scaled_size;
-	    }
-	  }
-	  blkn += compptr->MCU_width;
-	  output_ptr += compptr->DCT_scaled_size;
-	}
-      }
-    }
-    /* Completed an MCU row, but perhaps not an iMCU row */
-    coef->MCU_ctr = 0;
-  }
-  /* Completed the iMCU row, advance counters for next one */
-  cinfo->output_iMCU_row++;
-  if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) {
-    start_iMCU_row(cinfo);
-    return JPEG_ROW_COMPLETED;
-  }
-  /* Completed the scan */
-  (*cinfo->inputctl->finish_input_pass) (cinfo);
-  return JPEG_SCAN_COMPLETED;
-}
-
-
-/*
- * Dummy consume-input routine for single-pass operation.
- */
-
-METHODDEF(int)
-dummy_consume_data (j_decompress_ptr cinfo)
-{
-  return JPEG_SUSPENDED;	/* Always indicate nothing was done */
-}
-
-
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-
-/*
- * Consume input data and store it in the full-image coefficient buffer.
- * We read as much as one fully interleaved MCU row ("iMCU" row) per call,
- * ie, v_samp_factor block rows for each component in the scan.
- * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED.
- */
-
-METHODDEF(int)
-consume_data (j_decompress_ptr cinfo)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION MCU_col_num;	/* index of current MCU within row */
-  int blkn, ci, xindex, yindex, yoffset;
-  JDIMENSION start_col;
-  JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN];
-  JBLOCKROW buffer_ptr;
-  jpeg_component_info *compptr;
-
-  /* Align the virtual buffers for the components used in this scan. */
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    buffer[ci] = (*cinfo->mem->access_virt_barray)
-      ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index],
-       cinfo->input_iMCU_row * compptr->v_samp_factor,
-       (JDIMENSION) compptr->v_samp_factor, TRUE);
-    /* Note: entropy decoder expects buffer to be zeroed,
-     * but this is handled automatically by the memory manager
-     * because we requested a pre-zeroed array.
-     */
-  }
-
-  /* Loop to process one whole iMCU row */
-  for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row;
-       yoffset++) {
-    for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row;
-	 MCU_col_num++) {
-      /* Construct list of pointers to DCT blocks belonging to this MCU */
-      blkn = 0;			/* index of current DCT block within MCU */
-      for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-	compptr = cinfo->cur_comp_info[ci];
-	start_col = MCU_col_num * compptr->MCU_width;
-	for (yindex = 0; yindex < compptr->MCU_height; yindex++) {
-	  buffer_ptr = buffer[ci][yindex+yoffset] + start_col;
-	  for (xindex = 0; xindex < compptr->MCU_width; xindex++) {
-	    coef->MCU_buffer[blkn++] = buffer_ptr++;
-	  }
-	}
-      }
-      /* Try to fetch the MCU. */
-      if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) {
-	/* Suspension forced; update state counters and exit */
-	coef->MCU_vert_offset = yoffset;
-	coef->MCU_ctr = MCU_col_num;
-	return JPEG_SUSPENDED;
-      }
-    }
-    /* Completed an MCU row, but perhaps not an iMCU row */
-    coef->MCU_ctr = 0;
-  }
-  /* Completed the iMCU row, advance counters for next one */
-  if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) {
-    start_iMCU_row(cinfo);
-    return JPEG_ROW_COMPLETED;
-  }
-  /* Completed the scan */
-  (*cinfo->inputctl->finish_input_pass) (cinfo);
-  return JPEG_SCAN_COMPLETED;
-}
-
-
-/*
- * Decompress and return some data in the multi-pass case.
- * Always attempts to emit one fully interleaved MCU row ("iMCU" row).
- * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED.
- *
- * NB: output_buf contains a plane for each component in image.
- */
-
-METHODDEF(int)
-decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
-  JDIMENSION block_num;
-  int ci, block_row, block_rows;
-  JBLOCKARRAY buffer;
-  JBLOCKROW buffer_ptr;
-  JSAMPARRAY output_ptr;
-  JDIMENSION output_col;
-  jpeg_component_info *compptr;
-  inverse_DCT_method_ptr inverse_DCT;
-
-  /* Force some input to be done if we are getting ahead of the input. */
-  while (cinfo->input_scan_number < cinfo->output_scan_number ||
-	 (cinfo->input_scan_number == cinfo->output_scan_number &&
-	  cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) {
-    if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED)
-      return JPEG_SUSPENDED;
-  }
-
-  /* OK, output from the virtual arrays. */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Don't bother to IDCT an uninteresting component. */
-    if (! compptr->component_needed)
-      continue;
-    /* Align the virtual buffer for this component. */
-    buffer = (*cinfo->mem->access_virt_barray)
-      ((j_common_ptr) cinfo, coef->whole_image[ci],
-       cinfo->output_iMCU_row * compptr->v_samp_factor,
-       (JDIMENSION) compptr->v_samp_factor, FALSE);
-    /* Count non-dummy DCT block rows in this iMCU row. */
-    if (cinfo->output_iMCU_row < last_iMCU_row)
-      block_rows = compptr->v_samp_factor;
-    else {
-      /* NB: can't use last_row_height here; it is input-side-dependent! */
-      block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor);
-      if (block_rows == 0) block_rows = compptr->v_samp_factor;
-    }
-    inverse_DCT = cinfo->idct->inverse_DCT[ci];
-    output_ptr = output_buf[ci];
-    /* Loop over all DCT blocks to be processed. */
-    for (block_row = 0; block_row < block_rows; block_row++) {
-      buffer_ptr = buffer[block_row];
-      output_col = 0;
-      for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) {
-	(*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr,
-			output_ptr, output_col);
-	buffer_ptr++;
-	output_col += compptr->DCT_scaled_size;
-      }
-      output_ptr += compptr->DCT_scaled_size;
-    }
-  }
-
-  if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows)
-    return JPEG_ROW_COMPLETED;
-  return JPEG_SCAN_COMPLETED;
-}
-
-#endif /* D_MULTISCAN_FILES_SUPPORTED */
-
-
-#ifdef BLOCK_SMOOTHING_SUPPORTED
-
-/*
- * This code applies interblock smoothing as described by section K.8
- * of the JPEG standard: the first 5 AC coefficients are estimated from
- * the DC values of a DCT block and its 8 neighboring blocks.
- * We apply smoothing only for progressive JPEG decoding, and only if
- * the coefficients it can estimate are not yet known to full precision.
- */
-
-/* Natural-order array positions of the first 5 zigzag-order coefficients */
-#define Q01_POS  1
-#define Q10_POS  8
-#define Q20_POS  16
-#define Q11_POS  9
-#define Q02_POS  2
-
-/*
- * Determine whether block smoothing is applicable and safe.
- * We also latch the current states of the coef_bits[] entries for the
- * AC coefficients; otherwise, if the input side of the decompressor
- * advances into a new scan, we might think the coefficients are known
- * more accurately than they really are.
- */
-
-LOCAL(boolean)
-smoothing_ok (j_decompress_ptr cinfo)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  boolean smoothing_useful = FALSE;
-  int ci, coefi;
-  jpeg_component_info *compptr;
-  JQUANT_TBL * qtable;
-  int * coef_bits;
-  int * coef_bits_latch;
-
-  if (! cinfo->progressive_mode || cinfo->coef_bits == NULL)
-    return FALSE;
-
-  /* Allocate latch area if not already done */
-  if (coef->coef_bits_latch == NULL)
-    coef->coef_bits_latch = (int *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  cinfo->num_components *
-				  (SAVED_COEFS * SIZEOF(int)));
-  coef_bits_latch = coef->coef_bits_latch;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* All components' quantization values must already be latched. */
-    if ((qtable = compptr->quant_table) == NULL)
-      return FALSE;
-    /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */
-    if (qtable->quantval[0] == 0 ||
-	qtable->quantval[Q01_POS] == 0 ||
-	qtable->quantval[Q10_POS] == 0 ||
-	qtable->quantval[Q20_POS] == 0 ||
-	qtable->quantval[Q11_POS] == 0 ||
-	qtable->quantval[Q02_POS] == 0)
-      return FALSE;
-    /* DC values must be at least partly known for all components. */
-    coef_bits = cinfo->coef_bits[ci];
-    if (coef_bits[0] < 0)
-      return FALSE;
-    /* Block smoothing is helpful if some AC coefficients remain inaccurate. */
-    for (coefi = 1; coefi <= 5; coefi++) {
-      coef_bits_latch[coefi] = coef_bits[coefi];
-      if (coef_bits[coefi] != 0)
-	smoothing_useful = TRUE;
-    }
-    coef_bits_latch += SAVED_COEFS;
-  }
-
-  return smoothing_useful;
-}
-
-
-/*
- * Variant of decompress_data for use when doing block smoothing.
- */
-
-METHODDEF(int)
-decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf)
-{
-  my_coef_ptr coef = (my_coef_ptr) cinfo->coef;
-  JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
-  JDIMENSION block_num, last_block_column;
-  int ci, block_row, block_rows, access_rows;
-  JBLOCKARRAY buffer;
-  JBLOCKROW buffer_ptr, prev_block_row, next_block_row;
-  JSAMPARRAY output_ptr;
-  JDIMENSION output_col;
-  jpeg_component_info *compptr;
-  inverse_DCT_method_ptr inverse_DCT;
-  boolean first_row, last_row;
-  JBLOCK workspace;
-  int *coef_bits;
-  JQUANT_TBL *quanttbl;
-  INT32 Q00,Q01,Q02,Q10,Q11,Q20, num;
-  int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9;
-  int Al, pred;
-
-  /* Force some input to be done if we are getting ahead of the input. */
-  while (cinfo->input_scan_number <= cinfo->output_scan_number &&
-	 ! cinfo->inputctl->eoi_reached) {
-    if (cinfo->input_scan_number == cinfo->output_scan_number) {
-      /* If input is working on current scan, we ordinarily want it to
-       * have completed the current row.  But if input scan is DC,
-       * we want it to keep one row ahead so that next block row's DC
-       * values are up to date.
-       */
-      JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0;
-      if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta)
-	break;
-    }
-    if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED)
-      return JPEG_SUSPENDED;
-  }
-
-  /* OK, output from the virtual arrays. */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Don't bother to IDCT an uninteresting component. */
-    if (! compptr->component_needed)
-      continue;
-    /* Count non-dummy DCT block rows in this iMCU row. */
-    if (cinfo->output_iMCU_row < last_iMCU_row) {
-      block_rows = compptr->v_samp_factor;
-      access_rows = block_rows * 2; /* this and next iMCU row */
-      last_row = FALSE;
-    } else {
-      /* NB: can't use last_row_height here; it is input-side-dependent! */
-      block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor);
-      if (block_rows == 0) block_rows = compptr->v_samp_factor;
-      access_rows = block_rows; /* this iMCU row only */
-      last_row = TRUE;
-    }
-    /* Align the virtual buffer for this component. */
-    if (cinfo->output_iMCU_row > 0) {
-      access_rows += compptr->v_samp_factor; /* prior iMCU row too */
-      buffer = (*cinfo->mem->access_virt_barray)
-	((j_common_ptr) cinfo, coef->whole_image[ci],
-	 (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor,
-	 (JDIMENSION) access_rows, FALSE);
-      buffer += compptr->v_samp_factor;	/* point to current iMCU row */
-      first_row = FALSE;
-    } else {
-      buffer = (*cinfo->mem->access_virt_barray)
-	((j_common_ptr) cinfo, coef->whole_image[ci],
-	 (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE);
-      first_row = TRUE;
-    }
-    /* Fetch component-dependent info */
-    coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS);
-    quanttbl = compptr->quant_table;
-    Q00 = quanttbl->quantval[0];
-    Q01 = quanttbl->quantval[Q01_POS];
-    Q10 = quanttbl->quantval[Q10_POS];
-    Q20 = quanttbl->quantval[Q20_POS];
-    Q11 = quanttbl->quantval[Q11_POS];
-    Q02 = quanttbl->quantval[Q02_POS];
-    inverse_DCT = cinfo->idct->inverse_DCT[ci];
-    output_ptr = output_buf[ci];
-    /* Loop over all DCT blocks to be processed. */
-    for (block_row = 0; block_row < block_rows; block_row++) {
-      buffer_ptr = buffer[block_row];
-      if (first_row && block_row == 0)
-	prev_block_row = buffer_ptr;
-      else
-	prev_block_row = buffer[block_row-1];
-      if (last_row && block_row == block_rows-1)
-	next_block_row = buffer_ptr;
-      else
-	next_block_row = buffer[block_row+1];
-      /* We fetch the surrounding DC values using a sliding-register approach.
-       * Initialize all nine here so as to do the right thing on narrow pics.
-       */
-      DC1 = DC2 = DC3 = (int) prev_block_row[0][0];
-      DC4 = DC5 = DC6 = (int) buffer_ptr[0][0];
-      DC7 = DC8 = DC9 = (int) next_block_row[0][0];
-      output_col = 0;
-      last_block_column = compptr->width_in_blocks - 1;
-      for (block_num = 0; block_num <= last_block_column; block_num++) {
-	/* Fetch current DCT block into workspace so we can modify it. */
-	jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1);
-	/* Update DC values */
-	if (block_num < last_block_column) {
-	  DC3 = (int) prev_block_row[1][0];
-	  DC6 = (int) buffer_ptr[1][0];
-	  DC9 = (int) next_block_row[1][0];
-	}
-	/* Compute coefficient estimates per K.8.
-	 * An estimate is applied only if coefficient is still zero,
-	 * and is not known to be fully accurate.
-	 */
-	/* AC01 */
-	if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) {
-	  num = 36 * Q00 * (DC4 - DC6);
-	  if (num >= 0) {
-	    pred = (int) (((Q01<<7) + num) / (Q01<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	  } else {
-	    pred = (int) (((Q01<<7) - num) / (Q01<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	    pred = -pred;
-	  }
-	  workspace[1] = (JCOEF) pred;
-	}
-	/* AC10 */
-	if ((Al=coef_bits[2]) != 0 && workspace[8] == 0) {
-	  num = 36 * Q00 * (DC2 - DC8);
-	  if (num >= 0) {
-	    pred = (int) (((Q10<<7) + num) / (Q10<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	  } else {
-	    pred = (int) (((Q10<<7) - num) / (Q10<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	    pred = -pred;
-	  }
-	  workspace[8] = (JCOEF) pred;
-	}
-	/* AC20 */
-	if ((Al=coef_bits[3]) != 0 && workspace[16] == 0) {
-	  num = 9 * Q00 * (DC2 + DC8 - 2*DC5);
-	  if (num >= 0) {
-	    pred = (int) (((Q20<<7) + num) / (Q20<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	  } else {
-	    pred = (int) (((Q20<<7) - num) / (Q20<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	    pred = -pred;
-	  }
-	  workspace[16] = (JCOEF) pred;
-	}
-	/* AC11 */
-	if ((Al=coef_bits[4]) != 0 && workspace[9] == 0) {
-	  num = 5 * Q00 * (DC1 - DC3 - DC7 + DC9);
-	  if (num >= 0) {
-	    pred = (int) (((Q11<<7) + num) / (Q11<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	  } else {
-	    pred = (int) (((Q11<<7) - num) / (Q11<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	    pred = -pred;
-	  }
-	  workspace[9] = (JCOEF) pred;
-	}
-	/* AC02 */
-	if ((Al=coef_bits[5]) != 0 && workspace[2] == 0) {
-	  num = 9 * Q00 * (DC4 + DC6 - 2*DC5);
-	  if (num >= 0) {
-	    pred = (int) (((Q02<<7) + num) / (Q02<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	  } else {
-	    pred = (int) (((Q02<<7) - num) / (Q02<<8));
-	    if (Al > 0 && pred >= (1<<Al))
-	      pred = (1<<Al)-1;
-	    pred = -pred;
-	  }
-	  workspace[2] = (JCOEF) pred;
-	}
-	/* OK, do the IDCT */
-	(*inverse_DCT) (cinfo, compptr, (JCOEFPTR) workspace,
-			output_ptr, output_col);
-	/* Advance for next column */
-	DC1 = DC2; DC2 = DC3;
-	DC4 = DC5; DC5 = DC6;
-	DC7 = DC8; DC8 = DC9;
-	buffer_ptr++, prev_block_row++, next_block_row++;
-	output_col += compptr->DCT_scaled_size;
-      }
-      output_ptr += compptr->DCT_scaled_size;
-    }
-  }
-
-  if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows)
-    return JPEG_ROW_COMPLETED;
-  return JPEG_SCAN_COMPLETED;
-}
-
-#endif /* BLOCK_SMOOTHING_SUPPORTED */
-
-
-/*
- * Initialize coefficient buffer controller.
- */
-
-GLOBAL(void)
-jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer)
-{
-  my_coef_ptr coef;
-
-  coef = (my_coef_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_coef_controller));
-  cinfo->coef = (struct jpeg_d_coef_controller *) coef;
-  coef->pub.start_input_pass = start_input_pass;
-  coef->pub.start_output_pass = start_output_pass;
-#ifdef BLOCK_SMOOTHING_SUPPORTED
-  coef->coef_bits_latch = NULL;
-#endif
-
-  /* Create the coefficient buffer. */
-  if (need_full_buffer) {
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-    /* Allocate a full-image virtual array for each component, */
-    /* padded to a multiple of samp_factor DCT blocks in each direction. */
-    /* Note we ask for a pre-zeroed array. */
-    int ci, access_rows;
-    jpeg_component_info *compptr;
-
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      access_rows = compptr->v_samp_factor;
-#ifdef BLOCK_SMOOTHING_SUPPORTED
-      /* If block smoothing could be used, need a bigger window */
-      if (cinfo->progressive_mode)
-	access_rows *= 3;
-#endif
-      coef->whole_image[ci] = (*cinfo->mem->request_virt_barray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE,
-	 (JDIMENSION) jround_up((long) compptr->width_in_blocks,
-				(long) compptr->h_samp_factor),
-	 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
-				(long) compptr->v_samp_factor),
-	 (JDIMENSION) access_rows);
-    }
-    coef->pub.consume_data = consume_data;
-    coef->pub.decompress_data = decompress_data;
-    coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-  } else {
-    /* We only need a single-MCU buffer. */
-    JBLOCKROW buffer;
-    int i;
-
-    buffer = (JBLOCKROW)
-      (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK));
-    for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) {
-      coef->MCU_buffer[i] = buffer + i;
-    }
-    coef->pub.consume_data = dummy_consume_data;
-    coef->pub.decompress_data = decompress_onepass;
-    coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */
-  }
-}
diff --git a/third_party/libjpeg/jdcolor.c b/third_party/libjpeg/jdcolor.c
deleted file mode 100644
index 6c04dfe..0000000
--- a/third_party/libjpeg/jdcolor.c
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- * jdcolor.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains output colorspace conversion routines.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Private subobject */
-
-typedef struct {
-  struct jpeg_color_deconverter pub; /* public fields */
-
-  /* Private state for YCC->RGB conversion */
-  int * Cr_r_tab;		/* => table for Cr to R conversion */
-  int * Cb_b_tab;		/* => table for Cb to B conversion */
-  INT32 * Cr_g_tab;		/* => table for Cr to G conversion */
-  INT32 * Cb_g_tab;		/* => table for Cb to G conversion */
-} my_color_deconverter;
-
-typedef my_color_deconverter * my_cconvert_ptr;
-
-
-/**************** YCbCr -> RGB conversion: most common case **************/
-
-/*
- * YCbCr is defined per CCIR 601-1, except that Cb and Cr are
- * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5.
- * The conversion equations to be implemented are therefore
- *	R = Y                + 1.40200 * Cr
- *	G = Y - 0.34414 * Cb - 0.71414 * Cr
- *	B = Y + 1.77200 * Cb
- * where Cb and Cr represent the incoming values less CENTERJSAMPLE.
- * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.)
- *
- * To avoid floating-point arithmetic, we represent the fractional constants
- * as integers scaled up by 2^16 (about 4 digits precision); we have to divide
- * the products by 2^16, with appropriate rounding, to get the correct answer.
- * Notice that Y, being an integral input, does not contribute any fraction
- * so it need not participate in the rounding.
- *
- * For even more speed, we avoid doing any multiplications in the inner loop
- * by precalculating the constants times Cb and Cr for all possible values.
- * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table);
- * for 12-bit samples it is still acceptable.  It's not very reasonable for
- * 16-bit samples, but if you want lossless storage you shouldn't be changing
- * colorspace anyway.
- * The Cr=>R and Cb=>B values can be rounded to integers in advance; the
- * values for the G calculation are left scaled up, since we must add them
- * together before rounding.
- */
-
-#define SCALEBITS	16	/* speediest right-shift on some machines */
-#define ONE_HALF	((INT32) 1 << (SCALEBITS-1))
-#define FIX(x)		((INT32) ((x) * (1L<<SCALEBITS) + 0.5))
-
-
-/*
- * Initialize tables for YCC->RGB colorspace conversion.
- */
-
-LOCAL(void)
-build_ycc_rgb_table (j_decompress_ptr cinfo)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  int i;
-  INT32 x;
-  SHIFT_TEMPS
-
-  cconvert->Cr_r_tab = (int *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(int));
-  cconvert->Cb_b_tab = (int *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(int));
-  cconvert->Cr_g_tab = (INT32 *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(INT32));
-  cconvert->Cb_g_tab = (INT32 *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(INT32));
-
-  for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
-    /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */
-    /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */
-    /* Cr=>R value is nearest int to 1.40200 * x */
-    cconvert->Cr_r_tab[i] = (int)
-		    RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS);
-    /* Cb=>B value is nearest int to 1.77200 * x */
-    cconvert->Cb_b_tab[i] = (int)
-		    RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS);
-    /* Cr=>G value is scaled-up -0.71414 * x */
-    cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x;
-    /* Cb=>G value is scaled-up -0.34414 * x */
-    /* We also add in ONE_HALF so that need not do it in inner loop */
-    cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF;
-  }
-}
-
-
-/*
- * Convert some rows of samples to the output colorspace.
- *
- * Note that we change from noninterleaved, one-plane-per-component format
- * to interleaved-pixel format.  The output buffer is therefore three times
- * as wide as the input buffer.
- * A starting row offset is provided only for the input buffer.  The caller
- * can easily adjust the passed output_buf value to accommodate any row
- * offset required on that side.
- */
-
-METHODDEF(void)
-ycc_rgb_convert (j_decompress_ptr cinfo,
-		 JSAMPIMAGE input_buf, JDIMENSION input_row,
-		 JSAMPARRAY output_buf, int num_rows)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  register int y, cb, cr;
-  register JSAMPROW outptr;
-  register JSAMPROW inptr0, inptr1, inptr2;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->output_width;
-  /* copy these pointers into registers if possible */
-  register JSAMPLE * range_limit = cinfo->sample_range_limit;
-  register int * Crrtab = cconvert->Cr_r_tab;
-  register int * Cbbtab = cconvert->Cb_b_tab;
-  register INT32 * Crgtab = cconvert->Cr_g_tab;
-  register INT32 * Cbgtab = cconvert->Cb_g_tab;
-  SHIFT_TEMPS
-
-  while (--num_rows >= 0) {
-    inptr0 = input_buf[0][input_row];
-    inptr1 = input_buf[1][input_row];
-    inptr2 = input_buf[2][input_row];
-    input_row++;
-    outptr = *output_buf++;
-    for (col = 0; col < num_cols; col++) {
-      y  = GETJSAMPLE(inptr0[col]);
-      cb = GETJSAMPLE(inptr1[col]);
-      cr = GETJSAMPLE(inptr2[col]);
-      /* Range-limiting is essential due to noise introduced by DCT losses. */
-      outptr[RGB_RED] =   range_limit[y + Crrtab[cr]];
-      outptr[RGB_GREEN] = range_limit[y +
-			      ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr],
-						 SCALEBITS))];
-      outptr[RGB_BLUE] =  range_limit[y + Cbbtab[cb]];
-      outptr += RGB_PIXELSIZE;
-    }
-  }
-}
-
-
-/**************** Cases other than YCbCr -> RGB **************/
-
-
-/*
- * Color conversion for no colorspace change: just copy the data,
- * converting from separate-planes to interleaved representation.
- */
-
-METHODDEF(void)
-null_convert (j_decompress_ptr cinfo,
-	      JSAMPIMAGE input_buf, JDIMENSION input_row,
-	      JSAMPARRAY output_buf, int num_rows)
-{
-  register JSAMPROW inptr, outptr;
-  register JDIMENSION count;
-  register int num_components = cinfo->num_components;
-  JDIMENSION num_cols = cinfo->output_width;
-  int ci;
-
-  while (--num_rows >= 0) {
-    for (ci = 0; ci < num_components; ci++) {
-      inptr = input_buf[ci][input_row];
-      outptr = output_buf[0] + ci;
-      for (count = num_cols; count > 0; count--) {
-	*outptr = *inptr++;	/* needn't bother with GETJSAMPLE() here */
-	outptr += num_components;
-      }
-    }
-    input_row++;
-    output_buf++;
-  }
-}
-
-
-/*
- * Color conversion for grayscale: just copy the data.
- * This also works for YCbCr -> grayscale conversion, in which
- * we just copy the Y (luminance) component and ignore chrominance.
- */
-
-METHODDEF(void)
-grayscale_convert (j_decompress_ptr cinfo,
-		   JSAMPIMAGE input_buf, JDIMENSION input_row,
-		   JSAMPARRAY output_buf, int num_rows)
-{
-  jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0,
-		    num_rows, cinfo->output_width);
-}
-
-
-/*
- * Convert grayscale to RGB: just duplicate the graylevel three times.
- * This is provided to support applications that don't want to cope
- * with grayscale as a separate case.
- */
-
-METHODDEF(void)
-gray_rgb_convert (j_decompress_ptr cinfo,
-		  JSAMPIMAGE input_buf, JDIMENSION input_row,
-		  JSAMPARRAY output_buf, int num_rows)
-{
-  register JSAMPROW inptr, outptr;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->output_width;
-
-  while (--num_rows >= 0) {
-    inptr = input_buf[0][input_row++];
-    outptr = *output_buf++;
-    for (col = 0; col < num_cols; col++) {
-      /* We can dispense with GETJSAMPLE() here */
-      outptr[RGB_RED] = outptr[RGB_GREEN] = outptr[RGB_BLUE] = inptr[col];
-      outptr += RGB_PIXELSIZE;
-    }
-  }
-}
-
-
-/*
- * Adobe-style YCCK->CMYK conversion.
- * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same
- * conversion as above, while passing K (black) unchanged.
- * We assume build_ycc_rgb_table has been called.
- */
-
-METHODDEF(void)
-ycck_cmyk_convert (j_decompress_ptr cinfo,
-		   JSAMPIMAGE input_buf, JDIMENSION input_row,
-		   JSAMPARRAY output_buf, int num_rows)
-{
-  my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert;
-  register int y, cb, cr;
-  register JSAMPROW outptr;
-  register JSAMPROW inptr0, inptr1, inptr2, inptr3;
-  register JDIMENSION col;
-  JDIMENSION num_cols = cinfo->output_width;
-  /* copy these pointers into registers if possible */
-  register JSAMPLE * range_limit = cinfo->sample_range_limit;
-  register int * Crrtab = cconvert->Cr_r_tab;
-  register int * Cbbtab = cconvert->Cb_b_tab;
-  register INT32 * Crgtab = cconvert->Cr_g_tab;
-  register INT32 * Cbgtab = cconvert->Cb_g_tab;
-  SHIFT_TEMPS
-
-  while (--num_rows >= 0) {
-    inptr0 = input_buf[0][input_row];
-    inptr1 = input_buf[1][input_row];
-    inptr2 = input_buf[2][input_row];
-    inptr3 = input_buf[3][input_row];
-    input_row++;
-    outptr = *output_buf++;
-    for (col = 0; col < num_cols; col++) {
-      y  = GETJSAMPLE(inptr0[col]);
-      cb = GETJSAMPLE(inptr1[col]);
-      cr = GETJSAMPLE(inptr2[col]);
-      /* Range-limiting is essential due to noise introduced by DCT losses. */
-      outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])];	/* red */
-      outptr[1] = range_limit[MAXJSAMPLE - (y +			/* green */
-			      ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr],
-						 SCALEBITS)))];
-      outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])];	/* blue */
-      /* K passes through unchanged */
-      outptr[3] = inptr3[col];	/* don't need GETJSAMPLE here */
-      outptr += 4;
-    }
-  }
-}
-
-
-/*
- * Empty method for start_pass.
- */
-
-METHODDEF(void)
-start_pass_dcolor (j_decompress_ptr cinfo)
-{
-  /* no work needed */
-}
-
-
-/*
- * Module initialization routine for output colorspace conversion.
- */
-
-GLOBAL(void)
-jinit_color_deconverter (j_decompress_ptr cinfo)
-{
-  my_cconvert_ptr cconvert;
-  int ci;
-
-  cconvert = (my_cconvert_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_color_deconverter));
-  cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert;
-  cconvert->pub.start_pass = start_pass_dcolor;
-
-  /* Make sure num_components agrees with jpeg_color_space */
-  switch (cinfo->jpeg_color_space) {
-  case JCS_GRAYSCALE:
-    if (cinfo->num_components != 1)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    break;
-
-  case JCS_RGB:
-  case JCS_YCbCr:
-    if (cinfo->num_components != 3)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    break;
-
-  case JCS_CMYK:
-  case JCS_YCCK:
-    if (cinfo->num_components != 4)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    break;
-
-  default:			/* JCS_UNKNOWN can be anything */
-    if (cinfo->num_components < 1)
-      ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
-    break;
-  }
-
-  /* Set out_color_components and conversion method based on requested space.
-   * Also clear the component_needed flags for any unused components,
-   * so that earlier pipeline stages can avoid useless computation.
-   */
-
-  switch (cinfo->out_color_space) {
-  case JCS_GRAYSCALE:
-    cinfo->out_color_components = 1;
-    if (cinfo->jpeg_color_space == JCS_GRAYSCALE ||
-	cinfo->jpeg_color_space == JCS_YCbCr) {
-      cconvert->pub.color_convert = grayscale_convert;
-      /* For color->grayscale conversion, only the Y (0) component is needed */
-      for (ci = 1; ci < cinfo->num_components; ci++)
-	cinfo->comp_info[ci].component_needed = FALSE;
-    } else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  case JCS_RGB:
-    cinfo->out_color_components = RGB_PIXELSIZE;
-    if (cinfo->jpeg_color_space == JCS_YCbCr) {
-      cconvert->pub.color_convert = ycc_rgb_convert;
-      build_ycc_rgb_table(cinfo);
-    } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
-      cconvert->pub.color_convert = gray_rgb_convert;
-    } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) {
-      cconvert->pub.color_convert = null_convert;
-    } else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  case JCS_CMYK:
-    cinfo->out_color_components = 4;
-    if (cinfo->jpeg_color_space == JCS_YCCK) {
-      cconvert->pub.color_convert = ycck_cmyk_convert;
-      build_ycc_rgb_table(cinfo);
-    } else if (cinfo->jpeg_color_space == JCS_CMYK) {
-      cconvert->pub.color_convert = null_convert;
-    } else
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-
-  default:
-    /* Permit null conversion to same output space */
-    if (cinfo->out_color_space == cinfo->jpeg_color_space) {
-      cinfo->out_color_components = cinfo->num_components;
-      cconvert->pub.color_convert = null_convert;
-    } else			/* unsupported non-null conversion */
-      ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
-    break;
-  }
-
-  if (cinfo->quantize_colors)
-    cinfo->output_components = 1; /* single colormapped output component */
-  else
-    cinfo->output_components = cinfo->out_color_components;
-}
diff --git a/third_party/libjpeg/jdct.h b/third_party/libjpeg/jdct.h
deleted file mode 100644
index 04192a2..0000000
--- a/third_party/libjpeg/jdct.h
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * jdct.h
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This include file contains common declarations for the forward and
- * inverse DCT modules.  These declarations are private to the DCT managers
- * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms.
- * The individual DCT algorithms are kept in separate files to ease 
- * machine-dependent tuning (e.g., assembly coding).
- */
-
-
-/*
- * A forward DCT routine is given a pointer to a work area of type DCTELEM[];
- * the DCT is to be performed in-place in that buffer.  Type DCTELEM is int
- * for 8-bit samples, INT32 for 12-bit samples.  (NOTE: Floating-point DCT
- * implementations use an array of type FAST_FLOAT, instead.)
- * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE).
- * The DCT outputs are returned scaled up by a factor of 8; they therefore
- * have a range of +-8K for 8-bit data, +-128K for 12-bit data.  This
- * convention improves accuracy in integer implementations and saves some
- * work in floating-point ones.
- * Quantization of the output coefficients is done by jcdctmgr.c.
- */
-
-#if BITS_IN_JSAMPLE == 8
-typedef int DCTELEM;		/* 16 or 32 bits is fine */
-#else
-typedef INT32 DCTELEM;		/* must have 32 bits */
-#endif
-
-typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data));
-typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data));
-
-
-/*
- * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer
- * to an output sample array.  The routine must dequantize the input data as
- * well as perform the IDCT; for dequantization, it uses the multiplier table
- * pointed to by compptr->dct_table.  The output data is to be placed into the
- * sample array starting at a specified column.  (Any row offset needed will
- * be applied to the array pointer before it is passed to the IDCT code.)
- * Note that the number of samples emitted by the IDCT routine is
- * DCT_scaled_size * DCT_scaled_size.
- */
-
-/* typedef inverse_DCT_method_ptr is declared in jpegint.h */
-
-/*
- * Each IDCT routine has its own ideas about the best dct_table element type.
- */
-
-typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */
-#if BITS_IN_JSAMPLE == 8
-typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */
-#define IFAST_SCALE_BITS  2	/* fractional bits in scale factors */
-#else
-typedef INT32 IFAST_MULT_TYPE;	/* need 32 bits for scaled quantizers */
-#define IFAST_SCALE_BITS  13	/* fractional bits in scale factors */
-#endif
-typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */
-
-
-/*
- * Each IDCT routine is responsible for range-limiting its results and
- * converting them to unsigned form (0..MAXJSAMPLE).  The raw outputs could
- * be quite far out of range if the input data is corrupt, so a bulletproof
- * range-limiting step is required.  We use a mask-and-table-lookup method
- * to do the combined operations quickly.  See the comments with
- * prepare_range_limit_table (in jdmaster.c) for more info.
- */
-
-#define IDCT_range_limit(cinfo)  ((cinfo)->sample_range_limit + CENTERJSAMPLE)
-
-#define RANGE_MASK  (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */
-
-
-/* Short forms of external names for systems with brain-damaged linkers. */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jpeg_fdct_islow		jFDislow
-#define jpeg_fdct_ifast		jFDifast
-#define jpeg_fdct_float		jFDfloat
-#define jpeg_idct_islow		jRDislow
-#define jpeg_idct_ifast		jRDifast
-#define jpeg_idct_float		jRDfloat
-#define jpeg_idct_4x4		jRD4x4
-#define jpeg_idct_2x2		jRD2x2
-#define jpeg_idct_1x1		jRD1x1
-#endif /* NEED_SHORT_EXTERNAL_NAMES */
-
-/* Extern declarations for the forward and inverse DCT routines. */
-
-EXTERN(void) jpeg_fdct_islow JPP((DCTELEM * data));
-EXTERN(void) jpeg_fdct_ifast JPP((DCTELEM * data));
-EXTERN(void) jpeg_fdct_float JPP((FAST_FLOAT * data));
-
-EXTERN(void) jpeg_idct_islow
-    JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	 JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col));
-EXTERN(void) jpeg_idct_ifast
-    JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	 JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col));
-EXTERN(void) jpeg_idct_float
-    JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	 JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col));
-EXTERN(void) jpeg_idct_4x4
-    JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	 JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col));
-EXTERN(void) jpeg_idct_2x2
-    JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	 JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col));
-EXTERN(void) jpeg_idct_1x1
-    JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	 JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col));
-
-
-/*
- * Macros for handling fixed-point arithmetic; these are used by many
- * but not all of the DCT/IDCT modules.
- *
- * All values are expected to be of type INT32.
- * Fractional constants are scaled left by CONST_BITS bits.
- * CONST_BITS is defined within each module using these macros,
- * and may differ from one module to the next.
- */
-
-#define ONE	((INT32) 1)
-#define CONST_SCALE (ONE << CONST_BITS)
-
-/* Convert a positive real constant to an integer scaled by CONST_SCALE.
- * Caution: some C compilers fail to reduce "FIX(constant)" at compile time,
- * thus causing a lot of useless floating-point operations at run time.
- */
-
-#define FIX(x)	((INT32) ((x) * CONST_SCALE + 0.5))
-
-/* Descale and correctly round an INT32 value that's scaled by N bits.
- * We assume RIGHT_SHIFT rounds towards minus infinity, so adding
- * the fudge factor is correct for either sign of X.
- */
-
-#define DESCALE(x,n)  RIGHT_SHIFT((x) + (ONE << ((n)-1)), n)
-
-/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result.
- * This macro is used only when the two inputs will actually be no more than
- * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a
- * full 32x32 multiply.  This provides a useful speedup on many machines.
- * Unfortunately there is no way to specify a 16x16->32 multiply portably
- * in C, but some C compilers will do the right thing if you provide the
- * correct combination of casts.
- */
-
-#ifdef SHORTxSHORT_32		/* may work if 'int' is 32 bits */
-#define MULTIPLY16C16(var,const)  (((INT16) (var)) * ((INT16) (const)))
-#endif
-#ifdef SHORTxLCONST_32		/* known to work with Microsoft C 6.0 */
-#define MULTIPLY16C16(var,const)  (((INT16) (var)) * ((INT32) (const)))
-#endif
-
-#ifndef MULTIPLY16C16		/* default definition */
-#define MULTIPLY16C16(var,const)  ((var) * (const))
-#endif
-
-/* Same except both inputs are variables. */
-
-#ifdef SHORTxSHORT_32		/* may work if 'int' is 32 bits */
-#define MULTIPLY16V16(var1,var2)  (((INT16) (var1)) * ((INT16) (var2)))
-#endif
-
-#ifndef MULTIPLY16V16		/* default definition */
-#define MULTIPLY16V16(var1,var2)  ((var1) * (var2))
-#endif
diff --git a/third_party/libjpeg/jddctmgr.c b/third_party/libjpeg/jddctmgr.c
deleted file mode 100644
index bbf8d0e..0000000
--- a/third_party/libjpeg/jddctmgr.c
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * jddctmgr.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the inverse-DCT management logic.
- * This code selects a particular IDCT implementation to be used,
- * and it performs related housekeeping chores.  No code in this file
- * is executed per IDCT step, only during output pass setup.
- *
- * Note that the IDCT routines are responsible for performing coefficient
- * dequantization as well as the IDCT proper.  This module sets up the
- * dequantization multiplier table needed by the IDCT routine.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-
-/*
- * The decompressor input side (jdinput.c) saves away the appropriate
- * quantization table for each component at the start of the first scan
- * involving that component.  (This is necessary in order to correctly
- * decode files that reuse Q-table slots.)
- * When we are ready to make an output pass, the saved Q-table is converted
- * to a multiplier table that will actually be used by the IDCT routine.
- * The multiplier table contents are IDCT-method-dependent.  To support
- * application changes in IDCT method between scans, we can remake the
- * multiplier tables if necessary.
- * In buffered-image mode, the first output pass may occur before any data
- * has been seen for some components, and thus before their Q-tables have
- * been saved away.  To handle this case, multiplier tables are preset
- * to zeroes; the result of the IDCT will be a neutral gray level.
- */
-
-
-/* Private subobject for this module */
-
-typedef struct {
-  struct jpeg_inverse_dct pub;	/* public fields */
-
-  /* This array contains the IDCT method code that each multiplier table
-   * is currently set up for, or -1 if it's not yet set up.
-   * The actual multiplier tables are pointed to by dct_table in the
-   * per-component comp_info structures.
-   */
-  int cur_method[MAX_COMPONENTS];
-} my_idct_controller;
-
-typedef my_idct_controller * my_idct_ptr;
-
-
-/* Allocated multiplier tables: big enough for any supported variant */
-
-typedef union {
-  ISLOW_MULT_TYPE islow_array[DCTSIZE2];
-#ifdef DCT_IFAST_SUPPORTED
-  IFAST_MULT_TYPE ifast_array[DCTSIZE2];
-#endif
-#ifdef DCT_FLOAT_SUPPORTED
-  FLOAT_MULT_TYPE float_array[DCTSIZE2];
-#endif
-} multiplier_table;
-
-
-/* The current scaled-IDCT routines require ISLOW-style multiplier tables,
- * so be sure to compile that code if either ISLOW or SCALING is requested.
- */
-#ifdef DCT_ISLOW_SUPPORTED
-#define PROVIDE_ISLOW_TABLES
-#else
-#ifdef IDCT_SCALING_SUPPORTED
-#define PROVIDE_ISLOW_TABLES
-#endif
-#endif
-
-
-/*
- * Prepare for an output pass.
- * Here we select the proper IDCT routine for each component and build
- * a matching multiplier table.
- */
-
-METHODDEF(void)
-start_pass (j_decompress_ptr cinfo)
-{
-  my_idct_ptr idct = (my_idct_ptr) cinfo->idct;
-  int ci, i;
-  jpeg_component_info *compptr;
-  int method = 0;
-  inverse_DCT_method_ptr method_ptr = NULL;
-  JQUANT_TBL * qtbl;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Select the proper IDCT routine for this component's scaling */
-    switch (compptr->DCT_scaled_size) {
-#ifdef IDCT_SCALING_SUPPORTED
-    case 1:
-      method_ptr = jpeg_idct_1x1;
-      method = JDCT_ISLOW;	/* jidctred uses islow-style table */
-      break;
-    case 2:
-      method_ptr = jpeg_idct_2x2;
-      method = JDCT_ISLOW;	/* jidctred uses islow-style table */
-      break;
-    case 4:
-      method_ptr = jpeg_idct_4x4;
-      method = JDCT_ISLOW;	/* jidctred uses islow-style table */
-      break;
-#endif
-    case DCTSIZE:
-      switch (cinfo->dct_method) {
-#ifdef DCT_ISLOW_SUPPORTED
-      case JDCT_ISLOW:
-	method_ptr = jpeg_idct_islow;
-	method = JDCT_ISLOW;
-	break;
-#endif
-#ifdef DCT_IFAST_SUPPORTED
-      case JDCT_IFAST:
-	method_ptr = jpeg_idct_ifast;
-	method = JDCT_IFAST;
-	break;
-#endif
-#ifdef DCT_FLOAT_SUPPORTED
-      case JDCT_FLOAT:
-	method_ptr = jpeg_idct_float;
-	method = JDCT_FLOAT;
-	break;
-#endif
-      default:
-	ERREXIT(cinfo, JERR_NOT_COMPILED);
-	break;
-      }
-      break;
-    default:
-      ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size);
-      break;
-    }
-    idct->pub.inverse_DCT[ci] = method_ptr;
-    /* Create multiplier table from quant table.
-     * However, we can skip this if the component is uninteresting
-     * or if we already built the table.  Also, if no quant table
-     * has yet been saved for the component, we leave the
-     * multiplier table all-zero; we'll be reading zeroes from the
-     * coefficient controller's buffer anyway.
-     */
-    if (! compptr->component_needed || idct->cur_method[ci] == method)
-      continue;
-    qtbl = compptr->quant_table;
-    if (qtbl == NULL)		/* happens if no data yet for component */
-      continue;
-    idct->cur_method[ci] = method;
-    switch (method) {
-#ifdef PROVIDE_ISLOW_TABLES
-    case JDCT_ISLOW:
-      {
-	/* For LL&M IDCT method, multipliers are equal to raw quantization
-	 * coefficients, but are stored as ints to ensure access efficiency.
-	 */
-	ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table;
-	for (i = 0; i < DCTSIZE2; i++) {
-	  ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[i];
-	}
-      }
-      break;
-#endif
-#ifdef DCT_IFAST_SUPPORTED
-    case JDCT_IFAST:
-      {
-	/* For AA&N IDCT method, multipliers are equal to quantization
-	 * coefficients scaled by scalefactor[row]*scalefactor[col], where
-	 *   scalefactor[0] = 1
-	 *   scalefactor[k] = cos(k*PI/16) * sqrt(2)    for k=1..7
-	 * For integer operation, the multiplier table is to be scaled by
-	 * IFAST_SCALE_BITS.
-	 */
-	IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table;
-#define CONST_BITS 14
-	static const INT16 aanscales[DCTSIZE2] = {
-	  /* precomputed values scaled up by 14 bits */
-	  16384, 22725, 21407, 19266, 16384, 12873,  8867,  4520,
-	  22725, 31521, 29692, 26722, 22725, 17855, 12299,  6270,
-	  21407, 29692, 27969, 25172, 21407, 16819, 11585,  5906,
-	  19266, 26722, 25172, 22654, 19266, 15137, 10426,  5315,
-	  16384, 22725, 21407, 19266, 16384, 12873,  8867,  4520,
-	  12873, 17855, 16819, 15137, 12873, 10114,  6967,  3552,
-	   8867, 12299, 11585, 10426,  8867,  6967,  4799,  2446,
-	   4520,  6270,  5906,  5315,  4520,  3552,  2446,  1247
-	};
-	SHIFT_TEMPS
-
-	for (i = 0; i < DCTSIZE2; i++) {
-	  ifmtbl[i] = (IFAST_MULT_TYPE)
-	    DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i],
-				  (INT32) aanscales[i]),
-		    CONST_BITS-IFAST_SCALE_BITS);
-	}
-      }
-      break;
-#endif
-#ifdef DCT_FLOAT_SUPPORTED
-    case JDCT_FLOAT:
-      {
-	/* For float AA&N IDCT method, multipliers are equal to quantization
-	 * coefficients scaled by scalefactor[row]*scalefactor[col], where
-	 *   scalefactor[0] = 1
-	 *   scalefactor[k] = cos(k*PI/16) * sqrt(2)    for k=1..7
-	 */
-	FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table;
-	int row, col;
-	static const double aanscalefactor[DCTSIZE] = {
-	  1.0, 1.387039845, 1.306562965, 1.175875602,
-	  1.0, 0.785694958, 0.541196100, 0.275899379
-	};
-
-	i = 0;
-	for (row = 0; row < DCTSIZE; row++) {
-	  for (col = 0; col < DCTSIZE; col++) {
-	    fmtbl[i] = (FLOAT_MULT_TYPE)
-	      ((double) qtbl->quantval[i] *
-	       aanscalefactor[row] * aanscalefactor[col]);
-	    i++;
-	  }
-	}
-      }
-      break;
-#endif
-    default:
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-      break;
-    }
-  }
-}
-
-
-/*
- * Initialize IDCT manager.
- */
-
-GLOBAL(void)
-jinit_inverse_dct (j_decompress_ptr cinfo)
-{
-  my_idct_ptr idct;
-  int ci;
-  jpeg_component_info *compptr;
-
-  idct = (my_idct_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_idct_controller));
-  cinfo->idct = (struct jpeg_inverse_dct *) idct;
-  idct->pub.start_pass = start_pass;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Allocate and pre-zero a multiplier table for each component */
-    compptr->dct_table =
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  SIZEOF(multiplier_table));
-    MEMZERO(compptr->dct_table, SIZEOF(multiplier_table));
-    /* Mark multiplier table not yet set up for any method */
-    idct->cur_method[ci] = -1;
-  }
-}
diff --git a/third_party/libjpeg/jdhuff.c b/third_party/libjpeg/jdhuff.c
deleted file mode 100644
index b5ba39f..0000000
--- a/third_party/libjpeg/jdhuff.c
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * jdhuff.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains Huffman entropy decoding routines.
- *
- * Much of the complexity here has to do with supporting input suspension.
- * If the data source module demands suspension, we want to be able to back
- * up to the start of the current MCU.  To do this, we copy state variables
- * into local working storage, and update them back to the permanent
- * storage only upon successful completion of an MCU.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdhuff.h"		/* Declarations shared with jdphuff.c */
-
-
-/*
- * Expanded entropy decoder object for Huffman decoding.
- *
- * The savable_state subrecord contains fields that change within an MCU,
- * but must not be updated permanently until we complete the MCU.
- */
-
-typedef struct {
-  int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */
-} savable_state;
-
-/* This macro is to work around compilers with missing or broken
- * structure assignment.  You'll need to fix this code if you have
- * such a compiler and you change MAX_COMPS_IN_SCAN.
- */
-
-#ifndef NO_STRUCT_ASSIGN
-#define ASSIGN_STATE(dest,src)  ((dest) = (src))
-#else
-#if MAX_COMPS_IN_SCAN == 4
-#define ASSIGN_STATE(dest,src)  \
-	((dest).last_dc_val[0] = (src).last_dc_val[0], \
-	 (dest).last_dc_val[1] = (src).last_dc_val[1], \
-	 (dest).last_dc_val[2] = (src).last_dc_val[2], \
-	 (dest).last_dc_val[3] = (src).last_dc_val[3])
-#endif
-#endif
-
-
-typedef struct {
-  struct jpeg_entropy_decoder pub; /* public fields */
-
-  /* These fields are loaded into local variables at start of each MCU.
-   * In case of suspension, we exit WITHOUT updating them.
-   */
-  bitread_perm_state bitstate;	/* Bit buffer at start of MCU */
-  savable_state saved;		/* Other state at start of MCU */
-
-  /* These fields are NOT loaded into local working state. */
-  unsigned int restarts_to_go;	/* MCUs left in this restart interval */
-
-  /* Pointers to derived tables (these workspaces have image lifespan) */
-  d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS];
-  d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS];
-
-  /* Precalculated info set up by start_pass for use in decode_mcu: */
-
-  /* Pointers to derived tables to be used for each block within an MCU */
-  d_derived_tbl * dc_cur_tbls[D_MAX_BLOCKS_IN_MCU];
-  d_derived_tbl * ac_cur_tbls[D_MAX_BLOCKS_IN_MCU];
-  /* Whether we care about the DC and AC coefficient values for each block */
-  boolean dc_needed[D_MAX_BLOCKS_IN_MCU];
-  boolean ac_needed[D_MAX_BLOCKS_IN_MCU];
-} huff_entropy_decoder;
-
-typedef huff_entropy_decoder * huff_entropy_ptr;
-
-
-/*
- * Initialize for a Huffman-compressed scan.
- */
-
-METHODDEF(void)
-start_pass_huff_decoder (j_decompress_ptr cinfo)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  int ci, blkn, dctbl, actbl;
-  jpeg_component_info * compptr;
-
-  /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG.
-   * This ought to be an error condition, but we make it a warning because
-   * there are some baseline files out there with all zeroes in these bytes.
-   */
-  if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 ||
-      cinfo->Ah != 0 || cinfo->Al != 0)
-    WARNMS(cinfo, JWRN_NOT_SEQUENTIAL);
-
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    dctbl = compptr->dc_tbl_no;
-    actbl = compptr->ac_tbl_no;
-    /* Compute derived values for Huffman tables */
-    /* We may do this more than once for a table, but it's not expensive */
-    jpeg_make_d_derived_tbl(cinfo, TRUE, dctbl,
-			    & entropy->dc_derived_tbls[dctbl]);
-    jpeg_make_d_derived_tbl(cinfo, FALSE, actbl,
-			    & entropy->ac_derived_tbls[actbl]);
-    /* Initialize DC predictions to 0 */
-    entropy->saved.last_dc_val[ci] = 0;
-  }
-
-  /* Precalculate decoding info for each block in an MCU of this scan */
-  for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-    ci = cinfo->MCU_membership[blkn];
-    compptr = cinfo->cur_comp_info[ci];
-    /* Precalculate which table to use for each block */
-    entropy->dc_cur_tbls[blkn] = entropy->dc_derived_tbls[compptr->dc_tbl_no];
-    entropy->ac_cur_tbls[blkn] = entropy->ac_derived_tbls[compptr->ac_tbl_no];
-    /* Decide whether we really care about the coefficient values */
-    if (compptr->component_needed) {
-      entropy->dc_needed[blkn] = TRUE;
-      /* we don't need the ACs if producing a 1/8th-size image */
-      entropy->ac_needed[blkn] = (compptr->DCT_scaled_size > 1);
-    } else {
-      entropy->dc_needed[blkn] = entropy->ac_needed[blkn] = FALSE;
-    }
-  }
-
-  /* Initialize bitread state variables */
-  entropy->bitstate.bits_left = 0;
-  entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */
-  entropy->pub.insufficient_data = FALSE;
-
-  /* Initialize restart counter */
-  entropy->restarts_to_go = cinfo->restart_interval;
-}
-
-
-/*
- * Compute the derived values for a Huffman table.
- * This routine also performs some validation checks on the table.
- *
- * Note this is also used by jdphuff.c.
- */
-
-GLOBAL(void)
-jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno,
-			 d_derived_tbl ** pdtbl)
-{
-  JHUFF_TBL *htbl;
-  d_derived_tbl *dtbl;
-  int p, i, l, si, numsymbols;
-  int lookbits, ctr;
-  char huffsize[257];
-  unsigned int huffcode[257];
-  unsigned int code;
-
-  /* Note that huffsize[] and huffcode[] are filled in code-length order,
-   * paralleling the order of the symbols themselves in htbl->huffval[].
-   */
-
-  /* Find the input Huffman table */
-  if (tblno < 0 || tblno >= NUM_HUFF_TBLS)
-    ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno);
-  htbl =
-    isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno];
-  if (htbl == NULL)
-    ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno);
-
-  /* Allocate a workspace if we haven't already done so. */
-  if (*pdtbl == NULL)
-    *pdtbl = (d_derived_tbl *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  SIZEOF(d_derived_tbl));
-  dtbl = *pdtbl;
-  dtbl->pub = htbl;		/* fill in back link */
-  
-  /* Figure C.1: make table of Huffman code length for each symbol */
-
-  p = 0;
-  for (l = 1; l <= 16; l++) {
-    i = (int) htbl->bits[l];
-    if (i < 0 || p + i > 256)	/* protect against table overrun */
-      ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-    while (i--)
-      huffsize[p++] = (char) l;
-  }
-  huffsize[p] = 0;
-  numsymbols = p;
-  
-  /* Figure C.2: generate the codes themselves */
-  /* We also validate that the counts represent a legal Huffman code tree. */
-  
-  code = 0;
-  si = huffsize[0];
-  p = 0;
-  while (huffsize[p]) {
-    while (((int) huffsize[p]) == si) {
-      huffcode[p++] = code;
-      code++;
-    }
-    /* code is now 1 more than the last code used for codelength si; but
-     * it must still fit in si bits, since no code is allowed to be all ones.
-     */
-    if (((INT32) code) >= (((INT32) 1) << si))
-      ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-    code <<= 1;
-    si++;
-  }
-
-  /* Figure F.15: generate decoding tables for bit-sequential decoding */
-
-  p = 0;
-  for (l = 1; l <= 16; l++) {
-    if (htbl->bits[l]) {
-      /* valoffset[l] = huffval[] index of 1st symbol of code length l,
-       * minus the minimum code of length l
-       */
-      dtbl->valoffset[l] = (INT32) p - (INT32) huffcode[p];
-      p += htbl->bits[l];
-      dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */
-    } else {
-      dtbl->maxcode[l] = -1;	/* -1 if no codes of this length */
-    }
-  }
-  dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */
-
-  /* Compute lookahead tables to speed up decoding.
-   * First we set all the table entries to 0, indicating "too long";
-   * then we iterate through the Huffman codes that are short enough and
-   * fill in all the entries that correspond to bit sequences starting
-   * with that code.
-   */
-
-  MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits));
-
-  p = 0;
-  for (l = 1; l <= HUFF_LOOKAHEAD; l++) {
-    for (i = 1; i <= (int) htbl->bits[l]; i++, p++) {
-      /* l = current code's length, p = its index in huffcode[] & huffval[]. */
-      /* Generate left-justified code followed by all possible bit sequences */
-      lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l);
-      for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) {
-	dtbl->look_nbits[lookbits] = l;
-	dtbl->look_sym[lookbits] = htbl->huffval[p];
-	lookbits++;
-      }
-    }
-  }
-
-  /* Validate symbols as being reasonable.
-   * For AC tables, we make no check, but accept all byte values 0..255.
-   * For DC tables, we require the symbols to be in range 0..15.
-   * (Tighter bounds could be applied depending on the data depth and mode,
-   * but this is sufficient to ensure safe decoding.)
-   */
-  if (isDC) {
-    for (i = 0; i < numsymbols; i++) {
-      int sym = htbl->huffval[i];
-      if (sym < 0 || sym > 15)
-	ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-    }
-  }
-}
-
-
-/*
- * Out-of-line code for bit fetching (shared with jdphuff.c).
- * See jdhuff.h for info about usage.
- * Note: current values of get_buffer and bits_left are passed as parameters,
- * but are returned in the corresponding fields of the state struct.
- *
- * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width
- * of get_buffer to be used.  (On machines with wider words, an even larger
- * buffer could be used.)  However, on some machines 32-bit shifts are
- * quite slow and take time proportional to the number of places shifted.
- * (This is true with most PC compilers, for instance.)  In this case it may
- * be a win to set MIN_GET_BITS to the minimum value of 15.  This reduces the
- * average shift distance at the cost of more calls to jpeg_fill_bit_buffer.
- */
-
-#ifdef SLOW_SHIFT_32
-#define MIN_GET_BITS  15	/* minimum allowable value */
-#else
-#define MIN_GET_BITS  (BIT_BUF_SIZE-7)
-#endif
-
-
-GLOBAL(boolean)
-jpeg_fill_bit_buffer (bitread_working_state * state,
-		      register bit_buf_type get_buffer, register int bits_left,
-		      int nbits)
-/* Load up the bit buffer to a depth of at least nbits */
-{
-  /* Copy heavily used state fields into locals (hopefully registers) */
-  register const JOCTET * next_input_byte = state->next_input_byte;
-  register size_t bytes_in_buffer = state->bytes_in_buffer;
-  j_decompress_ptr cinfo = state->cinfo;
-
-  /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */
-  /* (It is assumed that no request will be for more than that many bits.) */
-  /* We fail to do so only if we hit a marker or are forced to suspend. */
-
-  if (cinfo->unread_marker == 0) {	/* cannot advance past a marker */
-    while (bits_left < MIN_GET_BITS) {
-      register int c;
-
-      /* Attempt to read a byte */
-      if (bytes_in_buffer == 0) {
-	if (! (*cinfo->src->fill_input_buffer) (cinfo))
-	  return FALSE;
-	next_input_byte = cinfo->src->next_input_byte;
-	bytes_in_buffer = cinfo->src->bytes_in_buffer;
-      }
-      bytes_in_buffer--;
-      c = GETJOCTET(*next_input_byte++);
-
-      /* If it's 0xFF, check and discard stuffed zero byte */
-      if (c == 0xFF) {
-	/* Loop here to discard any padding FF's on terminating marker,
-	 * so that we can save a valid unread_marker value.  NOTE: we will
-	 * accept multiple FF's followed by a 0 as meaning a single FF data
-	 * byte.  This data pattern is not valid according to the standard.
-	 */
-	do {
-	  if (bytes_in_buffer == 0) {
-	    if (! (*cinfo->src->fill_input_buffer) (cinfo))
-	      return FALSE;
-	    next_input_byte = cinfo->src->next_input_byte;
-	    bytes_in_buffer = cinfo->src->bytes_in_buffer;
-	  }
-	  bytes_in_buffer--;
-	  c = GETJOCTET(*next_input_byte++);
-	} while (c == 0xFF);
-
-	if (c == 0) {
-	  /* Found FF/00, which represents an FF data byte */
-	  c = 0xFF;
-	} else {
-	  /* Oops, it's actually a marker indicating end of compressed data.
-	   * Save the marker code for later use.
-	   * Fine point: it might appear that we should save the marker into
-	   * bitread working state, not straight into permanent state.  But
-	   * once we have hit a marker, we cannot need to suspend within the
-	   * current MCU, because we will read no more bytes from the data
-	   * source.  So it is OK to update permanent state right away.
-	   */
-	  cinfo->unread_marker = c;
-	  /* See if we need to insert some fake zero bits. */
-	  goto no_more_bytes;
-	}
-      }
-
-      /* OK, load c into get_buffer */
-      get_buffer = (get_buffer << 8) | c;
-      bits_left += 8;
-    } /* end while */
-  } else {
-  no_more_bytes:
-    /* We get here if we've read the marker that terminates the compressed
-     * data segment.  There should be enough bits in the buffer register
-     * to satisfy the request; if so, no problem.
-     */
-    if (nbits > bits_left) {
-      /* Uh-oh.  Report corrupted data to user and stuff zeroes into
-       * the data stream, so that we can produce some kind of image.
-       * We use a nonvolatile flag to ensure that only one warning message
-       * appears per data segment.
-       */
-      if (! cinfo->entropy->insufficient_data) {
-	WARNMS(cinfo, JWRN_HIT_MARKER);
-	cinfo->entropy->insufficient_data = TRUE;
-      }
-      /* Fill the buffer with zero bits */
-      get_buffer <<= MIN_GET_BITS - bits_left;
-      bits_left = MIN_GET_BITS;
-    }
-  }
-
-  /* Unload the local registers */
-  state->next_input_byte = next_input_byte;
-  state->bytes_in_buffer = bytes_in_buffer;
-  state->get_buffer = get_buffer;
-  state->bits_left = bits_left;
-
-  return TRUE;
-}
-
-
-/*
- * Out-of-line code for Huffman code decoding.
- * See jdhuff.h for info about usage.
- */
-
-GLOBAL(int)
-jpeg_huff_decode (bitread_working_state * state,
-		  register bit_buf_type get_buffer, register int bits_left,
-		  d_derived_tbl * htbl, int min_bits)
-{
-  register int l = min_bits;
-  register INT32 code;
-
-  /* HUFF_DECODE has determined that the code is at least min_bits */
-  /* bits long, so fetch that many bits in one swoop. */
-
-  CHECK_BIT_BUFFER(*state, l, return -1);
-  code = GET_BITS(l);
-
-  /* Collect the rest of the Huffman code one bit at a time. */
-  /* This is per Figure F.16 in the JPEG spec. */
-
-  while (code > htbl->maxcode[l]) {
-    code <<= 1;
-    CHECK_BIT_BUFFER(*state, 1, return -1);
-    code |= GET_BITS(1);
-    l++;
-  }
-
-  /* Unload the local registers */
-  state->get_buffer = get_buffer;
-  state->bits_left = bits_left;
-
-  /* With garbage input we may reach the sentinel value l = 17. */
-
-  if (l > 16) {
-    WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE);
-    return 0;			/* fake a zero as the safest result */
-  }
-
-  return htbl->pub->huffval[ (int) (code + htbl->valoffset[l]) ];
-}
-
-
-/*
- * Figure F.12: extend sign bit.
- * On some machines, a shift and add will be faster than a table lookup.
- */
-
-#ifdef AVOID_TABLES
-
-#define HUFF_EXTEND(x,s)  ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x))
-
-#else
-
-#define HUFF_EXTEND(x,s)  ((x) < extend_test[s] ? (x) + extend_offset[s] : (x))
-
-static const int extend_test[16] =   /* entry n is 2**(n-1) */
-  { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
-    0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 };
-
-static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */
-  { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1,
-    ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1,
-    ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1,
-    ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 };
-
-#endif /* AVOID_TABLES */
-
-
-/*
- * Check for a restart marker & resynchronize decoder.
- * Returns FALSE if must suspend.
- */
-
-LOCAL(boolean)
-process_restart (j_decompress_ptr cinfo)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  int ci;
-
-  /* Throw away any unused bits remaining in bit buffer; */
-  /* include any full bytes in next_marker's count of discarded bytes */
-  cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8;
-  entropy->bitstate.bits_left = 0;
-
-  /* Advance past the RSTn marker */
-  if (! (*cinfo->marker->read_restart_marker) (cinfo))
-    return FALSE;
-
-  /* Re-initialize DC predictions to 0 */
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++)
-    entropy->saved.last_dc_val[ci] = 0;
-
-  /* Reset restart counter */
-  entropy->restarts_to_go = cinfo->restart_interval;
-
-  /* Reset out-of-data flag, unless read_restart_marker left us smack up
-   * against a marker.  In that case we will end up treating the next data
-   * segment as empty, and we can avoid producing bogus output pixels by
-   * leaving the flag set.
-   */
-  if (cinfo->unread_marker == 0)
-    entropy->pub.insufficient_data = FALSE;
-
-  return TRUE;
-}
-
-
-/*
- * Decode and return one MCU's worth of Huffman-compressed coefficients.
- * The coefficients are reordered from zigzag order into natural array order,
- * but are not dequantized.
- *
- * The i'th block of the MCU is stored into the block pointed to by
- * MCU_data[i].  WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER.
- * (Wholesale zeroing is usually a little faster than retail...)
- *
- * Returns FALSE if data source requested suspension.  In that case no
- * changes have been made to permanent state.  (Exception: some output
- * coefficients may already have been assigned.  This is harmless for
- * this module, since we'll just re-assign them on the next call.)
- */
-
-METHODDEF(boolean)
-decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data)
-{
-  huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy;
-  int blkn;
-  BITREAD_STATE_VARS;
-  savable_state state;
-
-  /* Process restart marker if needed; may have to suspend */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0)
-      if (! process_restart(cinfo))
-	return FALSE;
-  }
-
-  /* If we've run out of data, just leave the MCU set to zeroes.
-   * This way, we return uniform gray for the remainder of the segment.
-   */
-  if (! entropy->pub.insufficient_data) {
-
-    /* Load up working state */
-    BITREAD_LOAD_STATE(cinfo,entropy->bitstate);
-    ASSIGN_STATE(state, entropy->saved);
-
-    /* Outer loop handles each block in the MCU */
-
-    for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-      JBLOCKROW block = MCU_data[blkn];
-      d_derived_tbl * dctbl = entropy->dc_cur_tbls[blkn];
-      d_derived_tbl * actbl = entropy->ac_cur_tbls[blkn];
-      register int s, k, r;
-
-      /* Decode a single block's worth of coefficients */
-
-      /* Section F.2.2.1: decode the DC coefficient difference */
-      HUFF_DECODE(s, br_state, dctbl, return FALSE, label1);
-      if (s) {
-	CHECK_BIT_BUFFER(br_state, s, return FALSE);
-	r = GET_BITS(s);
-	s = HUFF_EXTEND(r, s);
-      }
-
-      if (entropy->dc_needed[blkn]) {
-	/* Convert DC difference to actual value, update last_dc_val */
-	int ci = cinfo->MCU_membership[blkn];
-	s += state.last_dc_val[ci];
-	state.last_dc_val[ci] = s;
-	/* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */
-	(*block)[0] = (JCOEF) s;
-      }
-
-      if (entropy->ac_needed[blkn]) {
-
-	/* Section F.2.2.2: decode the AC coefficients */
-	/* Since zeroes are skipped, output area must be cleared beforehand */
-	for (k = 1; k < DCTSIZE2; k++) {
-	  HUFF_DECODE(s, br_state, actbl, return FALSE, label2);
-      
-	  r = s >> 4;
-	  s &= 15;
-      
-	  if (s) {
-	    k += r;
-	    CHECK_BIT_BUFFER(br_state, s, return FALSE);
-	    r = GET_BITS(s);
-	    s = HUFF_EXTEND(r, s);
-	    /* Output coefficient in natural (dezigzagged) order.
-	     * Note: the extra entries in jpeg_natural_order[] will save us
-	     * if k >= DCTSIZE2, which could happen if the data is corrupted.
-	     */
-	    (*block)[jpeg_natural_order[k]] = (JCOEF) s;
-	  } else {
-	    if (r != 15)
-	      break;
-	    k += 15;
-	  }
-	}
-
-      } else {
-
-	/* Section F.2.2.2: decode the AC coefficients */
-	/* In this path we just discard the values */
-	for (k = 1; k < DCTSIZE2; k++) {
-	  HUFF_DECODE(s, br_state, actbl, return FALSE, label3);
-      
-	  r = s >> 4;
-	  s &= 15;
-      
-	  if (s) {
-	    k += r;
-	    CHECK_BIT_BUFFER(br_state, s, return FALSE);
-	    DROP_BITS(s);
-	  } else {
-	    if (r != 15)
-	      break;
-	    k += 15;
-	  }
-	}
-
-      }
-    }
-
-    /* Completed MCU, so update state */
-    BITREAD_SAVE_STATE(cinfo,entropy->bitstate);
-    ASSIGN_STATE(entropy->saved, state);
-  }
-
-  /* Account for restart interval (no-op if not using restarts) */
-  entropy->restarts_to_go--;
-
-  return TRUE;
-}
-
-
-/*
- * Module initialization routine for Huffman entropy decoding.
- */
-
-GLOBAL(void)
-jinit_huff_decoder (j_decompress_ptr cinfo)
-{
-  huff_entropy_ptr entropy;
-  int i;
-
-  entropy = (huff_entropy_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(huff_entropy_decoder));
-  cinfo->entropy = (struct jpeg_entropy_decoder *) entropy;
-  entropy->pub.start_pass = start_pass_huff_decoder;
-  entropy->pub.decode_mcu = decode_mcu;
-
-  /* Mark tables unallocated */
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL;
-  }
-}
diff --git a/third_party/libjpeg/jdhuff.h b/third_party/libjpeg/jdhuff.h
deleted file mode 100644
index ae19b6c..0000000
--- a/third_party/libjpeg/jdhuff.h
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * jdhuff.h
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains declarations for Huffman entropy decoding routines
- * that are shared between the sequential decoder (jdhuff.c) and the
- * progressive decoder (jdphuff.c).  No other modules need to see these.
- */
-
-/* Short forms of external names for systems with brain-damaged linkers. */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jpeg_make_d_derived_tbl	jMkDDerived
-#define jpeg_fill_bit_buffer	jFilBitBuf
-#define jpeg_huff_decode	jHufDecode
-#endif /* NEED_SHORT_EXTERNAL_NAMES */
-
-
-/* Derived data constructed for each Huffman table */
-
-#define HUFF_LOOKAHEAD	8	/* # of bits of lookahead */
-
-typedef struct {
-  /* Basic tables: (element [0] of each array is unused) */
-  INT32 maxcode[18];		/* largest code of length k (-1 if none) */
-  /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */
-  INT32 valoffset[17];		/* huffval[] offset for codes of length k */
-  /* valoffset[k] = huffval[] index of 1st symbol of code length k, less
-   * the smallest code of length k; so given a code of length k, the
-   * corresponding symbol is huffval[code + valoffset[k]]
-   */
-
-  /* Link to public Huffman table (needed only in jpeg_huff_decode) */
-  JHUFF_TBL *pub;
-
-  /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of
-   * the input data stream.  If the next Huffman code is no more
-   * than HUFF_LOOKAHEAD bits long, we can obtain its length and
-   * the corresponding symbol directly from these tables.
-   */
-  int look_nbits[1<<HUFF_LOOKAHEAD]; /* # bits, or 0 if too long */
-  UINT8 look_sym[1<<HUFF_LOOKAHEAD]; /* symbol, or unused */
-} d_derived_tbl;
-
-/* Expand a Huffman table definition into the derived format */
-EXTERN(void) jpeg_make_d_derived_tbl
-	JPP((j_decompress_ptr cinfo, boolean isDC, int tblno,
-	     d_derived_tbl ** pdtbl));
-
-
-/*
- * Fetching the next N bits from the input stream is a time-critical operation
- * for the Huffman decoders.  We implement it with a combination of inline
- * macros and out-of-line subroutines.  Note that N (the number of bits
- * demanded at one time) never exceeds 15 for JPEG use.
- *
- * We read source bytes into get_buffer and dole out bits as needed.
- * If get_buffer already contains enough bits, they are fetched in-line
- * by the macros CHECK_BIT_BUFFER and GET_BITS.  When there aren't enough
- * bits, jpeg_fill_bit_buffer is called; it will attempt to fill get_buffer
- * as full as possible (not just to the number of bits needed; this
- * prefetching reduces the overhead cost of calling jpeg_fill_bit_buffer).
- * Note that jpeg_fill_bit_buffer may return FALSE to indicate suspension.
- * On TRUE return, jpeg_fill_bit_buffer guarantees that get_buffer contains
- * at least the requested number of bits --- dummy zeroes are inserted if
- * necessary.
- */
-
-typedef INT32 bit_buf_type;	/* type of bit-extraction buffer */
-#define BIT_BUF_SIZE  32	/* size of buffer in bits */
-
-/* If long is > 32 bits on your machine, and shifting/masking longs is
- * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE
- * appropriately should be a win.  Unfortunately we can't define the size
- * with something like  #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8)
- * because not all machines measure sizeof in 8-bit bytes.
- */
-
-typedef struct {		/* Bitreading state saved across MCUs */
-  bit_buf_type get_buffer;	/* current bit-extraction buffer */
-  int bits_left;		/* # of unused bits in it */
-} bitread_perm_state;
-
-typedef struct {		/* Bitreading working state within an MCU */
-  /* Current data source location */
-  /* We need a copy, rather than munging the original, in case of suspension */
-  const JOCTET * next_input_byte; /* => next byte to read from source */
-  size_t bytes_in_buffer;	/* # of bytes remaining in source buffer */
-  /* Bit input buffer --- note these values are kept in register variables,
-   * not in this struct, inside the inner loops.
-   */
-  bit_buf_type get_buffer;	/* current bit-extraction buffer */
-  int bits_left;		/* # of unused bits in it */
-  /* Pointer needed by jpeg_fill_bit_buffer. */
-  j_decompress_ptr cinfo;	/* back link to decompress master record */
-} bitread_working_state;
-
-/* Macros to declare and load/save bitread local variables. */
-#define BITREAD_STATE_VARS  \
-	register bit_buf_type get_buffer;  \
-	register int bits_left;  \
-	bitread_working_state br_state
-
-#define BITREAD_LOAD_STATE(cinfop,permstate)  \
-	br_state.cinfo = cinfop; \
-	br_state.next_input_byte = cinfop->src->next_input_byte; \
-	br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \
-	get_buffer = permstate.get_buffer; \
-	bits_left = permstate.bits_left;
-
-#define BITREAD_SAVE_STATE(cinfop,permstate)  \
-	cinfop->src->next_input_byte = br_state.next_input_byte; \
-	cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \
-	permstate.get_buffer = get_buffer; \
-	permstate.bits_left = bits_left
-
-/*
- * These macros provide the in-line portion of bit fetching.
- * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer
- * before using GET_BITS, PEEK_BITS, or DROP_BITS.
- * The variables get_buffer and bits_left are assumed to be locals,
- * but the state struct might not be (jpeg_huff_decode needs this).
- *	CHECK_BIT_BUFFER(state,n,action);
- *		Ensure there are N bits in get_buffer; if suspend, take action.
- *      val = GET_BITS(n);
- *		Fetch next N bits.
- *      val = PEEK_BITS(n);
- *		Fetch next N bits without removing them from the buffer.
- *	DROP_BITS(n);
- *		Discard next N bits.
- * The value N should be a simple variable, not an expression, because it
- * is evaluated multiple times.
- */
-
-#define CHECK_BIT_BUFFER(state,nbits,action) \
-	{ if (bits_left < (nbits)) {  \
-	    if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits))  \
-	      { action; }  \
-	    get_buffer = (state).get_buffer; bits_left = (state).bits_left; } }
-
-#define GET_BITS(nbits) \
-	(((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1))
-
-#define PEEK_BITS(nbits) \
-	(((int) (get_buffer >> (bits_left -  (nbits)))) & ((1<<(nbits))-1))
-
-#define DROP_BITS(nbits) \
-	(bits_left -= (nbits))
-
-/* Load up the bit buffer to a depth of at least nbits */
-EXTERN(boolean) jpeg_fill_bit_buffer
-	JPP((bitread_working_state * state, register bit_buf_type get_buffer,
-	     register int bits_left, int nbits));
-
-
-/*
- * Code for extracting next Huffman-coded symbol from input bit stream.
- * Again, this is time-critical and we make the main paths be macros.
- *
- * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits
- * without looping.  Usually, more than 95% of the Huffman codes will be 8
- * or fewer bits long.  The few overlength codes are handled with a loop,
- * which need not be inline code.
- *
- * Notes about the HUFF_DECODE macro:
- * 1. Near the end of the data segment, we may fail to get enough bits
- *    for a lookahead.  In that case, we do it the hard way.
- * 2. If the lookahead table contains no entry, the next code must be
- *    more than HUFF_LOOKAHEAD bits long.
- * 3. jpeg_huff_decode returns -1 if forced to suspend.
- */
-
-#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \
-{ register int nb, look; \
-  if (bits_left < HUFF_LOOKAHEAD) { \
-    if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \
-    get_buffer = state.get_buffer; bits_left = state.bits_left; \
-    if (bits_left < HUFF_LOOKAHEAD) { \
-      nb = 1; goto slowlabel; \
-    } \
-  } \
-  look = PEEK_BITS(HUFF_LOOKAHEAD); \
-  if ((nb = htbl->look_nbits[look]) != 0) { \
-    DROP_BITS(nb); \
-    result = htbl->look_sym[look]; \
-  } else { \
-    nb = HUFF_LOOKAHEAD+1; \
-slowlabel: \
-    if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \
-	{ failaction; } \
-    get_buffer = state.get_buffer; bits_left = state.bits_left; \
-  } \
-}
-
-/* Out-of-line case for Huffman code fetching */
-EXTERN(int) jpeg_huff_decode
-	JPP((bitread_working_state * state, register bit_buf_type get_buffer,
-	     register int bits_left, d_derived_tbl * htbl, int min_bits));
diff --git a/third_party/libjpeg/jdinput.c b/third_party/libjpeg/jdinput.c
deleted file mode 100644
index 0c2ac8f..0000000
--- a/third_party/libjpeg/jdinput.c
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * jdinput.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains input control logic for the JPEG decompressor.
- * These routines are concerned with controlling the decompressor's input
- * processing (marker reading and coefficient decoding).  The actual input
- * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Private state */
-
-typedef struct {
-  struct jpeg_input_controller pub; /* public fields */
-
-  boolean inheaders;		/* TRUE until first SOS is reached */
-} my_input_controller;
-
-typedef my_input_controller * my_inputctl_ptr;
-
-
-/* Forward declarations */
-METHODDEF(int) consume_markers JPP((j_decompress_ptr cinfo));
-
-
-/*
- * Routines to calculate various quantities related to the size of the image.
- */
-
-LOCAL(void)
-initial_setup (j_decompress_ptr cinfo)
-/* Called once, when first SOS marker is reached */
-{
-  int ci;
-  jpeg_component_info *compptr;
-
-  /* Make sure image isn't bigger than I can handle */
-  if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION ||
-      (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION)
-    ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION);
-
-  /* For now, precision must match compiled-in value... */
-  if (cinfo->data_precision != BITS_IN_JSAMPLE)
-    ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
-
-  /* Check that number of components won't exceed internal array sizes */
-  if (cinfo->num_components > MAX_COMPONENTS)
-    ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components,
-	     MAX_COMPONENTS);
-
-  /* Compute maximum sampling factors; check factor validity */
-  cinfo->max_h_samp_factor = 1;
-  cinfo->max_v_samp_factor = 1;
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR ||
-	compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR)
-      ERREXIT(cinfo, JERR_BAD_SAMPLING);
-    cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor,
-				   compptr->h_samp_factor);
-    cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor,
-				   compptr->v_samp_factor);
-  }
-
-  /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE.
-   * In the full decompressor, this will be overridden by jdmaster.c;
-   * but in the transcoder, jdmaster.c is not used, so we must do it here.
-   */
-  cinfo->min_DCT_scaled_size = DCTSIZE;
-
-  /* Compute dimensions of components */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    compptr->DCT_scaled_size = DCTSIZE;
-    /* Size in DCT blocks */
-    compptr->width_in_blocks = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor,
-		    (long) (cinfo->max_h_samp_factor * DCTSIZE));
-    compptr->height_in_blocks = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor,
-		    (long) (cinfo->max_v_samp_factor * DCTSIZE));
-    /* downsampled_width and downsampled_height will also be overridden by
-     * jdmaster.c if we are doing full decompression.  The transcoder library
-     * doesn't use these values, but the calling application might.
-     */
-    /* Size in samples */
-    compptr->downsampled_width = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor,
-		    (long) cinfo->max_h_samp_factor);
-    compptr->downsampled_height = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor,
-		    (long) cinfo->max_v_samp_factor);
-    /* Mark component needed, until color conversion says otherwise */
-    compptr->component_needed = TRUE;
-    /* Mark no quantization table yet saved for component */
-    compptr->quant_table = NULL;
-  }
-
-  /* Compute number of fully interleaved MCU rows. */
-  cinfo->total_iMCU_rows = (JDIMENSION)
-    jdiv_round_up((long) cinfo->image_height,
-		  (long) (cinfo->max_v_samp_factor*DCTSIZE));
-
-  /* Decide whether file contains multiple scans */
-  if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode)
-    cinfo->inputctl->has_multiple_scans = TRUE;
-  else
-    cinfo->inputctl->has_multiple_scans = FALSE;
-}
-
-
-LOCAL(void)
-per_scan_setup (j_decompress_ptr cinfo)
-/* Do computations that are needed before processing a JPEG scan */
-/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */
-{
-  int ci, mcublks, tmp;
-  jpeg_component_info *compptr;
-  
-  if (cinfo->comps_in_scan == 1) {
-    
-    /* Noninterleaved (single-component) scan */
-    compptr = cinfo->cur_comp_info[0];
-    
-    /* Overall image size in MCUs */
-    cinfo->MCUs_per_row = compptr->width_in_blocks;
-    cinfo->MCU_rows_in_scan = compptr->height_in_blocks;
-    
-    /* For noninterleaved scan, always one block per MCU */
-    compptr->MCU_width = 1;
-    compptr->MCU_height = 1;
-    compptr->MCU_blocks = 1;
-    compptr->MCU_sample_width = compptr->DCT_scaled_size;
-    compptr->last_col_width = 1;
-    /* For noninterleaved scans, it is convenient to define last_row_height
-     * as the number of block rows present in the last iMCU row.
-     */
-    tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor);
-    if (tmp == 0) tmp = compptr->v_samp_factor;
-    compptr->last_row_height = tmp;
-    
-    /* Prepare array describing MCU composition */
-    cinfo->blocks_in_MCU = 1;
-    cinfo->MCU_membership[0] = 0;
-    
-  } else {
-    
-    /* Interleaved (multi-component) scan */
-    if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN)
-      ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan,
-	       MAX_COMPS_IN_SCAN);
-    
-    /* Overall image size in MCUs */
-    cinfo->MCUs_per_row = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width,
-		    (long) (cinfo->max_h_samp_factor*DCTSIZE));
-    cinfo->MCU_rows_in_scan = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height,
-		    (long) (cinfo->max_v_samp_factor*DCTSIZE));
-    
-    cinfo->blocks_in_MCU = 0;
-    
-    for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-      compptr = cinfo->cur_comp_info[ci];
-      /* Sampling factors give # of blocks of component in each MCU */
-      compptr->MCU_width = compptr->h_samp_factor;
-      compptr->MCU_height = compptr->v_samp_factor;
-      compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height;
-      compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size;
-      /* Figure number of non-dummy blocks in last MCU column & row */
-      tmp = (int) (compptr->width_in_blocks % compptr->MCU_width);
-      if (tmp == 0) tmp = compptr->MCU_width;
-      compptr->last_col_width = tmp;
-      tmp = (int) (compptr->height_in_blocks % compptr->MCU_height);
-      if (tmp == 0) tmp = compptr->MCU_height;
-      compptr->last_row_height = tmp;
-      /* Prepare array describing MCU composition */
-      mcublks = compptr->MCU_blocks;
-      if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU)
-	ERREXIT(cinfo, JERR_BAD_MCU_SIZE);
-      while (mcublks-- > 0) {
-	cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci;
-      }
-    }
-    
-  }
-}
-
-
-/*
- * Save away a copy of the Q-table referenced by each component present
- * in the current scan, unless already saved during a prior scan.
- *
- * In a multiple-scan JPEG file, the encoder could assign different components
- * the same Q-table slot number, but change table definitions between scans
- * so that each component uses a different Q-table.  (The IJG encoder is not
- * currently capable of doing this, but other encoders might.)  Since we want
- * to be able to dequantize all the components at the end of the file, this
- * means that we have to save away the table actually used for each component.
- * We do this by copying the table at the start of the first scan containing
- * the component.
- * The JPEG spec prohibits the encoder from changing the contents of a Q-table
- * slot between scans of a component using that slot.  If the encoder does so
- * anyway, this decoder will simply use the Q-table values that were current
- * at the start of the first scan for the component.
- *
- * The decompressor output side looks only at the saved quant tables,
- * not at the current Q-table slots.
- */
-
-LOCAL(void)
-latch_quant_tables (j_decompress_ptr cinfo)
-{
-  int ci, qtblno;
-  jpeg_component_info *compptr;
-  JQUANT_TBL * qtbl;
-
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    /* No work if we already saved Q-table for this component */
-    if (compptr->quant_table != NULL)
-      continue;
-    /* Make sure specified quantization table is present */
-    qtblno = compptr->quant_tbl_no;
-    if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS ||
-	cinfo->quant_tbl_ptrs[qtblno] == NULL)
-      ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno);
-    /* OK, save away the quantization table */
-    qtbl = (JQUANT_TBL *)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  SIZEOF(JQUANT_TBL));
-    MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL));
-    compptr->quant_table = qtbl;
-  }
-}
-
-
-/*
- * Initialize the input modules to read a scan of compressed data.
- * The first call to this is done by jdmaster.c after initializing
- * the entire decompressor (during jpeg_start_decompress).
- * Subsequent calls come from consume_markers, below.
- */
-
-METHODDEF(void)
-start_input_pass (j_decompress_ptr cinfo)
-{
-  per_scan_setup(cinfo);
-  latch_quant_tables(cinfo);
-  (*cinfo->entropy->start_pass) (cinfo);
-  (*cinfo->coef->start_input_pass) (cinfo);
-  cinfo->inputctl->consume_input = cinfo->coef->consume_data;
-}
-
-
-/*
- * Finish up after inputting a compressed-data scan.
- * This is called by the coefficient controller after it's read all
- * the expected data of the scan.
- */
-
-METHODDEF(void)
-finish_input_pass (j_decompress_ptr cinfo)
-{
-  cinfo->inputctl->consume_input = consume_markers;
-}
-
-
-/*
- * Read JPEG markers before, between, or after compressed-data scans.
- * Change state as necessary when a new scan is reached.
- * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI.
- *
- * The consume_input method pointer points either here or to the
- * coefficient controller's consume_data routine, depending on whether
- * we are reading a compressed data segment or inter-segment markers.
- */
-
-METHODDEF(int)
-consume_markers (j_decompress_ptr cinfo)
-{
-  my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl;
-  int val;
-
-  if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */
-    return JPEG_REACHED_EOI;
-
-  val = (*cinfo->marker->read_markers) (cinfo);
-
-  switch (val) {
-  case JPEG_REACHED_SOS:	/* Found SOS */
-    if (inputctl->inheaders) {	/* 1st SOS */
-      initial_setup(cinfo);
-      inputctl->inheaders = FALSE;
-      /* Note: start_input_pass must be called by jdmaster.c
-       * before any more input can be consumed.  jdapimin.c is
-       * responsible for enforcing this sequencing.
-       */
-    } else {			/* 2nd or later SOS marker */
-      if (! inputctl->pub.has_multiple_scans)
-	ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */
-      start_input_pass(cinfo);
-    }
-    break;
-  case JPEG_REACHED_EOI:	/* Found EOI */
-    inputctl->pub.eoi_reached = TRUE;
-    if (inputctl->inheaders) {	/* Tables-only datastream, apparently */
-      if (cinfo->marker->saw_SOF)
-	ERREXIT(cinfo, JERR_SOF_NO_SOS);
-    } else {
-      /* Prevent infinite loop in coef ctlr's decompress_data routine
-       * if user set output_scan_number larger than number of scans.
-       */
-      if (cinfo->output_scan_number > cinfo->input_scan_number)
-	cinfo->output_scan_number = cinfo->input_scan_number;
-    }
-    break;
-  case JPEG_SUSPENDED:
-    break;
-  }
-
-  return val;
-}
-
-
-/*
- * Reset state to begin a fresh datastream.
- */
-
-METHODDEF(void)
-reset_input_controller (j_decompress_ptr cinfo)
-{
-  my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl;
-
-  inputctl->pub.consume_input = consume_markers;
-  inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */
-  inputctl->pub.eoi_reached = FALSE;
-  inputctl->inheaders = TRUE;
-  /* Reset other modules */
-  (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo);
-  (*cinfo->marker->reset_marker_reader) (cinfo);
-  /* Reset progression state -- would be cleaner if entropy decoder did this */
-  cinfo->coef_bits = NULL;
-}
-
-
-/*
- * Initialize the input controller module.
- * This is called only once, when the decompression object is created.
- */
-
-GLOBAL(void)
-jinit_input_controller (j_decompress_ptr cinfo)
-{
-  my_inputctl_ptr inputctl;
-
-  /* Create subobject in permanent pool */
-  inputctl = (my_inputctl_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-				SIZEOF(my_input_controller));
-  cinfo->inputctl = (struct jpeg_input_controller *) inputctl;
-  /* Initialize method pointers */
-  inputctl->pub.consume_input = consume_markers;
-  inputctl->pub.reset_input_controller = reset_input_controller;
-  inputctl->pub.start_input_pass = start_input_pass;
-  inputctl->pub.finish_input_pass = finish_input_pass;
-  /* Initialize state: can't use reset_input_controller since we don't
-   * want to try to reset other modules yet.
-   */
-  inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */
-  inputctl->pub.eoi_reached = FALSE;
-  inputctl->inheaders = TRUE;
-}
diff --git a/third_party/libjpeg/jdmainct.c b/third_party/libjpeg/jdmainct.c
deleted file mode 100644
index 13c956f..0000000
--- a/third_party/libjpeg/jdmainct.c
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * jdmainct.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the main buffer controller for decompression.
- * The main buffer lies between the JPEG decompressor proper and the
- * post-processor; it holds downsampled data in the JPEG colorspace.
- *
- * Note that this code is bypassed in raw-data mode, since the application
- * supplies the equivalent of the main buffer in that case.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * In the current system design, the main buffer need never be a full-image
- * buffer; any full-height buffers will be found inside the coefficient or
- * postprocessing controllers.  Nonetheless, the main controller is not
- * trivial.  Its responsibility is to provide context rows for upsampling/
- * rescaling, and doing this in an efficient fashion is a bit tricky.
- *
- * Postprocessor input data is counted in "row groups".  A row group
- * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size)
- * sample rows of each component.  (We require DCT_scaled_size values to be
- * chosen such that these numbers are integers.  In practice DCT_scaled_size
- * values will likely be powers of two, so we actually have the stronger
- * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.)
- * Upsampling will typically produce max_v_samp_factor pixel rows from each
- * row group (times any additional scale factor that the upsampler is
- * applying).
- *
- * The coefficient controller will deliver data to us one iMCU row at a time;
- * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or
- * exactly min_DCT_scaled_size row groups.  (This amount of data corresponds
- * to one row of MCUs when the image is fully interleaved.)  Note that the
- * number of sample rows varies across components, but the number of row
- * groups does not.  Some garbage sample rows may be included in the last iMCU
- * row at the bottom of the image.
- *
- * Depending on the vertical scaling algorithm used, the upsampler may need
- * access to the sample row(s) above and below its current input row group.
- * The upsampler is required to set need_context_rows TRUE at global selection
- * time if so.  When need_context_rows is FALSE, this controller can simply
- * obtain one iMCU row at a time from the coefficient controller and dole it
- * out as row groups to the postprocessor.
- *
- * When need_context_rows is TRUE, this controller guarantees that the buffer
- * passed to postprocessing contains at least one row group's worth of samples
- * above and below the row group(s) being processed.  Note that the context
- * rows "above" the first passed row group appear at negative row offsets in
- * the passed buffer.  At the top and bottom of the image, the required
- * context rows are manufactured by duplicating the first or last real sample
- * row; this avoids having special cases in the upsampling inner loops.
- *
- * The amount of context is fixed at one row group just because that's a
- * convenient number for this controller to work with.  The existing
- * upsamplers really only need one sample row of context.  An upsampler
- * supporting arbitrary output rescaling might wish for more than one row
- * group of context when shrinking the image; tough, we don't handle that.
- * (This is justified by the assumption that downsizing will be handled mostly
- * by adjusting the DCT_scaled_size values, so that the actual scale factor at
- * the upsample step needn't be much less than one.)
- *
- * To provide the desired context, we have to retain the last two row groups
- * of one iMCU row while reading in the next iMCU row.  (The last row group
- * can't be processed until we have another row group for its below-context,
- * and so we have to save the next-to-last group too for its above-context.)
- * We could do this most simply by copying data around in our buffer, but
- * that'd be very slow.  We can avoid copying any data by creating a rather
- * strange pointer structure.  Here's how it works.  We allocate a workspace
- * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number
- * of row groups per iMCU row).  We create two sets of redundant pointers to
- * the workspace.  Labeling the physical row groups 0 to M+1, the synthesized
- * pointer lists look like this:
- *                   M+1                          M-1
- * master pointer --> 0         master pointer --> 0
- *                    1                            1
- *                   ...                          ...
- *                   M-3                          M-3
- *                   M-2                           M
- *                   M-1                          M+1
- *                    M                           M-2
- *                   M+1                          M-1
- *                    0                            0
- * We read alternate iMCU rows using each master pointer; thus the last two
- * row groups of the previous iMCU row remain un-overwritten in the workspace.
- * The pointer lists are set up so that the required context rows appear to
- * be adjacent to the proper places when we pass the pointer lists to the
- * upsampler.
- *
- * The above pictures describe the normal state of the pointer lists.
- * At top and bottom of the image, we diddle the pointer lists to duplicate
- * the first or last sample row as necessary (this is cheaper than copying
- * sample rows around).
- *
- * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1.  In that
- * situation each iMCU row provides only one row group so the buffering logic
- * must be different (eg, we must read two iMCU rows before we can emit the
- * first row group).  For now, we simply do not support providing context
- * rows when min_DCT_scaled_size is 1.  That combination seems unlikely to
- * be worth providing --- if someone wants a 1/8th-size preview, they probably
- * want it quick and dirty, so a context-free upsampler is sufficient.
- */
-
-
-/* Private buffer controller object */
-
-typedef struct {
-  struct jpeg_d_main_controller pub; /* public fields */
-
-  /* Pointer to allocated workspace (M or M+2 row groups). */
-  JSAMPARRAY buffer[MAX_COMPONENTS];
-
-  boolean buffer_full;		/* Have we gotten an iMCU row from decoder? */
-  JDIMENSION rowgroup_ctr;	/* counts row groups output to postprocessor */
-
-  /* Remaining fields are only used in the context case. */
-
-  /* These are the master pointers to the funny-order pointer lists. */
-  JSAMPIMAGE xbuffer[2];	/* pointers to weird pointer lists */
-
-  int whichptr;			/* indicates which pointer set is now in use */
-  int context_state;		/* process_data state machine status */
-  JDIMENSION rowgroups_avail;	/* row groups available to postprocessor */
-  JDIMENSION iMCU_row_ctr;	/* counts iMCU rows to detect image top/bot */
-} my_main_controller;
-
-typedef my_main_controller * my_main_ptr;
-
-/* context_state values: */
-#define CTX_PREPARE_FOR_IMCU	0	/* need to prepare for MCU row */
-#define CTX_PROCESS_IMCU	1	/* feeding iMCU to postprocessor */
-#define CTX_POSTPONED_ROW	2	/* feeding postponed row group */
-
-
-/* Forward declarations */
-METHODDEF(void) process_data_simple_main
-	JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf,
-	     JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail));
-METHODDEF(void) process_data_context_main
-	JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf,
-	     JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail));
-#ifdef QUANT_2PASS_SUPPORTED
-METHODDEF(void) process_data_crank_post
-	JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf,
-	     JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail));
-#endif
-
-
-LOCAL(void)
-alloc_funny_pointers (j_decompress_ptr cinfo)
-/* Allocate space for the funny pointer lists.
- * This is done only once, not once per pass.
- */
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-  int ci, rgroup;
-  int M = cinfo->min_DCT_scaled_size;
-  jpeg_component_info *compptr;
-  JSAMPARRAY xbuf;
-
-  /* Get top-level space for component array pointers.
-   * We alloc both arrays with one call to save a few cycles.
-   */
-  main->xbuffer[0] = (JSAMPIMAGE)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				cinfo->num_components * 2 * SIZEOF(JSAMPARRAY));
-  main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) /
-      cinfo->min_DCT_scaled_size; /* height of a row group of component */
-    /* Get space for pointer lists --- M+4 row groups in each list.
-     * We alloc both pointer lists with one call to save a few cycles.
-     */
-    xbuf = (JSAMPARRAY)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW));
-    xbuf += rgroup;		/* want one row group at negative offsets */
-    main->xbuffer[0][ci] = xbuf;
-    xbuf += rgroup * (M + 4);
-    main->xbuffer[1][ci] = xbuf;
-  }
-}
-
-
-LOCAL(void)
-make_funny_pointers (j_decompress_ptr cinfo)
-/* Create the funny pointer lists discussed in the comments above.
- * The actual workspace is already allocated (in main->buffer),
- * and the space for the pointer lists is allocated too.
- * This routine just fills in the curiously ordered lists.
- * This will be repeated at the beginning of each pass.
- */
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-  int ci, i, rgroup;
-  int M = cinfo->min_DCT_scaled_size;
-  jpeg_component_info *compptr;
-  JSAMPARRAY buf, xbuf0, xbuf1;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) /
-      cinfo->min_DCT_scaled_size; /* height of a row group of component */
-    xbuf0 = main->xbuffer[0][ci];
-    xbuf1 = main->xbuffer[1][ci];
-    /* First copy the workspace pointers as-is */
-    buf = main->buffer[ci];
-    for (i = 0; i < rgroup * (M + 2); i++) {
-      xbuf0[i] = xbuf1[i] = buf[i];
-    }
-    /* In the second list, put the last four row groups in swapped order */
-    for (i = 0; i < rgroup * 2; i++) {
-      xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i];
-      xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i];
-    }
-    /* The wraparound pointers at top and bottom will be filled later
-     * (see set_wraparound_pointers, below).  Initially we want the "above"
-     * pointers to duplicate the first actual data line.  This only needs
-     * to happen in xbuffer[0].
-     */
-    for (i = 0; i < rgroup; i++) {
-      xbuf0[i - rgroup] = xbuf0[0];
-    }
-  }
-}
-
-
-LOCAL(void)
-set_wraparound_pointers (j_decompress_ptr cinfo)
-/* Set up the "wraparound" pointers at top and bottom of the pointer lists.
- * This changes the pointer list state from top-of-image to the normal state.
- */
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-  int ci, i, rgroup;
-  int M = cinfo->min_DCT_scaled_size;
-  jpeg_component_info *compptr;
-  JSAMPARRAY xbuf0, xbuf1;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) /
-      cinfo->min_DCT_scaled_size; /* height of a row group of component */
-    xbuf0 = main->xbuffer[0][ci];
-    xbuf1 = main->xbuffer[1][ci];
-    for (i = 0; i < rgroup; i++) {
-      xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i];
-      xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i];
-      xbuf0[rgroup*(M+2) + i] = xbuf0[i];
-      xbuf1[rgroup*(M+2) + i] = xbuf1[i];
-    }
-  }
-}
-
-
-LOCAL(void)
-set_bottom_pointers (j_decompress_ptr cinfo)
-/* Change the pointer lists to duplicate the last sample row at the bottom
- * of the image.  whichptr indicates which xbuffer holds the final iMCU row.
- * Also sets rowgroups_avail to indicate number of nondummy row groups in row.
- */
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-  int ci, i, rgroup, iMCUheight, rows_left;
-  jpeg_component_info *compptr;
-  JSAMPARRAY xbuf;
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Count sample rows in one iMCU row and in one row group */
-    iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size;
-    rgroup = iMCUheight / cinfo->min_DCT_scaled_size;
-    /* Count nondummy sample rows remaining for this component */
-    rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight);
-    if (rows_left == 0) rows_left = iMCUheight;
-    /* Count nondummy row groups.  Should get same answer for each component,
-     * so we need only do it once.
-     */
-    if (ci == 0) {
-      main->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1);
-    }
-    /* Duplicate the last real sample row rgroup*2 times; this pads out the
-     * last partial rowgroup and ensures at least one full rowgroup of context.
-     */
-    xbuf = main->xbuffer[main->whichptr][ci];
-    for (i = 0; i < rgroup * 2; i++) {
-      xbuf[rows_left + i] = xbuf[rows_left-1];
-    }
-  }
-}
-
-
-/*
- * Initialize for a processing pass.
- */
-
-METHODDEF(void)
-start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-
-  switch (pass_mode) {
-  case JBUF_PASS_THRU:
-    if (cinfo->upsample->need_context_rows) {
-      main->pub.process_data = process_data_context_main;
-      make_funny_pointers(cinfo); /* Create the xbuffer[] lists */
-      main->whichptr = 0;	/* Read first iMCU row into xbuffer[0] */
-      main->context_state = CTX_PREPARE_FOR_IMCU;
-      main->iMCU_row_ctr = 0;
-    } else {
-      /* Simple case with no context needed */
-      main->pub.process_data = process_data_simple_main;
-    }
-    main->buffer_full = FALSE;	/* Mark buffer empty */
-    main->rowgroup_ctr = 0;
-    break;
-#ifdef QUANT_2PASS_SUPPORTED
-  case JBUF_CRANK_DEST:
-    /* For last pass of 2-pass quantization, just crank the postprocessor */
-    main->pub.process_data = process_data_crank_post;
-    break;
-#endif
-  default:
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    break;
-  }
-}
-
-
-/*
- * Process some data.
- * This handles the simple case where no context is required.
- */
-
-METHODDEF(void)
-process_data_simple_main (j_decompress_ptr cinfo,
-			  JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-			  JDIMENSION out_rows_avail)
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-  JDIMENSION rowgroups_avail;
-
-  /* Read input data if we haven't filled the main buffer yet */
-  if (! main->buffer_full) {
-    if (! (*cinfo->coef->decompress_data) (cinfo, main->buffer))
-      return;			/* suspension forced, can do nothing more */
-    main->buffer_full = TRUE;	/* OK, we have an iMCU row to work with */
-  }
-
-  /* There are always min_DCT_scaled_size row groups in an iMCU row. */
-  rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size;
-  /* Note: at the bottom of the image, we may pass extra garbage row groups
-   * to the postprocessor.  The postprocessor has to check for bottom
-   * of image anyway (at row resolution), so no point in us doing it too.
-   */
-
-  /* Feed the postprocessor */
-  (*cinfo->post->post_process_data) (cinfo, main->buffer,
-				     &main->rowgroup_ctr, rowgroups_avail,
-				     output_buf, out_row_ctr, out_rows_avail);
-
-  /* Has postprocessor consumed all the data yet? If so, mark buffer empty */
-  if (main->rowgroup_ctr >= rowgroups_avail) {
-    main->buffer_full = FALSE;
-    main->rowgroup_ctr = 0;
-  }
-}
-
-
-/*
- * Process some data.
- * This handles the case where context rows must be provided.
- */
-
-METHODDEF(void)
-process_data_context_main (j_decompress_ptr cinfo,
-			   JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-			   JDIMENSION out_rows_avail)
-{
-  my_main_ptr main = (my_main_ptr) cinfo->main;
-
-  /* Read input data if we haven't filled the main buffer yet */
-  if (! main->buffer_full) {
-    if (! (*cinfo->coef->decompress_data) (cinfo,
-					   main->xbuffer[main->whichptr]))
-      return;			/* suspension forced, can do nothing more */
-    main->buffer_full = TRUE;	/* OK, we have an iMCU row to work with */
-    main->iMCU_row_ctr++;	/* count rows received */
-  }
-
-  /* Postprocessor typically will not swallow all the input data it is handed
-   * in one call (due to filling the output buffer first).  Must be prepared
-   * to exit and restart.  This switch lets us keep track of how far we got.
-   * Note that each case falls through to the next on successful completion.
-   */
-  switch (main->context_state) {
-  case CTX_POSTPONED_ROW:
-    /* Call postprocessor using previously set pointers for postponed row */
-    (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr],
-			&main->rowgroup_ctr, main->rowgroups_avail,
-			output_buf, out_row_ctr, out_rows_avail);
-    if (main->rowgroup_ctr < main->rowgroups_avail)
-      return;			/* Need to suspend */
-    main->context_state = CTX_PREPARE_FOR_IMCU;
-    if (*out_row_ctr >= out_rows_avail)
-      return;			/* Postprocessor exactly filled output buf */
-    /*FALLTHROUGH*/
-  case CTX_PREPARE_FOR_IMCU:
-    /* Prepare to process first M-1 row groups of this iMCU row */
-    main->rowgroup_ctr = 0;
-    main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1);
-    /* Check for bottom of image: if so, tweak pointers to "duplicate"
-     * the last sample row, and adjust rowgroups_avail to ignore padding rows.
-     */
-    if (main->iMCU_row_ctr == cinfo->total_iMCU_rows)
-      set_bottom_pointers(cinfo);
-    main->context_state = CTX_PROCESS_IMCU;
-    /*FALLTHROUGH*/
-  case CTX_PROCESS_IMCU:
-    /* Call postprocessor using previously set pointers */
-    (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr],
-			&main->rowgroup_ctr, main->rowgroups_avail,
-			output_buf, out_row_ctr, out_rows_avail);
-    if (main->rowgroup_ctr < main->rowgroups_avail)
-      return;			/* Need to suspend */
-    /* After the first iMCU, change wraparound pointers to normal state */
-    if (main->iMCU_row_ctr == 1)
-      set_wraparound_pointers(cinfo);
-    /* Prepare to load new iMCU row using other xbuffer list */
-    main->whichptr ^= 1;	/* 0=>1 or 1=>0 */
-    main->buffer_full = FALSE;
-    /* Still need to process last row group of this iMCU row, */
-    /* which is saved at index M+1 of the other xbuffer */
-    main->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1);
-    main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2);
-    main->context_state = CTX_POSTPONED_ROW;
-  }
-}
-
-
-/*
- * Process some data.
- * Final pass of two-pass quantization: just call the postprocessor.
- * Source data will be the postprocessor controller's internal buffer.
- */
-
-#ifdef QUANT_2PASS_SUPPORTED
-
-METHODDEF(void)
-process_data_crank_post (j_decompress_ptr cinfo,
-			 JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-			 JDIMENSION out_rows_avail)
-{
-  (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL,
-				     (JDIMENSION *) NULL, (JDIMENSION) 0,
-				     output_buf, out_row_ctr, out_rows_avail);
-}
-
-#endif /* QUANT_2PASS_SUPPORTED */
-
-
-/*
- * Initialize main buffer controller.
- */
-
-GLOBAL(void)
-jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer)
-{
-  my_main_ptr main;
-  int ci, rgroup, ngroups;
-  jpeg_component_info *compptr;
-
-  main = (my_main_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_main_controller));
-  cinfo->main = (struct jpeg_d_main_controller *) main;
-  main->pub.start_pass = start_pass_main;
-
-  if (need_full_buffer)		/* shouldn't happen */
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-
-  /* Allocate the workspace.
-   * ngroups is the number of row groups we need.
-   */
-  if (cinfo->upsample->need_context_rows) {
-    if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */
-      ERREXIT(cinfo, JERR_NOTIMPL);
-    alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */
-    ngroups = cinfo->min_DCT_scaled_size + 2;
-  } else {
-    ngroups = cinfo->min_DCT_scaled_size;
-  }
-
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) /
-      cinfo->min_DCT_scaled_size; /* height of a row group of component */
-    main->buffer[ci] = (*cinfo->mem->alloc_sarray)
-			((j_common_ptr) cinfo, JPOOL_IMAGE,
-			 compptr->width_in_blocks * compptr->DCT_scaled_size,
-			 (JDIMENSION) (rgroup * ngroups));
-  }
-}
diff --git a/third_party/libjpeg/jdmarker.c b/third_party/libjpeg/jdmarker.c
deleted file mode 100644
index f4cca8c..0000000
--- a/third_party/libjpeg/jdmarker.c
+++ /dev/null
@@ -1,1360 +0,0 @@
-/*
- * jdmarker.c
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains routines to decode JPEG datastream markers.
- * Most of the complexity arises from our desire to support input
- * suspension: if not all of the data for a marker is available,
- * we must exit back to the application.  On resumption, we reprocess
- * the marker.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-typedef enum {			/* JPEG marker codes */
-  M_SOF0  = 0xc0,
-  M_SOF1  = 0xc1,
-  M_SOF2  = 0xc2,
-  M_SOF3  = 0xc3,
-  
-  M_SOF5  = 0xc5,
-  M_SOF6  = 0xc6,
-  M_SOF7  = 0xc7,
-  
-  M_JPG   = 0xc8,
-  M_SOF9  = 0xc9,
-  M_SOF10 = 0xca,
-  M_SOF11 = 0xcb,
-  
-  M_SOF13 = 0xcd,
-  M_SOF14 = 0xce,
-  M_SOF15 = 0xcf,
-  
-  M_DHT   = 0xc4,
-  
-  M_DAC   = 0xcc,
-  
-  M_RST0  = 0xd0,
-  M_RST1  = 0xd1,
-  M_RST2  = 0xd2,
-  M_RST3  = 0xd3,
-  M_RST4  = 0xd4,
-  M_RST5  = 0xd5,
-  M_RST6  = 0xd6,
-  M_RST7  = 0xd7,
-  
-  M_SOI   = 0xd8,
-  M_EOI   = 0xd9,
-  M_SOS   = 0xda,
-  M_DQT   = 0xdb,
-  M_DNL   = 0xdc,
-  M_DRI   = 0xdd,
-  M_DHP   = 0xde,
-  M_EXP   = 0xdf,
-  
-  M_APP0  = 0xe0,
-  M_APP1  = 0xe1,
-  M_APP2  = 0xe2,
-  M_APP3  = 0xe3,
-  M_APP4  = 0xe4,
-  M_APP5  = 0xe5,
-  M_APP6  = 0xe6,
-  M_APP7  = 0xe7,
-  M_APP8  = 0xe8,
-  M_APP9  = 0xe9,
-  M_APP10 = 0xea,
-  M_APP11 = 0xeb,
-  M_APP12 = 0xec,
-  M_APP13 = 0xed,
-  M_APP14 = 0xee,
-  M_APP15 = 0xef,
-  
-  M_JPG0  = 0xf0,
-  M_JPG13 = 0xfd,
-  M_COM   = 0xfe,
-  
-  M_TEM   = 0x01,
-  
-  M_ERROR = 0x100
-} JPEG_MARKER;
-
-
-/* Private state */
-
-typedef struct {
-  struct jpeg_marker_reader pub; /* public fields */
-
-  /* Application-overridable marker processing methods */
-  jpeg_marker_parser_method process_COM;
-  jpeg_marker_parser_method process_APPn[16];
-
-  /* Limit on marker data length to save for each marker type */
-  unsigned int length_limit_COM;
-  unsigned int length_limit_APPn[16];
-
-  /* Status of COM/APPn marker saving */
-  jpeg_saved_marker_ptr cur_marker;	/* NULL if not processing a marker */
-  unsigned int bytes_read;		/* data bytes read so far in marker */
-  /* Note: cur_marker is not linked into marker_list until it's all read. */
-} my_marker_reader;
-
-typedef my_marker_reader * my_marker_ptr;
-
-
-/*
- * Macros for fetching data from the data source module.
- *
- * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect
- * the current restart point; we update them only when we have reached a
- * suitable place to restart if a suspension occurs.
- */
-
-/* Declare and initialize local copies of input pointer/count */
-#define INPUT_VARS(cinfo)  \
-	struct jpeg_source_mgr * datasrc = (cinfo)->src;  \
-	const JOCTET * next_input_byte = datasrc->next_input_byte;  \
-	size_t bytes_in_buffer = datasrc->bytes_in_buffer
-
-/* Unload the local copies --- do this only at a restart boundary */
-#define INPUT_SYNC(cinfo)  \
-	( datasrc->next_input_byte = next_input_byte,  \
-	  datasrc->bytes_in_buffer = bytes_in_buffer )
-
-/* Reload the local copies --- used only in MAKE_BYTE_AVAIL */
-#define INPUT_RELOAD(cinfo)  \
-	( next_input_byte = datasrc->next_input_byte,  \
-	  bytes_in_buffer = datasrc->bytes_in_buffer )
-
-/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available.
- * Note we do *not* do INPUT_SYNC before calling fill_input_buffer,
- * but we must reload the local copies after a successful fill.
- */
-#define MAKE_BYTE_AVAIL(cinfo,action)  \
-	if (bytes_in_buffer == 0) {  \
-	  if (! (*datasrc->fill_input_buffer) (cinfo))  \
-	    { action; }  \
-	  INPUT_RELOAD(cinfo);  \
-	}
-
-/* Read a byte into variable V.
- * If must suspend, take the specified action (typically "return FALSE").
- */
-#define INPUT_BYTE(cinfo,V,action)  \
-	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
-		  bytes_in_buffer--; \
-		  V = GETJOCTET(*next_input_byte++); )
-
-/* As above, but read two bytes interpreted as an unsigned 16-bit integer.
- * V should be declared unsigned int or perhaps INT32.
- */
-#define INPUT_2BYTES(cinfo,V,action)  \
-	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
-		  bytes_in_buffer--; \
-		  V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \
-		  MAKE_BYTE_AVAIL(cinfo,action); \
-		  bytes_in_buffer--; \
-		  V += GETJOCTET(*next_input_byte++); )
-
-
-/*
- * Routines to process JPEG markers.
- *
- * Entry condition: JPEG marker itself has been read and its code saved
- *   in cinfo->unread_marker; input restart point is just after the marker.
- *
- * Exit: if return TRUE, have read and processed any parameters, and have
- *   updated the restart point to point after the parameters.
- *   If return FALSE, was forced to suspend before reaching end of
- *   marker parameters; restart point has not been moved.  Same routine
- *   will be called again after application supplies more input data.
- *
- * This approach to suspension assumes that all of a marker's parameters
- * can fit into a single input bufferload.  This should hold for "normal"
- * markers.  Some COM/APPn markers might have large parameter segments
- * that might not fit.  If we are simply dropping such a marker, we use
- * skip_input_data to get past it, and thereby put the problem on the
- * source manager's shoulders.  If we are saving the marker's contents
- * into memory, we use a slightly different convention: when forced to
- * suspend, the marker processor updates the restart point to the end of
- * what it's consumed (ie, the end of the buffer) before returning FALSE.
- * On resumption, cinfo->unread_marker still contains the marker code,
- * but the data source will point to the next chunk of marker data.
- * The marker processor must retain internal state to deal with this.
- *
- * Note that we don't bother to avoid duplicate trace messages if a
- * suspension occurs within marker parameters.  Other side effects
- * require more care.
- */
-
-
-LOCAL(boolean)
-get_soi (j_decompress_ptr cinfo)
-/* Process an SOI marker */
-{
-  int i;
-  
-  TRACEMS(cinfo, 1, JTRC_SOI);
-
-  if (cinfo->marker->saw_SOI)
-    ERREXIT(cinfo, JERR_SOI_DUPLICATE);
-
-  /* Reset all parameters that are defined to be reset by SOI */
-
-  for (i = 0; i < NUM_ARITH_TBLS; i++) {
-    cinfo->arith_dc_L[i] = 0;
-    cinfo->arith_dc_U[i] = 1;
-    cinfo->arith_ac_K[i] = 5;
-  }
-  cinfo->restart_interval = 0;
-
-  /* Set initial assumptions for colorspace etc */
-
-  cinfo->jpeg_color_space = JCS_UNKNOWN;
-  cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */
-
-  cinfo->saw_JFIF_marker = FALSE;
-  cinfo->JFIF_major_version = 1; /* set default JFIF APP0 values */
-  cinfo->JFIF_minor_version = 1;
-  cinfo->density_unit = 0;
-  cinfo->X_density = 1;
-  cinfo->Y_density = 1;
-  cinfo->saw_Adobe_marker = FALSE;
-  cinfo->Adobe_transform = 0;
-
-  cinfo->marker->saw_SOI = TRUE;
-
-  return TRUE;
-}
-
-
-LOCAL(boolean)
-get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith)
-/* Process a SOFn marker */
-{
-  INT32 length;
-  int c, ci;
-  jpeg_component_info * compptr;
-  INPUT_VARS(cinfo);
-
-  cinfo->progressive_mode = is_prog;
-  cinfo->arith_code = is_arith;
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-
-  INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE);
-  INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE);
-  INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE);
-  INPUT_BYTE(cinfo, cinfo->num_components, return FALSE);
-
-  length -= 8;
-
-  TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker,
-	   (int) cinfo->image_width, (int) cinfo->image_height,
-	   cinfo->num_components);
-
-  if (cinfo->marker->saw_SOF)
-    ERREXIT(cinfo, JERR_SOF_DUPLICATE);
-
-  /* We don't support files in which the image height is initially specified */
-  /* as 0 and is later redefined by DNL.  As long as we have to check that,  */
-  /* might as well have a general sanity check. */
-  if (cinfo->image_height <= 0 || cinfo->image_width <= 0
-      || cinfo->num_components <= 0)
-    ERREXIT(cinfo, JERR_EMPTY_IMAGE);
-
-  if (length != (cinfo->num_components * 3))
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  if (cinfo->comp_info == NULL)	/* do only once, even if suspend */
-    cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small)
-			((j_common_ptr) cinfo, JPOOL_IMAGE,
-			 cinfo->num_components * SIZEOF(jpeg_component_info));
-  
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    compptr->component_index = ci;
-    INPUT_BYTE(cinfo, compptr->component_id, return FALSE);
-    INPUT_BYTE(cinfo, c, return FALSE);
-    compptr->h_samp_factor = (c >> 4) & 15;
-    compptr->v_samp_factor = (c     ) & 15;
-    INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE);
-
-    TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT,
-	     compptr->component_id, compptr->h_samp_factor,
-	     compptr->v_samp_factor, compptr->quant_tbl_no);
-  }
-
-  cinfo->marker->saw_SOF = TRUE;
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-LOCAL(boolean)
-get_sos (j_decompress_ptr cinfo)
-/* Process a SOS marker */
-{
-  INT32 length;
-  int i, ci, n, c, cc;
-  jpeg_component_info * compptr;
-  INPUT_VARS(cinfo);
-
-  if (! cinfo->marker->saw_SOF)
-    ERREXIT(cinfo, JERR_SOS_NO_SOF);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-
-  INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */
-
-  TRACEMS1(cinfo, 1, JTRC_SOS, n);
-
-  if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN)
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  cinfo->comps_in_scan = n;
-
-  /* Collect the component-spec parameters */
-
-  for (i = 0; i < n; i++) {
-    INPUT_BYTE(cinfo, cc, return FALSE);
-    INPUT_BYTE(cinfo, c, return FALSE);
-    
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      if (cc == compptr->component_id)
-	goto id_found;
-    }
-
-    ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc);
-
-  id_found:
-
-    cinfo->cur_comp_info[i] = compptr;
-    compptr->dc_tbl_no = (c >> 4) & 15;
-    compptr->ac_tbl_no = (c     ) & 15;
-    
-    TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc,
-	     compptr->dc_tbl_no, compptr->ac_tbl_no);
-  }
-
-  /* Collect the additional scan parameters Ss, Se, Ah/Al. */
-  INPUT_BYTE(cinfo, c, return FALSE);
-  cinfo->Ss = c;
-  INPUT_BYTE(cinfo, c, return FALSE);
-  cinfo->Se = c;
-  INPUT_BYTE(cinfo, c, return FALSE);
-  cinfo->Ah = (c >> 4) & 15;
-  cinfo->Al = (c     ) & 15;
-
-  TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se,
-	   cinfo->Ah, cinfo->Al);
-
-  /* Prepare to scan data & restart markers */
-  cinfo->marker->next_restart_num = 0;
-
-  /* Count another SOS marker */
-  cinfo->input_scan_number++;
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-#ifdef D_ARITH_CODING_SUPPORTED
-
-LOCAL(boolean)
-get_dac (j_decompress_ptr cinfo)
-/* Process a DAC marker */
-{
-  INT32 length;
-  int index, val;
-  INPUT_VARS(cinfo);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-  length -= 2;
-  
-  while (length > 0) {
-    INPUT_BYTE(cinfo, index, return FALSE);
-    INPUT_BYTE(cinfo, val, return FALSE);
-
-    length -= 2;
-
-    TRACEMS2(cinfo, 1, JTRC_DAC, index, val);
-
-    if (index < 0 || index >= (2*NUM_ARITH_TBLS))
-      ERREXIT1(cinfo, JERR_DAC_INDEX, index);
-
-    if (index >= NUM_ARITH_TBLS) { /* define AC table */
-      cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val;
-    } else {			/* define DC table */
-      cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F);
-      cinfo->arith_dc_U[index] = (UINT8) (val >> 4);
-      if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index])
-	ERREXIT1(cinfo, JERR_DAC_VALUE, val);
-    }
-  }
-
-  if (length != 0)
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-#else /* ! D_ARITH_CODING_SUPPORTED */
-
-#define get_dac(cinfo)  skip_variable(cinfo)
-
-#endif /* D_ARITH_CODING_SUPPORTED */
-
-
-LOCAL(boolean)
-get_dht (j_decompress_ptr cinfo)
-/* Process a DHT marker */
-{
-  INT32 length;
-  UINT8 bits[17];
-  UINT8 huffval[256];
-  int i, index, count;
-  JHUFF_TBL **htblptr;
-  INPUT_VARS(cinfo);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-  length -= 2;
-  
-  while (length > 16) {
-    INPUT_BYTE(cinfo, index, return FALSE);
-
-    TRACEMS1(cinfo, 1, JTRC_DHT, index);
-      
-    bits[0] = 0;
-    count = 0;
-    for (i = 1; i <= 16; i++) {
-      INPUT_BYTE(cinfo, bits[i], return FALSE);
-      count += bits[i];
-    }
-
-    length -= 1 + 16;
-
-    TRACEMS8(cinfo, 2, JTRC_HUFFBITS,
-	     bits[1], bits[2], bits[3], bits[4],
-	     bits[5], bits[6], bits[7], bits[8]);
-    TRACEMS8(cinfo, 2, JTRC_HUFFBITS,
-	     bits[9], bits[10], bits[11], bits[12],
-	     bits[13], bits[14], bits[15], bits[16]);
-
-    /* Here we just do minimal validation of the counts to avoid walking
-     * off the end of our table space.  jdhuff.c will check more carefully.
-     */
-    if (count > 256 || ((INT32) count) > length)
-      ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
-
-    for (i = 0; i < count; i++)
-      INPUT_BYTE(cinfo, huffval[i], return FALSE);
-
-    length -= count;
-
-    if (index & 0x10) {		/* AC table definition */
-      index -= 0x10;
-      htblptr = &cinfo->ac_huff_tbl_ptrs[index];
-    } else {			/* DC table definition */
-      htblptr = &cinfo->dc_huff_tbl_ptrs[index];
-    }
-
-    if (index < 0 || index >= NUM_HUFF_TBLS)
-      ERREXIT1(cinfo, JERR_DHT_INDEX, index);
-
-    if (*htblptr == NULL)
-      *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo);
-  
-    MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits));
-    MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval));
-  }
-
-  if (length != 0)
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-LOCAL(boolean)
-get_dqt (j_decompress_ptr cinfo)
-/* Process a DQT marker */
-{
-  INT32 length;
-  int n, i, prec;
-  unsigned int tmp;
-  JQUANT_TBL *quant_ptr;
-  INPUT_VARS(cinfo);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-  length -= 2;
-
-  while (length > 0) {
-    INPUT_BYTE(cinfo, n, return FALSE);
-    prec = n >> 4;
-    n &= 0x0F;
-
-    TRACEMS2(cinfo, 1, JTRC_DQT, n, prec);
-
-    if (n >= NUM_QUANT_TBLS)
-      ERREXIT1(cinfo, JERR_DQT_INDEX, n);
-      
-    if (cinfo->quant_tbl_ptrs[n] == NULL)
-      cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo);
-    quant_ptr = cinfo->quant_tbl_ptrs[n];
-
-    for (i = 0; i < DCTSIZE2; i++) {
-      if (prec)
-	INPUT_2BYTES(cinfo, tmp, return FALSE);
-      else
-	INPUT_BYTE(cinfo, tmp, return FALSE);
-      /* We convert the zigzag-order table to natural array order. */
-      quant_ptr->quantval[jpeg_natural_order[i]] = (UINT16) tmp;
-    }
-
-    if (cinfo->err->trace_level >= 2) {
-      for (i = 0; i < DCTSIZE2; i += 8) {
-	TRACEMS8(cinfo, 2, JTRC_QUANTVALS,
-		 quant_ptr->quantval[i],   quant_ptr->quantval[i+1],
-		 quant_ptr->quantval[i+2], quant_ptr->quantval[i+3],
-		 quant_ptr->quantval[i+4], quant_ptr->quantval[i+5],
-		 quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]);
-      }
-    }
-
-    length -= DCTSIZE2+1;
-    if (prec) length -= DCTSIZE2;
-  }
-
-  if (length != 0)
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-LOCAL(boolean)
-get_dri (j_decompress_ptr cinfo)
-/* Process a DRI marker */
-{
-  INT32 length;
-  unsigned int tmp;
-  INPUT_VARS(cinfo);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-  
-  if (length != 4)
-    ERREXIT(cinfo, JERR_BAD_LENGTH);
-
-  INPUT_2BYTES(cinfo, tmp, return FALSE);
-
-  TRACEMS1(cinfo, 1, JTRC_DRI, tmp);
-
-  cinfo->restart_interval = tmp;
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-/*
- * Routines for processing APPn and COM markers.
- * These are either saved in memory or discarded, per application request.
- * APP0 and APP14 are specially checked to see if they are
- * JFIF and Adobe markers, respectively.
- */
-
-#define APP0_DATA_LEN	14	/* Length of interesting data in APP0 */
-#define APP14_DATA_LEN	12	/* Length of interesting data in APP14 */
-#define APPN_DATA_LEN	14	/* Must be the largest of the above!! */
-
-
-LOCAL(void)
-examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data,
-	      unsigned int datalen, INT32 remaining)
-/* Examine first few bytes from an APP0.
- * Take appropriate action if it is a JFIF marker.
- * datalen is # of bytes at data[], remaining is length of rest of marker data.
- */
-{
-  INT32 totallen = (INT32) datalen + remaining;
-
-  if (datalen >= APP0_DATA_LEN &&
-      GETJOCTET(data[0]) == 0x4A &&
-      GETJOCTET(data[1]) == 0x46 &&
-      GETJOCTET(data[2]) == 0x49 &&
-      GETJOCTET(data[3]) == 0x46 &&
-      GETJOCTET(data[4]) == 0) {
-    /* Found JFIF APP0 marker: save info */
-    cinfo->saw_JFIF_marker = TRUE;
-    cinfo->JFIF_major_version = GETJOCTET(data[5]);
-    cinfo->JFIF_minor_version = GETJOCTET(data[6]);
-    cinfo->density_unit = GETJOCTET(data[7]);
-    cinfo->X_density = (GETJOCTET(data[8]) << 8) + GETJOCTET(data[9]);
-    cinfo->Y_density = (GETJOCTET(data[10]) << 8) + GETJOCTET(data[11]);
-    /* Check version.
-     * Major version must be 1, anything else signals an incompatible change.
-     * (We used to treat this as an error, but now it's a nonfatal warning,
-     * because some bozo at Hijaak couldn't read the spec.)
-     * Minor version should be 0..2, but process anyway if newer.
-     */
-    if (cinfo->JFIF_major_version != 1)
-      WARNMS2(cinfo, JWRN_JFIF_MAJOR,
-	      cinfo->JFIF_major_version, cinfo->JFIF_minor_version);
-    /* Generate trace messages */
-    TRACEMS5(cinfo, 1, JTRC_JFIF,
-	     cinfo->JFIF_major_version, cinfo->JFIF_minor_version,
-	     cinfo->X_density, cinfo->Y_density, cinfo->density_unit);
-    /* Validate thumbnail dimensions and issue appropriate messages */
-    if (GETJOCTET(data[12]) | GETJOCTET(data[13]))
-      TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL,
-	       GETJOCTET(data[12]), GETJOCTET(data[13]));
-    totallen -= APP0_DATA_LEN;
-    if (totallen !=
-	((INT32)GETJOCTET(data[12]) * (INT32)GETJOCTET(data[13]) * (INT32) 3))
-      TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) totallen);
-  } else if (datalen >= 6 &&
-      GETJOCTET(data[0]) == 0x4A &&
-      GETJOCTET(data[1]) == 0x46 &&
-      GETJOCTET(data[2]) == 0x58 &&
-      GETJOCTET(data[3]) == 0x58 &&
-      GETJOCTET(data[4]) == 0) {
-    /* Found JFIF "JFXX" extension APP0 marker */
-    /* The library doesn't actually do anything with these,
-     * but we try to produce a helpful trace message.
-     */
-    switch (GETJOCTET(data[5])) {
-    case 0x10:
-      TRACEMS1(cinfo, 1, JTRC_THUMB_JPEG, (int) totallen);
-      break;
-    case 0x11:
-      TRACEMS1(cinfo, 1, JTRC_THUMB_PALETTE, (int) totallen);
-      break;
-    case 0x13:
-      TRACEMS1(cinfo, 1, JTRC_THUMB_RGB, (int) totallen);
-      break;
-    default:
-      TRACEMS2(cinfo, 1, JTRC_JFIF_EXTENSION,
-	       GETJOCTET(data[5]), (int) totallen);
-      break;
-    }
-  } else {
-    /* Start of APP0 does not match "JFIF" or "JFXX", or too short */
-    TRACEMS1(cinfo, 1, JTRC_APP0, (int) totallen);
-  }
-}
-
-
-LOCAL(void)
-examine_app14 (j_decompress_ptr cinfo, JOCTET FAR * data,
-	       unsigned int datalen, INT32 remaining)
-/* Examine first few bytes from an APP14.
- * Take appropriate action if it is an Adobe marker.
- * datalen is # of bytes at data[], remaining is length of rest of marker data.
- */
-{
-  unsigned int version, flags0, flags1, transform;
-
-  if (datalen >= APP14_DATA_LEN &&
-      GETJOCTET(data[0]) == 0x41 &&
-      GETJOCTET(data[1]) == 0x64 &&
-      GETJOCTET(data[2]) == 0x6F &&
-      GETJOCTET(data[3]) == 0x62 &&
-      GETJOCTET(data[4]) == 0x65) {
-    /* Found Adobe APP14 marker */
-    version = (GETJOCTET(data[5]) << 8) + GETJOCTET(data[6]);
-    flags0 = (GETJOCTET(data[7]) << 8) + GETJOCTET(data[8]);
-    flags1 = (GETJOCTET(data[9]) << 8) + GETJOCTET(data[10]);
-    transform = GETJOCTET(data[11]);
-    TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform);
-    cinfo->saw_Adobe_marker = TRUE;
-    cinfo->Adobe_transform = (UINT8) transform;
-  } else {
-    /* Start of APP14 does not match "Adobe", or too short */
-    TRACEMS1(cinfo, 1, JTRC_APP14, (int) (datalen + remaining));
-  }
-}
-
-
-METHODDEF(boolean)
-get_interesting_appn (j_decompress_ptr cinfo)
-/* Process an APP0 or APP14 marker without saving it */
-{
-  INT32 length;
-  JOCTET b[APPN_DATA_LEN];
-  unsigned int i, numtoread;
-  INPUT_VARS(cinfo);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-  length -= 2;
-
-  /* get the interesting part of the marker data */
-  if (length >= APPN_DATA_LEN)
-    numtoread = APPN_DATA_LEN;
-  else if (length > 0)
-    numtoread = (unsigned int) length;
-  else
-    numtoread = 0;
-  for (i = 0; i < numtoread; i++)
-    INPUT_BYTE(cinfo, b[i], return FALSE);
-  length -= numtoread;
-
-  /* process it */
-  switch (cinfo->unread_marker) {
-  case M_APP0:
-    examine_app0(cinfo, (JOCTET FAR *) b, numtoread, length);
-    break;
-  case M_APP14:
-    examine_app14(cinfo, (JOCTET FAR *) b, numtoread, length);
-    break;
-  default:
-    /* can't get here unless jpeg_save_markers chooses wrong processor */
-    ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker);
-    break;
-  }
-
-  /* skip any remaining data -- could be lots */
-  INPUT_SYNC(cinfo);
-  if (length > 0)
-    (*cinfo->src->skip_input_data) (cinfo, (long) length);
-
-  return TRUE;
-}
-
-
-#ifdef SAVE_MARKERS_SUPPORTED
-
-METHODDEF(boolean)
-save_marker (j_decompress_ptr cinfo)
-/* Save an APPn or COM marker into the marker list */
-{
-  my_marker_ptr marker = (my_marker_ptr) cinfo->marker;
-  jpeg_saved_marker_ptr cur_marker = marker->cur_marker;
-  unsigned int bytes_read, data_length;
-  JOCTET FAR * data;
-  INT32 length = 0;
-  INPUT_VARS(cinfo);
-
-  if (cur_marker == NULL) {
-    /* begin reading a marker */
-    INPUT_2BYTES(cinfo, length, return FALSE);
-    length -= 2;
-    if (length >= 0) {		/* watch out for bogus length word */
-      /* figure out how much we want to save */
-      unsigned int limit;
-      if (cinfo->unread_marker == (int) M_COM)
-	limit = marker->length_limit_COM;
-      else
-	limit = marker->length_limit_APPn[cinfo->unread_marker - (int) M_APP0];
-      if ((unsigned int) length < limit)
-	limit = (unsigned int) length;
-      /* allocate and initialize the marker item */
-      cur_marker = (jpeg_saved_marker_ptr)
-	(*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				    SIZEOF(struct jpeg_marker_struct) + limit);
-      cur_marker->next = NULL;
-      cur_marker->marker = (UINT8) cinfo->unread_marker;
-      cur_marker->original_length = (unsigned int) length;
-      cur_marker->data_length = limit;
-      /* data area is just beyond the jpeg_marker_struct */
-      data = cur_marker->data = (JOCTET FAR *) (cur_marker + 1);
-      marker->cur_marker = cur_marker;
-      marker->bytes_read = 0;
-      bytes_read = 0;
-      data_length = limit;
-    } else {
-      /* deal with bogus length word */
-      bytes_read = data_length = 0;
-      data = NULL;
-    }
-  } else {
-    /* resume reading a marker */
-    bytes_read = marker->bytes_read;
-    data_length = cur_marker->data_length;
-    data = cur_marker->data + bytes_read;
-  }
-
-  while (bytes_read < data_length) {
-    INPUT_SYNC(cinfo);		/* move the restart point to here */
-    marker->bytes_read = bytes_read;
-    /* If there's not at least one byte in buffer, suspend */
-    MAKE_BYTE_AVAIL(cinfo, return FALSE);
-    /* Copy bytes with reasonable rapidity */
-    while (bytes_read < data_length && bytes_in_buffer > 0) {
-      *data++ = *next_input_byte++;
-      bytes_in_buffer--;
-      bytes_read++;
-    }
-  }
-
-  /* Done reading what we want to read */
-  if (cur_marker != NULL) {	/* will be NULL if bogus length word */
-    /* Add new marker to end of list */
-    if (cinfo->marker_list == NULL) {
-      cinfo->marker_list = cur_marker;
-    } else {
-      jpeg_saved_marker_ptr prev = cinfo->marker_list;
-      while (prev->next != NULL)
-	prev = prev->next;
-      prev->next = cur_marker;
-    }
-    /* Reset pointer & calc remaining data length */
-    data = cur_marker->data;
-    length = cur_marker->original_length - data_length;
-  }
-  /* Reset to initial state for next marker */
-  marker->cur_marker = NULL;
-
-  /* Process the marker if interesting; else just make a generic trace msg */
-  switch (cinfo->unread_marker) {
-  case M_APP0:
-    examine_app0(cinfo, data, data_length, length);
-    break;
-  case M_APP14:
-    examine_app14(cinfo, data, data_length, length);
-    break;
-  default:
-    TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker,
-	     (int) (data_length + length));
-    break;
-  }
-
-  /* skip any remaining data -- could be lots */
-  INPUT_SYNC(cinfo);		/* do before skip_input_data */
-  if (length > 0)
-    (*cinfo->src->skip_input_data) (cinfo, (long) length);
-
-  return TRUE;
-}
-
-#endif /* SAVE_MARKERS_SUPPORTED */
-
-
-METHODDEF(boolean)
-skip_variable (j_decompress_ptr cinfo)
-/* Skip over an unknown or uninteresting variable-length marker */
-{
-  INT32 length;
-  INPUT_VARS(cinfo);
-
-  INPUT_2BYTES(cinfo, length, return FALSE);
-  length -= 2;
-  
-  TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length);
-
-  INPUT_SYNC(cinfo);		/* do before skip_input_data */
-  if (length > 0)
-    (*cinfo->src->skip_input_data) (cinfo, (long) length);
-
-  return TRUE;
-}
-
-
-/*
- * Find the next JPEG marker, save it in cinfo->unread_marker.
- * Returns FALSE if had to suspend before reaching a marker;
- * in that case cinfo->unread_marker is unchanged.
- *
- * Note that the result might not be a valid marker code,
- * but it will never be 0 or FF.
- */
-
-LOCAL(boolean)
-next_marker (j_decompress_ptr cinfo)
-{
-  int c;
-  INPUT_VARS(cinfo);
-
-  for (;;) {
-    INPUT_BYTE(cinfo, c, return FALSE);
-    /* Skip any non-FF bytes.
-     * This may look a bit inefficient, but it will not occur in a valid file.
-     * We sync after each discarded byte so that a suspending data source
-     * can discard the byte from its buffer.
-     */
-    while (c != 0xFF) {
-      cinfo->marker->discarded_bytes++;
-      INPUT_SYNC(cinfo);
-      INPUT_BYTE(cinfo, c, return FALSE);
-    }
-    /* This loop swallows any duplicate FF bytes.  Extra FFs are legal as
-     * pad bytes, so don't count them in discarded_bytes.  We assume there
-     * will not be so many consecutive FF bytes as to overflow a suspending
-     * data source's input buffer.
-     */
-    do {
-      INPUT_BYTE(cinfo, c, return FALSE);
-    } while (c == 0xFF);
-    if (c != 0)
-      break;			/* found a valid marker, exit loop */
-    /* Reach here if we found a stuffed-zero data sequence (FF/00).
-     * Discard it and loop back to try again.
-     */
-    cinfo->marker->discarded_bytes += 2;
-    INPUT_SYNC(cinfo);
-  }
-
-  if (cinfo->marker->discarded_bytes != 0) {
-    WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c);
-    cinfo->marker->discarded_bytes = 0;
-  }
-
-  cinfo->unread_marker = c;
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-LOCAL(boolean)
-first_marker (j_decompress_ptr cinfo)
-/* Like next_marker, but used to obtain the initial SOI marker. */
-/* For this marker, we do not allow preceding garbage or fill; otherwise,
- * we might well scan an entire input file before realizing it ain't JPEG.
- * If an application wants to process non-JFIF files, it must seek to the
- * SOI before calling the JPEG library.
- */
-{
-  int c, c2;
-  INPUT_VARS(cinfo);
-
-  INPUT_BYTE(cinfo, c, return FALSE);
-  INPUT_BYTE(cinfo, c2, return FALSE);
-  if (c != 0xFF || c2 != (int) M_SOI)
-    ERREXIT2(cinfo, JERR_NO_SOI, c, c2);
-
-  cinfo->unread_marker = c2;
-
-  INPUT_SYNC(cinfo);
-  return TRUE;
-}
-
-
-/*
- * Read markers until SOS or EOI.
- *
- * Returns same codes as are defined for jpeg_consume_input:
- * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI.
- */
-
-METHODDEF(int)
-read_markers (j_decompress_ptr cinfo)
-{
-  /* Outer loop repeats once for each marker. */
-  for (;;) {
-    /* Collect the marker proper, unless we already did. */
-    /* NB: first_marker() enforces the requirement that SOI appear first. */
-    if (cinfo->unread_marker == 0) {
-      if (! cinfo->marker->saw_SOI) {
-	if (! first_marker(cinfo))
-	  return JPEG_SUSPENDED;
-      } else {
-	if (! next_marker(cinfo))
-	  return JPEG_SUSPENDED;
-      }
-    }
-    /* At this point cinfo->unread_marker contains the marker code and the
-     * input point is just past the marker proper, but before any parameters.
-     * A suspension will cause us to return with this state still true.
-     */
-    switch (cinfo->unread_marker) {
-    case M_SOI:
-      if (! get_soi(cinfo))
-	return JPEG_SUSPENDED;
-      break;
-
-    case M_SOF0:		/* Baseline */
-    case M_SOF1:		/* Extended sequential, Huffman */
-      if (! get_sof(cinfo, FALSE, FALSE))
-	return JPEG_SUSPENDED;
-      break;
-
-    case M_SOF2:		/* Progressive, Huffman */
-      if (! get_sof(cinfo, TRUE, FALSE))
-	return JPEG_SUSPENDED;
-      break;
-
-    case M_SOF9:		/* Extended sequential, arithmetic */
-      if (! get_sof(cinfo, FALSE, TRUE))
-	return JPEG_SUSPENDED;
-      break;
-
-    case M_SOF10:		/* Progressive, arithmetic */
-      if (! get_sof(cinfo, TRUE, TRUE))
-	return JPEG_SUSPENDED;
-      break;
-
-    /* Currently unsupported SOFn types */
-    case M_SOF3:		/* Lossless, Huffman */
-    case M_SOF5:		/* Differential sequential, Huffman */
-    case M_SOF6:		/* Differential progressive, Huffman */
-    case M_SOF7:		/* Differential lossless, Huffman */
-    case M_JPG:			/* Reserved for JPEG extensions */
-    case M_SOF11:		/* Lossless, arithmetic */
-    case M_SOF13:		/* Differential sequential, arithmetic */
-    case M_SOF14:		/* Differential progressive, arithmetic */
-    case M_SOF15:		/* Differential lossless, arithmetic */
-      ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker);
-      break;
-
-    case M_SOS:
-      if (! get_sos(cinfo))
-	return JPEG_SUSPENDED;
-      cinfo->unread_marker = 0;	/* processed the marker */
-      return JPEG_REACHED_SOS;
-    
-    case M_EOI:
-      TRACEMS(cinfo, 1, JTRC_EOI);
-      cinfo->unread_marker = 0;	/* processed the marker */
-      return JPEG_REACHED_EOI;
-      
-    case M_DAC:
-      if (! get_dac(cinfo))
-	return JPEG_SUSPENDED;
-      break;
-      
-    case M_DHT:
-      if (! get_dht(cinfo))
-	return JPEG_SUSPENDED;
-      break;
-      
-    case M_DQT:
-      if (! get_dqt(cinfo))
-	return JPEG_SUSPENDED;
-      break;
-      
-    case M_DRI:
-      if (! get_dri(cinfo))
-	return JPEG_SUSPENDED;
-      break;
-      
-    case M_APP0:
-    case M_APP1:
-    case M_APP2:
-    case M_APP3:
-    case M_APP4:
-    case M_APP5:
-    case M_APP6:
-    case M_APP7:
-    case M_APP8:
-    case M_APP9:
-    case M_APP10:
-    case M_APP11:
-    case M_APP12:
-    case M_APP13:
-    case M_APP14:
-    case M_APP15:
-      if (! (*((my_marker_ptr) cinfo->marker)->process_APPn[
-		cinfo->unread_marker - (int) M_APP0]) (cinfo))
-	return JPEG_SUSPENDED;
-      break;
-      
-    case M_COM:
-      if (! (*((my_marker_ptr) cinfo->marker)->process_COM) (cinfo))
-	return JPEG_SUSPENDED;
-      break;
-
-    case M_RST0:		/* these are all parameterless */
-    case M_RST1:
-    case M_RST2:
-    case M_RST3:
-    case M_RST4:
-    case M_RST5:
-    case M_RST6:
-    case M_RST7:
-    case M_TEM:
-      TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker);
-      break;
-
-    case M_DNL:			/* Ignore DNL ... perhaps the wrong thing */
-      if (! skip_variable(cinfo))
-	return JPEG_SUSPENDED;
-      break;
-
-    default:			/* must be DHP, EXP, JPGn, or RESn */
-      /* For now, we treat the reserved markers as fatal errors since they are
-       * likely to be used to signal incompatible JPEG Part 3 extensions.
-       * Once the JPEG 3 version-number marker is well defined, this code
-       * ought to change!
-       */
-      ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker);
-      break;
-    }
-    /* Successfully processed marker, so reset state variable */
-    cinfo->unread_marker = 0;
-  } /* end loop */
-}
-
-
-/*
- * Read a restart marker, which is expected to appear next in the datastream;
- * if the marker is not there, take appropriate recovery action.
- * Returns FALSE if suspension is required.
- *
- * This is called by the entropy decoder after it has read an appropriate
- * number of MCUs.  cinfo->unread_marker may be nonzero if the entropy decoder
- * has already read a marker from the data source.  Under normal conditions
- * cinfo->unread_marker will be reset to 0 before returning; if not reset,
- * it holds a marker which the decoder will be unable to read past.
- */
-
-METHODDEF(boolean)
-read_restart_marker (j_decompress_ptr cinfo)
-{
-  /* Obtain a marker unless we already did. */
-  /* Note that next_marker will complain if it skips any data. */
-  if (cinfo->unread_marker == 0) {
-    if (! next_marker(cinfo))
-      return FALSE;
-  }
-
-  if (cinfo->unread_marker ==
-      ((int) M_RST0 + cinfo->marker->next_restart_num)) {
-    /* Normal case --- swallow the marker and let entropy decoder continue */
-    TRACEMS1(cinfo, 3, JTRC_RST, cinfo->marker->next_restart_num);
-    cinfo->unread_marker = 0;
-  } else {
-    /* Uh-oh, the restart markers have been messed up. */
-    /* Let the data source manager determine how to resync. */
-    if (! (*cinfo->src->resync_to_restart) (cinfo,
-					    cinfo->marker->next_restart_num))
-      return FALSE;
-  }
-
-  /* Update next-restart state */
-  cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7;
-
-  return TRUE;
-}
-
-
-/*
- * This is the default resync_to_restart method for data source managers
- * to use if they don't have any better approach.  Some data source managers
- * may be able to back up, or may have additional knowledge about the data
- * which permits a more intelligent recovery strategy; such managers would
- * presumably supply their own resync method.
- *
- * read_restart_marker calls resync_to_restart if it finds a marker other than
- * the restart marker it was expecting.  (This code is *not* used unless
- * a nonzero restart interval has been declared.)  cinfo->unread_marker is
- * the marker code actually found (might be anything, except 0 or FF).
- * The desired restart marker number (0..7) is passed as a parameter.
- * This routine is supposed to apply whatever error recovery strategy seems
- * appropriate in order to position the input stream to the next data segment.
- * Note that cinfo->unread_marker is treated as a marker appearing before
- * the current data-source input point; usually it should be reset to zero
- * before returning.
- * Returns FALSE if suspension is required.
- *
- * This implementation is substantially constrained by wanting to treat the
- * input as a data stream; this means we can't back up.  Therefore, we have
- * only the following actions to work with:
- *   1. Simply discard the marker and let the entropy decoder resume at next
- *      byte of file.
- *   2. Read forward until we find another marker, discarding intervening
- *      data.  (In theory we could look ahead within the current bufferload,
- *      without having to discard data if we don't find the desired marker.
- *      This idea is not implemented here, in part because it makes behavior
- *      dependent on buffer size and chance buffer-boundary positions.)
- *   3. Leave the marker unread (by failing to zero cinfo->unread_marker).
- *      This will cause the entropy decoder to process an empty data segment,
- *      inserting dummy zeroes, and then we will reprocess the marker.
- *
- * #2 is appropriate if we think the desired marker lies ahead, while #3 is
- * appropriate if the found marker is a future restart marker (indicating
- * that we have missed the desired restart marker, probably because it got
- * corrupted).
- * We apply #2 or #3 if the found marker is a restart marker no more than
- * two counts behind or ahead of the expected one.  We also apply #2 if the
- * found marker is not a legal JPEG marker code (it's certainly bogus data).
- * If the found marker is a restart marker more than 2 counts away, we do #1
- * (too much risk that the marker is erroneous; with luck we will be able to
- * resync at some future point).
- * For any valid non-restart JPEG marker, we apply #3.  This keeps us from
- * overrunning the end of a scan.  An implementation limited to single-scan
- * files might find it better to apply #2 for markers other than EOI, since
- * any other marker would have to be bogus data in that case.
- */
-
-GLOBAL(boolean)
-jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired)
-{
-  int marker = cinfo->unread_marker;
-  int action = 1;
-  
-  /* Always put up a warning. */
-  WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired);
-  
-  /* Outer loop handles repeated decision after scanning forward. */
-  for (;;) {
-    if (marker < (int) M_SOF0)
-      action = 2;		/* invalid marker */
-    else if (marker < (int) M_RST0 || marker > (int) M_RST7)
-      action = 3;		/* valid non-restart marker */
-    else {
-      if (marker == ((int) M_RST0 + ((desired+1) & 7)) ||
-	  marker == ((int) M_RST0 + ((desired+2) & 7)))
-	action = 3;		/* one of the next two expected restarts */
-      else if (marker == ((int) M_RST0 + ((desired-1) & 7)) ||
-	       marker == ((int) M_RST0 + ((desired-2) & 7)))
-	action = 2;		/* a prior restart, so advance */
-      else
-	action = 1;		/* desired restart or too far away */
-    }
-    TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action);
-    switch (action) {
-    case 1:
-      /* Discard marker and let entropy decoder resume processing. */
-      cinfo->unread_marker = 0;
-      return TRUE;
-    case 2:
-      /* Scan to the next marker, and repeat the decision loop. */
-      if (! next_marker(cinfo))
-	return FALSE;
-      marker = cinfo->unread_marker;
-      break;
-    case 3:
-      /* Return without advancing past this marker. */
-      /* Entropy decoder will be forced to process an empty segment. */
-      return TRUE;
-    }
-  } /* end loop */
-}
-
-
-/*
- * Reset marker processing state to begin a fresh datastream.
- */
-
-METHODDEF(void)
-reset_marker_reader (j_decompress_ptr cinfo)
-{
-  my_marker_ptr marker = (my_marker_ptr) cinfo->marker;
-
-  cinfo->comp_info = NULL;		/* until allocated by get_sof */
-  cinfo->input_scan_number = 0;		/* no SOS seen yet */
-  cinfo->unread_marker = 0;		/* no pending marker */
-  marker->pub.saw_SOI = FALSE;		/* set internal state too */
-  marker->pub.saw_SOF = FALSE;
-  marker->pub.discarded_bytes = 0;
-  marker->cur_marker = NULL;
-}
-
-
-/*
- * Initialize the marker reader module.
- * This is called only once, when the decompression object is created.
- */
-
-GLOBAL(void)
-jinit_marker_reader (j_decompress_ptr cinfo)
-{
-  my_marker_ptr marker;
-  int i;
-
-  /* Create subobject in permanent pool */
-  marker = (my_marker_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
-				SIZEOF(my_marker_reader));
-  cinfo->marker = (struct jpeg_marker_reader *) marker;
-  /* Initialize public method pointers */
-  marker->pub.reset_marker_reader = reset_marker_reader;
-  marker->pub.read_markers = read_markers;
-  marker->pub.read_restart_marker = read_restart_marker;
-  /* Initialize COM/APPn processing.
-   * By default, we examine and then discard APP0 and APP14,
-   * but simply discard COM and all other APPn.
-   */
-  marker->process_COM = skip_variable;
-  marker->length_limit_COM = 0;
-  for (i = 0; i < 16; i++) {
-    marker->process_APPn[i] = skip_variable;
-    marker->length_limit_APPn[i] = 0;
-  }
-  marker->process_APPn[0] = get_interesting_appn;
-  marker->process_APPn[14] = get_interesting_appn;
-  /* Reset marker processing state */
-  reset_marker_reader(cinfo);
-}
-
-
-/*
- * Control saving of COM and APPn markers into marker_list.
- */
-
-#ifdef SAVE_MARKERS_SUPPORTED
-
-GLOBAL(void)
-jpeg_save_markers (j_decompress_ptr cinfo, int marker_code,
-		   unsigned int length_limit)
-{
-  my_marker_ptr marker = (my_marker_ptr) cinfo->marker;
-  long maxlength;
-  jpeg_marker_parser_method processor;
-
-  /* Length limit mustn't be larger than what we can allocate
-   * (should only be a concern in a 16-bit environment).
-   */
-  maxlength = cinfo->mem->max_alloc_chunk - SIZEOF(struct jpeg_marker_struct);
-  if (((long) length_limit) > maxlength)
-    length_limit = (unsigned int) maxlength;
-
-  /* Choose processor routine to use.
-   * APP0/APP14 have special requirements.
-   */
-  if (length_limit) {
-    processor = save_marker;
-    /* If saving APP0/APP14, save at least enough for our internal use. */
-    if (marker_code == (int) M_APP0 && length_limit < APP0_DATA_LEN)
-      length_limit = APP0_DATA_LEN;
-    else if (marker_code == (int) M_APP14 && length_limit < APP14_DATA_LEN)
-      length_limit = APP14_DATA_LEN;
-  } else {
-    processor = skip_variable;
-    /* If discarding APP0/APP14, use our regular on-the-fly processor. */
-    if (marker_code == (int) M_APP0 || marker_code == (int) M_APP14)
-      processor = get_interesting_appn;
-  }
-
-  if (marker_code == (int) M_COM) {
-    marker->process_COM = processor;
-    marker->length_limit_COM = length_limit;
-  } else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) {
-    marker->process_APPn[marker_code - (int) M_APP0] = processor;
-    marker->length_limit_APPn[marker_code - (int) M_APP0] = length_limit;
-  } else
-    ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code);
-}
-
-#endif /* SAVE_MARKERS_SUPPORTED */
-
-
-/*
- * Install a special processing method for COM or APPn markers.
- */
-
-GLOBAL(void)
-jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code,
-			   jpeg_marker_parser_method routine)
-{
-  my_marker_ptr marker = (my_marker_ptr) cinfo->marker;
-
-  if (marker_code == (int) M_COM)
-    marker->process_COM = routine;
-  else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15)
-    marker->process_APPn[marker_code - (int) M_APP0] = routine;
-  else
-    ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code);
-}
diff --git a/third_party/libjpeg/jdmaster.c b/third_party/libjpeg/jdmaster.c
deleted file mode 100644
index 2802c5b..0000000
--- a/third_party/libjpeg/jdmaster.c
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * jdmaster.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains master control logic for the JPEG decompressor.
- * These routines are concerned with selecting the modules to be executed
- * and with determining the number of passes and the work to be done in each
- * pass.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Private state */
-
-typedef struct {
-  struct jpeg_decomp_master pub; /* public fields */
-
-  int pass_number;		/* # of passes completed */
-
-  boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */
-
-  /* Saved references to initialized quantizer modules,
-   * in case we need to switch modes.
-   */
-  struct jpeg_color_quantizer * quantizer_1pass;
-  struct jpeg_color_quantizer * quantizer_2pass;
-} my_decomp_master;
-
-typedef my_decomp_master * my_master_ptr;
-
-
-/*
- * Determine whether merged upsample/color conversion should be used.
- * CRUCIAL: this must match the actual capabilities of jdmerge.c!
- */
-
-LOCAL(boolean)
-use_merged_upsample (j_decompress_ptr cinfo)
-{
-#ifdef UPSAMPLE_MERGING_SUPPORTED
-  /* Merging is the equivalent of plain box-filter upsampling */
-  if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling)
-    return FALSE;
-  /* jdmerge.c only supports YCC=>RGB color conversion */
-  if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 ||
-      cinfo->out_color_space != JCS_RGB ||
-      cinfo->out_color_components != RGB_PIXELSIZE)
-    return FALSE;
-  /* and it only handles 2h1v or 2h2v sampling ratios */
-  if (cinfo->comp_info[0].h_samp_factor != 2 ||
-      cinfo->comp_info[1].h_samp_factor != 1 ||
-      cinfo->comp_info[2].h_samp_factor != 1 ||
-      cinfo->comp_info[0].v_samp_factor >  2 ||
-      cinfo->comp_info[1].v_samp_factor != 1 ||
-      cinfo->comp_info[2].v_samp_factor != 1)
-    return FALSE;
-  /* furthermore, it doesn't work if we've scaled the IDCTs differently */
-  if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size ||
-      cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size ||
-      cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size)
-    return FALSE;
-  /* ??? also need to test for upsample-time rescaling, when & if supported */
-  return TRUE;			/* by golly, it'll work... */
-#else
-  return FALSE;
-#endif
-}
-
-
-/*
- * Compute output image dimensions and related values.
- * NOTE: this is exported for possible use by application.
- * Hence it mustn't do anything that can't be done twice.
- * Also note that it may be called before the master module is initialized!
- */
-
-GLOBAL(void)
-jpeg_calc_output_dimensions (j_decompress_ptr cinfo)
-/* Do computations that are needed before master selection phase */
-{
-#ifdef IDCT_SCALING_SUPPORTED
-  int ci;
-  jpeg_component_info *compptr;
-#endif
-
-  /* Prevent application from calling me at wrong times */
-  if (cinfo->global_state != DSTATE_READY)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-#ifdef IDCT_SCALING_SUPPORTED
-
-  /* Compute actual output image dimensions and DCT scaling choices. */
-  if (cinfo->scale_num * 8 <= cinfo->scale_denom) {
-    /* Provide 1/8 scaling */
-    cinfo->output_width = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width, 8L);
-    cinfo->output_height = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height, 8L);
-    cinfo->min_DCT_scaled_size = 1;
-  } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) {
-    /* Provide 1/4 scaling */
-    cinfo->output_width = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width, 4L);
-    cinfo->output_height = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height, 4L);
-    cinfo->min_DCT_scaled_size = 2;
-  } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) {
-    /* Provide 1/2 scaling */
-    cinfo->output_width = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width, 2L);
-    cinfo->output_height = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height, 2L);
-    cinfo->min_DCT_scaled_size = 4;
-  } else {
-    /* Provide 1/1 scaling */
-    cinfo->output_width = cinfo->image_width;
-    cinfo->output_height = cinfo->image_height;
-    cinfo->min_DCT_scaled_size = DCTSIZE;
-  }
-  /* In selecting the actual DCT scaling for each component, we try to
-   * scale up the chroma components via IDCT scaling rather than upsampling.
-   * This saves time if the upsampler gets to use 1:1 scaling.
-   * Note this code assumes that the supported DCT scalings are powers of 2.
-   */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    int ssize = cinfo->min_DCT_scaled_size;
-    while (ssize < DCTSIZE &&
-	   (compptr->h_samp_factor * ssize * 2 <=
-	    cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) &&
-	   (compptr->v_samp_factor * ssize * 2 <=
-	    cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) {
-      ssize = ssize * 2;
-    }
-    compptr->DCT_scaled_size = ssize;
-  }
-
-  /* Recompute downsampled dimensions of components;
-   * application needs to know these if using raw downsampled data.
-   */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Size in samples, after IDCT scaling */
-    compptr->downsampled_width = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_width *
-		    (long) (compptr->h_samp_factor * compptr->DCT_scaled_size),
-		    (long) (cinfo->max_h_samp_factor * DCTSIZE));
-    compptr->downsampled_height = (JDIMENSION)
-      jdiv_round_up((long) cinfo->image_height *
-		    (long) (compptr->v_samp_factor * compptr->DCT_scaled_size),
-		    (long) (cinfo->max_v_samp_factor * DCTSIZE));
-  }
-
-#else /* !IDCT_SCALING_SUPPORTED */
-
-  /* Hardwire it to "no scaling" */
-  cinfo->output_width = cinfo->image_width;
-  cinfo->output_height = cinfo->image_height;
-  /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE,
-   * and has computed unscaled downsampled_width and downsampled_height.
-   */
-
-#endif /* IDCT_SCALING_SUPPORTED */
-
-  /* Report number of components in selected colorspace. */
-  /* Probably this should be in the color conversion module... */
-  switch (cinfo->out_color_space) {
-  case JCS_GRAYSCALE:
-    cinfo->out_color_components = 1;
-    break;
-  case JCS_RGB:
-#if RGB_PIXELSIZE != 3
-    cinfo->out_color_components = RGB_PIXELSIZE;
-    break;
-#endif /* else share code with YCbCr */
-  case JCS_YCbCr:
-    cinfo->out_color_components = 3;
-    break;
-  case JCS_CMYK:
-  case JCS_YCCK:
-    cinfo->out_color_components = 4;
-    break;
-  default:			/* else must be same colorspace as in file */
-    cinfo->out_color_components = cinfo->num_components;
-    break;
-  }
-  cinfo->output_components = (cinfo->quantize_colors ? 1 :
-			      cinfo->out_color_components);
-
-  /* See if upsampler will want to emit more than one row at a time */
-  if (use_merged_upsample(cinfo))
-    cinfo->rec_outbuf_height = cinfo->max_v_samp_factor;
-  else
-    cinfo->rec_outbuf_height = 1;
-}
-
-
-/*
- * Several decompression processes need to range-limit values to the range
- * 0..MAXJSAMPLE; the input value may fall somewhat outside this range
- * due to noise introduced by quantization, roundoff error, etc.  These
- * processes are inner loops and need to be as fast as possible.  On most
- * machines, particularly CPUs with pipelines or instruction prefetch,
- * a (subscript-check-less) C table lookup
- *		x = sample_range_limit[x];
- * is faster than explicit tests
- *		if (x < 0)  x = 0;
- *		else if (x > MAXJSAMPLE)  x = MAXJSAMPLE;
- * These processes all use a common table prepared by the routine below.
- *
- * For most steps we can mathematically guarantee that the initial value
- * of x is within MAXJSAMPLE+1 of the legal range, so a table running from
- * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient.  But for the initial
- * limiting step (just after the IDCT), a wildly out-of-range value is 
- * possible if the input data is corrupt.  To avoid any chance of indexing
- * off the end of memory and getting a bad-pointer trap, we perform the
- * post-IDCT limiting thus:
- *		x = range_limit[x & MASK];
- * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit
- * samples.  Under normal circumstances this is more than enough range and
- * a correct output will be generated; with bogus input data the mask will
- * cause wraparound, and we will safely generate a bogus-but-in-range output.
- * For the post-IDCT step, we want to convert the data from signed to unsigned
- * representation by adding CENTERJSAMPLE at the same time that we limit it.
- * So the post-IDCT limiting table ends up looking like this:
- *   CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE,
- *   MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times),
- *   0          (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times),
- *   0,1,...,CENTERJSAMPLE-1
- * Negative inputs select values from the upper half of the table after
- * masking.
- *
- * We can save some space by overlapping the start of the post-IDCT table
- * with the simpler range limiting table.  The post-IDCT table begins at
- * sample_range_limit + CENTERJSAMPLE.
- *
- * Note that the table is allocated in near data space on PCs; it's small
- * enough and used often enough to justify this.
- */
-
-LOCAL(void)
-prepare_range_limit_table (j_decompress_ptr cinfo)
-/* Allocate and fill in the sample_range_limit table */
-{
-  JSAMPLE * table;
-  int i;
-
-  table = (JSAMPLE *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-		(5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE));
-  table += (MAXJSAMPLE+1);	/* allow negative subscripts of simple table */
-  cinfo->sample_range_limit = table;
-  /* First segment of "simple" table: limit[x] = 0 for x < 0 */
-  MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE));
-  /* Main part of "simple" table: limit[x] = x */
-  for (i = 0; i <= MAXJSAMPLE; i++)
-    table[i] = (JSAMPLE) i;
-  table += CENTERJSAMPLE;	/* Point to where post-IDCT table starts */
-  /* End of simple table, rest of first half of post-IDCT table */
-  for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++)
-    table[i] = MAXJSAMPLE;
-  /* Second half of post-IDCT table */
-  MEMZERO(table + (2 * (MAXJSAMPLE+1)),
-	  (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE));
-  MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE),
-	  cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE));
-}
-
-
-/*
- * Master selection of decompression modules.
- * This is done once at jpeg_start_decompress time.  We determine
- * which modules will be used and give them appropriate initialization calls.
- * We also initialize the decompressor input side to begin consuming data.
- *
- * Since jpeg_read_header has finished, we know what is in the SOF
- * and (first) SOS markers.  We also have all the application parameter
- * settings.
- */
-
-LOCAL(void)
-master_selection (j_decompress_ptr cinfo)
-{
-  my_master_ptr master = (my_master_ptr) cinfo->master;
-  boolean use_c_buffer;
-  long samplesperrow;
-  JDIMENSION jd_samplesperrow;
-
-  /* Initialize dimensions and other stuff */
-  jpeg_calc_output_dimensions(cinfo);
-  prepare_range_limit_table(cinfo);
-
-  /* Width of an output scanline must be representable as JDIMENSION. */
-  samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components;
-  jd_samplesperrow = (JDIMENSION) samplesperrow;
-  if ((long) jd_samplesperrow != samplesperrow)
-    ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
-
-  /* Initialize my private state */
-  master->pass_number = 0;
-  master->using_merged_upsample = use_merged_upsample(cinfo);
-
-  /* Color quantizer selection */
-  master->quantizer_1pass = NULL;
-  master->quantizer_2pass = NULL;
-  /* No mode changes if not using buffered-image mode. */
-  if (! cinfo->quantize_colors || ! cinfo->buffered_image) {
-    cinfo->enable_1pass_quant = FALSE;
-    cinfo->enable_external_quant = FALSE;
-    cinfo->enable_2pass_quant = FALSE;
-  }
-  if (cinfo->quantize_colors) {
-    if (cinfo->raw_data_out)
-      ERREXIT(cinfo, JERR_NOTIMPL);
-    /* 2-pass quantizer only works in 3-component color space. */
-    if (cinfo->out_color_components != 3) {
-      cinfo->enable_1pass_quant = TRUE;
-      cinfo->enable_external_quant = FALSE;
-      cinfo->enable_2pass_quant = FALSE;
-      cinfo->colormap = NULL;
-    } else if (cinfo->colormap != NULL) {
-      cinfo->enable_external_quant = TRUE;
-    } else if (cinfo->two_pass_quantize) {
-      cinfo->enable_2pass_quant = TRUE;
-    } else {
-      cinfo->enable_1pass_quant = TRUE;
-    }
-
-    if (cinfo->enable_1pass_quant) {
-#ifdef QUANT_1PASS_SUPPORTED
-      jinit_1pass_quantizer(cinfo);
-      master->quantizer_1pass = cinfo->cquantize;
-#else
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-    }
-
-    /* We use the 2-pass code to map to external colormaps. */
-    if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) {
-#ifdef QUANT_2PASS_SUPPORTED
-      jinit_2pass_quantizer(cinfo);
-      master->quantizer_2pass = cinfo->cquantize;
-#else
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-    }
-    /* If both quantizers are initialized, the 2-pass one is left active;
-     * this is necessary for starting with quantization to an external map.
-     */
-  }
-
-  /* Post-processing: in particular, color conversion first */
-  if (! cinfo->raw_data_out) {
-    if (master->using_merged_upsample) {
-#ifdef UPSAMPLE_MERGING_SUPPORTED
-      jinit_merged_upsampler(cinfo); /* does color conversion too */
-#else
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-    } else {
-      jinit_color_deconverter(cinfo);
-      jinit_upsampler(cinfo);
-    }
-    jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant);
-  }
-  /* Inverse DCT */
-  jinit_inverse_dct(cinfo);
-  /* Entropy decoding: either Huffman or arithmetic coding. */
-  if (cinfo->arith_code) {
-    ERREXIT(cinfo, JERR_ARITH_NOTIMPL);
-  } else {
-    if (cinfo->progressive_mode) {
-#ifdef D_PROGRESSIVE_SUPPORTED
-      jinit_phuff_decoder(cinfo);
-#else
-      ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif
-    } else
-      jinit_huff_decoder(cinfo);
-  }
-
-  /* Initialize principal buffer controllers. */
-  use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image;
-  jinit_d_coef_controller(cinfo, use_c_buffer);
-
-  if (! cinfo->raw_data_out)
-    jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */);
-
-  /* We can now tell the memory manager to allocate virtual arrays. */
-  (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo);
-
-  /* Initialize input side of decompressor to consume first scan. */
-  (*cinfo->inputctl->start_input_pass) (cinfo);
-
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-  /* If jpeg_start_decompress will read the whole file, initialize
-   * progress monitoring appropriately.  The input step is counted
-   * as one pass.
-   */
-  if (cinfo->progress != NULL && ! cinfo->buffered_image &&
-      cinfo->inputctl->has_multiple_scans) {
-    int nscans;
-    /* Estimate number of scans to set pass_limit. */
-    if (cinfo->progressive_mode) {
-      /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */
-      nscans = 2 + 3 * cinfo->num_components;
-    } else {
-      /* For a nonprogressive multiscan file, estimate 1 scan per component. */
-      nscans = cinfo->num_components;
-    }
-    cinfo->progress->pass_counter = 0L;
-    cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans;
-    cinfo->progress->completed_passes = 0;
-    cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2);
-    /* Count the input pass as done */
-    master->pass_number++;
-  }
-#endif /* D_MULTISCAN_FILES_SUPPORTED */
-}
-
-
-/*
- * Per-pass setup.
- * This is called at the beginning of each output pass.  We determine which
- * modules will be active during this pass and give them appropriate
- * start_pass calls.  We also set is_dummy_pass to indicate whether this
- * is a "real" output pass or a dummy pass for color quantization.
- * (In the latter case, jdapistd.c will crank the pass to completion.)
- */
-
-METHODDEF(void)
-prepare_for_output_pass (j_decompress_ptr cinfo)
-{
-  my_master_ptr master = (my_master_ptr) cinfo->master;
-
-  if (master->pub.is_dummy_pass) {
-#ifdef QUANT_2PASS_SUPPORTED
-    /* Final pass of 2-pass quantization */
-    master->pub.is_dummy_pass = FALSE;
-    (*cinfo->cquantize->start_pass) (cinfo, FALSE);
-    (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST);
-    (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST);
-#else
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-#endif /* QUANT_2PASS_SUPPORTED */
-  } else {
-    if (cinfo->quantize_colors && cinfo->colormap == NULL) {
-      /* Select new quantization method */
-      if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) {
-	cinfo->cquantize = master->quantizer_2pass;
-	master->pub.is_dummy_pass = TRUE;
-      } else if (cinfo->enable_1pass_quant) {
-	cinfo->cquantize = master->quantizer_1pass;
-      } else {
-	ERREXIT(cinfo, JERR_MODE_CHANGE);
-      }
-    }
-    (*cinfo->idct->start_pass) (cinfo);
-    (*cinfo->coef->start_output_pass) (cinfo);
-    if (! cinfo->raw_data_out) {
-      if (! master->using_merged_upsample)
-	(*cinfo->cconvert->start_pass) (cinfo);
-      (*cinfo->upsample->start_pass) (cinfo);
-      if (cinfo->quantize_colors)
-	(*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass);
-      (*cinfo->post->start_pass) (cinfo,
-	    (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU));
-      (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU);
-    }
-  }
-
-  /* Set up progress monitor's pass info if present */
-  if (cinfo->progress != NULL) {
-    cinfo->progress->completed_passes = master->pass_number;
-    cinfo->progress->total_passes = master->pass_number +
-				    (master->pub.is_dummy_pass ? 2 : 1);
-    /* In buffered-image mode, we assume one more output pass if EOI not
-     * yet reached, but no more passes if EOI has been reached.
-     */
-    if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) {
-      cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1);
-    }
-  }
-}
-
-
-/*
- * Finish up at end of an output pass.
- */
-
-METHODDEF(void)
-finish_output_pass (j_decompress_ptr cinfo)
-{
-  my_master_ptr master = (my_master_ptr) cinfo->master;
-
-  if (cinfo->quantize_colors)
-    (*cinfo->cquantize->finish_pass) (cinfo);
-  master->pass_number++;
-}
-
-
-#ifdef D_MULTISCAN_FILES_SUPPORTED
-
-/*
- * Switch to a new external colormap between output passes.
- */
-
-GLOBAL(void)
-jpeg_new_colormap (j_decompress_ptr cinfo)
-{
-  my_master_ptr master = (my_master_ptr) cinfo->master;
-
-  /* Prevent application from calling me at wrong times */
-  if (cinfo->global_state != DSTATE_BUFIMAGE)
-    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
-
-  if (cinfo->quantize_colors && cinfo->enable_external_quant &&
-      cinfo->colormap != NULL) {
-    /* Select 2-pass quantizer for external colormap use */
-    cinfo->cquantize = master->quantizer_2pass;
-    /* Notify quantizer of colormap change */
-    (*cinfo->cquantize->new_color_map) (cinfo);
-    master->pub.is_dummy_pass = FALSE; /* just in case */
-  } else
-    ERREXIT(cinfo, JERR_MODE_CHANGE);
-}
-
-#endif /* D_MULTISCAN_FILES_SUPPORTED */
-
-
-/*
- * Initialize master decompression control and select active modules.
- * This is performed at the start of jpeg_start_decompress.
- */
-
-GLOBAL(void)
-jinit_master_decompress (j_decompress_ptr cinfo)
-{
-  my_master_ptr master;
-
-  master = (my_master_ptr)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				  SIZEOF(my_decomp_master));
-  cinfo->master = (struct jpeg_decomp_master *) master;
-  master->pub.prepare_for_output_pass = prepare_for_output_pass;
-  master->pub.finish_output_pass = finish_output_pass;
-
-  master->pub.is_dummy_pass = FALSE;
-
-  master_selection(cinfo);
-}
diff --git a/third_party/libjpeg/jdmerge.c b/third_party/libjpeg/jdmerge.c
deleted file mode 100644
index 3744446..0000000
--- a/third_party/libjpeg/jdmerge.c
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * jdmerge.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains code for merged upsampling/color conversion.
- *
- * This file combines functions from jdsample.c and jdcolor.c;
- * read those files first to understand what's going on.
- *
- * When the chroma components are to be upsampled by simple replication
- * (ie, box filtering), we can save some work in color conversion by
- * calculating all the output pixels corresponding to a pair of chroma
- * samples at one time.  In the conversion equations
- *	R = Y           + K1 * Cr
- *	G = Y + K2 * Cb + K3 * Cr
- *	B = Y + K4 * Cb
- * only the Y term varies among the group of pixels corresponding to a pair
- * of chroma samples, so the rest of the terms can be calculated just once.
- * At typical sampling ratios, this eliminates half or three-quarters of the
- * multiplications needed for color conversion.
- *
- * This file currently provides implementations for the following cases:
- *	YCbCr => RGB color conversion only.
- *	Sampling ratios of 2h1v or 2h2v.
- *	No scaling needed at upsample time.
- *	Corner-aligned (non-CCIR601) sampling alignment.
- * Other special cases could be added, but in most applications these are
- * the only common cases.  (For uncommon cases we fall back on the more
- * general code in jdsample.c and jdcolor.c.)
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-#ifdef UPSAMPLE_MERGING_SUPPORTED
-
-
-/* Private subobject */
-
-typedef struct {
-  struct jpeg_upsampler pub;	/* public fields */
-
-  /* Pointer to routine to do actual upsampling/conversion of one row group */
-  JMETHOD(void, upmethod, (j_decompress_ptr cinfo,
-			   JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr,
-			   JSAMPARRAY output_buf));
-
-  /* Private state for YCC->RGB conversion */
-  int * Cr_r_tab;		/* => table for Cr to R conversion */
-  int * Cb_b_tab;		/* => table for Cb to B conversion */
-  INT32 * Cr_g_tab;		/* => table for Cr to G conversion */
-  INT32 * Cb_g_tab;		/* => table for Cb to G conversion */
-
-  /* For 2:1 vertical sampling, we produce two output rows at a time.
-   * We need a "spare" row buffer to hold the second output row if the
-   * application provides just a one-row buffer; we also use the spare
-   * to discard the dummy last row if the image height is odd.
-   */
-  JSAMPROW spare_row;
-  boolean spare_full;		/* T if spare buffer is occupied */
-
-  JDIMENSION out_row_width;	/* samples per output row */
-  JDIMENSION rows_to_go;	/* counts rows remaining in image */
-} my_upsampler;
-
-typedef my_upsampler * my_upsample_ptr;
-
-#define SCALEBITS	16	/* speediest right-shift on some machines */
-#define ONE_HALF	((INT32) 1 << (SCALEBITS-1))
-#define FIX(x)		((INT32) ((x) * (1L<<SCALEBITS) + 0.5))
-
-
-/*
- * Initialize tables for YCC->RGB colorspace conversion.
- * This is taken directly from jdcolor.c; see that file for more info.
- */
-
-LOCAL(void)
-build_ycc_rgb_table (j_decompress_ptr cinfo)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-  int i;
-  INT32 x;
-  SHIFT_TEMPS
-
-  upsample->Cr_r_tab = (int *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(int));
-  upsample->Cb_b_tab = (int *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(int));
-  upsample->Cr_g_tab = (INT32 *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(INT32));
-  upsample->Cb_g_tab = (INT32 *)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				(MAXJSAMPLE+1) * SIZEOF(INT32));
-
-  for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
-    /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */
-    /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */
-    /* Cr=>R value is nearest int to 1.40200 * x */
-    upsample->Cr_r_tab[i] = (int)
-		    RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS);
-    /* Cb=>B value is nearest int to 1.77200 * x */
-    upsample->Cb_b_tab[i] = (int)
-		    RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS);
-    /* Cr=>G value is scaled-up -0.71414 * x */
-    upsample->Cr_g_tab[i] = (- FIX(0.71414)) * x;
-    /* Cb=>G value is scaled-up -0.34414 * x */
-    /* We also add in ONE_HALF so that need not do it in inner loop */
-    upsample->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF;
-  }
-}
-
-
-/*
- * Initialize for an upsampling pass.
- */
-
-METHODDEF(void)
-start_pass_merged_upsample (j_decompress_ptr cinfo)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-
-  /* Mark the spare buffer empty */
-  upsample->spare_full = FALSE;
-  /* Initialize total-height counter for detecting bottom of image */
-  upsample->rows_to_go = cinfo->output_height;
-}
-
-
-/*
- * Control routine to do upsampling (and color conversion).
- *
- * The control routine just handles the row buffering considerations.
- */
-
-METHODDEF(void)
-merged_2v_upsample (j_decompress_ptr cinfo,
-		    JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-		    JDIMENSION in_row_groups_avail,
-		    JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-		    JDIMENSION out_rows_avail)
-/* 2:1 vertical sampling case: may need a spare row. */
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-  JSAMPROW work_ptrs[2];
-  JDIMENSION num_rows;		/* number of rows returned to caller */
-
-  if (upsample->spare_full) {
-    /* If we have a spare row saved from a previous cycle, just return it. */
-    jcopy_sample_rows(& upsample->spare_row, 0, output_buf + *out_row_ctr, 0,
-		      1, upsample->out_row_width);
-    num_rows = 1;
-    upsample->spare_full = FALSE;
-  } else {
-    /* Figure number of rows to return to caller. */
-    num_rows = 2;
-    /* Not more than the distance to the end of the image. */
-    if (num_rows > upsample->rows_to_go)
-      num_rows = upsample->rows_to_go;
-    /* And not more than what the client can accept: */
-    out_rows_avail -= *out_row_ctr;
-    if (num_rows > out_rows_avail)
-      num_rows = out_rows_avail;
-    /* Create output pointer array for upsampler. */
-    work_ptrs[0] = output_buf[*out_row_ctr];
-    if (num_rows > 1) {
-      work_ptrs[1] = output_buf[*out_row_ctr + 1];
-    } else {
-      work_ptrs[1] = upsample->spare_row;
-      upsample->spare_full = TRUE;
-    }
-    /* Now do the upsampling. */
-    (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, work_ptrs);
-  }
-
-  /* Adjust counts */
-  *out_row_ctr += num_rows;
-  upsample->rows_to_go -= num_rows;
-  /* When the buffer is emptied, declare this input row group consumed */
-  if (! upsample->spare_full)
-    (*in_row_group_ctr)++;
-}
-
-
-METHODDEF(void)
-merged_1v_upsample (j_decompress_ptr cinfo,
-		    JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-		    JDIMENSION in_row_groups_avail,
-		    JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-		    JDIMENSION out_rows_avail)
-/* 1:1 vertical sampling case: much easier, never need a spare row. */
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-
-  /* Just do the upsampling. */
-  (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr,
-			 output_buf + *out_row_ctr);
-  /* Adjust counts */
-  (*out_row_ctr)++;
-  (*in_row_group_ctr)++;
-}
-
-
-/*
- * These are the routines invoked by the control routines to do
- * the actual upsampling/conversion.  One row group is processed per call.
- *
- * Note: since we may be writing directly into application-supplied buffers,
- * we have to be honest about the output width; we can't assume the buffer
- * has been rounded up to an even width.
- */
-
-
-/*
- * Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical.
- */
-
-METHODDEF(void)
-h2v1_merged_upsample (j_decompress_ptr cinfo,
-		      JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr,
-		      JSAMPARRAY output_buf)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-  register int y, cred, cgreen, cblue;
-  int cb, cr;
-  register JSAMPROW outptr;
-  JSAMPROW inptr0, inptr1, inptr2;
-  JDIMENSION col;
-  /* copy these pointers into registers if possible */
-  register JSAMPLE * range_limit = cinfo->sample_range_limit;
-  int * Crrtab = upsample->Cr_r_tab;
-  int * Cbbtab = upsample->Cb_b_tab;
-  INT32 * Crgtab = upsample->Cr_g_tab;
-  INT32 * Cbgtab = upsample->Cb_g_tab;
-  SHIFT_TEMPS
-
-  inptr0 = input_buf[0][in_row_group_ctr];
-  inptr1 = input_buf[1][in_row_group_ctr];
-  inptr2 = input_buf[2][in_row_group_ctr];
-  outptr = output_buf[0];
-  /* Loop for each pair of output pixels */
-  for (col = cinfo->output_width >> 1; col > 0; col--) {
-    /* Do the chroma part of the calculation */
-    cb = GETJSAMPLE(*inptr1++);
-    cr = GETJSAMPLE(*inptr2++);
-    cred = Crrtab[cr];
-    cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS);
-    cblue = Cbbtab[cb];
-    /* Fetch 2 Y values and emit 2 pixels */
-    y  = GETJSAMPLE(*inptr0++);
-    outptr[RGB_RED] =   range_limit[y + cred];
-    outptr[RGB_GREEN] = range_limit[y + cgreen];
-    outptr[RGB_BLUE] =  range_limit[y + cblue];
-    outptr += RGB_PIXELSIZE;
-    y  = GETJSAMPLE(*inptr0++);
-    outptr[RGB_RED] =   range_limit[y + cred];
-    outptr[RGB_GREEN] = range_limit[y + cgreen];
-    outptr[RGB_BLUE] =  range_limit[y + cblue];
-    outptr += RGB_PIXELSIZE;
-  }
-  /* If image width is odd, do the last output column separately */
-  if (cinfo->output_width & 1) {
-    cb = GETJSAMPLE(*inptr1);
-    cr = GETJSAMPLE(*inptr2);
-    cred = Crrtab[cr];
-    cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS);
-    cblue = Cbbtab[cb];
-    y  = GETJSAMPLE(*inptr0);
-    outptr[RGB_RED] =   range_limit[y + cred];
-    outptr[RGB_GREEN] = range_limit[y + cgreen];
-    outptr[RGB_BLUE] =  range_limit[y + cblue];
-  }
-}
-
-
-/*
- * Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical.
- */
-
-METHODDEF(void)
-h2v2_merged_upsample (j_decompress_ptr cinfo,
-		      JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr,
-		      JSAMPARRAY output_buf)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-  register int y, cred, cgreen, cblue;
-  int cb, cr;
-  register JSAMPROW outptr0, outptr1;
-  JSAMPROW inptr00, inptr01, inptr1, inptr2;
-  JDIMENSION col;
-  /* copy these pointers into registers if possible */
-  register JSAMPLE * range_limit = cinfo->sample_range_limit;
-  int * Crrtab = upsample->Cr_r_tab;
-  int * Cbbtab = upsample->Cb_b_tab;
-  INT32 * Crgtab = upsample->Cr_g_tab;
-  INT32 * Cbgtab = upsample->Cb_g_tab;
-  SHIFT_TEMPS
-
-  inptr00 = input_buf[0][in_row_group_ctr*2];
-  inptr01 = input_buf[0][in_row_group_ctr*2 + 1];
-  inptr1 = input_buf[1][in_row_group_ctr];
-  inptr2 = input_buf[2][in_row_group_ctr];
-  outptr0 = output_buf[0];
-  outptr1 = output_buf[1];
-  /* Loop for each group of output pixels */
-  for (col = cinfo->output_width >> 1; col > 0; col--) {
-    /* Do the chroma part of the calculation */
-    cb = GETJSAMPLE(*inptr1++);
-    cr = GETJSAMPLE(*inptr2++);
-    cred = Crrtab[cr];
-    cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS);
-    cblue = Cbbtab[cb];
-    /* Fetch 4 Y values and emit 4 pixels */
-    y  = GETJSAMPLE(*inptr00++);
-    outptr0[RGB_RED] =   range_limit[y + cred];
-    outptr0[RGB_GREEN] = range_limit[y + cgreen];
-    outptr0[RGB_BLUE] =  range_limit[y + cblue];
-    outptr0 += RGB_PIXELSIZE;
-    y  = GETJSAMPLE(*inptr00++);
-    outptr0[RGB_RED] =   range_limit[y + cred];
-    outptr0[RGB_GREEN] = range_limit[y + cgreen];
-    outptr0[RGB_BLUE] =  range_limit[y + cblue];
-    outptr0 += RGB_PIXELSIZE;
-    y  = GETJSAMPLE(*inptr01++);
-    outptr1[RGB_RED] =   range_limit[y + cred];
-    outptr1[RGB_GREEN] = range_limit[y + cgreen];
-    outptr1[RGB_BLUE] =  range_limit[y + cblue];
-    outptr1 += RGB_PIXELSIZE;
-    y  = GETJSAMPLE(*inptr01++);
-    outptr1[RGB_RED] =   range_limit[y + cred];
-    outptr1[RGB_GREEN] = range_limit[y + cgreen];
-    outptr1[RGB_BLUE] =  range_limit[y + cblue];
-    outptr1 += RGB_PIXELSIZE;
-  }
-  /* If image width is odd, do the last output column separately */
-  if (cinfo->output_width & 1) {
-    cb = GETJSAMPLE(*inptr1);
-    cr = GETJSAMPLE(*inptr2);
-    cred = Crrtab[cr];
-    cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS);
-    cblue = Cbbtab[cb];
-    y  = GETJSAMPLE(*inptr00);
-    outptr0[RGB_RED] =   range_limit[y + cred];
-    outptr0[RGB_GREEN] = range_limit[y + cgreen];
-    outptr0[RGB_BLUE] =  range_limit[y + cblue];
-    y  = GETJSAMPLE(*inptr01);
-    outptr1[RGB_RED] =   range_limit[y + cred];
-    outptr1[RGB_GREEN] = range_limit[y + cgreen];
-    outptr1[RGB_BLUE] =  range_limit[y + cblue];
-  }
-}
-
-
-/*
- * Module initialization routine for merged upsampling/color conversion.
- *
- * NB: this is called under the conditions determined by use_merged_upsample()
- * in jdmaster.c.  That routine MUST correspond to the actual capabilities
- * of this module; no safety checks are made here.
- */
-
-GLOBAL(void)
-jinit_merged_upsampler (j_decompress_ptr cinfo)
-{
-  my_upsample_ptr upsample;
-
-  upsample = (my_upsample_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_upsampler));
-  cinfo->upsample = (struct jpeg_upsampler *) upsample;
-  upsample->pub.start_pass = start_pass_merged_upsample;
-  upsample->pub.need_context_rows = FALSE;
-
-  upsample->out_row_width = cinfo->output_width * cinfo->out_color_components;
-
-  if (cinfo->max_v_samp_factor == 2) {
-    upsample->pub.upsample = merged_2v_upsample;
-    upsample->upmethod = h2v2_merged_upsample;
-    /* Allocate a spare row buffer */
-    upsample->spare_row = (JSAMPROW)
-      (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-		(size_t) (upsample->out_row_width * SIZEOF(JSAMPLE)));
-  } else {
-    upsample->pub.upsample = merged_1v_upsample;
-    upsample->upmethod = h2v1_merged_upsample;
-    /* No spare row needed */
-    upsample->spare_row = NULL;
-  }
-
-  build_ycc_rgb_table(cinfo);
-}
-
-#endif /* UPSAMPLE_MERGING_SUPPORTED */
diff --git a/third_party/libjpeg/jdphuff.c b/third_party/libjpeg/jdphuff.c
deleted file mode 100644
index 2267809..0000000
--- a/third_party/libjpeg/jdphuff.c
+++ /dev/null
@@ -1,668 +0,0 @@
-/*
- * jdphuff.c
- *
- * Copyright (C) 1995-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains Huffman entropy decoding routines for progressive JPEG.
- *
- * Much of the complexity here has to do with supporting input suspension.
- * If the data source module demands suspension, we want to be able to back
- * up to the start of the current MCU.  To do this, we copy state variables
- * into local working storage, and update them back to the permanent
- * storage only upon successful completion of an MCU.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdhuff.h"		/* Declarations shared with jdhuff.c */
-
-
-#ifdef D_PROGRESSIVE_SUPPORTED
-
-/*
- * Expanded entropy decoder object for progressive Huffman decoding.
- *
- * The savable_state subrecord contains fields that change within an MCU,
- * but must not be updated permanently until we complete the MCU.
- */
-
-typedef struct {
-  unsigned int EOBRUN;			/* remaining EOBs in EOBRUN */
-  int last_dc_val[MAX_COMPS_IN_SCAN];	/* last DC coef for each component */
-} savable_state;
-
-/* This macro is to work around compilers with missing or broken
- * structure assignment.  You'll need to fix this code if you have
- * such a compiler and you change MAX_COMPS_IN_SCAN.
- */
-
-#ifndef NO_STRUCT_ASSIGN
-#define ASSIGN_STATE(dest,src)  ((dest) = (src))
-#else
-#if MAX_COMPS_IN_SCAN == 4
-#define ASSIGN_STATE(dest,src)  \
-	((dest).EOBRUN = (src).EOBRUN, \
-	 (dest).last_dc_val[0] = (src).last_dc_val[0], \
-	 (dest).last_dc_val[1] = (src).last_dc_val[1], \
-	 (dest).last_dc_val[2] = (src).last_dc_val[2], \
-	 (dest).last_dc_val[3] = (src).last_dc_val[3])
-#endif
-#endif
-
-
-typedef struct {
-  struct jpeg_entropy_decoder pub; /* public fields */
-
-  /* These fields are loaded into local variables at start of each MCU.
-   * In case of suspension, we exit WITHOUT updating them.
-   */
-  bitread_perm_state bitstate;	/* Bit buffer at start of MCU */
-  savable_state saved;		/* Other state at start of MCU */
-
-  /* These fields are NOT loaded into local working state. */
-  unsigned int restarts_to_go;	/* MCUs left in this restart interval */
-
-  /* Pointers to derived tables (these workspaces have image lifespan) */
-  d_derived_tbl * derived_tbls[NUM_HUFF_TBLS];
-
-  d_derived_tbl * ac_derived_tbl; /* active table during an AC scan */
-} phuff_entropy_decoder;
-
-typedef phuff_entropy_decoder * phuff_entropy_ptr;
-
-/* Forward declarations */
-METHODDEF(boolean) decode_mcu_DC_first JPP((j_decompress_ptr cinfo,
-					    JBLOCKROW *MCU_data));
-METHODDEF(boolean) decode_mcu_AC_first JPP((j_decompress_ptr cinfo,
-					    JBLOCKROW *MCU_data));
-METHODDEF(boolean) decode_mcu_DC_refine JPP((j_decompress_ptr cinfo,
-					     JBLOCKROW *MCU_data));
-METHODDEF(boolean) decode_mcu_AC_refine JPP((j_decompress_ptr cinfo,
-					     JBLOCKROW *MCU_data));
-
-
-/*
- * Initialize for a Huffman-compressed scan.
- */
-
-METHODDEF(void)
-start_pass_phuff_decoder (j_decompress_ptr cinfo)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  boolean is_DC_band, bad;
-  int ci, coefi, tbl;
-  int *coef_bit_ptr;
-  jpeg_component_info * compptr;
-
-  is_DC_band = (cinfo->Ss == 0);
-
-  /* Validate scan parameters */
-  bad = FALSE;
-  if (is_DC_band) {
-    if (cinfo->Se != 0)
-      bad = TRUE;
-  } else {
-    /* need not check Ss/Se < 0 since they came from unsigned bytes */
-    if (cinfo->Ss > cinfo->Se || cinfo->Se >= DCTSIZE2)
-      bad = TRUE;
-    /* AC scans may have only one component */
-    if (cinfo->comps_in_scan != 1)
-      bad = TRUE;
-  }
-  if (cinfo->Ah != 0) {
-    /* Successive approximation refinement scan: must have Al = Ah-1. */
-    if (cinfo->Al != cinfo->Ah-1)
-      bad = TRUE;
-  }
-  if (cinfo->Al > 13)		/* need not check for < 0 */
-    bad = TRUE;
-  /* Arguably the maximum Al value should be less than 13 for 8-bit precision,
-   * but the spec doesn't say so, and we try to be liberal about what we
-   * accept.  Note: large Al values could result in out-of-range DC
-   * coefficients during early scans, leading to bizarre displays due to
-   * overflows in the IDCT math.  But we won't crash.
-   */
-  if (bad)
-    ERREXIT4(cinfo, JERR_BAD_PROGRESSION,
-	     cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al);
-  /* Update progression status, and verify that scan order is legal.
-   * Note that inter-scan inconsistencies are treated as warnings
-   * not fatal errors ... not clear if this is right way to behave.
-   */
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    int cindex = cinfo->cur_comp_info[ci]->component_index;
-    coef_bit_ptr = & cinfo->coef_bits[cindex][0];
-    if (!is_DC_band && coef_bit_ptr[0] < 0) /* AC without prior DC scan */
-      WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0);
-    for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) {
-      int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi];
-      if (cinfo->Ah != expected)
-	WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi);
-      coef_bit_ptr[coefi] = cinfo->Al;
-    }
-  }
-
-  /* Select MCU decoding routine */
-  if (cinfo->Ah == 0) {
-    if (is_DC_band)
-      entropy->pub.decode_mcu = decode_mcu_DC_first;
-    else
-      entropy->pub.decode_mcu = decode_mcu_AC_first;
-  } else {
-    if (is_DC_band)
-      entropy->pub.decode_mcu = decode_mcu_DC_refine;
-    else
-      entropy->pub.decode_mcu = decode_mcu_AC_refine;
-  }
-
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
-    compptr = cinfo->cur_comp_info[ci];
-    /* Make sure requested tables are present, and compute derived tables.
-     * We may build same derived table more than once, but it's not expensive.
-     */
-    if (is_DC_band) {
-      if (cinfo->Ah == 0) {	/* DC refinement needs no table */
-	tbl = compptr->dc_tbl_no;
-	jpeg_make_d_derived_tbl(cinfo, TRUE, tbl,
-				& entropy->derived_tbls[tbl]);
-      }
-    } else {
-      tbl = compptr->ac_tbl_no;
-      jpeg_make_d_derived_tbl(cinfo, FALSE, tbl,
-			      & entropy->derived_tbls[tbl]);
-      /* remember the single active table */
-      entropy->ac_derived_tbl = entropy->derived_tbls[tbl];
-    }
-    /* Initialize DC predictions to 0 */
-    entropy->saved.last_dc_val[ci] = 0;
-  }
-
-  /* Initialize bitread state variables */
-  entropy->bitstate.bits_left = 0;
-  entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */
-  entropy->pub.insufficient_data = FALSE;
-
-  /* Initialize private state variables */
-  entropy->saved.EOBRUN = 0;
-
-  /* Initialize restart counter */
-  entropy->restarts_to_go = cinfo->restart_interval;
-}
-
-
-/*
- * Figure F.12: extend sign bit.
- * On some machines, a shift and add will be faster than a table lookup.
- */
-
-#ifdef AVOID_TABLES
-
-#define HUFF_EXTEND(x,s)  ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x))
-
-#else
-
-#define HUFF_EXTEND(x,s)  ((x) < extend_test[s] ? (x) + extend_offset[s] : (x))
-
-static const int extend_test[16] =   /* entry n is 2**(n-1) */
-  { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
-    0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 };
-
-static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */
-  { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1,
-    ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1,
-    ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1,
-    ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 };
-
-#endif /* AVOID_TABLES */
-
-
-/*
- * Check for a restart marker & resynchronize decoder.
- * Returns FALSE if must suspend.
- */
-
-LOCAL(boolean)
-process_restart (j_decompress_ptr cinfo)
-{
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  int ci;
-
-  /* Throw away any unused bits remaining in bit buffer; */
-  /* include any full bytes in next_marker's count of discarded bytes */
-  cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8;
-  entropy->bitstate.bits_left = 0;
-
-  /* Advance past the RSTn marker */
-  if (! (*cinfo->marker->read_restart_marker) (cinfo))
-    return FALSE;
-
-  /* Re-initialize DC predictions to 0 */
-  for (ci = 0; ci < cinfo->comps_in_scan; ci++)
-    entropy->saved.last_dc_val[ci] = 0;
-  /* Re-init EOB run count, too */
-  entropy->saved.EOBRUN = 0;
-
-  /* Reset restart counter */
-  entropy->restarts_to_go = cinfo->restart_interval;
-
-  /* Reset out-of-data flag, unless read_restart_marker left us smack up
-   * against a marker.  In that case we will end up treating the next data
-   * segment as empty, and we can avoid producing bogus output pixels by
-   * leaving the flag set.
-   */
-  if (cinfo->unread_marker == 0)
-    entropy->pub.insufficient_data = FALSE;
-
-  return TRUE;
-}
-
-
-/*
- * Huffman MCU decoding.
- * Each of these routines decodes and returns one MCU's worth of
- * Huffman-compressed coefficients. 
- * The coefficients are reordered from zigzag order into natural array order,
- * but are not dequantized.
- *
- * The i'th block of the MCU is stored into the block pointed to by
- * MCU_data[i].  WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER.
- *
- * We return FALSE if data source requested suspension.  In that case no
- * changes have been made to permanent state.  (Exception: some output
- * coefficients may already have been assigned.  This is harmless for
- * spectral selection, since we'll just re-assign them on the next call.
- * Successive approximation AC refinement has to be more careful, however.)
- */
-
-/*
- * MCU decoding for DC initial scan (either spectral selection,
- * or first pass of successive approximation).
- */
-
-METHODDEF(boolean)
-decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data)
-{   
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  int Al = cinfo->Al;
-  register int s, r;
-  int blkn, ci;
-  JBLOCKROW block;
-  BITREAD_STATE_VARS;
-  savable_state state;
-  d_derived_tbl * tbl;
-  jpeg_component_info * compptr;
-
-  /* Process restart marker if needed; may have to suspend */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0)
-      if (! process_restart(cinfo))
-	return FALSE;
-  }
-
-  /* If we've run out of data, just leave the MCU set to zeroes.
-   * This way, we return uniform gray for the remainder of the segment.
-   */
-  if (! entropy->pub.insufficient_data) {
-
-    /* Load up working state */
-    BITREAD_LOAD_STATE(cinfo,entropy->bitstate);
-    ASSIGN_STATE(state, entropy->saved);
-
-    /* Outer loop handles each block in the MCU */
-
-    for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-      block = MCU_data[blkn];
-      ci = cinfo->MCU_membership[blkn];
-      compptr = cinfo->cur_comp_info[ci];
-      tbl = entropy->derived_tbls[compptr->dc_tbl_no];
-
-      /* Decode a single block's worth of coefficients */
-
-      /* Section F.2.2.1: decode the DC coefficient difference */
-      HUFF_DECODE(s, br_state, tbl, return FALSE, label1);
-      if (s) {
-	CHECK_BIT_BUFFER(br_state, s, return FALSE);
-	r = GET_BITS(s);
-	s = HUFF_EXTEND(r, s);
-      }
-
-      /* Convert DC difference to actual value, update last_dc_val */
-      s += state.last_dc_val[ci];
-      state.last_dc_val[ci] = s;
-      /* Scale and output the coefficient (assumes jpeg_natural_order[0]=0) */
-      (*block)[0] = (JCOEF) (s << Al);
-    }
-
-    /* Completed MCU, so update state */
-    BITREAD_SAVE_STATE(cinfo,entropy->bitstate);
-    ASSIGN_STATE(entropy->saved, state);
-  }
-
-  /* Account for restart interval (no-op if not using restarts) */
-  entropy->restarts_to_go--;
-
-  return TRUE;
-}
-
-
-/*
- * MCU decoding for AC initial scan (either spectral selection,
- * or first pass of successive approximation).
- */
-
-METHODDEF(boolean)
-decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data)
-{   
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  int Se = cinfo->Se;
-  int Al = cinfo->Al;
-  register int s, k, r;
-  unsigned int EOBRUN;
-  JBLOCKROW block;
-  BITREAD_STATE_VARS;
-  d_derived_tbl * tbl;
-
-  /* Process restart marker if needed; may have to suspend */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0)
-      if (! process_restart(cinfo))
-	return FALSE;
-  }
-
-  /* If we've run out of data, just leave the MCU set to zeroes.
-   * This way, we return uniform gray for the remainder of the segment.
-   */
-  if (! entropy->pub.insufficient_data) {
-
-    /* Load up working state.
-     * We can avoid loading/saving bitread state if in an EOB run.
-     */
-    EOBRUN = entropy->saved.EOBRUN;	/* only part of saved state we need */
-
-    /* There is always only one block per MCU */
-
-    if (EOBRUN > 0)		/* if it's a band of zeroes... */
-      EOBRUN--;			/* ...process it now (we do nothing) */
-    else {
-      BITREAD_LOAD_STATE(cinfo,entropy->bitstate);
-      block = MCU_data[0];
-      tbl = entropy->ac_derived_tbl;
-
-      for (k = cinfo->Ss; k <= Se; k++) {
-	HUFF_DECODE(s, br_state, tbl, return FALSE, label2);
-	r = s >> 4;
-	s &= 15;
-	if (s) {
-	  k += r;
-	  CHECK_BIT_BUFFER(br_state, s, return FALSE);
-	  r = GET_BITS(s);
-	  s = HUFF_EXTEND(r, s);
-	  /* Scale and output coefficient in natural (dezigzagged) order */
-	  (*block)[jpeg_natural_order[k]] = (JCOEF) (s << Al);
-	} else {
-	  if (r == 15) {	/* ZRL */
-	    k += 15;		/* skip 15 zeroes in band */
-	  } else {		/* EOBr, run length is 2^r + appended bits */
-	    EOBRUN = 1 << r;
-	    if (r) {		/* EOBr, r > 0 */
-	      CHECK_BIT_BUFFER(br_state, r, return FALSE);
-	      r = GET_BITS(r);
-	      EOBRUN += r;
-	    }
-	    EOBRUN--;		/* this band is processed at this moment */
-	    break;		/* force end-of-band */
-	  }
-	}
-      }
-
-      BITREAD_SAVE_STATE(cinfo,entropy->bitstate);
-    }
-
-    /* Completed MCU, so update state */
-    entropy->saved.EOBRUN = EOBRUN;	/* only part of saved state we need */
-  }
-
-  /* Account for restart interval (no-op if not using restarts) */
-  entropy->restarts_to_go--;
-
-  return TRUE;
-}
-
-
-/*
- * MCU decoding for DC successive approximation refinement scan.
- * Note: we assume such scans can be multi-component, although the spec
- * is not very clear on the point.
- */
-
-METHODDEF(boolean)
-decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data)
-{   
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  int p1 = 1 << cinfo->Al;	/* 1 in the bit position being coded */
-  int blkn;
-  JBLOCKROW block;
-  BITREAD_STATE_VARS;
-
-  /* Process restart marker if needed; may have to suspend */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0)
-      if (! process_restart(cinfo))
-	return FALSE;
-  }
-
-  /* Not worth the cycles to check insufficient_data here,
-   * since we will not change the data anyway if we read zeroes.
-   */
-
-  /* Load up working state */
-  BITREAD_LOAD_STATE(cinfo,entropy->bitstate);
-
-  /* Outer loop handles each block in the MCU */
-
-  for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
-    block = MCU_data[blkn];
-
-    /* Encoded data is simply the next bit of the two's-complement DC value */
-    CHECK_BIT_BUFFER(br_state, 1, return FALSE);
-    if (GET_BITS(1))
-      (*block)[0] |= p1;
-    /* Note: since we use |=, repeating the assignment later is safe */
-  }
-
-  /* Completed MCU, so update state */
-  BITREAD_SAVE_STATE(cinfo,entropy->bitstate);
-
-  /* Account for restart interval (no-op if not using restarts) */
-  entropy->restarts_to_go--;
-
-  return TRUE;
-}
-
-
-/*
- * MCU decoding for AC successive approximation refinement scan.
- */
-
-METHODDEF(boolean)
-decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data)
-{   
-  phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy;
-  int Se = cinfo->Se;
-  int p1 = 1 << cinfo->Al;	/* 1 in the bit position being coded */
-  int m1 = (-1) << cinfo->Al;	/* -1 in the bit position being coded */
-  register int s, k, r;
-  unsigned int EOBRUN;
-  JBLOCKROW block;
-  JCOEFPTR thiscoef;
-  BITREAD_STATE_VARS;
-  d_derived_tbl * tbl;
-  int num_newnz;
-  int newnz_pos[DCTSIZE2];
-
-  /* Process restart marker if needed; may have to suspend */
-  if (cinfo->restart_interval) {
-    if (entropy->restarts_to_go == 0)
-      if (! process_restart(cinfo))
-	return FALSE;
-  }
-
-  /* If we've run out of data, don't modify the MCU.
-   */
-  if (! entropy->pub.insufficient_data) {
-
-    /* Load up working state */
-    BITREAD_LOAD_STATE(cinfo,entropy->bitstate);
-    EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */
-
-    /* There is always only one block per MCU */
-    block = MCU_data[0];
-    tbl = entropy->ac_derived_tbl;
-
-    /* If we are forced to suspend, we must undo the assignments to any newly
-     * nonzero coefficients in the block, because otherwise we'd get confused
-     * next time about which coefficients were already nonzero.
-     * But we need not undo addition of bits to already-nonzero coefficients;
-     * instead, we can test the current bit to see if we already did it.
-     */
-    num_newnz = 0;
-
-    /* initialize coefficient loop counter to start of band */
-    k = cinfo->Ss;
-
-    if (EOBRUN == 0) {
-      for (; k <= Se; k++) {
-	HUFF_DECODE(s, br_state, tbl, goto undoit, label3);
-	r = s >> 4;
-	s &= 15;
-	if (s) {
-	  if (s != 1)		/* size of new coef should always be 1 */
-	    WARNMS(cinfo, JWRN_HUFF_BAD_CODE);
-	  CHECK_BIT_BUFFER(br_state, 1, goto undoit);
-	  if (GET_BITS(1))
-	    s = p1;		/* newly nonzero coef is positive */
-	  else
-	    s = m1;		/* newly nonzero coef is negative */
-	} else {
-	  if (r != 15) {
-	    EOBRUN = 1 << r;	/* EOBr, run length is 2^r + appended bits */
-	    if (r) {
-	      CHECK_BIT_BUFFER(br_state, r, goto undoit);
-	      r = GET_BITS(r);
-	      EOBRUN += r;
-	    }
-	    break;		/* rest of block is handled by EOB logic */
-	  }
-	  /* note s = 0 for processing ZRL */
-	}
-	/* Advance over already-nonzero coefs and r still-zero coefs,
-	 * appending correction bits to the nonzeroes.  A correction bit is 1
-	 * if the absolute value of the coefficient must be increased.
-	 */
-	do {
-	  thiscoef = *block + jpeg_natural_order[k];
-	  if (*thiscoef != 0) {
-	    CHECK_BIT_BUFFER(br_state, 1, goto undoit);
-	    if (GET_BITS(1)) {
-	      if ((*thiscoef & p1) == 0) { /* do nothing if already set it */
-		if (*thiscoef >= 0)
-		  *thiscoef += p1;
-		else
-		  *thiscoef += m1;
-	      }
-	    }
-	  } else {
-	    if (--r < 0)
-	      break;		/* reached target zero coefficient */
-	  }
-	  k++;
-	} while (k <= Se);
-	if (s) {
-	  int pos = jpeg_natural_order[k];
-	  /* Output newly nonzero coefficient */
-	  (*block)[pos] = (JCOEF) s;
-	  /* Remember its position in case we have to suspend */
-	  newnz_pos[num_newnz++] = pos;
-	}
-      }
-    }
-
-    if (EOBRUN > 0) {
-      /* Scan any remaining coefficient positions after the end-of-band
-       * (the last newly nonzero coefficient, if any).  Append a correction
-       * bit to each already-nonzero coefficient.  A correction bit is 1
-       * if the absolute value of the coefficient must be increased.
-       */
-      for (; k <= Se; k++) {
-	thiscoef = *block + jpeg_natural_order[k];
-	if (*thiscoef != 0) {
-	  CHECK_BIT_BUFFER(br_state, 1, goto undoit);
-	  if (GET_BITS(1)) {
-	    if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */
-	      if (*thiscoef >= 0)
-		*thiscoef += p1;
-	      else
-		*thiscoef += m1;
-	    }
-	  }
-	}
-      }
-      /* Count one block completed in EOB run */
-      EOBRUN--;
-    }
-
-    /* Completed MCU, so update state */
-    BITREAD_SAVE_STATE(cinfo,entropy->bitstate);
-    entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */
-  }
-
-  /* Account for restart interval (no-op if not using restarts) */
-  entropy->restarts_to_go--;
-
-  return TRUE;
-
-undoit:
-  /* Re-zero any output coefficients that we made newly nonzero */
-  while (num_newnz > 0)
-    (*block)[newnz_pos[--num_newnz]] = 0;
-
-  return FALSE;
-}
-
-
-/*
- * Module initialization routine for progressive Huffman entropy decoding.
- */
-
-GLOBAL(void)
-jinit_phuff_decoder (j_decompress_ptr cinfo)
-{
-  phuff_entropy_ptr entropy;
-  int *coef_bit_ptr;
-  int ci, i;
-
-  entropy = (phuff_entropy_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(phuff_entropy_decoder));
-  cinfo->entropy = (struct jpeg_entropy_decoder *) entropy;
-  entropy->pub.start_pass = start_pass_phuff_decoder;
-
-  /* Mark derived tables unallocated */
-  for (i = 0; i < NUM_HUFF_TBLS; i++) {
-    entropy->derived_tbls[i] = NULL;
-  }
-
-  /* Create progression status table */
-  cinfo->coef_bits = (int (*)[DCTSIZE2])
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				cinfo->num_components*DCTSIZE2*SIZEOF(int));
-  coef_bit_ptr = & cinfo->coef_bits[0][0];
-  for (ci = 0; ci < cinfo->num_components; ci++) 
-    for (i = 0; i < DCTSIZE2; i++)
-      *coef_bit_ptr++ = -1;
-}
-
-#endif /* D_PROGRESSIVE_SUPPORTED */
diff --git a/third_party/libjpeg/jdpostct.c b/third_party/libjpeg/jdpostct.c
deleted file mode 100644
index 571563d..0000000
--- a/third_party/libjpeg/jdpostct.c
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * jdpostct.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the decompression postprocessing controller.
- * This controller manages the upsampling, color conversion, and color
- * quantization/reduction steps; specifically, it controls the buffering
- * between upsample/color conversion and color quantization/reduction.
- *
- * If no color quantization/reduction is required, then this module has no
- * work to do, and it just hands off to the upsample/color conversion code.
- * An integrated upsample/convert/quantize process would replace this module
- * entirely.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Private buffer controller object */
-
-typedef struct {
-  struct jpeg_d_post_controller pub; /* public fields */
-
-  /* Color quantization source buffer: this holds output data from
-   * the upsample/color conversion step to be passed to the quantizer.
-   * For two-pass color quantization, we need a full-image buffer;
-   * for one-pass operation, a strip buffer is sufficient.
-   */
-  jvirt_sarray_ptr whole_image;	/* virtual array, or NULL if one-pass */
-  JSAMPARRAY buffer;		/* strip buffer, or current strip of virtual */
-  JDIMENSION strip_height;	/* buffer size in rows */
-  /* for two-pass mode only: */
-  JDIMENSION starting_row;	/* row # of first row in current strip */
-  JDIMENSION next_row;		/* index of next row to fill/empty in strip */
-} my_post_controller;
-
-typedef my_post_controller * my_post_ptr;
-
-
-/* Forward declarations */
-METHODDEF(void) post_process_1pass
-	JPP((j_decompress_ptr cinfo,
-	     JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-	     JDIMENSION in_row_groups_avail,
-	     JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-	     JDIMENSION out_rows_avail));
-#ifdef QUANT_2PASS_SUPPORTED
-METHODDEF(void) post_process_prepass
-	JPP((j_decompress_ptr cinfo,
-	     JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-	     JDIMENSION in_row_groups_avail,
-	     JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-	     JDIMENSION out_rows_avail));
-METHODDEF(void) post_process_2pass
-	JPP((j_decompress_ptr cinfo,
-	     JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-	     JDIMENSION in_row_groups_avail,
-	     JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-	     JDIMENSION out_rows_avail));
-#endif
-
-
-/*
- * Initialize for a processing pass.
- */
-
-METHODDEF(void)
-start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)
-{
-  my_post_ptr post = (my_post_ptr) cinfo->post;
-
-  switch (pass_mode) {
-  case JBUF_PASS_THRU:
-    if (cinfo->quantize_colors) {
-      /* Single-pass processing with color quantization. */
-      post->pub.post_process_data = post_process_1pass;
-      /* We could be doing buffered-image output before starting a 2-pass
-       * color quantization; in that case, jinit_d_post_controller did not
-       * allocate a strip buffer.  Use the virtual-array buffer as workspace.
-       */
-      if (post->buffer == NULL) {
-	post->buffer = (*cinfo->mem->access_virt_sarray)
-	  ((j_common_ptr) cinfo, post->whole_image,
-	   (JDIMENSION) 0, post->strip_height, TRUE);
-      }
-    } else {
-      /* For single-pass processing without color quantization,
-       * I have no work to do; just call the upsampler directly.
-       */
-      post->pub.post_process_data = cinfo->upsample->upsample;
-    }
-    break;
-#ifdef QUANT_2PASS_SUPPORTED
-  case JBUF_SAVE_AND_PASS:
-    /* First pass of 2-pass quantization */
-    if (post->whole_image == NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    post->pub.post_process_data = post_process_prepass;
-    break;
-  case JBUF_CRANK_DEST:
-    /* Second pass of 2-pass quantization */
-    if (post->whole_image == NULL)
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    post->pub.post_process_data = post_process_2pass;
-    break;
-#endif /* QUANT_2PASS_SUPPORTED */
-  default:
-    ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-    break;
-  }
-  post->starting_row = post->next_row = 0;
-}
-
-
-/*
- * Process some data in the one-pass (strip buffer) case.
- * This is used for color precision reduction as well as one-pass quantization.
- */
-
-METHODDEF(void)
-post_process_1pass (j_decompress_ptr cinfo,
-		    JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-		    JDIMENSION in_row_groups_avail,
-		    JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-		    JDIMENSION out_rows_avail)
-{
-  my_post_ptr post = (my_post_ptr) cinfo->post;
-  JDIMENSION num_rows, max_rows;
-
-  /* Fill the buffer, but not more than what we can dump out in one go. */
-  /* Note we rely on the upsampler to detect bottom of image. */
-  max_rows = out_rows_avail - *out_row_ctr;
-  if (max_rows > post->strip_height)
-    max_rows = post->strip_height;
-  num_rows = 0;
-  (*cinfo->upsample->upsample) (cinfo,
-		input_buf, in_row_group_ctr, in_row_groups_avail,
-		post->buffer, &num_rows, max_rows);
-  /* Quantize and emit data. */
-  (*cinfo->cquantize->color_quantize) (cinfo,
-		post->buffer, output_buf + *out_row_ctr, (int) num_rows);
-  *out_row_ctr += num_rows;
-}
-
-
-#ifdef QUANT_2PASS_SUPPORTED
-
-/*
- * Process some data in the first pass of 2-pass quantization.
- */
-
-METHODDEF(void)
-post_process_prepass (j_decompress_ptr cinfo,
-		      JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-		      JDIMENSION in_row_groups_avail,
-		      JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-		      JDIMENSION out_rows_avail)
-{
-  my_post_ptr post = (my_post_ptr) cinfo->post;
-  JDIMENSION old_next_row, num_rows;
-
-  /* Reposition virtual buffer if at start of strip. */
-  if (post->next_row == 0) {
-    post->buffer = (*cinfo->mem->access_virt_sarray)
-	((j_common_ptr) cinfo, post->whole_image,
-	 post->starting_row, post->strip_height, TRUE);
-  }
-
-  /* Upsample some data (up to a strip height's worth). */
-  old_next_row = post->next_row;
-  (*cinfo->upsample->upsample) (cinfo,
-		input_buf, in_row_group_ctr, in_row_groups_avail,
-		post->buffer, &post->next_row, post->strip_height);
-
-  /* Allow quantizer to scan new data.  No data is emitted, */
-  /* but we advance out_row_ctr so outer loop can tell when we're done. */
-  if (post->next_row > old_next_row) {
-    num_rows = post->next_row - old_next_row;
-    (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row,
-					 (JSAMPARRAY) NULL, (int) num_rows);
-    *out_row_ctr += num_rows;
-  }
-
-  /* Advance if we filled the strip. */
-  if (post->next_row >= post->strip_height) {
-    post->starting_row += post->strip_height;
-    post->next_row = 0;
-  }
-}
-
-
-/*
- * Process some data in the second pass of 2-pass quantization.
- */
-
-METHODDEF(void)
-post_process_2pass (j_decompress_ptr cinfo,
-		    JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-		    JDIMENSION in_row_groups_avail,
-		    JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-		    JDIMENSION out_rows_avail)
-{
-  my_post_ptr post = (my_post_ptr) cinfo->post;
-  JDIMENSION num_rows, max_rows;
-
-  /* Reposition virtual buffer if at start of strip. */
-  if (post->next_row == 0) {
-    post->buffer = (*cinfo->mem->access_virt_sarray)
-	((j_common_ptr) cinfo, post->whole_image,
-	 post->starting_row, post->strip_height, FALSE);
-  }
-
-  /* Determine number of rows to emit. */
-  num_rows = post->strip_height - post->next_row; /* available in strip */
-  max_rows = out_rows_avail - *out_row_ctr; /* available in output area */
-  if (num_rows > max_rows)
-    num_rows = max_rows;
-  /* We have to check bottom of image here, can't depend on upsampler. */
-  max_rows = cinfo->output_height - post->starting_row;
-  if (num_rows > max_rows)
-    num_rows = max_rows;
-
-  /* Quantize and emit data. */
-  (*cinfo->cquantize->color_quantize) (cinfo,
-		post->buffer + post->next_row, output_buf + *out_row_ctr,
-		(int) num_rows);
-  *out_row_ctr += num_rows;
-
-  /* Advance if we filled the strip. */
-  post->next_row += num_rows;
-  if (post->next_row >= post->strip_height) {
-    post->starting_row += post->strip_height;
-    post->next_row = 0;
-  }
-}
-
-#endif /* QUANT_2PASS_SUPPORTED */
-
-
-/*
- * Initialize postprocessing controller.
- */
-
-GLOBAL(void)
-jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer)
-{
-  my_post_ptr post;
-
-  post = (my_post_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_post_controller));
-  cinfo->post = (struct jpeg_d_post_controller *) post;
-  post->pub.start_pass = start_pass_dpost;
-  post->whole_image = NULL;	/* flag for no virtual arrays */
-  post->buffer = NULL;		/* flag for no strip buffer */
-
-  /* Create the quantization buffer, if needed */
-  if (cinfo->quantize_colors) {
-    /* The buffer strip height is max_v_samp_factor, which is typically
-     * an efficient number of rows for upsampling to return.
-     * (In the presence of output rescaling, we might want to be smarter?)
-     */
-    post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor;
-    if (need_full_buffer) {
-      /* Two-pass color quantization: need full-image storage. */
-      /* We round up the number of rows to a multiple of the strip height. */
-#ifdef QUANT_2PASS_SUPPORTED
-      post->whole_image = (*cinfo->mem->request_virt_sarray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE,
-	 cinfo->output_width * cinfo->out_color_components,
-	 (JDIMENSION) jround_up((long) cinfo->output_height,
-				(long) post->strip_height),
-	 post->strip_height);
-#else
-      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
-#endif /* QUANT_2PASS_SUPPORTED */
-    } else {
-      /* One-pass color quantization: just make a strip buffer. */
-      post->buffer = (*cinfo->mem->alloc_sarray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE,
-	 cinfo->output_width * cinfo->out_color_components,
-	 post->strip_height);
-    }
-  }
-}
diff --git a/third_party/libjpeg/jdsample.c b/third_party/libjpeg/jdsample.c
deleted file mode 100644
index 80ffefb..0000000
--- a/third_party/libjpeg/jdsample.c
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * jdsample.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains upsampling routines.
- *
- * Upsampling input data is counted in "row groups".  A row group
- * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size)
- * sample rows of each component.  Upsampling will normally produce
- * max_v_samp_factor pixel rows from each row group (but this could vary
- * if the upsampler is applying a scale factor of its own).
- *
- * An excellent reference for image resampling is
- *   Digital Image Warping, George Wolberg, 1990.
- *   Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/* Pointer to routine to upsample a single component */
-typedef JMETHOD(void, upsample1_ptr,
-		(j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		 JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr));
-
-/* Private subobject */
-
-typedef struct {
-  struct jpeg_upsampler pub;	/* public fields */
-
-  /* Color conversion buffer.  When using separate upsampling and color
-   * conversion steps, this buffer holds one upsampled row group until it
-   * has been color converted and output.
-   * Note: we do not allocate any storage for component(s) which are full-size,
-   * ie do not need rescaling.  The corresponding entry of color_buf[] is
-   * simply set to point to the input data array, thereby avoiding copying.
-   */
-  JSAMPARRAY color_buf[MAX_COMPONENTS];
-
-  /* Per-component upsampling method pointers */
-  upsample1_ptr methods[MAX_COMPONENTS];
-
-  int next_row_out;		/* counts rows emitted from color_buf */
-  JDIMENSION rows_to_go;	/* counts rows remaining in image */
-
-  /* Height of an input row group for each component. */
-  int rowgroup_height[MAX_COMPONENTS];
-
-  /* These arrays save pixel expansion factors so that int_expand need not
-   * recompute them each time.  They are unused for other upsampling methods.
-   */
-  UINT8 h_expand[MAX_COMPONENTS];
-  UINT8 v_expand[MAX_COMPONENTS];
-} my_upsampler;
-
-typedef my_upsampler * my_upsample_ptr;
-
-
-/*
- * Initialize for an upsampling pass.
- */
-
-METHODDEF(void)
-start_pass_upsample (j_decompress_ptr cinfo)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-
-  /* Mark the conversion buffer empty */
-  upsample->next_row_out = cinfo->max_v_samp_factor;
-  /* Initialize total-height counter for detecting bottom of image */
-  upsample->rows_to_go = cinfo->output_height;
-}
-
-
-/*
- * Control routine to do upsampling (and color conversion).
- *
- * In this version we upsample each component independently.
- * We upsample one row group into the conversion buffer, then apply
- * color conversion a row at a time.
- */
-
-METHODDEF(void)
-sep_upsample (j_decompress_ptr cinfo,
-	      JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr,
-	      JDIMENSION in_row_groups_avail,
-	      JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-	      JDIMENSION out_rows_avail)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-  int ci;
-  jpeg_component_info * compptr;
-  JDIMENSION num_rows;
-
-  /* Fill the conversion buffer, if it's empty */
-  if (upsample->next_row_out >= cinfo->max_v_samp_factor) {
-    for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-	 ci++, compptr++) {
-      /* Invoke per-component upsample method.  Notice we pass a POINTER
-       * to color_buf[ci], so that fullsize_upsample can change it.
-       */
-      (*upsample->methods[ci]) (cinfo, compptr,
-	input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]),
-	upsample->color_buf + ci);
-    }
-    upsample->next_row_out = 0;
-  }
-
-  /* Color-convert and emit rows */
-
-  /* How many we have in the buffer: */
-  num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out);
-  /* Not more than the distance to the end of the image.  Need this test
-   * in case the image height is not a multiple of max_v_samp_factor:
-   */
-  if (num_rows > upsample->rows_to_go) 
-    num_rows = upsample->rows_to_go;
-  /* And not more than what the client can accept: */
-  out_rows_avail -= *out_row_ctr;
-  if (num_rows > out_rows_avail)
-    num_rows = out_rows_avail;
-
-  (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf,
-				     (JDIMENSION) upsample->next_row_out,
-				     output_buf + *out_row_ctr,
-				     (int) num_rows);
-
-  /* Adjust counts */
-  *out_row_ctr += num_rows;
-  upsample->rows_to_go -= num_rows;
-  upsample->next_row_out += num_rows;
-  /* When the buffer is emptied, declare this input row group consumed */
-  if (upsample->next_row_out >= cinfo->max_v_samp_factor)
-    (*in_row_group_ctr)++;
-}
-
-
-/*
- * These are the routines invoked by sep_upsample to upsample pixel values
- * of a single component.  One row group is processed per call.
- */
-
-
-/*
- * For full-size components, we just make color_buf[ci] point at the
- * input buffer, and thus avoid copying any data.  Note that this is
- * safe only because sep_upsample doesn't declare the input row group
- * "consumed" until we are done color converting and emitting it.
- */
-
-METHODDEF(void)
-fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		   JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  *output_data_ptr = input_data;
-}
-
-
-/*
- * This is a no-op version used for "uninteresting" components.
- * These components will not be referenced by color conversion.
- */
-
-METHODDEF(void)
-noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	       JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  *output_data_ptr = NULL;	/* safety check */
-}
-
-
-/*
- * This version handles any integral sampling ratios.
- * This is not used for typical JPEG files, so it need not be fast.
- * Nor, for that matter, is it particularly accurate: the algorithm is
- * simple replication of the input pixel onto the corresponding output
- * pixels.  The hi-falutin sampling literature refers to this as a
- * "box filter".  A box filter tends to introduce visible artifacts,
- * so if you are actually going to use 3:1 or 4:1 sampling ratios
- * you would be well advised to improve this code.
- */
-
-METHODDEF(void)
-int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	      JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
-  JSAMPARRAY output_data = *output_data_ptr;
-  register JSAMPROW inptr, outptr;
-  register JSAMPLE invalue;
-  register int h;
-  JSAMPROW outend;
-  int h_expand, v_expand;
-  int inrow, outrow;
-
-  h_expand = upsample->h_expand[compptr->component_index];
-  v_expand = upsample->v_expand[compptr->component_index];
-
-  inrow = outrow = 0;
-  while (outrow < cinfo->max_v_samp_factor) {
-    /* Generate one output row with proper horizontal expansion */
-    inptr = input_data[inrow];
-    outptr = output_data[outrow];
-    outend = outptr + cinfo->output_width;
-    while (outptr < outend) {
-      invalue = *inptr++;	/* don't need GETJSAMPLE() here */
-      for (h = h_expand; h > 0; h--) {
-	*outptr++ = invalue;
-      }
-    }
-    /* Generate any additional output rows by duplicating the first one */
-    if (v_expand > 1) {
-      jcopy_sample_rows(output_data, outrow, output_data, outrow+1,
-			v_expand-1, cinfo->output_width);
-    }
-    inrow++;
-    outrow += v_expand;
-  }
-}
-
-
-/*
- * Fast processing for the common case of 2:1 horizontal and 1:1 vertical.
- * It's still a box filter.
- */
-
-METHODDEF(void)
-h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	       JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  JSAMPARRAY output_data = *output_data_ptr;
-  register JSAMPROW inptr, outptr;
-  register JSAMPLE invalue;
-  JSAMPROW outend;
-  int inrow;
-
-  for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) {
-    inptr = input_data[inrow];
-    outptr = output_data[inrow];
-    outend = outptr + cinfo->output_width;
-    while (outptr < outend) {
-      invalue = *inptr++;	/* don't need GETJSAMPLE() here */
-      *outptr++ = invalue;
-      *outptr++ = invalue;
-    }
-  }
-}
-
-
-/*
- * Fast processing for the common case of 2:1 horizontal and 2:1 vertical.
- * It's still a box filter.
- */
-
-METHODDEF(void)
-h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	       JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  JSAMPARRAY output_data = *output_data_ptr;
-  register JSAMPROW inptr, outptr;
-  register JSAMPLE invalue;
-  JSAMPROW outend;
-  int inrow, outrow;
-
-  inrow = outrow = 0;
-  while (outrow < cinfo->max_v_samp_factor) {
-    inptr = input_data[inrow];
-    outptr = output_data[outrow];
-    outend = outptr + cinfo->output_width;
-    while (outptr < outend) {
-      invalue = *inptr++;	/* don't need GETJSAMPLE() here */
-      *outptr++ = invalue;
-      *outptr++ = invalue;
-    }
-    jcopy_sample_rows(output_data, outrow, output_data, outrow+1,
-		      1, cinfo->output_width);
-    inrow++;
-    outrow += 2;
-  }
-}
-
-
-/*
- * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical.
- *
- * The upsampling algorithm is linear interpolation between pixel centers,
- * also known as a "triangle filter".  This is a good compromise between
- * speed and visual quality.  The centers of the output pixels are 1/4 and 3/4
- * of the way between input pixel centers.
- *
- * A note about the "bias" calculations: when rounding fractional values to
- * integer, we do not want to always round 0.5 up to the next integer.
- * If we did that, we'd introduce a noticeable bias towards larger values.
- * Instead, this code is arranged so that 0.5 will be rounded up or down at
- * alternate pixel locations (a simple ordered dither pattern).
- */
-
-METHODDEF(void)
-h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		     JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  JSAMPARRAY output_data = *output_data_ptr;
-  register JSAMPROW inptr, outptr;
-  register int invalue;
-  register JDIMENSION colctr;
-  int inrow;
-
-  for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) {
-    inptr = input_data[inrow];
-    outptr = output_data[inrow];
-    /* Special case for first column */
-    invalue = GETJSAMPLE(*inptr++);
-    *outptr++ = (JSAMPLE) invalue;
-    *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2);
-
-    for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) {
-      /* General case: 3/4 * nearer pixel + 1/4 * further pixel */
-      invalue = GETJSAMPLE(*inptr++) * 3;
-      *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2);
-      *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2);
-    }
-
-    /* Special case for last column */
-    invalue = GETJSAMPLE(*inptr);
-    *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2);
-    *outptr++ = (JSAMPLE) invalue;
-  }
-}
-
-
-/*
- * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical.
- * Again a triangle filter; see comments for h2v1 case, above.
- *
- * It is OK for us to reference the adjacent input rows because we demanded
- * context from the main buffer controller (see initialization code).
- */
-
-METHODDEF(void)
-h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		     JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
-{
-  JSAMPARRAY output_data = *output_data_ptr;
-  register JSAMPROW inptr0, inptr1, outptr;
-#if BITS_IN_JSAMPLE == 8
-  register int thiscolsum, lastcolsum, nextcolsum;
-#else
-  register INT32 thiscolsum, lastcolsum, nextcolsum;
-#endif
-  register JDIMENSION colctr;
-  int inrow, outrow, v;
-
-  inrow = outrow = 0;
-  while (outrow < cinfo->max_v_samp_factor) {
-    for (v = 0; v < 2; v++) {
-      /* inptr0 points to nearest input row, inptr1 points to next nearest */
-      inptr0 = input_data[inrow];
-      if (v == 0)		/* next nearest is row above */
-	inptr1 = input_data[inrow-1];
-      else			/* next nearest is row below */
-	inptr1 = input_data[inrow+1];
-      outptr = output_data[outrow++];
-
-      /* Special case for first column */
-      thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++);
-      nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++);
-      *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4);
-      *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4);
-      lastcolsum = thiscolsum; thiscolsum = nextcolsum;
-
-      for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) {
-	/* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */
-	/* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */
-	nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++);
-	*outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4);
-	*outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4);
-	lastcolsum = thiscolsum; thiscolsum = nextcolsum;
-      }
-
-      /* Special case for last column */
-      *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4);
-      *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4);
-    }
-    inrow++;
-  }
-}
-
-
-/*
- * Module initialization routine for upsampling.
- */
-
-GLOBAL(void)
-jinit_upsampler (j_decompress_ptr cinfo)
-{
-  my_upsample_ptr upsample;
-  int ci;
-  jpeg_component_info * compptr;
-  boolean need_buffer, do_fancy;
-  int h_in_group, v_in_group, h_out_group, v_out_group;
-
-  upsample = (my_upsample_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_upsampler));
-  cinfo->upsample = (struct jpeg_upsampler *) upsample;
-  upsample->pub.start_pass = start_pass_upsample;
-  upsample->pub.upsample = sep_upsample;
-  upsample->pub.need_context_rows = FALSE; /* until we find out differently */
-
-  if (cinfo->CCIR601_sampling)	/* this isn't supported */
-    ERREXIT(cinfo, JERR_CCIR601_NOTIMPL);
-
-  /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1,
-   * so don't ask for it.
-   */
-  do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1;
-
-  /* Verify we can handle the sampling factors, select per-component methods,
-   * and create storage as needed.
-   */
-  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
-       ci++, compptr++) {
-    /* Compute size of an "input group" after IDCT scaling.  This many samples
-     * are to be converted to max_h_samp_factor * max_v_samp_factor pixels.
-     */
-    h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) /
-		 cinfo->min_DCT_scaled_size;
-    v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) /
-		 cinfo->min_DCT_scaled_size;
-    h_out_group = cinfo->max_h_samp_factor;
-    v_out_group = cinfo->max_v_samp_factor;
-    upsample->rowgroup_height[ci] = v_in_group; /* save for use later */
-    need_buffer = TRUE;
-    if (! compptr->component_needed) {
-      /* Don't bother to upsample an uninteresting component. */
-      upsample->methods[ci] = noop_upsample;
-      need_buffer = FALSE;
-    } else if (h_in_group == h_out_group && v_in_group == v_out_group) {
-      /* Fullsize components can be processed without any work. */
-      upsample->methods[ci] = fullsize_upsample;
-      need_buffer = FALSE;
-    } else if (h_in_group * 2 == h_out_group &&
-	       v_in_group == v_out_group) {
-      /* Special cases for 2h1v upsampling */
-      if (do_fancy && compptr->downsampled_width > 2)
-	upsample->methods[ci] = h2v1_fancy_upsample;
-      else
-	upsample->methods[ci] = h2v1_upsample;
-    } else if (h_in_group * 2 == h_out_group &&
-	       v_in_group * 2 == v_out_group) {
-      /* Special cases for 2h2v upsampling */
-      if (do_fancy && compptr->downsampled_width > 2) {
-	upsample->methods[ci] = h2v2_fancy_upsample;
-	upsample->pub.need_context_rows = TRUE;
-      } else
-	upsample->methods[ci] = h2v2_upsample;
-    } else if ((h_out_group % h_in_group) == 0 &&
-	       (v_out_group % v_in_group) == 0) {
-      /* Generic integral-factors upsampling method */
-      upsample->methods[ci] = int_upsample;
-      upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group);
-      upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group);
-    } else
-      ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL);
-    if (need_buffer) {
-      upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray)
-	((j_common_ptr) cinfo, JPOOL_IMAGE,
-	 (JDIMENSION) jround_up((long) cinfo->output_width,
-				(long) cinfo->max_h_samp_factor),
-	 (JDIMENSION) cinfo->max_v_samp_factor);
-    }
-  }
-}
diff --git a/third_party/libjpeg/jerror.c b/third_party/libjpeg/jerror.c
deleted file mode 100644
index 98be1a4..0000000
--- a/third_party/libjpeg/jerror.c
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * jerror.c
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains simple error-reporting and trace-message routines.
- * These are suitable for Unix-like systems and others where writing to
- * stderr is the right thing to do.  Many applications will want to replace
- * some or all of these routines.
- *
- * If you define USE_WINDOWS_MESSAGEBOX in jconfig.h or in the makefile,
- * you get a Windows-specific hack to display error messages in a dialog box.
- * It ain't much, but it beats dropping error messages into the bit bucket,
- * which is what happens to output to stderr under most Windows C compilers.
- *
- * These routines are used by both the compression and decompression code.
- */
-
-/* this is not a core library module, so it doesn't define JPEG_INTERNALS */
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jversion.h"
-#include "jerror.h"
-
-#ifdef USE_WINDOWS_MESSAGEBOX
-#include <windows.h>
-#endif
-
-#if defined(STARBOARD)
-#include "starboard/common/log.h"
-#include "starboard/string.h"
-#include "starboard/system.h"
-#define sprintf SbStringFormatUnsafeF
-// Applications should not leave the standard error handler registered.
-#define exit(x) SbSystemBreakIntoDebugger(x)
-#endif
-
-#ifndef EXIT_FAILURE		/* define exit() codes if not provided */
-#define EXIT_FAILURE  1
-#endif
-
-
-/*
- * Create the message string table.
- * We do this from the master message list in jerror.h by re-reading
- * jerror.h with a suitable definition for macro JMESSAGE.
- * The message table is made an external symbol just in case any applications
- * want to refer to it directly.
- */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jpeg_std_message_table	jMsgTable
-#endif
-
-#define JMESSAGE(code,string)	string ,
-
-const char * const jpeg_std_message_table[] = {
-#include "jerror.h"
-  NULL
-};
-
-
-/*
- * Error exit handler: must not return to caller.
- *
- * Applications may override this if they want to get control back after
- * an error.  Typically one would longjmp somewhere instead of exiting.
- * The setjmp buffer can be made a private field within an expanded error
- * handler object.  Note that the info needed to generate an error message
- * is stored in the error object, so you can generate the message now or
- * later, at your convenience.
- * You should make sure that the JPEG object is cleaned up (with jpeg_abort
- * or jpeg_destroy) at some point.
- */
-
-METHODDEF(void)
-error_exit (j_common_ptr cinfo)
-{
-  /* Always display the message */
-  (*cinfo->err->output_message) (cinfo);
-
-  /* Let the memory manager delete any temp files before we die */
-  jpeg_destroy(cinfo);
-
-  exit(EXIT_FAILURE);
-}
-
-
-/*
- * Actual output of an error or trace message.
- * Applications may override this method to send JPEG messages somewhere
- * other than stderr.
- *
- * On Windows, printing to stderr is generally completely useless,
- * so we provide optional code to produce an error-dialog popup.
- * Most Windows applications will still prefer to override this routine,
- * but if they don't, it'll do something at least marginally useful.
- *
- * NOTE: to use the library in an environment that doesn't support the
- * C stdio library, you may have to delete the call to fprintf() entirely,
- * not just not use this routine.
- */
-
-METHODDEF(void)
-output_message (j_common_ptr cinfo)
-{
-  char buffer[JMSG_LENGTH_MAX];
-
-  /* Create the message */
-  (*cinfo->err->format_message) (cinfo, buffer);
-
-#ifdef USE_WINDOWS_MESSAGEBOX
-  /* Display it in a message dialog box */
-  MessageBox(GetActiveWindow(), buffer, "JPEG Library Error",
-	     MB_OK | MB_ICONERROR);
-#elif defined(STARBOARD)
-  SbLogFormatF("%s\n", buffer);
-#else
-  /* Send it to stderr, adding a newline */
-  fprintf(stderr, "%s\n", buffer);
-#endif
-}
-
-
-/*
- * Decide whether to emit a trace or warning message.
- * msg_level is one of:
- *   -1: recoverable corrupt-data warning, may want to abort.
- *    0: important advisory messages (always display to user).
- *    1: first level of tracing detail.
- *    2,3,...: successively more detailed tracing messages.
- * An application might override this method if it wanted to abort on warnings
- * or change the policy about which messages to display.
- */
-
-METHODDEF(void)
-emit_message (j_common_ptr cinfo, int msg_level)
-{
-  struct jpeg_error_mgr * err = cinfo->err;
-
-  if (msg_level < 0) {
-    /* It's a warning message.  Since corrupt files may generate many warnings,
-     * the policy implemented here is to show only the first warning,
-     * unless trace_level >= 3.
-     */
-    if (err->num_warnings == 0 || err->trace_level >= 3)
-      (*err->output_message) (cinfo);
-    /* Always count warnings in num_warnings. */
-    err->num_warnings++;
-  } else {
-    /* It's a trace message.  Show it if trace_level >= msg_level. */
-    if (err->trace_level >= msg_level)
-      (*err->output_message) (cinfo);
-  }
-}
-
-
-/*
- * Format a message string for the most recent JPEG error or message.
- * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX
- * characters.  Note that no '\n' character is added to the string.
- * Few applications should need to override this method.
- */
-
-METHODDEF(void)
-format_message (j_common_ptr cinfo, char * buffer)
-{
-  struct jpeg_error_mgr * err = cinfo->err;
-  int msg_code = err->msg_code;
-  const char * msgtext = NULL;
-  const char * msgptr;
-  char ch;
-  boolean isstring;
-
-  /* Look up message string in proper table */
-  if (msg_code > 0 && msg_code <= err->last_jpeg_message) {
-    msgtext = err->jpeg_message_table[msg_code];
-  } else if (err->addon_message_table != NULL &&
-	     msg_code >= err->first_addon_message &&
-	     msg_code <= err->last_addon_message) {
-    msgtext = err->addon_message_table[msg_code - err->first_addon_message];
-  }
-
-  /* Defend against bogus message number */
-  if (msgtext == NULL) {
-    err->msg_parm.i[0] = msg_code;
-    msgtext = err->jpeg_message_table[0];
-  }
-
-  /* Check for string parameter, as indicated by %s in the message text */
-  isstring = FALSE;
-  msgptr = msgtext;
-  while ((ch = *msgptr++) != '\0') {
-    if (ch == '%') {
-      if (*msgptr == 's') isstring = TRUE;
-      break;
-    }
-  }
-
-  /* Format the message into the passed buffer */
-  if (isstring)
-    sprintf(buffer, msgtext, err->msg_parm.s);
-  else
-    sprintf(buffer, msgtext,
-	    err->msg_parm.i[0], err->msg_parm.i[1],
-	    err->msg_parm.i[2], err->msg_parm.i[3],
-	    err->msg_parm.i[4], err->msg_parm.i[5],
-	    err->msg_parm.i[6], err->msg_parm.i[7]);
-}
-
-
-/*
- * Reset error state variables at start of a new image.
- * This is called during compression startup to reset trace/error
- * processing to default state, without losing any application-specific
- * method pointers.  An application might possibly want to override
- * this method if it has additional error processing state.
- */
-
-METHODDEF(void)
-reset_error_mgr (j_common_ptr cinfo)
-{
-  cinfo->err->num_warnings = 0;
-  /* trace_level is not reset since it is an application-supplied parameter */
-  cinfo->err->msg_code = 0;	/* may be useful as a flag for "no error" */
-}
-
-
-/*
- * Fill in the standard error-handling methods in a jpeg_error_mgr object.
- * Typical call is:
- *	struct jpeg_compress_struct cinfo;
- *	struct jpeg_error_mgr err;
- *
- *	cinfo.err = jpeg_std_error(&err);
- * after which the application may override some of the methods.
- */
-
-GLOBAL(struct jpeg_error_mgr *)
-jpeg_std_error (struct jpeg_error_mgr * err)
-{
-  err->error_exit = error_exit;
-  err->emit_message = emit_message;
-  err->output_message = output_message;
-  err->format_message = format_message;
-  err->reset_error_mgr = reset_error_mgr;
-
-  err->trace_level = 0;		/* default = no tracing */
-  err->num_warnings = 0;	/* no warnings emitted yet */
-  err->msg_code = 0;		/* may be useful as a flag for "no error" */
-
-  /* Initialize message table pointers */
-  err->jpeg_message_table = jpeg_std_message_table;
-  err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1;
-
-  err->addon_message_table = NULL;
-  err->first_addon_message = 0;	/* for safety */
-  err->last_addon_message = 0;
-
-  return err;
-}
diff --git a/third_party/libjpeg/jerror.h b/third_party/libjpeg/jerror.h
deleted file mode 100644
index fc2fffe..0000000
--- a/third_party/libjpeg/jerror.h
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * jerror.h
- *
- * Copyright (C) 1994-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file defines the error and message codes for the JPEG library.
- * Edit this file to add new codes, or to translate the message strings to
- * some other language.
- * A set of error-reporting macros are defined too.  Some applications using
- * the JPEG library may wish to include this file to get the error codes
- * and/or the macros.
- */
-
-/*
- * To define the enum list of message codes, include this file without
- * defining macro JMESSAGE.  To create a message string table, include it
- * again with a suitable JMESSAGE definition (see jerror.c for an example).
- */
-#ifndef JMESSAGE
-#ifndef JERROR_H
-/* First time through, define the enum list */
-#define JMAKE_ENUM_LIST
-#else
-/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */
-#define JMESSAGE(code,string)
-#endif /* JERROR_H */
-#endif /* JMESSAGE */
-
-#ifdef JMAKE_ENUM_LIST
-
-typedef enum {
-
-#define JMESSAGE(code,string)	code ,
-
-#endif /* JMAKE_ENUM_LIST */
-
-JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */
-
-/* For maintenance convenience, list is alphabetical by message code name */
-JMESSAGE(JERR_ARITH_NOTIMPL,
-	 "Sorry, there are legal restrictions on arithmetic coding")
-JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix")
-JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix")
-JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode")
-JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS")
-JMESSAGE(JERR_BAD_DCT_COEF, "DCT coefficient out of range")
-JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported")
-JMESSAGE(JERR_BAD_HUFF_TABLE, "Bogus Huffman table definition")
-JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace")
-JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace")
-JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length")
-JMESSAGE(JERR_BAD_LIB_VERSION,
-	 "Wrong JPEG library version: library is %d, caller expects %d")
-JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan")
-JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d")
-JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d")
-JMESSAGE(JERR_BAD_PROGRESSION,
-	 "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d")
-JMESSAGE(JERR_BAD_PROG_SCRIPT,
-	 "Invalid progressive parameters at scan script entry %d")
-JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors")
-JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d")
-JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d")
-JMESSAGE(JERR_BAD_STRUCT_SIZE,
-	 "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u")
-JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access")
-JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small")
-JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here")
-JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet")
-JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d")
-JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request")
-JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d")
-JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x")
-JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d")
-JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d")
-JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)")
-JMESSAGE(JERR_EMS_READ, "Read from EMS failed")
-JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed")
-JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan")
-JMESSAGE(JERR_FILE_READ, "Input file read error")
-JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?")
-JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet")
-JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow")
-JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry")
-JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels")
-JMESSAGE(JERR_INPUT_EMPTY, "Empty input file")
-JMESSAGE(JERR_INPUT_EOF, "Premature end of input file")
-JMESSAGE(JERR_MISMATCHED_QUANT_TABLE,
-	 "Cannot transcode due to multiple use of quantization table %d")
-JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data")
-JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change")
-JMESSAGE(JERR_NOTIMPL, "Not implemented yet")
-JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time")
-JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported")
-JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined")
-JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image")
-JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined")
-JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x")
-JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)")
-JMESSAGE(JERR_QUANT_COMPONENTS,
-	 "Cannot quantize more than %d color components")
-JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors")
-JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors")
-JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers")
-JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker")
-JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x")
-JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers")
-JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF")
-JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s")
-JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file")
-JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file")
-JMESSAGE(JERR_TFILE_WRITE,
-	 "Write failed on temporary file --- out of disk space?")
-JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines")
-JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x")
-JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up")
-JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation")
-JMESSAGE(JERR_XMS_READ, "Read from XMS failed")
-JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed")
-JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT)
-JMESSAGE(JMSG_VERSION, JVERSION)
-JMESSAGE(JTRC_16BIT_TABLES,
-	 "Caution: quantization tables are too coarse for baseline JPEG")
-JMESSAGE(JTRC_ADOBE,
-	 "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d")
-JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u")
-JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u")
-JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x")
-JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x")
-JMESSAGE(JTRC_DQT, "Define Quantization Table %d  precision %d")
-JMESSAGE(JTRC_DRI, "Define Restart Interval %u")
-JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u")
-JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u")
-JMESSAGE(JTRC_EOI, "End Of Image")
-JMESSAGE(JTRC_HUFFBITS, "        %3d %3d %3d %3d %3d %3d %3d %3d")
-JMESSAGE(JTRC_JFIF, "JFIF APP0 marker: version %d.%02d, density %dx%d  %d")
-JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE,
-	 "Warning: thumbnail image size does not match data length %u")
-JMESSAGE(JTRC_JFIF_EXTENSION,
-	 "JFIF extension marker: type 0x%02x, length %u")
-JMESSAGE(JTRC_JFIF_THUMBNAIL, "    with %d x %d thumbnail image")
-JMESSAGE(JTRC_MISC_MARKER, "Miscellaneous marker 0x%02x, length %u")
-JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x")
-JMESSAGE(JTRC_QUANTVALS, "        %4u %4u %4u %4u %4u %4u %4u %4u")
-JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors")
-JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors")
-JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization")
-JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d")
-JMESSAGE(JTRC_RST, "RST%d")
-JMESSAGE(JTRC_SMOOTH_NOTIMPL,
-	 "Smoothing not supported with nonstandard sampling ratios")
-JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d")
-JMESSAGE(JTRC_SOF_COMPONENT, "    Component %d: %dhx%dv q=%d")
-JMESSAGE(JTRC_SOI, "Start of Image")
-JMESSAGE(JTRC_SOS, "Start Of Scan: %d components")
-JMESSAGE(JTRC_SOS_COMPONENT, "    Component %d: dc=%d ac=%d")
-JMESSAGE(JTRC_SOS_PARAMS, "  Ss=%d, Se=%d, Ah=%d, Al=%d")
-JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s")
-JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s")
-JMESSAGE(JTRC_THUMB_JPEG,
-	 "JFIF extension marker: JPEG-compressed thumbnail image, length %u")
-JMESSAGE(JTRC_THUMB_PALETTE,
-	 "JFIF extension marker: palette thumbnail image, length %u")
-JMESSAGE(JTRC_THUMB_RGB,
-	 "JFIF extension marker: RGB thumbnail image, length %u")
-JMESSAGE(JTRC_UNKNOWN_IDS,
-	 "Unrecognized component IDs %d %d %d, assuming YCbCr")
-JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u")
-JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u")
-JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d")
-JMESSAGE(JWRN_BOGUS_PROGRESSION,
-	 "Inconsistent progression sequence for component %d coefficient %d")
-JMESSAGE(JWRN_EXTRANEOUS_DATA,
-	 "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x")
-JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment")
-JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code")
-JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d")
-JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file")
-JMESSAGE(JWRN_MUST_RESYNC,
-	 "Corrupt JPEG data: found marker 0x%02x instead of RST%d")
-JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG")
-JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines")
-
-#ifdef JMAKE_ENUM_LIST
-
-  JMSG_LASTMSGCODE
-} J_MESSAGE_CODE;
-
-#undef JMAKE_ENUM_LIST
-#endif /* JMAKE_ENUM_LIST */
-
-/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */
-#undef JMESSAGE
-
-
-#ifndef JERROR_H
-#define JERROR_H
-
-/* Macros to simplify using the error and trace message stuff */
-/* The first parameter is either type of cinfo pointer */
-
-/* Fatal errors (print message and exit) */
-#define ERREXIT(cinfo,code)  \
-  ((cinfo)->err->msg_code = (code), \
-   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
-#define ERREXIT1(cinfo,code,p1)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
-#define ERREXIT2(cinfo,code,p1,p2)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (cinfo)->err->msg_parm.i[1] = (p2), \
-   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
-#define ERREXIT3(cinfo,code,p1,p2,p3)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (cinfo)->err->msg_parm.i[1] = (p2), \
-   (cinfo)->err->msg_parm.i[2] = (p3), \
-   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
-#define ERREXIT4(cinfo,code,p1,p2,p3,p4)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (cinfo)->err->msg_parm.i[1] = (p2), \
-   (cinfo)->err->msg_parm.i[2] = (p3), \
-   (cinfo)->err->msg_parm.i[3] = (p4), \
-   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
-#define ERREXITS(cinfo,code,str)  \
-  ((cinfo)->err->msg_code = (code), \
-   strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \
-   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
-
-#define MAKESTMT(stuff)		do { stuff } while (0)
-
-/* Nonfatal errors (we can keep going, but the data is probably corrupt) */
-#define WARNMS(cinfo,code)  \
-  ((cinfo)->err->msg_code = (code), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1))
-#define WARNMS1(cinfo,code,p1)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1))
-#define WARNMS2(cinfo,code,p1,p2)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (cinfo)->err->msg_parm.i[1] = (p2), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1))
-
-/* Informational/debugging messages */
-#define TRACEMS(cinfo,lvl,code)  \
-  ((cinfo)->err->msg_code = (code), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)))
-#define TRACEMS1(cinfo,lvl,code,p1)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)))
-#define TRACEMS2(cinfo,lvl,code,p1,p2)  \
-  ((cinfo)->err->msg_code = (code), \
-   (cinfo)->err->msg_parm.i[0] = (p1), \
-   (cinfo)->err->msg_parm.i[1] = (p2), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)))
-#define TRACEMS3(cinfo,lvl,code,p1,p2,p3)  \
-  MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \
-	   _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \
-	   (cinfo)->err->msg_code = (code); \
-	   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); )
-#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4)  \
-  MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \
-	   _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \
-	   (cinfo)->err->msg_code = (code); \
-	   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); )
-#define TRACEMS5(cinfo,lvl,code,p1,p2,p3,p4,p5)  \
-  MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \
-	   _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \
-	   _mp[4] = (p5); \
-	   (cinfo)->err->msg_code = (code); \
-	   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); )
-#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8)  \
-  MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \
-	   _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \
-	   _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \
-	   (cinfo)->err->msg_code = (code); \
-	   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); )
-#define TRACEMSS(cinfo,lvl,code,str)  \
-  ((cinfo)->err->msg_code = (code), \
-   strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \
-   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)))
-
-#endif /* JERROR_H */
diff --git a/third_party/libjpeg/jfdctflt.c b/third_party/libjpeg/jfdctflt.c
deleted file mode 100644
index 79d7a00..0000000
--- a/third_party/libjpeg/jfdctflt.c
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * jfdctflt.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains a floating-point implementation of the
- * forward DCT (Discrete Cosine Transform).
- *
- * This implementation should be more accurate than either of the integer
- * DCT implementations.  However, it may not give the same results on all
- * machines because of differences in roundoff behavior.  Speed will depend
- * on the hardware's floating point capacity.
- *
- * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT
- * on each column.  Direct algorithms are also available, but they are
- * much more complex and seem not to be any faster when reduced to code.
- *
- * This implementation is based on Arai, Agui, and Nakajima's algorithm for
- * scaled DCT.  Their original paper (Trans. IEICE E-71(11):1095) is in
- * Japanese, but the algorithm is described in the Pennebaker & Mitchell
- * JPEG textbook (see REFERENCES section in file README).  The following code
- * is based directly on figure 4-8 in P&M.
- * While an 8-point DCT cannot be done in less than 11 multiplies, it is
- * possible to arrange the computation so that many of the multiplies are
- * simple scalings of the final outputs.  These multiplies can then be
- * folded into the multiplications or divisions by the JPEG quantization
- * table entries.  The AA&N method leaves only 5 multiplies and 29 adds
- * to be done in the DCT itself.
- * The primary disadvantage of this method is that with a fixed-point
- * implementation, accuracy is lost due to imprecise representation of the
- * scaled quantization values.  However, that problem does not arise if
- * we use floating point arithmetic.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef DCT_FLOAT_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/*
- * Perform the forward DCT on one block of samples.
- */
-
-GLOBAL(void)
-jpeg_fdct_float (FAST_FLOAT * data)
-{
-  FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
-  FAST_FLOAT tmp10, tmp11, tmp12, tmp13;
-  FAST_FLOAT z1, z2, z3, z4, z5, z11, z13;
-  FAST_FLOAT *dataptr;
-  int ctr;
-
-  /* Pass 1: process rows. */
-
-  dataptr = data;
-  for (ctr = DCTSIZE-1; ctr >= 0; ctr--) {
-    tmp0 = dataptr[0] + dataptr[7];
-    tmp7 = dataptr[0] - dataptr[7];
-    tmp1 = dataptr[1] + dataptr[6];
-    tmp6 = dataptr[1] - dataptr[6];
-    tmp2 = dataptr[2] + dataptr[5];
-    tmp5 = dataptr[2] - dataptr[5];
-    tmp3 = dataptr[3] + dataptr[4];
-    tmp4 = dataptr[3] - dataptr[4];
-    
-    /* Even part */
-    
-    tmp10 = tmp0 + tmp3;	/* phase 2 */
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    dataptr[0] = tmp10 + tmp11; /* phase 3 */
-    dataptr[4] = tmp10 - tmp11;
-    
-    z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */
-    dataptr[2] = tmp13 + z1;	/* phase 5 */
-    dataptr[6] = tmp13 - z1;
-    
-    /* Odd part */
-
-    tmp10 = tmp4 + tmp5;	/* phase 2 */
-    tmp11 = tmp5 + tmp6;
-    tmp12 = tmp6 + tmp7;
-
-    /* The rotator is modified from fig 4-8 to avoid extra negations. */
-    z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */
-    z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */
-    z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */
-    z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */
-
-    z11 = tmp7 + z3;		/* phase 5 */
-    z13 = tmp7 - z3;
-
-    dataptr[5] = z13 + z2;	/* phase 6 */
-    dataptr[3] = z13 - z2;
-    dataptr[1] = z11 + z4;
-    dataptr[7] = z11 - z4;
-
-    dataptr += DCTSIZE;		/* advance pointer to next row */
-  }
-
-  /* Pass 2: process columns. */
-
-  dataptr = data;
-  for (ctr = DCTSIZE-1; ctr >= 0; ctr--) {
-    tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7];
-    tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7];
-    tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6];
-    tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6];
-    tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5];
-    tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5];
-    tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4];
-    tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4];
-    
-    /* Even part */
-    
-    tmp10 = tmp0 + tmp3;	/* phase 2 */
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */
-    dataptr[DCTSIZE*4] = tmp10 - tmp11;
-    
-    z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */
-    dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */
-    dataptr[DCTSIZE*6] = tmp13 - z1;
-    
-    /* Odd part */
-
-    tmp10 = tmp4 + tmp5;	/* phase 2 */
-    tmp11 = tmp5 + tmp6;
-    tmp12 = tmp6 + tmp7;
-
-    /* The rotator is modified from fig 4-8 to avoid extra negations. */
-    z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */
-    z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */
-    z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */
-    z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */
-
-    z11 = tmp7 + z3;		/* phase 5 */
-    z13 = tmp7 - z3;
-
-    dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */
-    dataptr[DCTSIZE*3] = z13 - z2;
-    dataptr[DCTSIZE*1] = z11 + z4;
-    dataptr[DCTSIZE*7] = z11 - z4;
-
-    dataptr++;			/* advance pointer to next column */
-  }
-}
-
-#endif /* DCT_FLOAT_SUPPORTED */
diff --git a/third_party/libjpeg/jfdctfst.c b/third_party/libjpeg/jfdctfst.c
deleted file mode 100644
index ccb378a..0000000
--- a/third_party/libjpeg/jfdctfst.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * jfdctfst.c
- *
- * Copyright (C) 1994-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains a fast, not so accurate integer implementation of the
- * forward DCT (Discrete Cosine Transform).
- *
- * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT
- * on each column.  Direct algorithms are also available, but they are
- * much more complex and seem not to be any faster when reduced to code.
- *
- * This implementation is based on Arai, Agui, and Nakajima's algorithm for
- * scaled DCT.  Their original paper (Trans. IEICE E-71(11):1095) is in
- * Japanese, but the algorithm is described in the Pennebaker & Mitchell
- * JPEG textbook (see REFERENCES section in file README).  The following code
- * is based directly on figure 4-8 in P&M.
- * While an 8-point DCT cannot be done in less than 11 multiplies, it is
- * possible to arrange the computation so that many of the multiplies are
- * simple scalings of the final outputs.  These multiplies can then be
- * folded into the multiplications or divisions by the JPEG quantization
- * table entries.  The AA&N method leaves only 5 multiplies and 29 adds
- * to be done in the DCT itself.
- * The primary disadvantage of this method is that with fixed-point math,
- * accuracy is lost due to imprecise representation of the scaled
- * quantization values.  The smaller the quantization table entry, the less
- * precise the scaled value, so this implementation does worse with high-
- * quality-setting files than with low-quality ones.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef DCT_IFAST_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/* Scaling decisions are generally the same as in the LL&M algorithm;
- * see jfdctint.c for more details.  However, we choose to descale
- * (right shift) multiplication products as soon as they are formed,
- * rather than carrying additional fractional bits into subsequent additions.
- * This compromises accuracy slightly, but it lets us save a few shifts.
- * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples)
- * everywhere except in the multiplications proper; this saves a good deal
- * of work on 16-bit-int machines.
- *
- * Again to save a few shifts, the intermediate results between pass 1 and
- * pass 2 are not upscaled, but are represented only to integral precision.
- *
- * A final compromise is to represent the multiplicative constants to only
- * 8 fractional bits, rather than 13.  This saves some shifting work on some
- * machines, and may also reduce the cost of multiplication (since there
- * are fewer one-bits in the constants).
- */
-
-#define CONST_BITS  8
-
-
-/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus
- * causing a lot of useless floating-point operations at run time.
- * To get around this we use the following pre-calculated constants.
- * If you change CONST_BITS you may want to add appropriate values.
- * (With a reasonable C compiler, you can just rely on the FIX() macro...)
- */
-
-#if CONST_BITS == 8
-#define FIX_0_382683433  ((INT32)   98)		/* FIX(0.382683433) */
-#define FIX_0_541196100  ((INT32)  139)		/* FIX(0.541196100) */
-#define FIX_0_707106781  ((INT32)  181)		/* FIX(0.707106781) */
-#define FIX_1_306562965  ((INT32)  334)		/* FIX(1.306562965) */
-#else
-#define FIX_0_382683433  FIX(0.382683433)
-#define FIX_0_541196100  FIX(0.541196100)
-#define FIX_0_707106781  FIX(0.707106781)
-#define FIX_1_306562965  FIX(1.306562965)
-#endif
-
-
-/* We can gain a little more speed, with a further compromise in accuracy,
- * by omitting the addition in a descaling shift.  This yields an incorrectly
- * rounded result half the time...
- */
-
-#ifndef USE_ACCURATE_ROUNDING
-#undef DESCALE
-#define DESCALE(x,n)  RIGHT_SHIFT(x, n)
-#endif
-
-
-/* Multiply a DCTELEM variable by an INT32 constant, and immediately
- * descale to yield a DCTELEM result.
- */
-
-#define MULTIPLY(var,const)  ((DCTELEM) DESCALE((var) * (const), CONST_BITS))
-
-
-/*
- * Perform the forward DCT on one block of samples.
- */
-
-GLOBAL(void)
-jpeg_fdct_ifast (DCTELEM * data)
-{
-  DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
-  DCTELEM tmp10, tmp11, tmp12, tmp13;
-  DCTELEM z1, z2, z3, z4, z5, z11, z13;
-  DCTELEM *dataptr;
-  int ctr;
-  SHIFT_TEMPS
-
-  /* Pass 1: process rows. */
-
-  dataptr = data;
-  for (ctr = DCTSIZE-1; ctr >= 0; ctr--) {
-    tmp0 = dataptr[0] + dataptr[7];
-    tmp7 = dataptr[0] - dataptr[7];
-    tmp1 = dataptr[1] + dataptr[6];
-    tmp6 = dataptr[1] - dataptr[6];
-    tmp2 = dataptr[2] + dataptr[5];
-    tmp5 = dataptr[2] - dataptr[5];
-    tmp3 = dataptr[3] + dataptr[4];
-    tmp4 = dataptr[3] - dataptr[4];
-    
-    /* Even part */
-    
-    tmp10 = tmp0 + tmp3;	/* phase 2 */
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    dataptr[0] = tmp10 + tmp11; /* phase 3 */
-    dataptr[4] = tmp10 - tmp11;
-    
-    z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */
-    dataptr[2] = tmp13 + z1;	/* phase 5 */
-    dataptr[6] = tmp13 - z1;
-    
-    /* Odd part */
-
-    tmp10 = tmp4 + tmp5;	/* phase 2 */
-    tmp11 = tmp5 + tmp6;
-    tmp12 = tmp6 + tmp7;
-
-    /* The rotator is modified from fig 4-8 to avoid extra negations. */
-    z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */
-    z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */
-    z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */
-    z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */
-
-    z11 = tmp7 + z3;		/* phase 5 */
-    z13 = tmp7 - z3;
-
-    dataptr[5] = z13 + z2;	/* phase 6 */
-    dataptr[3] = z13 - z2;
-    dataptr[1] = z11 + z4;
-    dataptr[7] = z11 - z4;
-
-    dataptr += DCTSIZE;		/* advance pointer to next row */
-  }
-
-  /* Pass 2: process columns. */
-
-  dataptr = data;
-  for (ctr = DCTSIZE-1; ctr >= 0; ctr--) {
-    tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7];
-    tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7];
-    tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6];
-    tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6];
-    tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5];
-    tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5];
-    tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4];
-    tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4];
-    
-    /* Even part */
-    
-    tmp10 = tmp0 + tmp3;	/* phase 2 */
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */
-    dataptr[DCTSIZE*4] = tmp10 - tmp11;
-    
-    z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */
-    dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */
-    dataptr[DCTSIZE*6] = tmp13 - z1;
-    
-    /* Odd part */
-
-    tmp10 = tmp4 + tmp5;	/* phase 2 */
-    tmp11 = tmp5 + tmp6;
-    tmp12 = tmp6 + tmp7;
-
-    /* The rotator is modified from fig 4-8 to avoid extra negations. */
-    z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */
-    z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */
-    z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */
-    z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */
-
-    z11 = tmp7 + z3;		/* phase 5 */
-    z13 = tmp7 - z3;
-
-    dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */
-    dataptr[DCTSIZE*3] = z13 - z2;
-    dataptr[DCTSIZE*1] = z11 + z4;
-    dataptr[DCTSIZE*7] = z11 - z4;
-
-    dataptr++;			/* advance pointer to next column */
-  }
-}
-
-#endif /* DCT_IFAST_SUPPORTED */
diff --git a/third_party/libjpeg/jfdctint.c b/third_party/libjpeg/jfdctint.c
deleted file mode 100644
index 0a78b64..0000000
--- a/third_party/libjpeg/jfdctint.c
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * jfdctint.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains a slow-but-accurate integer implementation of the
- * forward DCT (Discrete Cosine Transform).
- *
- * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT
- * on each column.  Direct algorithms are also available, but they are
- * much more complex and seem not to be any faster when reduced to code.
- *
- * This implementation is based on an algorithm described in
- *   C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT
- *   Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics,
- *   Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991.
- * The primary algorithm described there uses 11 multiplies and 29 adds.
- * We use their alternate method with 12 multiplies and 32 adds.
- * The advantage of this method is that no data path contains more than one
- * multiplication; this allows a very simple and accurate implementation in
- * scaled fixed-point arithmetic, with a minimal number of shifts.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef DCT_ISLOW_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/*
- * The poop on this scaling stuff is as follows:
- *
- * Each 1-D DCT step produces outputs which are a factor of sqrt(N)
- * larger than the true DCT outputs.  The final outputs are therefore
- * a factor of N larger than desired; since N=8 this can be cured by
- * a simple right shift at the end of the algorithm.  The advantage of
- * this arrangement is that we save two multiplications per 1-D DCT,
- * because the y0 and y4 outputs need not be divided by sqrt(N).
- * In the IJG code, this factor of 8 is removed by the quantization step
- * (in jcdctmgr.c), NOT in this module.
- *
- * We have to do addition and subtraction of the integer inputs, which
- * is no problem, and multiplication by fractional constants, which is
- * a problem to do in integer arithmetic.  We multiply all the constants
- * by CONST_SCALE and convert them to integer constants (thus retaining
- * CONST_BITS bits of precision in the constants).  After doing a
- * multiplication we have to divide the product by CONST_SCALE, with proper
- * rounding, to produce the correct output.  This division can be done
- * cheaply as a right shift of CONST_BITS bits.  We postpone shifting
- * as long as possible so that partial sums can be added together with
- * full fractional precision.
- *
- * The outputs of the first pass are scaled up by PASS1_BITS bits so that
- * they are represented to better-than-integral precision.  These outputs
- * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word
- * with the recommended scaling.  (For 12-bit sample data, the intermediate
- * array is INT32 anyway.)
- *
- * To avoid overflow of the 32-bit intermediate results in pass 2, we must
- * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26.  Error analysis
- * shows that the values given below are the most effective.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define CONST_BITS  13
-#define PASS1_BITS  2
-#else
-#define CONST_BITS  13
-#define PASS1_BITS  1		/* lose a little precision to avoid overflow */
-#endif
-
-/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus
- * causing a lot of useless floating-point operations at run time.
- * To get around this we use the following pre-calculated constants.
- * If you change CONST_BITS you may want to add appropriate values.
- * (With a reasonable C compiler, you can just rely on the FIX() macro...)
- */
-
-#if CONST_BITS == 13
-#define FIX_0_298631336  ((INT32)  2446)	/* FIX(0.298631336) */
-#define FIX_0_390180644  ((INT32)  3196)	/* FIX(0.390180644) */
-#define FIX_0_541196100  ((INT32)  4433)	/* FIX(0.541196100) */
-#define FIX_0_765366865  ((INT32)  6270)	/* FIX(0.765366865) */
-#define FIX_0_899976223  ((INT32)  7373)	/* FIX(0.899976223) */
-#define FIX_1_175875602  ((INT32)  9633)	/* FIX(1.175875602) */
-#define FIX_1_501321110  ((INT32)  12299)	/* FIX(1.501321110) */
-#define FIX_1_847759065  ((INT32)  15137)	/* FIX(1.847759065) */
-#define FIX_1_961570560  ((INT32)  16069)	/* FIX(1.961570560) */
-#define FIX_2_053119869  ((INT32)  16819)	/* FIX(2.053119869) */
-#define FIX_2_562915447  ((INT32)  20995)	/* FIX(2.562915447) */
-#define FIX_3_072711026  ((INT32)  25172)	/* FIX(3.072711026) */
-#else
-#define FIX_0_298631336  FIX(0.298631336)
-#define FIX_0_390180644  FIX(0.390180644)
-#define FIX_0_541196100  FIX(0.541196100)
-#define FIX_0_765366865  FIX(0.765366865)
-#define FIX_0_899976223  FIX(0.899976223)
-#define FIX_1_175875602  FIX(1.175875602)
-#define FIX_1_501321110  FIX(1.501321110)
-#define FIX_1_847759065  FIX(1.847759065)
-#define FIX_1_961570560  FIX(1.961570560)
-#define FIX_2_053119869  FIX(2.053119869)
-#define FIX_2_562915447  FIX(2.562915447)
-#define FIX_3_072711026  FIX(3.072711026)
-#endif
-
-
-/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result.
- * For 8-bit samples with the recommended scaling, all the variable
- * and constant values involved are no more than 16 bits wide, so a
- * 16x16->32 bit multiply can be used instead of a full 32x32 multiply.
- * For 12-bit samples, a full 32-bit multiplication will be needed.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define MULTIPLY(var,const)  MULTIPLY16C16(var,const)
-#else
-#define MULTIPLY(var,const)  ((var) * (const))
-#endif
-
-
-/*
- * Perform the forward DCT on one block of samples.
- */
-
-GLOBAL(void)
-jpeg_fdct_islow (DCTELEM * data)
-{
-  INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
-  INT32 tmp10, tmp11, tmp12, tmp13;
-  INT32 z1, z2, z3, z4, z5;
-  DCTELEM *dataptr;
-  int ctr;
-  SHIFT_TEMPS
-
-  /* Pass 1: process rows. */
-  /* Note results are scaled up by sqrt(8) compared to a true DCT; */
-  /* furthermore, we scale the results by 2**PASS1_BITS. */
-
-  dataptr = data;
-  for (ctr = DCTSIZE-1; ctr >= 0; ctr--) {
-    tmp0 = dataptr[0] + dataptr[7];
-    tmp7 = dataptr[0] - dataptr[7];
-    tmp1 = dataptr[1] + dataptr[6];
-    tmp6 = dataptr[1] - dataptr[6];
-    tmp2 = dataptr[2] + dataptr[5];
-    tmp5 = dataptr[2] - dataptr[5];
-    tmp3 = dataptr[3] + dataptr[4];
-    tmp4 = dataptr[3] - dataptr[4];
-    
-    /* Even part per LL&M figure 1 --- note that published figure is faulty;
-     * rotator "sqrt(2)*c1" should be "sqrt(2)*c6".
-     */
-    
-    tmp10 = tmp0 + tmp3;
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    dataptr[0] = (DCTELEM) ((tmp10 + tmp11) << PASS1_BITS);
-    dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << PASS1_BITS);
-    
-    z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100);
-    dataptr[2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865),
-				   CONST_BITS-PASS1_BITS);
-    dataptr[6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065),
-				   CONST_BITS-PASS1_BITS);
-    
-    /* Odd part per figure 8 --- note paper omits factor of sqrt(2).
-     * cK represents cos(K*pi/16).
-     * i0..i3 in the paper are tmp4..tmp7 here.
-     */
-    
-    z1 = tmp4 + tmp7;
-    z2 = tmp5 + tmp6;
-    z3 = tmp4 + tmp6;
-    z4 = tmp5 + tmp7;
-    z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */
-    
-    tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */
-    tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */
-    tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */
-    tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */
-    z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */
-    z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */
-    z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */
-    z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */
-    
-    z3 += z5;
-    z4 += z5;
-    
-    dataptr[7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, CONST_BITS-PASS1_BITS);
-    dataptr[5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, CONST_BITS-PASS1_BITS);
-    dataptr[3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, CONST_BITS-PASS1_BITS);
-    dataptr[1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, CONST_BITS-PASS1_BITS);
-    
-    dataptr += DCTSIZE;		/* advance pointer to next row */
-  }
-
-  /* Pass 2: process columns.
-   * We remove the PASS1_BITS scaling, but leave the results scaled up
-   * by an overall factor of 8.
-   */
-
-  dataptr = data;
-  for (ctr = DCTSIZE-1; ctr >= 0; ctr--) {
-    tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7];
-    tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7];
-    tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6];
-    tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6];
-    tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5];
-    tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5];
-    tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4];
-    tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4];
-    
-    /* Even part per LL&M figure 1 --- note that published figure is faulty;
-     * rotator "sqrt(2)*c1" should be "sqrt(2)*c6".
-     */
-    
-    tmp10 = tmp0 + tmp3;
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    dataptr[DCTSIZE*0] = (DCTELEM) DESCALE(tmp10 + tmp11, PASS1_BITS);
-    dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp10 - tmp11, PASS1_BITS);
-    
-    z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100);
-    dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865),
-					   CONST_BITS+PASS1_BITS);
-    dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065),
-					   CONST_BITS+PASS1_BITS);
-    
-    /* Odd part per figure 8 --- note paper omits factor of sqrt(2).
-     * cK represents cos(K*pi/16).
-     * i0..i3 in the paper are tmp4..tmp7 here.
-     */
-    
-    z1 = tmp4 + tmp7;
-    z2 = tmp5 + tmp6;
-    z3 = tmp4 + tmp6;
-    z4 = tmp5 + tmp7;
-    z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */
-    
-    tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */
-    tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */
-    tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */
-    tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */
-    z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */
-    z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */
-    z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */
-    z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */
-    
-    z3 += z5;
-    z4 += z5;
-    
-    dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp4 + z1 + z3,
-					   CONST_BITS+PASS1_BITS);
-    dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp5 + z2 + z4,
-					   CONST_BITS+PASS1_BITS);
-    dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp6 + z2 + z3,
-					   CONST_BITS+PASS1_BITS);
-    dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp7 + z1 + z4,
-					   CONST_BITS+PASS1_BITS);
-    
-    dataptr++;			/* advance pointer to next column */
-  }
-}
-
-#endif /* DCT_ISLOW_SUPPORTED */
diff --git a/third_party/libjpeg/jidctflt.c b/third_party/libjpeg/jidctflt.c
deleted file mode 100644
index 0188ce3..0000000
--- a/third_party/libjpeg/jidctflt.c
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * jidctflt.c
- *
- * Copyright (C) 1994-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains a floating-point implementation of the
- * inverse DCT (Discrete Cosine Transform).  In the IJG code, this routine
- * must also perform dequantization of the input coefficients.
- *
- * This implementation should be more accurate than either of the integer
- * IDCT implementations.  However, it may not give the same results on all
- * machines because of differences in roundoff behavior.  Speed will depend
- * on the hardware's floating point capacity.
- *
- * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT
- * on each row (or vice versa, but it's more convenient to emit a row at
- * a time).  Direct algorithms are also available, but they are much more
- * complex and seem not to be any faster when reduced to code.
- *
- * This implementation is based on Arai, Agui, and Nakajima's algorithm for
- * scaled DCT.  Their original paper (Trans. IEICE E-71(11):1095) is in
- * Japanese, but the algorithm is described in the Pennebaker & Mitchell
- * JPEG textbook (see REFERENCES section in file README).  The following code
- * is based directly on figure 4-8 in P&M.
- * While an 8-point DCT cannot be done in less than 11 multiplies, it is
- * possible to arrange the computation so that many of the multiplies are
- * simple scalings of the final outputs.  These multiplies can then be
- * folded into the multiplications or divisions by the JPEG quantization
- * table entries.  The AA&N method leaves only 5 multiplies and 29 adds
- * to be done in the DCT itself.
- * The primary disadvantage of this method is that with a fixed-point
- * implementation, accuracy is lost due to imprecise representation of the
- * scaled quantization values.  However, that problem does not arise if
- * we use floating point arithmetic.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef DCT_FLOAT_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/* Dequantize a coefficient by multiplying it by the multiplier-table
- * entry; produce a float result.
- */
-
-#define DEQUANTIZE(coef,quantval)  (((FAST_FLOAT) (coef)) * (quantval))
-
-
-/*
- * Perform dequantization and inverse DCT on one block of coefficients.
- */
-
-GLOBAL(void)
-jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		 JCOEFPTR coef_block,
-		 JSAMPARRAY output_buf, JDIMENSION output_col)
-{
-  FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
-  FAST_FLOAT tmp10, tmp11, tmp12, tmp13;
-  FAST_FLOAT z5, z10, z11, z12, z13;
-  JCOEFPTR inptr;
-  FLOAT_MULT_TYPE * quantptr;
-  FAST_FLOAT * wsptr;
-  JSAMPROW outptr;
-  JSAMPLE *range_limit = IDCT_range_limit(cinfo);
-  int ctr;
-  FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */
-  SHIFT_TEMPS
-
-  /* Pass 1: process columns from input, store into work array. */
-
-  inptr = coef_block;
-  quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table;
-  wsptr = workspace;
-  for (ctr = DCTSIZE; ctr > 0; ctr--) {
-    /* Due to quantization, we will usually find that many of the input
-     * coefficients are zero, especially the AC terms.  We can exploit this
-     * by short-circuiting the IDCT calculation for any column in which all
-     * the AC terms are zero.  In that case each output is equal to the
-     * DC coefficient (with scale factor as needed).
-     * With typical images and quantization tables, half or more of the
-     * column DCT calculations can be simplified this way.
-     */
-    
-    if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 &&
-	inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 &&
-	inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 &&
-	inptr[DCTSIZE*7] == 0) {
-      /* AC terms all zero */
-      FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-      
-      wsptr[DCTSIZE*0] = dcval;
-      wsptr[DCTSIZE*1] = dcval;
-      wsptr[DCTSIZE*2] = dcval;
-      wsptr[DCTSIZE*3] = dcval;
-      wsptr[DCTSIZE*4] = dcval;
-      wsptr[DCTSIZE*5] = dcval;
-      wsptr[DCTSIZE*6] = dcval;
-      wsptr[DCTSIZE*7] = dcval;
-      
-      inptr++;			/* advance pointers to next column */
-      quantptr++;
-      wsptr++;
-      continue;
-    }
-    
-    /* Even part */
-
-    tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-    tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]);
-    tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]);
-    tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]);
-
-    tmp10 = tmp0 + tmp2;	/* phase 3 */
-    tmp11 = tmp0 - tmp2;
-
-    tmp13 = tmp1 + tmp3;	/* phases 5-3 */
-    tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */
-
-    tmp0 = tmp10 + tmp13;	/* phase 2 */
-    tmp3 = tmp10 - tmp13;
-    tmp1 = tmp11 + tmp12;
-    tmp2 = tmp11 - tmp12;
-    
-    /* Odd part */
-
-    tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]);
-    tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]);
-    tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]);
-    tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]);
-
-    z13 = tmp6 + tmp5;		/* phase 6 */
-    z10 = tmp6 - tmp5;
-    z11 = tmp4 + tmp7;
-    z12 = tmp4 - tmp7;
-
-    tmp7 = z11 + z13;		/* phase 5 */
-    tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */
-
-    z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */
-    tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */
-    tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */
-
-    tmp6 = tmp12 - tmp7;	/* phase 2 */
-    tmp5 = tmp11 - tmp6;
-    tmp4 = tmp10 + tmp5;
-
-    wsptr[DCTSIZE*0] = tmp0 + tmp7;
-    wsptr[DCTSIZE*7] = tmp0 - tmp7;
-    wsptr[DCTSIZE*1] = tmp1 + tmp6;
-    wsptr[DCTSIZE*6] = tmp1 - tmp6;
-    wsptr[DCTSIZE*2] = tmp2 + tmp5;
-    wsptr[DCTSIZE*5] = tmp2 - tmp5;
-    wsptr[DCTSIZE*4] = tmp3 + tmp4;
-    wsptr[DCTSIZE*3] = tmp3 - tmp4;
-
-    inptr++;			/* advance pointers to next column */
-    quantptr++;
-    wsptr++;
-  }
-  
-  /* Pass 2: process rows from work array, store into output array. */
-  /* Note that we must descale the results by a factor of 8 == 2**3. */
-
-  wsptr = workspace;
-  for (ctr = 0; ctr < DCTSIZE; ctr++) {
-    outptr = output_buf[ctr] + output_col;
-    /* Rows of zeroes can be exploited in the same way as we did with columns.
-     * However, the column calculation has created many nonzero AC terms, so
-     * the simplification applies less often (typically 5% to 10% of the time).
-     * And testing floats for zero is relatively expensive, so we don't bother.
-     */
-    
-    /* Even part */
-
-    tmp10 = wsptr[0] + wsptr[4];
-    tmp11 = wsptr[0] - wsptr[4];
-
-    tmp13 = wsptr[2] + wsptr[6];
-    tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13;
-
-    tmp0 = tmp10 + tmp13;
-    tmp3 = tmp10 - tmp13;
-    tmp1 = tmp11 + tmp12;
-    tmp2 = tmp11 - tmp12;
-
-    /* Odd part */
-
-    z13 = wsptr[5] + wsptr[3];
-    z10 = wsptr[5] - wsptr[3];
-    z11 = wsptr[1] + wsptr[7];
-    z12 = wsptr[1] - wsptr[7];
-
-    tmp7 = z11 + z13;
-    tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562);
-
-    z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */
-    tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */
-    tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */
-
-    tmp6 = tmp12 - tmp7;
-    tmp5 = tmp11 - tmp6;
-    tmp4 = tmp10 + tmp5;
-
-    /* Final output stage: scale down by a factor of 8 and range-limit */
-
-    outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3)
-			    & RANGE_MASK];
-    outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3)
-			    & RANGE_MASK];
-    outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3)
-			    & RANGE_MASK];
-    outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3)
-			    & RANGE_MASK];
-    outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3)
-			    & RANGE_MASK];
-    outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3)
-			    & RANGE_MASK];
-    outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3)
-			    & RANGE_MASK];
-    outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3)
-			    & RANGE_MASK];
-    
-    wsptr += DCTSIZE;		/* advance pointer to next row */
-  }
-}
-
-#endif /* DCT_FLOAT_SUPPORTED */
diff --git a/third_party/libjpeg/jidctfst.c b/third_party/libjpeg/jidctfst.c
deleted file mode 100644
index dba4216..0000000
--- a/third_party/libjpeg/jidctfst.c
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * jidctfst.c
- *
- * Copyright (C) 1994-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains a fast, not so accurate integer implementation of the
- * inverse DCT (Discrete Cosine Transform).  In the IJG code, this routine
- * must also perform dequantization of the input coefficients.
- *
- * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT
- * on each row (or vice versa, but it's more convenient to emit a row at
- * a time).  Direct algorithms are also available, but they are much more
- * complex and seem not to be any faster when reduced to code.
- *
- * This implementation is based on Arai, Agui, and Nakajima's algorithm for
- * scaled DCT.  Their original paper (Trans. IEICE E-71(11):1095) is in
- * Japanese, but the algorithm is described in the Pennebaker & Mitchell
- * JPEG textbook (see REFERENCES section in file README).  The following code
- * is based directly on figure 4-8 in P&M.
- * While an 8-point DCT cannot be done in less than 11 multiplies, it is
- * possible to arrange the computation so that many of the multiplies are
- * simple scalings of the final outputs.  These multiplies can then be
- * folded into the multiplications or divisions by the JPEG quantization
- * table entries.  The AA&N method leaves only 5 multiplies and 29 adds
- * to be done in the DCT itself.
- * The primary disadvantage of this method is that with fixed-point math,
- * accuracy is lost due to imprecise representation of the scaled
- * quantization values.  The smaller the quantization table entry, the less
- * precise the scaled value, so this implementation does worse with high-
- * quality-setting files than with low-quality ones.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef DCT_IFAST_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/* Scaling decisions are generally the same as in the LL&M algorithm;
- * see jidctint.c for more details.  However, we choose to descale
- * (right shift) multiplication products as soon as they are formed,
- * rather than carrying additional fractional bits into subsequent additions.
- * This compromises accuracy slightly, but it lets us save a few shifts.
- * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples)
- * everywhere except in the multiplications proper; this saves a good deal
- * of work on 16-bit-int machines.
- *
- * The dequantized coefficients are not integers because the AA&N scaling
- * factors have been incorporated.  We represent them scaled up by PASS1_BITS,
- * so that the first and second IDCT rounds have the same input scaling.
- * For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = PASS1_BITS so as to
- * avoid a descaling shift; this compromises accuracy rather drastically
- * for small quantization table entries, but it saves a lot of shifts.
- * For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway,
- * so we use a much larger scaling factor to preserve accuracy.
- *
- * A final compromise is to represent the multiplicative constants to only
- * 8 fractional bits, rather than 13.  This saves some shifting work on some
- * machines, and may also reduce the cost of multiplication (since there
- * are fewer one-bits in the constants).
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define CONST_BITS  8
-#define PASS1_BITS  2
-#else
-#define CONST_BITS  8
-#define PASS1_BITS  1		/* lose a little precision to avoid overflow */
-#endif
-
-/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus
- * causing a lot of useless floating-point operations at run time.
- * To get around this we use the following pre-calculated constants.
- * If you change CONST_BITS you may want to add appropriate values.
- * (With a reasonable C compiler, you can just rely on the FIX() macro...)
- */
-
-#if CONST_BITS == 8
-#define FIX_1_082392200  ((INT32)  277)		/* FIX(1.082392200) */
-#define FIX_1_414213562  ((INT32)  362)		/* FIX(1.414213562) */
-#define FIX_1_847759065  ((INT32)  473)		/* FIX(1.847759065) */
-#define FIX_2_613125930  ((INT32)  669)		/* FIX(2.613125930) */
-#else
-#define FIX_1_082392200  FIX(1.082392200)
-#define FIX_1_414213562  FIX(1.414213562)
-#define FIX_1_847759065  FIX(1.847759065)
-#define FIX_2_613125930  FIX(2.613125930)
-#endif
-
-
-/* We can gain a little more speed, with a further compromise in accuracy,
- * by omitting the addition in a descaling shift.  This yields an incorrectly
- * rounded result half the time...
- */
-
-#ifndef USE_ACCURATE_ROUNDING
-#undef DESCALE
-#define DESCALE(x,n)  RIGHT_SHIFT(x, n)
-#endif
-
-
-/* Multiply a DCTELEM variable by an INT32 constant, and immediately
- * descale to yield a DCTELEM result.
- */
-
-#define MULTIPLY(var,const)  ((DCTELEM) DESCALE((var) * (const), CONST_BITS))
-
-
-/* Dequantize a coefficient by multiplying it by the multiplier-table
- * entry; produce a DCTELEM result.  For 8-bit data a 16x16->16
- * multiplication will do.  For 12-bit data, the multiplier table is
- * declared INT32, so a 32-bit multiply will be used.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define DEQUANTIZE(coef,quantval)  (((IFAST_MULT_TYPE) (coef)) * (quantval))
-#else
-#define DEQUANTIZE(coef,quantval)  \
-	DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS)
-#endif
-
-
-/* Like DESCALE, but applies to a DCTELEM and produces an int.
- * We assume that int right shift is unsigned if INT32 right shift is.
- */
-
-#ifdef RIGHT_SHIFT_IS_UNSIGNED
-#define ISHIFT_TEMPS	DCTELEM ishift_temp;
-#if BITS_IN_JSAMPLE == 8
-#define DCTELEMBITS  16		/* DCTELEM may be 16 or 32 bits */
-#else
-#define DCTELEMBITS  32		/* DCTELEM must be 32 bits */
-#endif
-#define IRIGHT_SHIFT(x,shft)  \
-    ((ishift_temp = (x)) < 0 ? \
-     (ishift_temp >> (shft)) | ((~((DCTELEM) 0)) << (DCTELEMBITS-(shft))) : \
-     (ishift_temp >> (shft)))
-#else
-#define ISHIFT_TEMPS
-#define IRIGHT_SHIFT(x,shft)	((x) >> (shft))
-#endif
-
-#ifdef USE_ACCURATE_ROUNDING
-#define IDESCALE(x,n)  ((int) IRIGHT_SHIFT((x) + (1 << ((n)-1)), n))
-#else
-#define IDESCALE(x,n)  ((int) IRIGHT_SHIFT(x, n))
-#endif
-
-
-/*
- * Perform dequantization and inverse DCT on one block of coefficients.
- */
-
-GLOBAL(void)
-jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		 JCOEFPTR coef_block,
-		 JSAMPARRAY output_buf, JDIMENSION output_col)
-{
-  DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
-  DCTELEM tmp10, tmp11, tmp12, tmp13;
-  DCTELEM z5, z10, z11, z12, z13;
-  JCOEFPTR inptr;
-  IFAST_MULT_TYPE * quantptr;
-  int * wsptr;
-  JSAMPROW outptr;
-  JSAMPLE *range_limit = IDCT_range_limit(cinfo);
-  int ctr;
-  int workspace[DCTSIZE2];	/* buffers data between passes */
-  SHIFT_TEMPS			/* for DESCALE */
-  ISHIFT_TEMPS			/* for IDESCALE */
-
-  /* Pass 1: process columns from input, store into work array. */
-
-  inptr = coef_block;
-  quantptr = (IFAST_MULT_TYPE *) compptr->dct_table;
-  wsptr = workspace;
-  for (ctr = DCTSIZE; ctr > 0; ctr--) {
-    /* Due to quantization, we will usually find that many of the input
-     * coefficients are zero, especially the AC terms.  We can exploit this
-     * by short-circuiting the IDCT calculation for any column in which all
-     * the AC terms are zero.  In that case each output is equal to the
-     * DC coefficient (with scale factor as needed).
-     * With typical images and quantization tables, half or more of the
-     * column DCT calculations can be simplified this way.
-     */
-    
-    if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 &&
-	inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 &&
-	inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 &&
-	inptr[DCTSIZE*7] == 0) {
-      /* AC terms all zero */
-      int dcval = (int) DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-
-      wsptr[DCTSIZE*0] = dcval;
-      wsptr[DCTSIZE*1] = dcval;
-      wsptr[DCTSIZE*2] = dcval;
-      wsptr[DCTSIZE*3] = dcval;
-      wsptr[DCTSIZE*4] = dcval;
-      wsptr[DCTSIZE*5] = dcval;
-      wsptr[DCTSIZE*6] = dcval;
-      wsptr[DCTSIZE*7] = dcval;
-      
-      inptr++;			/* advance pointers to next column */
-      quantptr++;
-      wsptr++;
-      continue;
-    }
-    
-    /* Even part */
-
-    tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-    tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]);
-    tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]);
-    tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]);
-
-    tmp10 = tmp0 + tmp2;	/* phase 3 */
-    tmp11 = tmp0 - tmp2;
-
-    tmp13 = tmp1 + tmp3;	/* phases 5-3 */
-    tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */
-
-    tmp0 = tmp10 + tmp13;	/* phase 2 */
-    tmp3 = tmp10 - tmp13;
-    tmp1 = tmp11 + tmp12;
-    tmp2 = tmp11 - tmp12;
-    
-    /* Odd part */
-
-    tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]);
-    tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]);
-    tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]);
-    tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]);
-
-    z13 = tmp6 + tmp5;		/* phase 6 */
-    z10 = tmp6 - tmp5;
-    z11 = tmp4 + tmp7;
-    z12 = tmp4 - tmp7;
-
-    tmp7 = z11 + z13;		/* phase 5 */
-    tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */
-
-    z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */
-    tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */
-    tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */
-
-    tmp6 = tmp12 - tmp7;	/* phase 2 */
-    tmp5 = tmp11 - tmp6;
-    tmp4 = tmp10 + tmp5;
-
-    wsptr[DCTSIZE*0] = (int) (tmp0 + tmp7);
-    wsptr[DCTSIZE*7] = (int) (tmp0 - tmp7);
-    wsptr[DCTSIZE*1] = (int) (tmp1 + tmp6);
-    wsptr[DCTSIZE*6] = (int) (tmp1 - tmp6);
-    wsptr[DCTSIZE*2] = (int) (tmp2 + tmp5);
-    wsptr[DCTSIZE*5] = (int) (tmp2 - tmp5);
-    wsptr[DCTSIZE*4] = (int) (tmp3 + tmp4);
-    wsptr[DCTSIZE*3] = (int) (tmp3 - tmp4);
-
-    inptr++;			/* advance pointers to next column */
-    quantptr++;
-    wsptr++;
-  }
-  
-  /* Pass 2: process rows from work array, store into output array. */
-  /* Note that we must descale the results by a factor of 8 == 2**3, */
-  /* and also undo the PASS1_BITS scaling. */
-
-  wsptr = workspace;
-  for (ctr = 0; ctr < DCTSIZE; ctr++) {
-    outptr = output_buf[ctr] + output_col;
-    /* Rows of zeroes can be exploited in the same way as we did with columns.
-     * However, the column calculation has created many nonzero AC terms, so
-     * the simplification applies less often (typically 5% to 10% of the time).
-     * On machines with very fast multiplication, it's possible that the
-     * test takes more time than it's worth.  In that case this section
-     * may be commented out.
-     */
-    
-#ifndef NO_ZERO_ROW_TEST
-    if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 &&
-	wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) {
-      /* AC terms all zero */
-      JSAMPLE dcval = range_limit[IDESCALE(wsptr[0], PASS1_BITS+3)
-				  & RANGE_MASK];
-      
-      outptr[0] = dcval;
-      outptr[1] = dcval;
-      outptr[2] = dcval;
-      outptr[3] = dcval;
-      outptr[4] = dcval;
-      outptr[5] = dcval;
-      outptr[6] = dcval;
-      outptr[7] = dcval;
-
-      wsptr += DCTSIZE;		/* advance pointer to next row */
-      continue;
-    }
-#endif
-    
-    /* Even part */
-
-    tmp10 = ((DCTELEM) wsptr[0] + (DCTELEM) wsptr[4]);
-    tmp11 = ((DCTELEM) wsptr[0] - (DCTELEM) wsptr[4]);
-
-    tmp13 = ((DCTELEM) wsptr[2] + (DCTELEM) wsptr[6]);
-    tmp12 = MULTIPLY((DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562)
-	    - tmp13;
-
-    tmp0 = tmp10 + tmp13;
-    tmp3 = tmp10 - tmp13;
-    tmp1 = tmp11 + tmp12;
-    tmp2 = tmp11 - tmp12;
-
-    /* Odd part */
-
-    z13 = (DCTELEM) wsptr[5] + (DCTELEM) wsptr[3];
-    z10 = (DCTELEM) wsptr[5] - (DCTELEM) wsptr[3];
-    z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7];
-    z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7];
-
-    tmp7 = z11 + z13;		/* phase 5 */
-    tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */
-
-    z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */
-    tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */
-    tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */
-
-    tmp6 = tmp12 - tmp7;	/* phase 2 */
-    tmp5 = tmp11 - tmp6;
-    tmp4 = tmp10 + tmp5;
-
-    /* Final output stage: scale down by a factor of 8 and range-limit */
-
-    outptr[0] = range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[7] = range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[1] = range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[6] = range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[2] = range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[5] = range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[4] = range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[3] = range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS+3)
-			    & RANGE_MASK];
-
-    wsptr += DCTSIZE;		/* advance pointer to next row */
-  }
-}
-
-#endif /* DCT_IFAST_SUPPORTED */
diff --git a/third_party/libjpeg/jidctint.c b/third_party/libjpeg/jidctint.c
deleted file mode 100644
index a72b320..0000000
--- a/third_party/libjpeg/jidctint.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * jidctint.c
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains a slow-but-accurate integer implementation of the
- * inverse DCT (Discrete Cosine Transform).  In the IJG code, this routine
- * must also perform dequantization of the input coefficients.
- *
- * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT
- * on each row (or vice versa, but it's more convenient to emit a row at
- * a time).  Direct algorithms are also available, but they are much more
- * complex and seem not to be any faster when reduced to code.
- *
- * This implementation is based on an algorithm described in
- *   C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT
- *   Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics,
- *   Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991.
- * The primary algorithm described there uses 11 multiplies and 29 adds.
- * We use their alternate method with 12 multiplies and 32 adds.
- * The advantage of this method is that no data path contains more than one
- * multiplication; this allows a very simple and accurate implementation in
- * scaled fixed-point arithmetic, with a minimal number of shifts.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef DCT_ISLOW_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/*
- * The poop on this scaling stuff is as follows:
- *
- * Each 1-D IDCT step produces outputs which are a factor of sqrt(N)
- * larger than the true IDCT outputs.  The final outputs are therefore
- * a factor of N larger than desired; since N=8 this can be cured by
- * a simple right shift at the end of the algorithm.  The advantage of
- * this arrangement is that we save two multiplications per 1-D IDCT,
- * because the y0 and y4 inputs need not be divided by sqrt(N).
- *
- * We have to do addition and subtraction of the integer inputs, which
- * is no problem, and multiplication by fractional constants, which is
- * a problem to do in integer arithmetic.  We multiply all the constants
- * by CONST_SCALE and convert them to integer constants (thus retaining
- * CONST_BITS bits of precision in the constants).  After doing a
- * multiplication we have to divide the product by CONST_SCALE, with proper
- * rounding, to produce the correct output.  This division can be done
- * cheaply as a right shift of CONST_BITS bits.  We postpone shifting
- * as long as possible so that partial sums can be added together with
- * full fractional precision.
- *
- * The outputs of the first pass are scaled up by PASS1_BITS bits so that
- * they are represented to better-than-integral precision.  These outputs
- * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word
- * with the recommended scaling.  (To scale up 12-bit sample data further, an
- * intermediate INT32 array would be needed.)
- *
- * To avoid overflow of the 32-bit intermediate results in pass 2, we must
- * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26.  Error analysis
- * shows that the values given below are the most effective.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define CONST_BITS  13
-#define PASS1_BITS  2
-#else
-#define CONST_BITS  13
-#define PASS1_BITS  1		/* lose a little precision to avoid overflow */
-#endif
-
-/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus
- * causing a lot of useless floating-point operations at run time.
- * To get around this we use the following pre-calculated constants.
- * If you change CONST_BITS you may want to add appropriate values.
- * (With a reasonable C compiler, you can just rely on the FIX() macro...)
- */
-
-#if CONST_BITS == 13
-#define FIX_0_298631336  ((INT32)  2446)	/* FIX(0.298631336) */
-#define FIX_0_390180644  ((INT32)  3196)	/* FIX(0.390180644) */
-#define FIX_0_541196100  ((INT32)  4433)	/* FIX(0.541196100) */
-#define FIX_0_765366865  ((INT32)  6270)	/* FIX(0.765366865) */
-#define FIX_0_899976223  ((INT32)  7373)	/* FIX(0.899976223) */
-#define FIX_1_175875602  ((INT32)  9633)	/* FIX(1.175875602) */
-#define FIX_1_501321110  ((INT32)  12299)	/* FIX(1.501321110) */
-#define FIX_1_847759065  ((INT32)  15137)	/* FIX(1.847759065) */
-#define FIX_1_961570560  ((INT32)  16069)	/* FIX(1.961570560) */
-#define FIX_2_053119869  ((INT32)  16819)	/* FIX(2.053119869) */
-#define FIX_2_562915447  ((INT32)  20995)	/* FIX(2.562915447) */
-#define FIX_3_072711026  ((INT32)  25172)	/* FIX(3.072711026) */
-#else
-#define FIX_0_298631336  FIX(0.298631336)
-#define FIX_0_390180644  FIX(0.390180644)
-#define FIX_0_541196100  FIX(0.541196100)
-#define FIX_0_765366865  FIX(0.765366865)
-#define FIX_0_899976223  FIX(0.899976223)
-#define FIX_1_175875602  FIX(1.175875602)
-#define FIX_1_501321110  FIX(1.501321110)
-#define FIX_1_847759065  FIX(1.847759065)
-#define FIX_1_961570560  FIX(1.961570560)
-#define FIX_2_053119869  FIX(2.053119869)
-#define FIX_2_562915447  FIX(2.562915447)
-#define FIX_3_072711026  FIX(3.072711026)
-#endif
-
-
-/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result.
- * For 8-bit samples with the recommended scaling, all the variable
- * and constant values involved are no more than 16 bits wide, so a
- * 16x16->32 bit multiply can be used instead of a full 32x32 multiply.
- * For 12-bit samples, a full 32-bit multiplication will be needed.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define MULTIPLY(var,const)  MULTIPLY16C16(var,const)
-#else
-#define MULTIPLY(var,const)  ((var) * (const))
-#endif
-
-
-/* Dequantize a coefficient by multiplying it by the multiplier-table
- * entry; produce an int result.  In this module, both inputs and result
- * are 16 bits or less, so either int or short multiply will work.
- */
-
-#define DEQUANTIZE(coef,quantval)  (((ISLOW_MULT_TYPE) (coef)) * (quantval))
-
-
-/*
- * Perform dequantization and inverse DCT on one block of coefficients.
- */
-
-GLOBAL(void)
-jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		 JCOEFPTR coef_block,
-		 JSAMPARRAY output_buf, JDIMENSION output_col)
-{
-  INT32 tmp0, tmp1, tmp2, tmp3;
-  INT32 tmp10, tmp11, tmp12, tmp13;
-  INT32 z1, z2, z3, z4, z5;
-  JCOEFPTR inptr;
-  ISLOW_MULT_TYPE * quantptr;
-  int * wsptr;
-  JSAMPROW outptr;
-  JSAMPLE *range_limit = IDCT_range_limit(cinfo);
-  int ctr;
-  int workspace[DCTSIZE2];	/* buffers data between passes */
-  SHIFT_TEMPS
-
-  /* Pass 1: process columns from input, store into work array. */
-  /* Note results are scaled up by sqrt(8) compared to a true IDCT; */
-  /* furthermore, we scale the results by 2**PASS1_BITS. */
-
-  inptr = coef_block;
-  quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table;
-  wsptr = workspace;
-  for (ctr = DCTSIZE; ctr > 0; ctr--) {
-    /* Due to quantization, we will usually find that many of the input
-     * coefficients are zero, especially the AC terms.  We can exploit this
-     * by short-circuiting the IDCT calculation for any column in which all
-     * the AC terms are zero.  In that case each output is equal to the
-     * DC coefficient (with scale factor as needed).
-     * With typical images and quantization tables, half or more of the
-     * column DCT calculations can be simplified this way.
-     */
-    
-    if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 &&
-	inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 &&
-	inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 &&
-	inptr[DCTSIZE*7] == 0) {
-      /* AC terms all zero */
-      int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS;
-      
-      wsptr[DCTSIZE*0] = dcval;
-      wsptr[DCTSIZE*1] = dcval;
-      wsptr[DCTSIZE*2] = dcval;
-      wsptr[DCTSIZE*3] = dcval;
-      wsptr[DCTSIZE*4] = dcval;
-      wsptr[DCTSIZE*5] = dcval;
-      wsptr[DCTSIZE*6] = dcval;
-      wsptr[DCTSIZE*7] = dcval;
-      
-      inptr++;			/* advance pointers to next column */
-      quantptr++;
-      wsptr++;
-      continue;
-    }
-    
-    /* Even part: reverse the even part of the forward DCT. */
-    /* The rotator is sqrt(2)*c(-6). */
-    
-    z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]);
-    z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]);
-    
-    z1 = MULTIPLY(z2 + z3, FIX_0_541196100);
-    tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065);
-    tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865);
-    
-    z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-    z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]);
-
-    tmp0 = (z2 + z3) << CONST_BITS;
-    tmp1 = (z2 - z3) << CONST_BITS;
-    
-    tmp10 = tmp0 + tmp3;
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    /* Odd part per figure 8; the matrix is unitary and hence its
-     * transpose is its inverse.  i0..i3 are y7,y5,y3,y1 respectively.
-     */
-    
-    tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]);
-    tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]);
-    tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]);
-    tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]);
-    
-    z1 = tmp0 + tmp3;
-    z2 = tmp1 + tmp2;
-    z3 = tmp0 + tmp2;
-    z4 = tmp1 + tmp3;
-    z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */
-    
-    tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */
-    tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */
-    tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */
-    tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */
-    z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */
-    z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */
-    z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */
-    z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */
-    
-    z3 += z5;
-    z4 += z5;
-    
-    tmp0 += z1 + z3;
-    tmp1 += z2 + z4;
-    tmp2 += z2 + z3;
-    tmp3 += z1 + z4;
-    
-    /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
-    
-    wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp3, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*7] = (int) DESCALE(tmp10 - tmp3, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*1] = (int) DESCALE(tmp11 + tmp2, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*6] = (int) DESCALE(tmp11 - tmp2, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 + tmp1, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*5] = (int) DESCALE(tmp12 - tmp1, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*3] = (int) DESCALE(tmp13 + tmp0, CONST_BITS-PASS1_BITS);
-    wsptr[DCTSIZE*4] = (int) DESCALE(tmp13 - tmp0, CONST_BITS-PASS1_BITS);
-    
-    inptr++;			/* advance pointers to next column */
-    quantptr++;
-    wsptr++;
-  }
-  
-  /* Pass 2: process rows from work array, store into output array. */
-  /* Note that we must descale the results by a factor of 8 == 2**3, */
-  /* and also undo the PASS1_BITS scaling. */
-
-  wsptr = workspace;
-  for (ctr = 0; ctr < DCTSIZE; ctr++) {
-    outptr = output_buf[ctr] + output_col;
-    /* Rows of zeroes can be exploited in the same way as we did with columns.
-     * However, the column calculation has created many nonzero AC terms, so
-     * the simplification applies less often (typically 5% to 10% of the time).
-     * On machines with very fast multiplication, it's possible that the
-     * test takes more time than it's worth.  In that case this section
-     * may be commented out.
-     */
-    
-#ifndef NO_ZERO_ROW_TEST
-    if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 &&
-	wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) {
-      /* AC terms all zero */
-      JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3)
-				  & RANGE_MASK];
-      
-      outptr[0] = dcval;
-      outptr[1] = dcval;
-      outptr[2] = dcval;
-      outptr[3] = dcval;
-      outptr[4] = dcval;
-      outptr[5] = dcval;
-      outptr[6] = dcval;
-      outptr[7] = dcval;
-
-      wsptr += DCTSIZE;		/* advance pointer to next row */
-      continue;
-    }
-#endif
-    
-    /* Even part: reverse the even part of the forward DCT. */
-    /* The rotator is sqrt(2)*c(-6). */
-    
-    z2 = (INT32) wsptr[2];
-    z3 = (INT32) wsptr[6];
-    
-    z1 = MULTIPLY(z2 + z3, FIX_0_541196100);
-    tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065);
-    tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865);
-    
-    tmp0 = ((INT32) wsptr[0] + (INT32) wsptr[4]) << CONST_BITS;
-    tmp1 = ((INT32) wsptr[0] - (INT32) wsptr[4]) << CONST_BITS;
-    
-    tmp10 = tmp0 + tmp3;
-    tmp13 = tmp0 - tmp3;
-    tmp11 = tmp1 + tmp2;
-    tmp12 = tmp1 - tmp2;
-    
-    /* Odd part per figure 8; the matrix is unitary and hence its
-     * transpose is its inverse.  i0..i3 are y7,y5,y3,y1 respectively.
-     */
-    
-    tmp0 = (INT32) wsptr[7];
-    tmp1 = (INT32) wsptr[5];
-    tmp2 = (INT32) wsptr[3];
-    tmp3 = (INT32) wsptr[1];
-    
-    z1 = tmp0 + tmp3;
-    z2 = tmp1 + tmp2;
-    z3 = tmp0 + tmp2;
-    z4 = tmp1 + tmp3;
-    z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */
-    
-    tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */
-    tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */
-    tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */
-    tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */
-    z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */
-    z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */
-    z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */
-    z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */
-    
-    z3 += z5;
-    z4 += z5;
-    
-    tmp0 += z1 + z3;
-    tmp1 += z2 + z4;
-    tmp2 += z2 + z3;
-    tmp3 += z1 + z4;
-    
-    /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
-    
-    outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp3,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[7] = range_limit[(int) DESCALE(tmp10 - tmp3,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[1] = range_limit[(int) DESCALE(tmp11 + tmp2,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[6] = range_limit[(int) DESCALE(tmp11 - tmp2,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[2] = range_limit[(int) DESCALE(tmp12 + tmp1,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[5] = range_limit[(int) DESCALE(tmp12 - tmp1,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[3] = range_limit[(int) DESCALE(tmp13 + tmp0,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    outptr[4] = range_limit[(int) DESCALE(tmp13 - tmp0,
-					  CONST_BITS+PASS1_BITS+3)
-			    & RANGE_MASK];
-    
-    wsptr += DCTSIZE;		/* advance pointer to next row */
-  }
-}
-
-#endif /* DCT_ISLOW_SUPPORTED */
diff --git a/third_party/libjpeg/jidctred.c b/third_party/libjpeg/jidctred.c
deleted file mode 100644
index 421f3c7..0000000
--- a/third_party/libjpeg/jidctred.c
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * jidctred.c
- *
- * Copyright (C) 1994-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains inverse-DCT routines that produce reduced-size output:
- * either 4x4, 2x2, or 1x1 pixels from an 8x8 DCT block.
- *
- * The implementation is based on the Loeffler, Ligtenberg and Moschytz (LL&M)
- * algorithm used in jidctint.c.  We simply replace each 8-to-8 1-D IDCT step
- * with an 8-to-4 step that produces the four averages of two adjacent outputs
- * (or an 8-to-2 step producing two averages of four outputs, for 2x2 output).
- * These steps were derived by computing the corresponding values at the end
- * of the normal LL&M code, then simplifying as much as possible.
- *
- * 1x1 is trivial: just take the DC coefficient divided by 8.
- *
- * See jidctint.c for additional comments.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jdct.h"		/* Private declarations for DCT subsystem */
-
-#ifdef IDCT_SCALING_SUPPORTED
-
-
-/*
- * This module is specialized to the case DCTSIZE = 8.
- */
-
-#if DCTSIZE != 8
-  Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */
-#endif
-
-
-/* Scaling is the same as in jidctint.c. */
-
-#if BITS_IN_JSAMPLE == 8
-#define CONST_BITS  13
-#define PASS1_BITS  2
-#else
-#define CONST_BITS  13
-#define PASS1_BITS  1		/* lose a little precision to avoid overflow */
-#endif
-
-/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus
- * causing a lot of useless floating-point operations at run time.
- * To get around this we use the following pre-calculated constants.
- * If you change CONST_BITS you may want to add appropriate values.
- * (With a reasonable C compiler, you can just rely on the FIX() macro...)
- */
-
-#if CONST_BITS == 13
-#define FIX_0_211164243  ((INT32)  1730)	/* FIX(0.211164243) */
-#define FIX_0_509795579  ((INT32)  4176)	/* FIX(0.509795579) */
-#define FIX_0_601344887  ((INT32)  4926)	/* FIX(0.601344887) */
-#define FIX_0_720959822  ((INT32)  5906)	/* FIX(0.720959822) */
-#define FIX_0_765366865  ((INT32)  6270)	/* FIX(0.765366865) */
-#define FIX_0_850430095  ((INT32)  6967)	/* FIX(0.850430095) */
-#define FIX_0_899976223  ((INT32)  7373)	/* FIX(0.899976223) */
-#define FIX_1_061594337  ((INT32)  8697)	/* FIX(1.061594337) */
-#define FIX_1_272758580  ((INT32)  10426)	/* FIX(1.272758580) */
-#define FIX_1_451774981  ((INT32)  11893)	/* FIX(1.451774981) */
-#define FIX_1_847759065  ((INT32)  15137)	/* FIX(1.847759065) */
-#define FIX_2_172734803  ((INT32)  17799)	/* FIX(2.172734803) */
-#define FIX_2_562915447  ((INT32)  20995)	/* FIX(2.562915447) */
-#define FIX_3_624509785  ((INT32)  29692)	/* FIX(3.624509785) */
-#else
-#define FIX_0_211164243  FIX(0.211164243)
-#define FIX_0_509795579  FIX(0.509795579)
-#define FIX_0_601344887  FIX(0.601344887)
-#define FIX_0_720959822  FIX(0.720959822)
-#define FIX_0_765366865  FIX(0.765366865)
-#define FIX_0_850430095  FIX(0.850430095)
-#define FIX_0_899976223  FIX(0.899976223)
-#define FIX_1_061594337  FIX(1.061594337)
-#define FIX_1_272758580  FIX(1.272758580)
-#define FIX_1_451774981  FIX(1.451774981)
-#define FIX_1_847759065  FIX(1.847759065)
-#define FIX_2_172734803  FIX(2.172734803)
-#define FIX_2_562915447  FIX(2.562915447)
-#define FIX_3_624509785  FIX(3.624509785)
-#endif
-
-
-/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result.
- * For 8-bit samples with the recommended scaling, all the variable
- * and constant values involved are no more than 16 bits wide, so a
- * 16x16->32 bit multiply can be used instead of a full 32x32 multiply.
- * For 12-bit samples, a full 32-bit multiplication will be needed.
- */
-
-#if BITS_IN_JSAMPLE == 8
-#define MULTIPLY(var,const)  MULTIPLY16C16(var,const)
-#else
-#define MULTIPLY(var,const)  ((var) * (const))
-#endif
-
-
-/* Dequantize a coefficient by multiplying it by the multiplier-table
- * entry; produce an int result.  In this module, both inputs and result
- * are 16 bits or less, so either int or short multiply will work.
- */
-
-#define DEQUANTIZE(coef,quantval)  (((ISLOW_MULT_TYPE) (coef)) * (quantval))
-
-
-/*
- * Perform dequantization and inverse DCT on one block of coefficients,
- * producing a reduced-size 4x4 output block.
- */
-
-GLOBAL(void)
-jpeg_idct_4x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	       JCOEFPTR coef_block,
-	       JSAMPARRAY output_buf, JDIMENSION output_col)
-{
-  INT32 tmp0, tmp2, tmp10, tmp12;
-  INT32 z1, z2, z3, z4;
-  JCOEFPTR inptr;
-  ISLOW_MULT_TYPE * quantptr;
-  int * wsptr;
-  JSAMPROW outptr;
-  JSAMPLE *range_limit = IDCT_range_limit(cinfo);
-  int ctr;
-  int workspace[DCTSIZE*4];	/* buffers data between passes */
-  SHIFT_TEMPS
-
-  /* Pass 1: process columns from input, store into work array. */
-
-  inptr = coef_block;
-  quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table;
-  wsptr = workspace;
-  for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) {
-    /* Don't bother to process column 4, because second pass won't use it */
-    if (ctr == DCTSIZE-4)
-      continue;
-    if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 &&
-	inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*5] == 0 &&
-	inptr[DCTSIZE*6] == 0 && inptr[DCTSIZE*7] == 0) {
-      /* AC terms all zero; we need not examine term 4 for 4x4 output */
-      int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS;
-      
-      wsptr[DCTSIZE*0] = dcval;
-      wsptr[DCTSIZE*1] = dcval;
-      wsptr[DCTSIZE*2] = dcval;
-      wsptr[DCTSIZE*3] = dcval;
-      
-      continue;
-    }
-    
-    /* Even part */
-    
-    tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-    tmp0 <<= (CONST_BITS+1);
-    
-    z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]);
-    z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]);
-
-    tmp2 = MULTIPLY(z2, FIX_1_847759065) + MULTIPLY(z3, - FIX_0_765366865);
-    
-    tmp10 = tmp0 + tmp2;
-    tmp12 = tmp0 - tmp2;
-    
-    /* Odd part */
-    
-    z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]);
-    z2 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]);
-    z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]);
-    z4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]);
-    
-    tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */
-	 + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */
-	 + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */
-	 + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */
-    
-    tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */
-	 + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */
-	 + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */
-	 + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */
-
-    /* Final output stage */
-    
-    wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp2, CONST_BITS-PASS1_BITS+1);
-    wsptr[DCTSIZE*3] = (int) DESCALE(tmp10 - tmp2, CONST_BITS-PASS1_BITS+1);
-    wsptr[DCTSIZE*1] = (int) DESCALE(tmp12 + tmp0, CONST_BITS-PASS1_BITS+1);
-    wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 - tmp0, CONST_BITS-PASS1_BITS+1);
-  }
-  
-  /* Pass 2: process 4 rows from work array, store into output array. */
-
-  wsptr = workspace;
-  for (ctr = 0; ctr < 4; ctr++) {
-    outptr = output_buf[ctr] + output_col;
-    /* It's not clear whether a zero row test is worthwhile here ... */
-
-#ifndef NO_ZERO_ROW_TEST
-    if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 &&
-	wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) {
-      /* AC terms all zero */
-      JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3)
-				  & RANGE_MASK];
-      
-      outptr[0] = dcval;
-      outptr[1] = dcval;
-      outptr[2] = dcval;
-      outptr[3] = dcval;
-      
-      wsptr += DCTSIZE;		/* advance pointer to next row */
-      continue;
-    }
-#endif
-    
-    /* Even part */
-    
-    tmp0 = ((INT32) wsptr[0]) << (CONST_BITS+1);
-    
-    tmp2 = MULTIPLY((INT32) wsptr[2], FIX_1_847759065)
-	 + MULTIPLY((INT32) wsptr[6], - FIX_0_765366865);
-    
-    tmp10 = tmp0 + tmp2;
-    tmp12 = tmp0 - tmp2;
-    
-    /* Odd part */
-    
-    z1 = (INT32) wsptr[7];
-    z2 = (INT32) wsptr[5];
-    z3 = (INT32) wsptr[3];
-    z4 = (INT32) wsptr[1];
-    
-    tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */
-	 + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */
-	 + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */
-	 + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */
-    
-    tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */
-	 + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */
-	 + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */
-	 + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */
-
-    /* Final output stage */
-    
-    outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp2,
-					  CONST_BITS+PASS1_BITS+3+1)
-			    & RANGE_MASK];
-    outptr[3] = range_limit[(int) DESCALE(tmp10 - tmp2,
-					  CONST_BITS+PASS1_BITS+3+1)
-			    & RANGE_MASK];
-    outptr[1] = range_limit[(int) DESCALE(tmp12 + tmp0,
-					  CONST_BITS+PASS1_BITS+3+1)
-			    & RANGE_MASK];
-    outptr[2] = range_limit[(int) DESCALE(tmp12 - tmp0,
-					  CONST_BITS+PASS1_BITS+3+1)
-			    & RANGE_MASK];
-    
-    wsptr += DCTSIZE;		/* advance pointer to next row */
-  }
-}
-
-
-/*
- * Perform dequantization and inverse DCT on one block of coefficients,
- * producing a reduced-size 2x2 output block.
- */
-
-GLOBAL(void)
-jpeg_idct_2x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	       JCOEFPTR coef_block,
-	       JSAMPARRAY output_buf, JDIMENSION output_col)
-{
-  INT32 tmp0, tmp10, z1;
-  JCOEFPTR inptr;
-  ISLOW_MULT_TYPE * quantptr;
-  int * wsptr;
-  JSAMPROW outptr;
-  JSAMPLE *range_limit = IDCT_range_limit(cinfo);
-  int ctr;
-  int workspace[DCTSIZE*2];	/* buffers data between passes */
-  SHIFT_TEMPS
-
-  /* Pass 1: process columns from input, store into work array. */
-
-  inptr = coef_block;
-  quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table;
-  wsptr = workspace;
-  for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) {
-    /* Don't bother to process columns 2,4,6 */
-    if (ctr == DCTSIZE-2 || ctr == DCTSIZE-4 || ctr == DCTSIZE-6)
-      continue;
-    if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*3] == 0 &&
-	inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*7] == 0) {
-      /* AC terms all zero; we need not examine terms 2,4,6 for 2x2 output */
-      int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS;
-      
-      wsptr[DCTSIZE*0] = dcval;
-      wsptr[DCTSIZE*1] = dcval;
-      
-      continue;
-    }
-    
-    /* Even part */
-    
-    z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]);
-    tmp10 = z1 << (CONST_BITS+2);
-    
-    /* Odd part */
-
-    z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]);
-    tmp0 = MULTIPLY(z1, - FIX_0_720959822); /* sqrt(2) * (c7-c5+c3-c1) */
-    z1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]);
-    tmp0 += MULTIPLY(z1, FIX_0_850430095); /* sqrt(2) * (-c1+c3+c5+c7) */
-    z1 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]);
-    tmp0 += MULTIPLY(z1, - FIX_1_272758580); /* sqrt(2) * (-c1+c3-c5-c7) */
-    z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]);
-    tmp0 += MULTIPLY(z1, FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */
-
-    /* Final output stage */
-    
-    wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp0, CONST_BITS-PASS1_BITS+2);
-    wsptr[DCTSIZE*1] = (int) DESCALE(tmp10 - tmp0, CONST_BITS-PASS1_BITS+2);
-  }
-  
-  /* Pass 2: process 2 rows from work array, store into output array. */
-
-  wsptr = workspace;
-  for (ctr = 0; ctr < 2; ctr++) {
-    outptr = output_buf[ctr] + output_col;
-    /* It's not clear whether a zero row test is worthwhile here ... */
-
-#ifndef NO_ZERO_ROW_TEST
-    if (wsptr[1] == 0 && wsptr[3] == 0 && wsptr[5] == 0 && wsptr[7] == 0) {
-      /* AC terms all zero */
-      JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3)
-				  & RANGE_MASK];
-      
-      outptr[0] = dcval;
-      outptr[1] = dcval;
-      
-      wsptr += DCTSIZE;		/* advance pointer to next row */
-      continue;
-    }
-#endif
-    
-    /* Even part */
-    
-    tmp10 = ((INT32) wsptr[0]) << (CONST_BITS+2);
-    
-    /* Odd part */
-
-    tmp0 = MULTIPLY((INT32) wsptr[7], - FIX_0_720959822) /* sqrt(2) * (c7-c5+c3-c1) */
-	 + MULTIPLY((INT32) wsptr[5], FIX_0_850430095) /* sqrt(2) * (-c1+c3+c5+c7) */
-	 + MULTIPLY((INT32) wsptr[3], - FIX_1_272758580) /* sqrt(2) * (-c1+c3-c5-c7) */
-	 + MULTIPLY((INT32) wsptr[1], FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */
-
-    /* Final output stage */
-    
-    outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp0,
-					  CONST_BITS+PASS1_BITS+3+2)
-			    & RANGE_MASK];
-    outptr[1] = range_limit[(int) DESCALE(tmp10 - tmp0,
-					  CONST_BITS+PASS1_BITS+3+2)
-			    & RANGE_MASK];
-    
-    wsptr += DCTSIZE;		/* advance pointer to next row */
-  }
-}
-
-
-/*
- * Perform dequantization and inverse DCT on one block of coefficients,
- * producing a reduced-size 1x1 output block.
- */
-
-GLOBAL(void)
-jpeg_idct_1x1 (j_decompress_ptr cinfo, jpeg_component_info * compptr,
-	       JCOEFPTR coef_block,
-	       JSAMPARRAY output_buf, JDIMENSION output_col)
-{
-  int dcval;
-  ISLOW_MULT_TYPE * quantptr;
-  JSAMPLE *range_limit = IDCT_range_limit(cinfo);
-  SHIFT_TEMPS
-
-  /* We hardly need an inverse DCT routine for this: just take the
-   * average pixel value, which is one-eighth of the DC coefficient.
-   */
-  quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table;
-  dcval = DEQUANTIZE(coef_block[0], quantptr[0]);
-  dcval = (int) DESCALE((INT32) dcval, 3);
-
-  output_buf[0][output_col] = range_limit[dcval & RANGE_MASK];
-}
-
-#endif /* IDCT_SCALING_SUPPORTED */
diff --git a/third_party/libjpeg/jinclude.h b/third_party/libjpeg/jinclude.h
deleted file mode 100644
index 90f5b6e..0000000
--- a/third_party/libjpeg/jinclude.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * jinclude.h
- *
- * Copyright (C) 1991-1994, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file exists to provide a single place to fix any problems with
- * including the wrong system include files.  (Common problems are taken
- * care of by the standard jconfig symbols, but on really weird systems
- * you may have to edit this file.)
- *
- * NOTE: this file is NOT intended to be included by applications using the
- * JPEG library.  Most applications need only include jpeglib.h.
- */
-
-
-/* Include auto-config file to find out which system include files we need. */
-
-#include "jconfig.h"		/* auto configuration options */
-#define JCONFIG_INCLUDED	/* so that jpeglib.h doesn't do it again */
-
-/*
- * We need the NULL macro and size_t typedef.
- * On an ANSI-conforming system it is sufficient to include <stddef.h>.
- * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to
- * pull in <sys/types.h> as well.
- * Note that the core JPEG library does not require <stdio.h>;
- * only the default error handler and data source/destination modules do.
- * But we must pull it in because of the references to FILE in jpeglib.h.
- * You can remove those references if you want to compile without <stdio.h>.
- */
-
-#ifdef HAVE_STDDEF_H
-#include <stddef.h>
-#endif
-
-#ifdef HAVE_STDLIB_H
-#include <stdlib.h>
-#endif
-
-#ifdef NEED_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#if !defined(JPEG_NO_STDIO)
-#include <stdio.h>
-#endif
-
-/*
- * We need memory copying and zeroing functions, plus strncpy().
- * ANSI and System V implementations declare these in <string.h>.
- * BSD doesn't have the mem() functions, but it does have bcopy()/bzero().
- * Some systems may declare memset and memcpy in <memory.h>.
- *
- * NOTE: we assume the size parameters to these functions are of type size_t.
- * Change the casts in these macros if not!
- */
-
-#ifdef NEED_BSD_STRINGS
-
-#include <strings.h>
-#define MEMZERO(target,size)	bzero((void *)(target), (size_t)(size))
-#define MEMCOPY(dest,src,size)	bcopy((const void *)(src), (void *)(dest), (size_t)(size))
-
-#elif defined(NEED_STARBOARD_MEMORY)
-
-#include "starboard/memory.h"
-#define MEMZERO(target,size)	memset((void *)(target), 0, (size_t)(size))
-#define MEMCOPY(dest,src,size)	memcpy((void *)(dest), (const void *)(src), (size_t)(size))
-
-#else /* not BSD, assume ANSI/SysV string lib */
-
-#include <string.h>
-#define MEMZERO(target,size)	memset((void *)(target), 0, (size_t)(size))
-#define MEMCOPY(dest,src,size)	memcpy((void *)(dest), (const void *)(src), (size_t)(size))
-
-#endif
-
-/*
- * In ANSI C, and indeed any rational implementation, size_t is also the
- * type returned by sizeof().  However, it seems there are some irrational
- * implementations out there, in which sizeof() returns an int even though
- * size_t is defined as long or unsigned long.  To ensure consistent results
- * we always use this SIZEOF() macro in place of using sizeof() directly.
- */
-
-#define SIZEOF(object)	((size_t) sizeof(object))
-
-/*
- * The modules that use fread() and fwrite() always invoke them through
- * these macros.  On some systems you may need to twiddle the argument casts.
- * CAUTION: argument order is different from underlying functions!
- */
-
-#define JFREAD(file,buf,sizeofbuf)  \
-  ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))
-#define JFWRITE(file,buf,sizeofbuf)  \
-  ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))
diff --git a/third_party/libjpeg/jmemmgr.c b/third_party/libjpeg/jmemmgr.c
deleted file mode 100644
index d801b32..0000000
--- a/third_party/libjpeg/jmemmgr.c
+++ /dev/null
@@ -1,1118 +0,0 @@
-/*
- * jmemmgr.c
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains the JPEG system-independent memory management
- * routines.  This code is usable across a wide variety of machines; most
- * of the system dependencies have been isolated in a separate file.
- * The major functions provided here are:
- *   * pool-based allocation and freeing of memory;
- *   * policy decisions about how to divide available memory among the
- *     virtual arrays;
- *   * control logic for swapping virtual arrays between main memory and
- *     backing storage.
- * The separate system-dependent file provides the actual backing-storage
- * access code, and it contains the policy decision about how much total
- * main memory to use.
- * This file is system-dependent in the sense that some of its functions
- * are unnecessary in some systems.  For example, if there is enough virtual
- * memory so that backing storage will never be used, much of the virtual
- * array control logic could be removed.  (Of course, if you have that much
- * memory then you shouldn't care about a little bit of unused code...)
- */
-
-#define JPEG_INTERNALS
-#define AM_MEMORY_MANAGER	/* we define jvirt_Xarray_control structs */
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jmemsys.h"		/* import the system-dependent declarations */
-
-#ifndef NO_GETENV
-#ifndef HAVE_STDLIB_H		/* <stdlib.h> should declare getenv() */
-extern char * getenv JPP((const char * name));
-#endif
-#endif
-
-
-/*
- * Some important notes:
- *   The allocation routines provided here must never return NULL.
- *   They should exit to error_exit if unsuccessful.
- *
- *   It's not a good idea to try to merge the sarray and barray routines,
- *   even though they are textually almost the same, because samples are
- *   usually stored as bytes while coefficients are shorts or ints.  Thus,
- *   in machines where byte pointers have a different representation from
- *   word pointers, the resulting machine code could not be the same.
- */
-
-
-/*
- * Many machines require storage alignment: longs must start on 4-byte
- * boundaries, doubles on 8-byte boundaries, etc.  On such machines, malloc()
- * always returns pointers that are multiples of the worst-case alignment
- * requirement, and we had better do so too.
- * There isn't any really portable way to determine the worst-case alignment
- * requirement.  This module assumes that the alignment requirement is
- * multiples of sizeof(ALIGN_TYPE).
- * By default, we define ALIGN_TYPE as double.  This is necessary on some
- * workstations (where doubles really do need 8-byte alignment) and will work
- * fine on nearly everything.  If your machine has lesser alignment needs,
- * you can save a few bytes by making ALIGN_TYPE smaller.
- * The only place I know of where this will NOT work is certain Macintosh
- * 680x0 compilers that define double as a 10-byte IEEE extended float.
- * Doing 10-byte alignment is counterproductive because longwords won't be
- * aligned well.  Put "#define ALIGN_TYPE long" in jconfig.h if you have
- * such a compiler.
- */
-
-#ifndef ALIGN_TYPE		/* so can override from jconfig.h */
-#define ALIGN_TYPE  double
-#endif
-
-
-/*
- * We allocate objects from "pools", where each pool is gotten with a single
- * request to jpeg_get_small() or jpeg_get_large().  There is no per-object
- * overhead within a pool, except for alignment padding.  Each pool has a
- * header with a link to the next pool of the same class.
- * Small and large pool headers are identical except that the latter's
- * link pointer must be FAR on 80x86 machines.
- * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE
- * field.  This forces the compiler to make SIZEOF(small_pool_hdr) a multiple
- * of the alignment requirement of ALIGN_TYPE.
- */
-
-typedef union small_pool_struct * small_pool_ptr;
-
-typedef union small_pool_struct {
-  struct {
-    small_pool_ptr next;	/* next in list of pools */
-    size_t bytes_used;		/* how many bytes already used within pool */
-    size_t bytes_left;		/* bytes still available in this pool */
-  } hdr;
-  ALIGN_TYPE dummy;		/* included in union to ensure alignment */
-} small_pool_hdr;
-
-typedef union large_pool_struct FAR * large_pool_ptr;
-
-typedef union large_pool_struct {
-  struct {
-    large_pool_ptr next;	/* next in list of pools */
-    size_t bytes_used;		/* how many bytes already used within pool */
-    size_t bytes_left;		/* bytes still available in this pool */
-  } hdr;
-  ALIGN_TYPE dummy;		/* included in union to ensure alignment */
-} large_pool_hdr;
-
-
-/*
- * Here is the full definition of a memory manager object.
- */
-
-typedef struct {
-  struct jpeg_memory_mgr pub;	/* public fields */
-
-  /* Each pool identifier (lifetime class) names a linked list of pools. */
-  small_pool_ptr small_list[JPOOL_NUMPOOLS];
-  large_pool_ptr large_list[JPOOL_NUMPOOLS];
-
-  /* Since we only have one lifetime class of virtual arrays, only one
-   * linked list is necessary (for each datatype).  Note that the virtual
-   * array control blocks being linked together are actually stored somewhere
-   * in the small-pool list.
-   */
-  jvirt_sarray_ptr virt_sarray_list;
-  jvirt_barray_ptr virt_barray_list;
-
-  /* This counts total space obtained from jpeg_get_small/large */
-  long total_space_allocated;
-
-  /* alloc_sarray and alloc_barray set this value for use by virtual
-   * array routines.
-   */
-  JDIMENSION last_rowsperchunk;	/* from most recent alloc_sarray/barray */
-} my_memory_mgr;
-
-typedef my_memory_mgr * my_mem_ptr;
-
-
-/*
- * The control blocks for virtual arrays.
- * Note that these blocks are allocated in the "small" pool area.
- * System-dependent info for the associated backing store (if any) is hidden
- * inside the backing_store_info struct.
- */
-
-struct jvirt_sarray_control {
-  JSAMPARRAY mem_buffer;	/* => the in-memory buffer */
-  JDIMENSION rows_in_array;	/* total virtual array height */
-  JDIMENSION samplesperrow;	/* width of array (and of memory buffer) */
-  JDIMENSION maxaccess;		/* max rows accessed by access_virt_sarray */
-  JDIMENSION rows_in_mem;	/* height of memory buffer */
-  JDIMENSION rowsperchunk;	/* allocation chunk size in mem_buffer */
-  JDIMENSION cur_start_row;	/* first logical row # in the buffer */
-  JDIMENSION first_undef_row;	/* row # of first uninitialized row */
-  boolean pre_zero;		/* pre-zero mode requested? */
-  boolean dirty;		/* do current buffer contents need written? */
-  boolean b_s_open;		/* is backing-store data valid? */
-  jvirt_sarray_ptr next;	/* link to next virtual sarray control block */
-  backing_store_info b_s_info;	/* System-dependent control info */
-};
-
-struct jvirt_barray_control {
-  JBLOCKARRAY mem_buffer;	/* => the in-memory buffer */
-  JDIMENSION rows_in_array;	/* total virtual array height */
-  JDIMENSION blocksperrow;	/* width of array (and of memory buffer) */
-  JDIMENSION maxaccess;		/* max rows accessed by access_virt_barray */
-  JDIMENSION rows_in_mem;	/* height of memory buffer */
-  JDIMENSION rowsperchunk;	/* allocation chunk size in mem_buffer */
-  JDIMENSION cur_start_row;	/* first logical row # in the buffer */
-  JDIMENSION first_undef_row;	/* row # of first uninitialized row */
-  boolean pre_zero;		/* pre-zero mode requested? */
-  boolean dirty;		/* do current buffer contents need written? */
-  boolean b_s_open;		/* is backing-store data valid? */
-  jvirt_barray_ptr next;	/* link to next virtual barray control block */
-  backing_store_info b_s_info;	/* System-dependent control info */
-};
-
-
-#ifdef MEM_STATS		/* optional extra stuff for statistics */
-
-LOCAL(void)
-print_mem_stats (j_common_ptr cinfo, int pool_id)
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  small_pool_ptr shdr_ptr;
-  large_pool_ptr lhdr_ptr;
-
-  /* Since this is only a debugging stub, we can cheat a little by using
-   * fprintf directly rather than going through the trace message code.
-   * This is helpful because message parm array can't handle longs.
-   */
-  fprintf(stderr, "Freeing pool %d, total space = %ld\n",
-	  pool_id, mem->total_space_allocated);
-
-  for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL;
-       lhdr_ptr = lhdr_ptr->hdr.next) {
-    fprintf(stderr, "  Large chunk used %ld\n",
-	    (long) lhdr_ptr->hdr.bytes_used);
-  }
-
-  for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL;
-       shdr_ptr = shdr_ptr->hdr.next) {
-    fprintf(stderr, "  Small chunk used %ld free %ld\n",
-	    (long) shdr_ptr->hdr.bytes_used,
-	    (long) shdr_ptr->hdr.bytes_left);
-  }
-}
-
-#endif /* MEM_STATS */
-
-
-LOCAL(void)
-out_of_memory (j_common_ptr cinfo, int which)
-/* Report an out-of-memory error and stop execution */
-/* If we compiled MEM_STATS support, report alloc requests before dying */
-{
-#ifdef MEM_STATS
-  cinfo->err->trace_level = 2;	/* force self_destruct to report stats */
-#endif
-  ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which);
-}
-
-
-/*
- * Allocation of "small" objects.
- *
- * For these, we use pooled storage.  When a new pool must be created,
- * we try to get enough space for the current request plus a "slop" factor,
- * where the slop will be the amount of leftover space in the new pool.
- * The speed vs. space tradeoff is largely determined by the slop values.
- * A different slop value is provided for each pool class (lifetime),
- * and we also distinguish the first pool of a class from later ones.
- * NOTE: the values given work fairly well on both 16- and 32-bit-int
- * machines, but may be too small if longs are 64 bits or more.
- */
-
-static const size_t first_pool_slop[JPOOL_NUMPOOLS] = 
-{
-	1600,			/* first PERMANENT pool */
-	16000			/* first IMAGE pool */
-};
-
-static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = 
-{
-	0,			/* additional PERMANENT pools */
-	5000			/* additional IMAGE pools */
-};
-
-#define MIN_SLOP  50		/* greater than 0 to avoid futile looping */
-
-
-METHODDEF(void *)
-alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject)
-/* Allocate a "small" object */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  small_pool_ptr hdr_ptr, prev_hdr_ptr;
-  char * data_ptr;
-  size_t odd_bytes, min_request, slop;
-
-  /* Check for unsatisfiable request (do now to ensure no overflow below) */
-  if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr)))
-    out_of_memory(cinfo, 1);	/* request exceeds malloc's ability */
-
-  /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */
-  odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE);
-  if (odd_bytes > 0)
-    sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes;
-
-  /* See if space is available in any existing pool */
-  if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS)
-    ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id);	/* safety check */
-  prev_hdr_ptr = NULL;
-  hdr_ptr = mem->small_list[pool_id];
-  while (hdr_ptr != NULL) {
-    if (hdr_ptr->hdr.bytes_left >= sizeofobject)
-      break;			/* found pool with enough space */
-    prev_hdr_ptr = hdr_ptr;
-    hdr_ptr = hdr_ptr->hdr.next;
-  }
-
-  /* Time to make a new pool? */
-  if (hdr_ptr == NULL) {
-    /* min_request is what we need now, slop is what will be leftover */
-    min_request = sizeofobject + SIZEOF(small_pool_hdr);
-    if (prev_hdr_ptr == NULL)	/* first pool in class? */
-      slop = first_pool_slop[pool_id];
-    else
-      slop = extra_pool_slop[pool_id];
-    /* Don't ask for more than MAX_ALLOC_CHUNK */
-    if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request))
-      slop = (size_t) (MAX_ALLOC_CHUNK-min_request);
-    /* Try to get space, if fail reduce slop and try again */
-    for (;;) {
-      hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop);
-      if (hdr_ptr != NULL)
-	break;
-      slop /= 2;
-      if (slop < MIN_SLOP)	/* give up when it gets real small */
-	out_of_memory(cinfo, 2); /* jpeg_get_small failed */
-    }
-    mem->total_space_allocated += min_request + slop;
-    /* Success, initialize the new pool header and add to end of list */
-    hdr_ptr->hdr.next = NULL;
-    hdr_ptr->hdr.bytes_used = 0;
-    hdr_ptr->hdr.bytes_left = sizeofobject + slop;
-    if (prev_hdr_ptr == NULL)	/* first pool in class? */
-      mem->small_list[pool_id] = hdr_ptr;
-    else
-      prev_hdr_ptr->hdr.next = hdr_ptr;
-  }
-
-  /* OK, allocate the object from the current pool */
-  data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */
-  data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */
-  hdr_ptr->hdr.bytes_used += sizeofobject;
-  hdr_ptr->hdr.bytes_left -= sizeofobject;
-
-  return (void *) data_ptr;
-}
-
-
-/*
- * Allocation of "large" objects.
- *
- * The external semantics of these are the same as "small" objects,
- * except that FAR pointers are used on 80x86.  However the pool
- * management heuristics are quite different.  We assume that each
- * request is large enough that it may as well be passed directly to
- * jpeg_get_large; the pool management just links everything together
- * so that we can free it all on demand.
- * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY
- * structures.  The routines that create these structures (see below)
- * deliberately bunch rows together to ensure a large request size.
- */
-
-METHODDEF(void FAR *)
-alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject)
-/* Allocate a "large" object */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  large_pool_ptr hdr_ptr;
-  size_t odd_bytes;
-
-  /* Check for unsatisfiable request (do now to ensure no overflow below) */
-  if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)))
-    out_of_memory(cinfo, 3);	/* request exceeds malloc's ability */
-
-  /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */
-  odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE);
-  if (odd_bytes > 0)
-    sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes;
-
-  /* Always make a new pool */
-  if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS)
-    ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id);	/* safety check */
-
-  hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject +
-					    SIZEOF(large_pool_hdr));
-  if (hdr_ptr == NULL)
-    out_of_memory(cinfo, 4);	/* jpeg_get_large failed */
-  mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr);
-
-  /* Success, initialize the new pool header and add to list */
-  hdr_ptr->hdr.next = mem->large_list[pool_id];
-  /* We maintain space counts in each pool header for statistical purposes,
-   * even though they are not needed for allocation.
-   */
-  hdr_ptr->hdr.bytes_used = sizeofobject;
-  hdr_ptr->hdr.bytes_left = 0;
-  mem->large_list[pool_id] = hdr_ptr;
-
-  return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */
-}
-
-
-/*
- * Creation of 2-D sample arrays.
- * The pointers are in near heap, the samples themselves in FAR heap.
- *
- * To minimize allocation overhead and to allow I/O of large contiguous
- * blocks, we allocate the sample rows in groups of as many rows as possible
- * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request.
- * NB: the virtual array control routines, later in this file, know about
- * this chunking of rows.  The rowsperchunk value is left in the mem manager
- * object so that it can be saved away if this sarray is the workspace for
- * a virtual array.
- */
-
-METHODDEF(JSAMPARRAY)
-alloc_sarray (j_common_ptr cinfo, int pool_id,
-	      JDIMENSION samplesperrow, JDIMENSION numrows)
-/* Allocate a 2-D sample array */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  JSAMPARRAY result;
-  JSAMPROW workspace;
-  JDIMENSION rowsperchunk, currow, i;
-  long ltemp;
-
-  /* Calculate max # of rows allowed in one allocation chunk */
-  ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) /
-	  ((long) samplesperrow * SIZEOF(JSAMPLE));
-  if (ltemp <= 0)
-    ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
-  if (ltemp < (long) numrows)
-    rowsperchunk = (JDIMENSION) ltemp;
-  else
-    rowsperchunk = numrows;
-  mem->last_rowsperchunk = rowsperchunk;
-
-  /* Get space for row pointers (small object) */
-  result = (JSAMPARRAY) alloc_small(cinfo, pool_id,
-				    (size_t) (numrows * SIZEOF(JSAMPROW)));
-
-  /* Get the rows themselves (large objects) */
-  currow = 0;
-  while (currow < numrows) {
-    rowsperchunk = MIN(rowsperchunk, numrows - currow);
-    workspace = (JSAMPROW) alloc_large(cinfo, pool_id,
-	(size_t) ((size_t) rowsperchunk * (size_t) samplesperrow
-		  * SIZEOF(JSAMPLE)));
-    for (i = rowsperchunk; i > 0; i--) {
-      result[currow++] = workspace;
-      workspace += samplesperrow;
-    }
-  }
-
-  return result;
-}
-
-
-/*
- * Creation of 2-D coefficient-block arrays.
- * This is essentially the same as the code for sample arrays, above.
- */
-
-METHODDEF(JBLOCKARRAY)
-alloc_barray (j_common_ptr cinfo, int pool_id,
-	      JDIMENSION blocksperrow, JDIMENSION numrows)
-/* Allocate a 2-D coefficient-block array */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  JBLOCKARRAY result;
-  JBLOCKROW workspace;
-  JDIMENSION rowsperchunk, currow, i;
-  long ltemp;
-
-  /* Calculate max # of rows allowed in one allocation chunk */
-  ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) /
-	  ((long) blocksperrow * SIZEOF(JBLOCK));
-  if (ltemp <= 0)
-    ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
-  if (ltemp < (long) numrows)
-    rowsperchunk = (JDIMENSION) ltemp;
-  else
-    rowsperchunk = numrows;
-  mem->last_rowsperchunk = rowsperchunk;
-
-  /* Get space for row pointers (small object) */
-  result = (JBLOCKARRAY) alloc_small(cinfo, pool_id,
-				     (size_t) (numrows * SIZEOF(JBLOCKROW)));
-
-  /* Get the rows themselves (large objects) */
-  currow = 0;
-  while (currow < numrows) {
-    rowsperchunk = MIN(rowsperchunk, numrows - currow);
-    workspace = (JBLOCKROW) alloc_large(cinfo, pool_id,
-	(size_t) ((size_t) rowsperchunk * (size_t) blocksperrow
-		  * SIZEOF(JBLOCK)));
-    for (i = rowsperchunk; i > 0; i--) {
-      result[currow++] = workspace;
-      workspace += blocksperrow;
-    }
-  }
-
-  return result;
-}
-
-
-/*
- * About virtual array management:
- *
- * The above "normal" array routines are only used to allocate strip buffers
- * (as wide as the image, but just a few rows high).  Full-image-sized buffers
- * are handled as "virtual" arrays.  The array is still accessed a strip at a
- * time, but the memory manager must save the whole array for repeated
- * accesses.  The intended implementation is that there is a strip buffer in
- * memory (as high as is possible given the desired memory limit), plus a
- * backing file that holds the rest of the array.
- *
- * The request_virt_array routines are told the total size of the image and
- * the maximum number of rows that will be accessed at once.  The in-memory
- * buffer must be at least as large as the maxaccess value.
- *
- * The request routines create control blocks but not the in-memory buffers.
- * That is postponed until realize_virt_arrays is called.  At that time the
- * total amount of space needed is known (approximately, anyway), so free
- * memory can be divided up fairly.
- *
- * The access_virt_array routines are responsible for making a specific strip
- * area accessible (after reading or writing the backing file, if necessary).
- * Note that the access routines are told whether the caller intends to modify
- * the accessed strip; during a read-only pass this saves having to rewrite
- * data to disk.  The access routines are also responsible for pre-zeroing
- * any newly accessed rows, if pre-zeroing was requested.
- *
- * In current usage, the access requests are usually for nonoverlapping
- * strips; that is, successive access start_row numbers differ by exactly
- * num_rows = maxaccess.  This means we can get good performance with simple
- * buffer dump/reload logic, by making the in-memory buffer be a multiple
- * of the access height; then there will never be accesses across bufferload
- * boundaries.  The code will still work with overlapping access requests,
- * but it doesn't handle bufferload overlaps very efficiently.
- */
-
-
-METHODDEF(jvirt_sarray_ptr)
-request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero,
-		     JDIMENSION samplesperrow, JDIMENSION numrows,
-		     JDIMENSION maxaccess)
-/* Request a virtual 2-D sample array */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  jvirt_sarray_ptr result;
-
-  /* Only IMAGE-lifetime virtual arrays are currently supported */
-  if (pool_id != JPOOL_IMAGE)
-    ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id);	/* safety check */
-
-  /* get control block */
-  result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id,
-					  SIZEOF(struct jvirt_sarray_control));
-
-  result->mem_buffer = NULL;	/* marks array not yet realized */
-  result->rows_in_array = numrows;
-  result->samplesperrow = samplesperrow;
-  result->maxaccess = maxaccess;
-  result->pre_zero = pre_zero;
-  result->b_s_open = FALSE;	/* no associated backing-store object */
-  result->next = mem->virt_sarray_list; /* add to list of virtual arrays */
-  mem->virt_sarray_list = result;
-
-  return result;
-}
-
-
-METHODDEF(jvirt_barray_ptr)
-request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero,
-		     JDIMENSION blocksperrow, JDIMENSION numrows,
-		     JDIMENSION maxaccess)
-/* Request a virtual 2-D coefficient-block array */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  jvirt_barray_ptr result;
-
-  /* Only IMAGE-lifetime virtual arrays are currently supported */
-  if (pool_id != JPOOL_IMAGE)
-    ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id);	/* safety check */
-
-  /* get control block */
-  result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id,
-					  SIZEOF(struct jvirt_barray_control));
-
-  result->mem_buffer = NULL;	/* marks array not yet realized */
-  result->rows_in_array = numrows;
-  result->blocksperrow = blocksperrow;
-  result->maxaccess = maxaccess;
-  result->pre_zero = pre_zero;
-  result->b_s_open = FALSE;	/* no associated backing-store object */
-  result->next = mem->virt_barray_list; /* add to list of virtual arrays */
-  mem->virt_barray_list = result;
-
-  return result;
-}
-
-
-METHODDEF(void)
-realize_virt_arrays (j_common_ptr cinfo)
-/* Allocate the in-memory buffers for any unrealized virtual arrays */
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  long space_per_minheight, maximum_space, avail_mem;
-  long minheights, max_minheights;
-  jvirt_sarray_ptr sptr;
-  jvirt_barray_ptr bptr;
-
-  /* Compute the minimum space needed (maxaccess rows in each buffer)
-   * and the maximum space needed (full image height in each buffer).
-   * These may be of use to the system-dependent jpeg_mem_available routine.
-   */
-  space_per_minheight = 0;
-  maximum_space = 0;
-  for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) {
-    if (sptr->mem_buffer == NULL) { /* if not realized yet */
-      space_per_minheight += (long) sptr->maxaccess *
-			     (long) sptr->samplesperrow * SIZEOF(JSAMPLE);
-      maximum_space += (long) sptr->rows_in_array *
-		       (long) sptr->samplesperrow * SIZEOF(JSAMPLE);
-    }
-  }
-  for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) {
-    if (bptr->mem_buffer == NULL) { /* if not realized yet */
-      space_per_minheight += (long) bptr->maxaccess *
-			     (long) bptr->blocksperrow * SIZEOF(JBLOCK);
-      maximum_space += (long) bptr->rows_in_array *
-		       (long) bptr->blocksperrow * SIZEOF(JBLOCK);
-    }
-  }
-
-  if (space_per_minheight <= 0)
-    return;			/* no unrealized arrays, no work */
-
-  /* Determine amount of memory to actually use; this is system-dependent. */
-  avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space,
-				 mem->total_space_allocated);
-
-  /* If the maximum space needed is available, make all the buffers full
-   * height; otherwise parcel it out with the same number of minheights
-   * in each buffer.
-   */
-  if (avail_mem >= maximum_space)
-    max_minheights = 1000000000L;
-  else {
-    max_minheights = avail_mem / space_per_minheight;
-    /* If there doesn't seem to be enough space, try to get the minimum
-     * anyway.  This allows a "stub" implementation of jpeg_mem_available().
-     */
-    if (max_minheights <= 0)
-      max_minheights = 1;
-  }
-
-  /* Allocate the in-memory buffers and initialize backing store as needed. */
-
-  for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) {
-    if (sptr->mem_buffer == NULL) { /* if not realized yet */
-      minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L;
-      if (minheights <= max_minheights) {
-	/* This buffer fits in memory */
-	sptr->rows_in_mem = sptr->rows_in_array;
-      } else {
-	/* It doesn't fit in memory, create backing store. */
-	sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess);
-	jpeg_open_backing_store(cinfo, & sptr->b_s_info,
-				(long) sptr->rows_in_array *
-				(long) sptr->samplesperrow *
-				(long) SIZEOF(JSAMPLE));
-	sptr->b_s_open = TRUE;
-      }
-      sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE,
-				      sptr->samplesperrow, sptr->rows_in_mem);
-      sptr->rowsperchunk = mem->last_rowsperchunk;
-      sptr->cur_start_row = 0;
-      sptr->first_undef_row = 0;
-      sptr->dirty = FALSE;
-    }
-  }
-
-  for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) {
-    if (bptr->mem_buffer == NULL) { /* if not realized yet */
-      minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L;
-      if (minheights <= max_minheights) {
-	/* This buffer fits in memory */
-	bptr->rows_in_mem = bptr->rows_in_array;
-      } else {
-	/* It doesn't fit in memory, create backing store. */
-	bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess);
-	jpeg_open_backing_store(cinfo, & bptr->b_s_info,
-				(long) bptr->rows_in_array *
-				(long) bptr->blocksperrow *
-				(long) SIZEOF(JBLOCK));
-	bptr->b_s_open = TRUE;
-      }
-      bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE,
-				      bptr->blocksperrow, bptr->rows_in_mem);
-      bptr->rowsperchunk = mem->last_rowsperchunk;
-      bptr->cur_start_row = 0;
-      bptr->first_undef_row = 0;
-      bptr->dirty = FALSE;
-    }
-  }
-}
-
-
-LOCAL(void)
-do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing)
-/* Do backing store read or write of a virtual sample array */
-{
-  long bytesperrow, file_offset, byte_count, rows, thisrow, i;
-
-  bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE);
-  file_offset = ptr->cur_start_row * bytesperrow;
-  /* Loop to read or write each allocation chunk in mem_buffer */
-  for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) {
-    /* One chunk, but check for short chunk at end of buffer */
-    rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i);
-    /* Transfer no more than is currently defined */
-    thisrow = (long) ptr->cur_start_row + i;
-    rows = MIN(rows, (long) ptr->first_undef_row - thisrow);
-    /* Transfer no more than fits in file */
-    rows = MIN(rows, (long) ptr->rows_in_array - thisrow);
-    if (rows <= 0)		/* this chunk might be past end of file! */
-      break;
-    byte_count = rows * bytesperrow;
-    if (writing)
-      (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info,
-					    (void FAR *) ptr->mem_buffer[i],
-					    file_offset, byte_count);
-    else
-      (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info,
-					   (void FAR *) ptr->mem_buffer[i],
-					   file_offset, byte_count);
-    file_offset += byte_count;
-  }
-}
-
-
-LOCAL(void)
-do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing)
-/* Do backing store read or write of a virtual coefficient-block array */
-{
-  long bytesperrow, file_offset, byte_count, rows, thisrow, i;
-
-  bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK);
-  file_offset = ptr->cur_start_row * bytesperrow;
-  /* Loop to read or write each allocation chunk in mem_buffer */
-  for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) {
-    /* One chunk, but check for short chunk at end of buffer */
-    rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i);
-    /* Transfer no more than is currently defined */
-    thisrow = (long) ptr->cur_start_row + i;
-    rows = MIN(rows, (long) ptr->first_undef_row - thisrow);
-    /* Transfer no more than fits in file */
-    rows = MIN(rows, (long) ptr->rows_in_array - thisrow);
-    if (rows <= 0)		/* this chunk might be past end of file! */
-      break;
-    byte_count = rows * bytesperrow;
-    if (writing)
-      (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info,
-					    (void FAR *) ptr->mem_buffer[i],
-					    file_offset, byte_count);
-    else
-      (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info,
-					   (void FAR *) ptr->mem_buffer[i],
-					   file_offset, byte_count);
-    file_offset += byte_count;
-  }
-}
-
-
-METHODDEF(JSAMPARRAY)
-access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr,
-		    JDIMENSION start_row, JDIMENSION num_rows,
-		    boolean writable)
-/* Access the part of a virtual sample array starting at start_row */
-/* and extending for num_rows rows.  writable is true if  */
-/* caller intends to modify the accessed area. */
-{
-  JDIMENSION end_row = start_row + num_rows;
-  JDIMENSION undef_row;
-
-  /* debugging check */
-  if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess ||
-      ptr->mem_buffer == NULL)
-    ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS);
-
-  /* Make the desired part of the virtual array accessible */
-  if (start_row < ptr->cur_start_row ||
-      end_row > ptr->cur_start_row+ptr->rows_in_mem) {
-    if (! ptr->b_s_open)
-      ERREXIT(cinfo, JERR_VIRTUAL_BUG);
-    /* Flush old buffer contents if necessary */
-    if (ptr->dirty) {
-      do_sarray_io(cinfo, ptr, TRUE);
-      ptr->dirty = FALSE;
-    }
-    /* Decide what part of virtual array to access.
-     * Algorithm: if target address > current window, assume forward scan,
-     * load starting at target address.  If target address < current window,
-     * assume backward scan, load so that target area is top of window.
-     * Note that when switching from forward write to forward read, will have
-     * start_row = 0, so the limiting case applies and we load from 0 anyway.
-     */
-    if (start_row > ptr->cur_start_row) {
-      ptr->cur_start_row = start_row;
-    } else {
-      /* use long arithmetic here to avoid overflow & unsigned problems */
-      long ltemp;
-
-      ltemp = (long) end_row - (long) ptr->rows_in_mem;
-      if (ltemp < 0)
-	ltemp = 0;		/* don't fall off front end of file */
-      ptr->cur_start_row = (JDIMENSION) ltemp;
-    }
-    /* Read in the selected part of the array.
-     * During the initial write pass, we will do no actual read
-     * because the selected part is all undefined.
-     */
-    do_sarray_io(cinfo, ptr, FALSE);
-  }
-  /* Ensure the accessed part of the array is defined; prezero if needed.
-   * To improve locality of access, we only prezero the part of the array
-   * that the caller is about to access, not the entire in-memory array.
-   */
-  if (ptr->first_undef_row < end_row) {
-    if (ptr->first_undef_row < start_row) {
-      if (writable)		/* writer skipped over a section of array */
-	ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS);
-      undef_row = start_row;	/* but reader is allowed to read ahead */
-    } else {
-      undef_row = ptr->first_undef_row;
-    }
-    if (writable)
-      ptr->first_undef_row = end_row;
-    if (ptr->pre_zero) {
-      size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE);
-      undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */
-      end_row -= ptr->cur_start_row;
-      while (undef_row < end_row) {
-	jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow);
-	undef_row++;
-      }
-    } else {
-      if (! writable)		/* reader looking at undefined data */
-	ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS);
-    }
-  }
-  /* Flag the buffer dirty if caller will write in it */
-  if (writable)
-    ptr->dirty = TRUE;
-  /* Return address of proper part of the buffer */
-  return ptr->mem_buffer + (start_row - ptr->cur_start_row);
-}
-
-
-METHODDEF(JBLOCKARRAY)
-access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr,
-		    JDIMENSION start_row, JDIMENSION num_rows,
-		    boolean writable)
-/* Access the part of a virtual block array starting at start_row */
-/* and extending for num_rows rows.  writable is true if  */
-/* caller intends to modify the accessed area. */
-{
-  JDIMENSION end_row = start_row + num_rows;
-  JDIMENSION undef_row;
-
-  /* debugging check */
-  if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess ||
-      ptr->mem_buffer == NULL)
-    ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS);
-
-  /* Make the desired part of the virtual array accessible */
-  if (start_row < ptr->cur_start_row ||
-      end_row > ptr->cur_start_row+ptr->rows_in_mem) {
-    if (! ptr->b_s_open)
-      ERREXIT(cinfo, JERR_VIRTUAL_BUG);
-    /* Flush old buffer contents if necessary */
-    if (ptr->dirty) {
-      do_barray_io(cinfo, ptr, TRUE);
-      ptr->dirty = FALSE;
-    }
-    /* Decide what part of virtual array to access.
-     * Algorithm: if target address > current window, assume forward scan,
-     * load starting at target address.  If target address < current window,
-     * assume backward scan, load so that target area is top of window.
-     * Note that when switching from forward write to forward read, will have
-     * start_row = 0, so the limiting case applies and we load from 0 anyway.
-     */
-    if (start_row > ptr->cur_start_row) {
-      ptr->cur_start_row = start_row;
-    } else {
-      /* use long arithmetic here to avoid overflow & unsigned problems */
-      long ltemp;
-
-      ltemp = (long) end_row - (long) ptr->rows_in_mem;
-      if (ltemp < 0)
-	ltemp = 0;		/* don't fall off front end of file */
-      ptr->cur_start_row = (JDIMENSION) ltemp;
-    }
-    /* Read in the selected part of the array.
-     * During the initial write pass, we will do no actual read
-     * because the selected part is all undefined.
-     */
-    do_barray_io(cinfo, ptr, FALSE);
-  }
-  /* Ensure the accessed part of the array is defined; prezero if needed.
-   * To improve locality of access, we only prezero the part of the array
-   * that the caller is about to access, not the entire in-memory array.
-   */
-  if (ptr->first_undef_row < end_row) {
-    if (ptr->first_undef_row < start_row) {
-      if (writable)		/* writer skipped over a section of array */
-	ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS);
-      undef_row = start_row;	/* but reader is allowed to read ahead */
-    } else {
-      undef_row = ptr->first_undef_row;
-    }
-    if (writable)
-      ptr->first_undef_row = end_row;
-    if (ptr->pre_zero) {
-      size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK);
-      undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */
-      end_row -= ptr->cur_start_row;
-      while (undef_row < end_row) {
-	jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow);
-	undef_row++;
-      }
-    } else {
-      if (! writable)		/* reader looking at undefined data */
-	ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS);
-    }
-  }
-  /* Flag the buffer dirty if caller will write in it */
-  if (writable)
-    ptr->dirty = TRUE;
-  /* Return address of proper part of the buffer */
-  return ptr->mem_buffer + (start_row - ptr->cur_start_row);
-}
-
-
-/*
- * Release all objects belonging to a specified pool.
- */
-
-METHODDEF(void)
-free_pool (j_common_ptr cinfo, int pool_id)
-{
-  my_mem_ptr mem = (my_mem_ptr) cinfo->mem;
-  small_pool_ptr shdr_ptr;
-  large_pool_ptr lhdr_ptr;
-  size_t space_freed;
-
-  if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS)
-    ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id);	/* safety check */
-
-#ifdef MEM_STATS
-  if (cinfo->err->trace_level > 1)
-    print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */
-#endif
-
-  /* If freeing IMAGE pool, close any virtual arrays first */
-  if (pool_id == JPOOL_IMAGE) {
-    jvirt_sarray_ptr sptr;
-    jvirt_barray_ptr bptr;
-
-    for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) {
-      if (sptr->b_s_open) {	/* there may be no backing store */
-	sptr->b_s_open = FALSE;	/* prevent recursive close if error */
-	(*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info);
-      }
-    }
-    mem->virt_sarray_list = NULL;
-    for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) {
-      if (bptr->b_s_open) {	/* there may be no backing store */
-	bptr->b_s_open = FALSE;	/* prevent recursive close if error */
-	(*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info);
-      }
-    }
-    mem->virt_barray_list = NULL;
-  }
-
-  /* Release large objects */
-  lhdr_ptr = mem->large_list[pool_id];
-  mem->large_list[pool_id] = NULL;
-
-  while (lhdr_ptr != NULL) {
-    large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next;
-    space_freed = lhdr_ptr->hdr.bytes_used +
-		  lhdr_ptr->hdr.bytes_left +
-		  SIZEOF(large_pool_hdr);
-    jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed);
-    mem->total_space_allocated -= space_freed;
-    lhdr_ptr = next_lhdr_ptr;
-  }
-
-  /* Release small objects */
-  shdr_ptr = mem->small_list[pool_id];
-  mem->small_list[pool_id] = NULL;
-
-  while (shdr_ptr != NULL) {
-    small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next;
-    space_freed = shdr_ptr->hdr.bytes_used +
-		  shdr_ptr->hdr.bytes_left +
-		  SIZEOF(small_pool_hdr);
-    jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed);
-    mem->total_space_allocated -= space_freed;
-    shdr_ptr = next_shdr_ptr;
-  }
-}
-
-
-/*
- * Close up shop entirely.
- * Note that this cannot be called unless cinfo->mem is non-NULL.
- */
-
-METHODDEF(void)
-self_destruct (j_common_ptr cinfo)
-{
-  int pool;
-
-  /* Close all backing store, release all memory.
-   * Releasing pools in reverse order might help avoid fragmentation
-   * with some (brain-damaged) malloc libraries.
-   */
-  for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) {
-    free_pool(cinfo, pool);
-  }
-
-  /* Release the memory manager control block too. */
-  jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr));
-  cinfo->mem = NULL;		/* ensures I will be called only once */
-
-  jpeg_mem_term(cinfo);		/* system-dependent cleanup */
-}
-
-
-/*
- * Memory manager initialization.
- * When this is called, only the error manager pointer is valid in cinfo!
- */
-
-GLOBAL(void)
-jinit_memory_mgr (j_common_ptr cinfo)
-{
-  my_mem_ptr mem;
-  long max_to_use;
-  int pool;
-  size_t test_mac;
-
-  cinfo->mem = NULL;		/* for safety if init fails */
-
-  /* Check for configuration errors.
-   * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably
-   * doesn't reflect any real hardware alignment requirement.
-   * The test is a little tricky: for X>0, X and X-1 have no one-bits
-   * in common if and only if X is a power of 2, ie has only one one-bit.
-   * Some compilers may give an "unreachable code" warning here; ignore it.
-   */
-  if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0)
-    ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE);
-  /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be
-   * a multiple of SIZEOF(ALIGN_TYPE).
-   * Again, an "unreachable code" warning may be ignored here.
-   * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK.
-   */
-  test_mac = (size_t) MAX_ALLOC_CHUNK;
-  if ((long) test_mac != MAX_ALLOC_CHUNK ||
-      (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0)
-    ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK);
-
-  max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */
-
-  /* Attempt to allocate memory manager's control block */
-  mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr));
-
-  if (mem == NULL) {
-    jpeg_mem_term(cinfo);	/* system-dependent cleanup */
-    ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0);
-  }
-
-  /* OK, fill in the method pointers */
-  mem->pub.alloc_small = alloc_small;
-  mem->pub.alloc_large = alloc_large;
-  mem->pub.alloc_sarray = alloc_sarray;
-  mem->pub.alloc_barray = alloc_barray;
-  mem->pub.request_virt_sarray = request_virt_sarray;
-  mem->pub.request_virt_barray = request_virt_barray;
-  mem->pub.realize_virt_arrays = realize_virt_arrays;
-  mem->pub.access_virt_sarray = access_virt_sarray;
-  mem->pub.access_virt_barray = access_virt_barray;
-  mem->pub.free_pool = free_pool;
-  mem->pub.self_destruct = self_destruct;
-
-  /* Make MAX_ALLOC_CHUNK accessible to other modules */
-  mem->pub.max_alloc_chunk = MAX_ALLOC_CHUNK;
-
-  /* Initialize working state */
-  mem->pub.max_memory_to_use = max_to_use;
-
-  for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) {
-    mem->small_list[pool] = NULL;
-    mem->large_list[pool] = NULL;
-  }
-  mem->virt_sarray_list = NULL;
-  mem->virt_barray_list = NULL;
-
-  mem->total_space_allocated = SIZEOF(my_memory_mgr);
-
-  /* Declare ourselves open for business */
-  cinfo->mem = & mem->pub;
-
-  /* Check for an environment variable JPEGMEM; if found, override the
-   * default max_memory setting from jpeg_mem_init.  Note that the
-   * surrounding application may again override this value.
-   * If your system doesn't support getenv(), define NO_GETENV to disable
-   * this feature.
-   */
-#ifndef NO_GETENV
-  { char * memenv;
-
-    if ((memenv = getenv("JPEGMEM")) != NULL) {
-      char ch = 'x';
-
-      if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) {
-	if (ch == 'm' || ch == 'M')
-	  max_to_use *= 1000L;
-	mem->pub.max_memory_to_use = max_to_use * 1000L;
-      }
-    }
-  }
-#endif
-
-}
diff --git a/third_party/libjpeg/jmemnobs.c b/third_party/libjpeg/jmemnobs.c
deleted file mode 100644
index 935ef6e..0000000
--- a/third_party/libjpeg/jmemnobs.c
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * jmemnobs.c
- *
- * Copyright (C) 1992-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file provides a really simple implementation of the system-
- * dependent portion of the JPEG memory manager.  This implementation
- * assumes that no backing-store files are needed: all required space
- * can be obtained from malloc().
- * This is very portable in the sense that it'll compile on almost anything,
- * but you'd better have lots of main memory (or virtual memory) if you want
- * to process big images.
- * Note that the max_memory_to_use option is ignored by this implementation.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-#include "jmemsys.h"		/* import the system-dependent declarations */
-
-#if defined(NEED_STARBOARD_MEMORY)
-#include "starboard/memory.h"
-#define malloc SbMemoryAllocate
-#define free SbMemoryDeallocate
-#elif !defined(HAVE_STDLIB_H)	/* <stdlib.h> should declare malloc(),free() */
-extern void * malloc JPP((size_t size));
-extern void free JPP((void *ptr));
-#endif
-
-
-/*
- * Memory allocation and freeing are controlled by the regular library
- * routines malloc() and free().
- */
-
-GLOBAL(void *)
-jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject)
-{
-  return (void *) malloc(sizeofobject);
-}
-
-GLOBAL(void)
-jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject)
-{
-  free(object);
-}
-
-
-/*
- * "Large" objects are treated the same as "small" ones.
- * NB: although we include FAR keywords in the routine declarations,
- * this file won't actually work in 80x86 small/medium model; at least,
- * you probably won't be able to process useful-size images in only 64KB.
- */
-
-GLOBAL(void FAR *)
-jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject)
-{
-  return (void FAR *) malloc(sizeofobject);
-}
-
-GLOBAL(void)
-jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject)
-{
-  free(object);
-}
-
-
-/*
- * This routine computes the total memory space available for allocation.
- * Here we always say, "we got all you want bud!"
- */
-
-GLOBAL(long)
-jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed,
-		    long max_bytes_needed, long already_allocated)
-{
-  return max_bytes_needed;
-}
-
-
-/*
- * Backing store (temporary file) management.
- * Since jpeg_mem_available always promised the moon,
- * this should never be called and we can just error out.
- */
-
-GLOBAL(void)
-jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info,
-			 long total_bytes_needed)
-{
-  ERREXIT(cinfo, JERR_NO_BACKING_STORE);
-}
-
-
-/*
- * These routines take care of any system-dependent initialization and
- * cleanup required.  Here, there isn't any.
- */
-
-GLOBAL(long)
-jpeg_mem_init (j_common_ptr cinfo)
-{
-  return 0;			/* just set max_memory_to_use to 0 */
-}
-
-GLOBAL(void)
-jpeg_mem_term (j_common_ptr cinfo)
-{
-  /* no work */
-}
diff --git a/third_party/libjpeg/jmemsys.h b/third_party/libjpeg/jmemsys.h
deleted file mode 100644
index 64f6788..0000000
--- a/third_party/libjpeg/jmemsys.h
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * jmemsys.h
- *
- * Copyright (C) 1992-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This include file defines the interface between the system-independent
- * and system-dependent portions of the JPEG memory manager.  No other
- * modules need include it.  (The system-independent portion is jmemmgr.c;
- * there are several different versions of the system-dependent portion.)
- *
- * This file works as-is for the system-dependent memory managers supplied
- * in the IJG distribution.  You may need to modify it if you write a
- * custom memory manager.  If system-dependent changes are needed in
- * this file, the best method is to #ifdef them based on a configuration
- * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR
- * and USE_MAC_MEMMGR.
- */
-
-
-/* Short forms of external names for systems with brain-damaged linkers. */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jpeg_get_small		jGetSmall
-#define jpeg_free_small		jFreeSmall
-#define jpeg_get_large		jGetLarge
-#define jpeg_free_large		jFreeLarge
-#define jpeg_mem_available	jMemAvail
-#define jpeg_open_backing_store	jOpenBackStore
-#define jpeg_mem_init		jMemInit
-#define jpeg_mem_term		jMemTerm
-#endif /* NEED_SHORT_EXTERNAL_NAMES */
-
-
-/*
- * These two functions are used to allocate and release small chunks of
- * memory.  (Typically the total amount requested through jpeg_get_small is
- * no more than 20K or so; this will be requested in chunks of a few K each.)
- * Behavior should be the same as for the standard library functions malloc
- * and free; in particular, jpeg_get_small must return NULL on failure.
- * On most systems, these ARE malloc and free.  jpeg_free_small is passed the
- * size of the object being freed, just in case it's needed.
- * On an 80x86 machine using small-data memory model, these manage near heap.
- */
-
-EXTERN(void *) jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject));
-EXTERN(void) jpeg_free_small JPP((j_common_ptr cinfo, void * object,
-				  size_t sizeofobject));
-
-/*
- * These two functions are used to allocate and release large chunks of
- * memory (up to the total free space designated by jpeg_mem_available).
- * The interface is the same as above, except that on an 80x86 machine,
- * far pointers are used.  On most other machines these are identical to
- * the jpeg_get/free_small routines; but we keep them separate anyway,
- * in case a different allocation strategy is desirable for large chunks.
- */
-
-EXTERN(void FAR *) jpeg_get_large JPP((j_common_ptr cinfo,
-				       size_t sizeofobject));
-EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object,
-				  size_t sizeofobject));
-
-/*
- * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may
- * be requested in a single call to jpeg_get_large (and jpeg_get_small for that
- * matter, but that case should never come into play).  This macro is needed
- * to model the 64Kb-segment-size limit of far addressing on 80x86 machines.
- * On those machines, we expect that jconfig.h will provide a proper value.
- * On machines with 32-bit flat address spaces, any large constant may be used.
- *
- * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type
- * size_t and will be a multiple of sizeof(align_type).
- */
-
-#ifndef MAX_ALLOC_CHUNK		/* may be overridden in jconfig.h */
-#define MAX_ALLOC_CHUNK  1000000000L
-#endif
-
-/*
- * This routine computes the total space still available for allocation by
- * jpeg_get_large.  If more space than this is needed, backing store will be
- * used.  NOTE: any memory already allocated must not be counted.
- *
- * There is a minimum space requirement, corresponding to the minimum
- * feasible buffer sizes; jmemmgr.c will request that much space even if
- * jpeg_mem_available returns zero.  The maximum space needed, enough to hold
- * all working storage in memory, is also passed in case it is useful.
- * Finally, the total space already allocated is passed.  If no better
- * method is available, cinfo->mem->max_memory_to_use - already_allocated
- * is often a suitable calculation.
- *
- * It is OK for jpeg_mem_available to underestimate the space available
- * (that'll just lead to more backing-store access than is really necessary).
- * However, an overestimate will lead to failure.  Hence it's wise to subtract
- * a slop factor from the true available space.  5% should be enough.
- *
- * On machines with lots of virtual memory, any large constant may be returned.
- * Conversely, zero may be returned to always use the minimum amount of memory.
- */
-
-EXTERN(long) jpeg_mem_available JPP((j_common_ptr cinfo,
-				     long min_bytes_needed,
-				     long max_bytes_needed,
-				     long already_allocated));
-
-
-/*
- * This structure holds whatever state is needed to access a single
- * backing-store object.  The read/write/close method pointers are called
- * by jmemmgr.c to manipulate the backing-store object; all other fields
- * are private to the system-dependent backing store routines.
- */
-
-#define TEMP_NAME_LENGTH   64	/* max length of a temporary file's name */
-
-
-#ifdef USE_MSDOS_MEMMGR		/* DOS-specific junk */
-
-typedef unsigned short XMSH;	/* type of extended-memory handles */
-typedef unsigned short EMSH;	/* type of expanded-memory handles */
-
-typedef union {
-  short file_handle;		/* DOS file handle if it's a temp file */
-  XMSH xms_handle;		/* handle if it's a chunk of XMS */
-  EMSH ems_handle;		/* handle if it's a chunk of EMS */
-} handle_union;
-
-#endif /* USE_MSDOS_MEMMGR */
-
-#ifdef USE_MAC_MEMMGR		/* Mac-specific junk */
-#include <Files.h>
-#endif /* USE_MAC_MEMMGR */
-
-
-typedef struct backing_store_struct * backing_store_ptr;
-
-typedef struct backing_store_struct {
-  /* Methods for reading/writing/closing this backing-store object */
-  JMETHOD(void, read_backing_store, (j_common_ptr cinfo,
-				     backing_store_ptr info,
-				     void FAR * buffer_address,
-				     long file_offset, long byte_count));
-  JMETHOD(void, write_backing_store, (j_common_ptr cinfo,
-				      backing_store_ptr info,
-				      void FAR * buffer_address,
-				      long file_offset, long byte_count));
-  JMETHOD(void, close_backing_store, (j_common_ptr cinfo,
-				      backing_store_ptr info));
-
-  /* Private fields for system-dependent backing-store management */
-#ifdef USE_MSDOS_MEMMGR
-  /* For the MS-DOS manager (jmemdos.c), we need: */
-  handle_union handle;		/* reference to backing-store storage object */
-  char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */
-#else
-#ifdef USE_MAC_MEMMGR
-  /* For the Mac manager (jmemmac.c), we need: */
-  short temp_file;		/* file reference number to temp file */
-  FSSpec tempSpec;		/* the FSSpec for the temp file */
-  char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */
-#elif !defined(JPEG_NO_STDIO)
-  /* For a typical implementation with temp files, we need: */
-  FILE * temp_file;		/* stdio reference to temp file */
-  char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */
-#endif
-#endif
-} backing_store_info;
-
-
-/*
- * Initial opening of a backing-store object.  This must fill in the
- * read/write/close pointers in the object.  The read/write routines
- * may take an error exit if the specified maximum file size is exceeded.
- * (If jpeg_mem_available always returns a large value, this routine can
- * just take an error exit.)
- */
-
-EXTERN(void) jpeg_open_backing_store JPP((j_common_ptr cinfo,
-					  backing_store_ptr info,
-					  long total_bytes_needed));
-
-
-/*
- * These routines take care of any system-dependent initialization and
- * cleanup required.  jpeg_mem_init will be called before anything is
- * allocated (and, therefore, nothing in cinfo is of use except the error
- * manager pointer).  It should return a suitable default value for
- * max_memory_to_use; this may subsequently be overridden by the surrounding
- * application.  (Note that max_memory_to_use is only important if
- * jpeg_mem_available chooses to consult it ... no one else will.)
- * jpeg_mem_term may assume that all requested memory has been freed and that
- * all opened backing-store objects have been closed.
- */
-
-EXTERN(long) jpeg_mem_init JPP((j_common_ptr cinfo));
-EXTERN(void) jpeg_mem_term JPP((j_common_ptr cinfo));
diff --git a/third_party/libjpeg/jmorecfg.h b/third_party/libjpeg/jmorecfg.h
deleted file mode 100644
index afb5bb4..0000000
--- a/third_party/libjpeg/jmorecfg.h
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * jmorecfg.h
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains additional configuration options that customize the
- * JPEG software for special applications or support machine-dependent
- * optimizations.  Most users will not need to touch this file.
- */
-
-/*
- * This file has been modified for the Mozilla/Netscape environment.
- * Modifications are distributed under the Netscape Public License and are
- * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
- * Reserved.
- */
-
-
-/*
- * Define BITS_IN_JSAMPLE as either
- *   8   for 8-bit sample values (the usual setting)
- *   12  for 12-bit sample values
- * Only 8 and 12 are legal data precisions for lossy JPEG according to the
- * JPEG standard, and the IJG code does not support anything else!
- * We do not support run-time selection of data precision, sorry.
- */
-
-#define BITS_IN_JSAMPLE  8	/* use 8 or 12 */
-
-
-/*
- * Maximum number of components (color channels) allowed in JPEG image.
- * To meet the letter of the JPEG spec, set this to 255.  However, darn
- * few applications need more than 4 channels (maybe 5 for CMYK + alpha
- * mask).  We recommend 10 as a reasonable compromise; use 4 if you are
- * really short on memory.  (Each allowed component costs a hundred or so
- * bytes of storage, whether actually used in an image or not.)
- */
-
-#define MAX_COMPONENTS  10	/* maximum number of image components */
-
-
-/*
- * Basic data types.
- * You may need to change these if you have a machine with unusual data
- * type sizes; for example, "char" not 8 bits, "short" not 16 bits,
- * or "long" not 32 bits.  We don't care whether "int" is 16 or 32 bits,
- * but it had better be at least 16.
- */
-
-/* Representation of a single sample (pixel element value).
- * We frequently allocate large arrays of these, so it's important to keep
- * them small.  But if you have memory to burn and access to char or short
- * arrays is very slow on your hardware, you might want to change these.
- */
-
-#if BITS_IN_JSAMPLE == 8
-/* JSAMPLE should be the smallest type that will hold the values 0..255.
- * You can use a signed char by having GETJSAMPLE mask it with 0xFF.
- */
-
-#ifdef HAVE_UNSIGNED_CHAR
-
-typedef unsigned char JSAMPLE;
-#define GETJSAMPLE(value)  ((int) (value))
-
-#else /* not HAVE_UNSIGNED_CHAR */
-
-typedef char JSAMPLE;
-#ifdef CHAR_IS_UNSIGNED
-#define GETJSAMPLE(value)  ((int) (value))
-#else
-#define GETJSAMPLE(value)  ((int) (value) & 0xFF)
-#endif /* CHAR_IS_UNSIGNED */
-
-#endif /* HAVE_UNSIGNED_CHAR */
-
-#define MAXJSAMPLE	255
-#define CENTERJSAMPLE	128
-
-#endif /* BITS_IN_JSAMPLE == 8 */
-
-
-#if BITS_IN_JSAMPLE == 12
-/* JSAMPLE should be the smallest type that will hold the values 0..4095.
- * On nearly all machines "short" will do nicely.
- */
-
-typedef short JSAMPLE;
-#define GETJSAMPLE(value)  ((int) (value))
-
-#define MAXJSAMPLE	4095
-#define CENTERJSAMPLE	2048
-
-#endif /* BITS_IN_JSAMPLE == 12 */
-
-
-/* Representation of a DCT frequency coefficient.
- * This should be a signed value of at least 16 bits; "short" is usually OK.
- * Again, we allocate large arrays of these, but you can change to int
- * if you have memory to burn and "short" is really slow.
- */
-
-typedef short JCOEF;
-
-/* Defines for MMX/SSE2 support. */
-
-#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__)
-#define HAVE_MMX_INTEL_MNEMONICS 
-
-/* SSE2 code appears broken for some cpus (bug 247437) */
-/* #define HAVE_SSE2_INTEL_MNEMONICS */
-#endif
-
-/* Compressed datastreams are represented as arrays of JOCTET.
- * These must be EXACTLY 8 bits wide, at least once they are written to
- * external storage.  Note that when using the stdio data source/destination
- * managers, this is also the data type passed to fread/fwrite.
- */
-
-#ifdef HAVE_UNSIGNED_CHAR
-
-typedef unsigned char JOCTET;
-#define GETJOCTET(value)  (value)
-
-#else /* not HAVE_UNSIGNED_CHAR */
-
-typedef char JOCTET;
-#ifdef CHAR_IS_UNSIGNED
-#define GETJOCTET(value)  (value)
-#else
-#define GETJOCTET(value)  ((value) & 0xFF)
-#endif /* CHAR_IS_UNSIGNED */
-
-#endif /* HAVE_UNSIGNED_CHAR */
-
-
-/* These typedefs are used for various table entries and so forth.
- * They must be at least as wide as specified; but making them too big
- * won't cost a huge amount of memory, so we don't provide special
- * extraction code like we did for JSAMPLE.  (In other words, these
- * typedefs live at a different point on the speed/space tradeoff curve.)
- */
-
-/* UINT8 must hold at least the values 0..255. */
-
-#ifdef HAVE_UNSIGNED_CHAR
-typedef unsigned char UINT8;
-#else /* not HAVE_UNSIGNED_CHAR */
-#ifdef CHAR_IS_UNSIGNED
-typedef char UINT8;
-#else /* not CHAR_IS_UNSIGNED */
-typedef short UINT8;
-#endif /* CHAR_IS_UNSIGNED */
-#endif /* HAVE_UNSIGNED_CHAR */
-
-/* UINT16 must hold at least the values 0..65535. */
-
-#ifdef HAVE_UNSIGNED_SHORT
-typedef unsigned short UINT16;
-#else /* not HAVE_UNSIGNED_SHORT */
-typedef unsigned int UINT16;
-#endif /* HAVE_UNSIGNED_SHORT */
-
-/* INT16 must hold at least the values -32768..32767. */
-
-#ifndef XMD_H			/* X11/xmd.h correctly defines INT16 */
-typedef short INT16;
-#endif
-
-/* INT32 must hold at least signed 32-bit values. */
-
-#ifndef XMD_H			/* X11/xmd.h correctly defines INT32 */
-#ifndef _BASETSD_H_		/* basetsd.h correctly defines INT32 */
-#ifndef _BASETSD_H
-typedef long INT32;
-#endif
-#endif
-#endif
-
-/* Datatype used for image dimensions.  The JPEG standard only supports
- * images up to 64K*64K due to 16-bit fields in SOF markers.  Therefore
- * "unsigned int" is sufficient on all machines.  However, if you need to
- * handle larger images and you don't mind deviating from the spec, you
- * can change this datatype.
- */
-
-typedef unsigned int JDIMENSION;
-
-#define JPEG_MAX_DIMENSION  65500L  /* a tad under 64K to prevent overflows */
-
-
-/* These macros are used in all function definitions and extern declarations.
- * You could modify them if you need to change function linkage conventions;
- * in particular, you'll need to do that to make the library a Windows DLL.
- * Another application is to make all functions global for use with debuggers
- * or code profilers that require it.
- */
-
-/* a function called through method pointers: */
-#define METHODDEF(type)		static type
-/* a function used only in its module: */
-#define LOCAL(type)		static type
-/* a function referenced thru EXTERNs: */
-#define GLOBAL(type)		type
-/* a reference to a GLOBAL function: */
-#define EXTERN(type)		extern type
-
-
-/* This macro is used to declare a "method", that is, a function pointer.
- * We want to supply prototype parameters if the compiler can cope.
- * Note that the arglist parameter must be parenthesized!
- * Again, you can customize this if you need special linkage keywords.
- */
-
-#ifdef HAVE_PROTOTYPES
-#define JMETHOD(type,methodname,arglist)  type (*methodname) arglist
-#else
-#define JMETHOD(type,methodname,arglist)  type (*methodname) ()
-#endif
-
-
-/* Here is the pseudo-keyword for declaring pointers that must be "far"
- * on 80x86 machines.  Most of the specialized coding for 80x86 is handled
- * by just saying "FAR *" where such a pointer is needed.  In a few places
- * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol.
- */
-
-#ifndef FAR
-#ifdef NEED_FAR_POINTERS
-#define FAR  far
-#else
-#define FAR
-#endif
-#endif
-
-
-/*
- * On a few systems, type boolean and/or its values FALSE, TRUE may appear
- * in standard header files.  Or you may have conflicts with application-
- * specific header files that you want to include together with these files.
- * Defining HAVE_BOOLEAN before including jpeglib.h should make it work.
- */
-
-/* Mozilla mod: IJG distribution makes boolean = int, but on Windows
- * it's far safer to define boolean = unsigned char.  Easier to switch
- * than fight.
- */
-
-/* For some reason, on SunOS 5.3 HAVE_BOOLEAN gets defined when using
- * gcc, but boolean doesn't.  Even if you use -UHAVE_BOOLEAN, it still
- * gets reset somewhere.
- */
-#if defined(MUST_UNDEF_HAVE_BOOLEAN_AFTER_INCLUDES) && defined(HAVE_BOOLEAN)
-#undef HAVE_BOOLEAN
-#endif
-#ifndef HAVE_BOOLEAN
-typedef unsigned char boolean;
-#endif
-#ifndef FALSE			/* in case these macros already exist */
-#define FALSE	0		/* values of boolean */
-#endif
-#ifndef TRUE
-#define TRUE	1
-#endif
-
-
-/*
- * The remaining options affect code selection within the JPEG library,
- * but they don't need to be visible to most applications using the library.
- * To minimize application namespace pollution, the symbols won't be
- * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined.
- */
-
-#ifdef JPEG_INTERNALS
-#define JPEG_INTERNAL_OPTIONS
-#endif
-
-#ifdef JPEG_INTERNAL_OPTIONS
-
-
-/*
- * These defines indicate whether to include various optional functions.
- * Undefining some of these symbols will produce a smaller but less capable
- * library.  Note that you can leave certain source files out of the
- * compilation/linking process if you've #undef'd the corresponding symbols.
- * (You may HAVE to do that if your compiler doesn't like null source files.)
- */
-
-/*
- * Mozilla mods here: undef some features not actually used by the browser.
- * This reduces object code size and more importantly allows us to compile
- * even with broken compilers that crash when fed certain modules of the
- * IJG sources.  Currently we undef:
- * DCT_FLOAT_SUPPORTED INPUT_SMOOTHING_SUPPORTED IDCT_SCALING_SUPPORTED
- * QUANT_1PASS_SUPPORTED QUANT_2PASS_SUPPORTED
- */
-
-/* Arithmetic coding is unsupported for legal reasons.  Complaints to IBM. */
-
-/* Capability options common to encoder and decoder: */
-
-#define DCT_ISLOW_SUPPORTED	/* slow but accurate integer algorithm */
-#undef  DCT_IFAST_SUPPORTED	/* faster, less accurate integer method */
-#undef  DCT_FLOAT_SUPPORTED	/* floating-point: accurate, fast on fast HW */
-
-/* Encoder capability options: */
-
-#undef  C_ARITH_CODING_SUPPORTED    /* Arithmetic coding back end? */
-#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */
-#define C_PROGRESSIVE_SUPPORTED	    /* Progressive JPEG? (Requires MULTISCAN)*/
-#define ENTROPY_OPT_SUPPORTED	    /* Optimization of entropy coding parms? */
-/* Note: if you selected 12-bit data precision, it is dangerous to turn off
- * ENTROPY_OPT_SUPPORTED.  The standard Huffman tables are only good for 8-bit
- * precision, so jchuff.c normally uses entropy optimization to compute
- * usable tables for higher precision.  If you don't want to do optimization,
- * you'll have to supply different default Huffman tables.
- * The exact same statements apply for progressive JPEG: the default tables
- * don't work for progressive mode.  (This may get fixed, however.)
- */
-#undef  INPUT_SMOOTHING_SUPPORTED   /* Input image smoothing option? */
-
-/* TextResourceDecoder capability options: */
-
-#undef  D_ARITH_CODING_SUPPORTED    /* Arithmetic coding back end? */
-#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */
-#define D_PROGRESSIVE_SUPPORTED	    /* Progressive JPEG? (Requires MULTISCAN)*/
-#define SAVE_MARKERS_SUPPORTED	    /* jpeg_save_markers() needed? */
-#define BLOCK_SMOOTHING_SUPPORTED   /* Block smoothing? (Progressive only) */
-#undef  IDCT_SCALING_SUPPORTED	    /* Output rescaling via IDCT? */
-#undef  UPSAMPLE_SCALING_SUPPORTED  /* Output rescaling at upsample stage? */
-#define UPSAMPLE_MERGING_SUPPORTED  /* Fast path for sloppy upsampling? */
-#undef  QUANT_1PASS_SUPPORTED	    /* 1-pass color quantization? */
-#undef  QUANT_2PASS_SUPPORTED	    /* 2-pass color quantization? */
-
-/* more capability options later, no doubt */
-
-
-/*
- * Ordering of RGB data in scanlines passed to or from the application.
- * If your application wants to deal with data in the order B,G,R, just
- * change these macros.  You can also deal with formats such as R,G,B,X
- * (one extra byte per pixel) by changing RGB_PIXELSIZE.  Note that changing
- * the offsets will also change the order in which colormap data is organized.
- * RESTRICTIONS:
- * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats.
- * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not
- *    useful if you are using JPEG color spaces other than YCbCr or grayscale.
- * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE
- *    is not 3 (they don't understand about dummy color components!).  So you
- *    can't use color quantization if you change that value.
- */
-
-#define RGB_RED		0	/* Offset of Red in an RGB scanline element */
-#define RGB_GREEN	1	/* Offset of Green */
-#define RGB_BLUE	2	/* Offset of Blue */
-#define RGB_PIXELSIZE	3	/* JSAMPLEs per RGB scanline element */
-
-
-/* Definitions for speed-related optimizations. */
-
-
-/* If your compiler supports inline functions, define INLINE
- * as the inline keyword; otherwise define it as empty.
- */
-
-/* Mozilla mods here: add more ways of defining INLINE */
-
-#ifndef INLINE
-#ifdef __GNUC__			/* for instance, GNU C knows about inline */
-#define INLINE __inline__
-#endif
-#if defined( __IBMC__ ) || defined (__IBMCPP__)
-#define INLINE _Inline
-#endif
-#ifndef INLINE
-#ifdef __cplusplus
-#define INLINE inline		/* a C++ compiler should have it too */
-#else
-#define INLINE			/* default is to define it as empty */
-#endif
-#endif
-#endif
-
-
-/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying
- * two 16-bit shorts is faster than multiplying two ints.  Define MULTIPLIER
- * as short on such a machine.  MULTIPLIER must be at least 16 bits wide.
- */
-
-#ifndef MULTIPLIER
-#define MULTIPLIER  int		/* type for fastest integer multiply */
-#endif
-
-
-/* FAST_FLOAT should be either float or double, whichever is done faster
- * by your compiler.  (Note that this type is only used in the floating point
- * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.)
- * Typically, float is faster in ANSI C compilers, while double is faster in
- * pre-ANSI compilers (because they insist on converting to double anyway).
- * The code below therefore chooses float if we have ANSI-style prototypes.
- */
-
-#ifndef FAST_FLOAT
-#ifdef HAVE_PROTOTYPES
-#define FAST_FLOAT  float
-#else
-#define FAST_FLOAT  double
-#endif
-#endif
-
-#endif /* JPEG_INTERNAL_OPTIONS */
diff --git a/third_party/libjpeg/jpegint.h b/third_party/libjpeg/jpegint.h
deleted file mode 100644
index 95b00d4..0000000
--- a/third_party/libjpeg/jpegint.h
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * jpegint.h
- *
- * Copyright (C) 1991-1997, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file provides common declarations for the various JPEG modules.
- * These declarations are considered internal to the JPEG library; most
- * applications using the library shouldn't need to include this file.
- */
-
-
-/* Declarations for both compression & decompression */
-
-typedef enum {			/* Operating modes for buffer controllers */
-	JBUF_PASS_THRU,		/* Plain stripwise operation */
-	/* Remaining modes require a full-image buffer to have been created */
-	JBUF_SAVE_SOURCE,	/* Run source subobject only, save output */
-	JBUF_CRANK_DEST,	/* Run dest subobject only, using saved data */
-	JBUF_SAVE_AND_PASS	/* Run both subobjects, save output */
-} J_BUF_MODE;
-
-/* Values of global_state field (jdapi.c has some dependencies on ordering!) */
-#define CSTATE_START	100	/* after create_compress */
-#define CSTATE_SCANNING	101	/* start_compress done, write_scanlines OK */
-#define CSTATE_RAW_OK	102	/* start_compress done, write_raw_data OK */
-#define CSTATE_WRCOEFS	103	/* jpeg_write_coefficients done */
-#define DSTATE_START	200	/* after create_decompress */
-#define DSTATE_INHEADER	201	/* reading header markers, no SOS yet */
-#define DSTATE_READY	202	/* found SOS, ready for start_decompress */
-#define DSTATE_PRELOAD	203	/* reading multiscan file in start_decompress*/
-#define DSTATE_PRESCAN	204	/* performing dummy pass for 2-pass quant */
-#define DSTATE_SCANNING	205	/* start_decompress done, read_scanlines OK */
-#define DSTATE_RAW_OK	206	/* start_decompress done, read_raw_data OK */
-#define DSTATE_BUFIMAGE	207	/* expecting jpeg_start_output */
-#define DSTATE_BUFPOST	208	/* looking for SOS/EOI in jpeg_finish_output */
-#define DSTATE_RDCOEFS	209	/* reading file in jpeg_read_coefficients */
-#define DSTATE_STOPPING	210	/* looking for EOI in jpeg_finish_decompress */
-
-
-/* Declarations for compression modules */
-
-/* Master control module */
-struct jpeg_comp_master {
-  JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo));
-  JMETHOD(void, pass_startup, (j_compress_ptr cinfo));
-  JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
-
-  /* State variables made visible to other modules */
-  boolean call_pass_startup;	/* True if pass_startup must be called */
-  boolean is_last_pass;		/* True during last pass */
-};
-
-/* Main buffer control (downsampled-data buffer) */
-struct jpeg_c_main_controller {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
-  JMETHOD(void, process_data, (j_compress_ptr cinfo,
-			       JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
-			       JDIMENSION in_rows_avail));
-};
-
-/* Compression preprocessing (downsampling input buffer control) */
-struct jpeg_c_prep_controller {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
-  JMETHOD(void, pre_process_data, (j_compress_ptr cinfo,
-				   JSAMPARRAY input_buf,
-				   JDIMENSION *in_row_ctr,
-				   JDIMENSION in_rows_avail,
-				   JSAMPIMAGE output_buf,
-				   JDIMENSION *out_row_group_ctr,
-				   JDIMENSION out_row_groups_avail));
-};
-
-/* Coefficient buffer control */
-struct jpeg_c_coef_controller {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
-  JMETHOD(boolean, compress_data, (j_compress_ptr cinfo,
-				   JSAMPIMAGE input_buf));
-};
-
-/* Colorspace conversion */
-struct jpeg_color_converter {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo));
-  JMETHOD(void, color_convert, (j_compress_ptr cinfo,
-				JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
-				JDIMENSION output_row, int num_rows));
-};
-
-/* Downsampling */
-struct jpeg_downsampler {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo));
-  JMETHOD(void, downsample, (j_compress_ptr cinfo,
-			     JSAMPIMAGE input_buf, JDIMENSION in_row_index,
-			     JSAMPIMAGE output_buf,
-			     JDIMENSION out_row_group_index));
-
-  boolean need_context_rows;	/* TRUE if need rows above & below */
-};
-
-/* Forward DCT (also controls coefficient quantization) */
-struct jpeg_forward_dct {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo));
-  /* perhaps this should be an array??? */
-  JMETHOD(void, forward_DCT, (j_compress_ptr cinfo,
-			      jpeg_component_info * compptr,
-			      JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
-			      JDIMENSION start_row, JDIMENSION start_col,
-			      JDIMENSION num_blocks));
-};
-
-/* Entropy encoding */
-struct jpeg_entropy_encoder {
-  JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics));
-  JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data));
-  JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
-};
-
-/* Marker writing */
-struct jpeg_marker_writer {
-  JMETHOD(void, write_file_header, (j_compress_ptr cinfo));
-  JMETHOD(void, write_frame_header, (j_compress_ptr cinfo));
-  JMETHOD(void, write_scan_header, (j_compress_ptr cinfo));
-  JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo));
-  JMETHOD(void, write_tables_only, (j_compress_ptr cinfo));
-  /* These routines are exported to allow insertion of extra markers */
-  /* Probably only COM and APPn markers should be written this way */
-  JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker,
-				      unsigned int datalen));
-  JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val));
-};
-
-
-/* Declarations for decompression modules */
-
-/* Master control module */
-struct jpeg_decomp_master {
-  JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo));
-  JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo));
-
-  /* State variables made visible to other modules */
-  boolean is_dummy_pass;	/* True during 1st pass for 2-pass quant */
-};
-
-/* Input control module */
-struct jpeg_input_controller {
-  JMETHOD(int, consume_input, (j_decompress_ptr cinfo));
-  JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo));
-  JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
-  JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo));
-
-  /* State variables made visible to other modules */
-  boolean has_multiple_scans;	/* True if file has multiple scans */
-  boolean eoi_reached;		/* True when EOI has been consumed */
-};
-
-/* Main buffer control (downsampled-data buffer) */
-struct jpeg_d_main_controller {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
-  JMETHOD(void, process_data, (j_decompress_ptr cinfo,
-			       JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
-			       JDIMENSION out_rows_avail));
-};
-
-/* Coefficient buffer control */
-struct jpeg_d_coef_controller {
-  JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
-  JMETHOD(int, consume_data, (j_decompress_ptr cinfo));
-  JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo));
-  JMETHOD(int, decompress_data, (j_decompress_ptr cinfo,
-				 JSAMPIMAGE output_buf));
-  /* Pointer to array of coefficient virtual arrays, or NULL if none */
-  jvirt_barray_ptr *coef_arrays;
-};
-
-/* Decompression postprocessing (color quantization buffer control) */
-struct jpeg_d_post_controller {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
-  JMETHOD(void, post_process_data, (j_decompress_ptr cinfo,
-				    JSAMPIMAGE input_buf,
-				    JDIMENSION *in_row_group_ctr,
-				    JDIMENSION in_row_groups_avail,
-				    JSAMPARRAY output_buf,
-				    JDIMENSION *out_row_ctr,
-				    JDIMENSION out_rows_avail));
-};
-
-/* Marker reading & parsing */
-struct jpeg_marker_reader {
-  JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo));
-  /* Read markers until SOS or EOI.
-   * Returns same codes as are defined for jpeg_consume_input:
-   * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI.
-   */
-  JMETHOD(int, read_markers, (j_decompress_ptr cinfo));
-  /* Read a restart marker --- exported for use by entropy decoder only */
-  jpeg_marker_parser_method read_restart_marker;
-
-  /* State of marker reader --- nominally internal, but applications
-   * supplying COM or APPn handlers might like to know the state.
-   */
-  boolean saw_SOI;		/* found SOI? */
-  boolean saw_SOF;		/* found SOF? */
-  int next_restart_num;		/* next restart number expected (0-7) */
-  unsigned int discarded_bytes;	/* # of bytes skipped looking for a marker */
-};
-
-/* Entropy decoding */
-struct jpeg_entropy_decoder {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
-  JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo,
-				JBLOCKROW *MCU_data));
-
-  /* This is here to share code between baseline and progressive decoders; */
-  /* other modules probably should not use it */
-  boolean insufficient_data;	/* set TRUE after emitting warning */
-};
-
-/* Inverse DCT (also performs dequantization) */
-typedef JMETHOD(void, inverse_DCT_method_ptr,
-		(j_decompress_ptr cinfo, jpeg_component_info * compptr,
-		 JCOEFPTR coef_block,
-		 JSAMPARRAY output_buf, JDIMENSION output_col));
-
-struct jpeg_inverse_dct {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
-  /* It is useful to allow each component to have a separate IDCT method. */
-  inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS];
-};
-
-/* Upsampling (note that upsampler must also call color converter) */
-struct jpeg_upsampler {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
-  JMETHOD(void, upsample, (j_decompress_ptr cinfo,
-			   JSAMPIMAGE input_buf,
-			   JDIMENSION *in_row_group_ctr,
-			   JDIMENSION in_row_groups_avail,
-			   JSAMPARRAY output_buf,
-			   JDIMENSION *out_row_ctr,
-			   JDIMENSION out_rows_avail));
-
-  boolean need_context_rows;	/* TRUE if need rows above & below */
-};
-
-/* Colorspace conversion */
-struct jpeg_color_deconverter {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
-  JMETHOD(void, color_convert, (j_decompress_ptr cinfo,
-				JSAMPIMAGE input_buf, JDIMENSION input_row,
-				JSAMPARRAY output_buf, int num_rows));
-};
-
-/* Color quantization or color precision reduction */
-struct jpeg_color_quantizer {
-  JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan));
-  JMETHOD(void, color_quantize, (j_decompress_ptr cinfo,
-				 JSAMPARRAY input_buf, JSAMPARRAY output_buf,
-				 int num_rows));
-  JMETHOD(void, finish_pass, (j_decompress_ptr cinfo));
-  JMETHOD(void, new_color_map, (j_decompress_ptr cinfo));
-};
-
-
-/* Miscellaneous useful macros */
-
-#undef MAX
-#define MAX(a,b)	((a) > (b) ? (a) : (b))
-#undef MIN
-#define MIN(a,b)	((a) < (b) ? (a) : (b))
-
-
-/* We assume that right shift corresponds to signed division by 2 with
- * rounding towards minus infinity.  This is correct for typical "arithmetic
- * shift" instructions that shift in copies of the sign bit.  But some
- * C compilers implement >> with an unsigned shift.  For these machines you
- * must define RIGHT_SHIFT_IS_UNSIGNED.
- * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity.
- * It is only applied with constant shift counts.  SHIFT_TEMPS must be
- * included in the variables of any routine using RIGHT_SHIFT.
- */
-
-#ifdef RIGHT_SHIFT_IS_UNSIGNED
-#define SHIFT_TEMPS	INT32 shift_temp;
-#define RIGHT_SHIFT(x,shft)  \
-	((shift_temp = (x)) < 0 ? \
-	 (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \
-	 (shift_temp >> (shft)))
-#else
-#define SHIFT_TEMPS
-#define RIGHT_SHIFT(x,shft)	((x) >> (shft))
-#endif
-
-
-/* Short forms of external names for systems with brain-damaged linkers. */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jinit_compress_master	jICompress
-#define jinit_c_master_control	jICMaster
-#define jinit_c_main_controller	jICMainC
-#define jinit_c_prep_controller	jICPrepC
-#define jinit_c_coef_controller	jICCoefC
-#define jinit_color_converter	jICColor
-#define jinit_downsampler	jIDownsampler
-#define jinit_forward_dct	jIFDCT
-#define jinit_huff_encoder	jIHEncoder
-#define jinit_phuff_encoder	jIPHEncoder
-#define jinit_marker_writer	jIMWriter
-#define jinit_master_decompress	jIDMaster
-#define jinit_d_main_controller	jIDMainC
-#define jinit_d_coef_controller	jIDCoefC
-#define jinit_d_post_controller	jIDPostC
-#define jinit_input_controller	jIInCtlr
-#define jinit_marker_reader	jIMReader
-#define jinit_huff_decoder	jIHDecoder
-#define jinit_phuff_decoder	jIPHDecoder
-#define jinit_inverse_dct	jIIDCT
-#define jinit_upsampler		jIUpsampler
-#define jinit_color_deconverter	jIDColor
-#define jinit_1pass_quantizer	jI1Quant
-#define jinit_2pass_quantizer	jI2Quant
-#define jinit_merged_upsampler	jIMUpsampler
-#define jinit_memory_mgr	jIMemMgr
-#define jdiv_round_up		jDivRound
-#define jround_up		jRound
-#define jcopy_sample_rows	jCopySamples
-#define jcopy_block_row		jCopyBlocks
-#define jzero_far		jZeroFar
-#define jpeg_zigzag_order	jZIGTable
-#define jpeg_natural_order	jZAGTable
-#endif /* NEED_SHORT_EXTERNAL_NAMES */
-
-
-/* Compression module initialization routines */
-EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo));
-EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo,
-					 boolean transcode_only));
-EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo,
-					  boolean need_full_buffer));
-EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo,
-					  boolean need_full_buffer));
-EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo,
-					  boolean need_full_buffer));
-EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo));
-EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo));
-EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo));
-EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo));
-EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo));
-EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo));
-/* Decompression module initialization routines */
-EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo,
-					  boolean need_full_buffer));
-EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo,
-					  boolean need_full_buffer));
-EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo,
-					  boolean need_full_buffer));
-EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo));
-EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo));
-/* Memory manager initialization */
-EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo));
-
-/* Utility routines in jutils.c */
-EXTERN(long) jdiv_round_up JPP((long a, long b));
-EXTERN(long) jround_up JPP((long a, long b));
-EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row,
-				    JSAMPARRAY output_array, int dest_row,
-				    int num_rows, JDIMENSION num_cols));
-EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row,
-				  JDIMENSION num_blocks));
-EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero));
-/* Constant tables in jutils.c */
-#if 0				/* This table is not actually needed in v6a */
-extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */
-#endif
-extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */
-
-/* Suppress undefined-structure complaints if necessary. */
-
-#ifdef INCOMPLETE_TYPES_BROKEN
-#ifndef AM_MEMORY_MANAGER	/* only jmemmgr.c defines these */
-struct jvirt_sarray_control { long dummy; };
-struct jvirt_barray_control { long dummy; };
-#endif
-#endif /* INCOMPLETE_TYPES_BROKEN */
diff --git a/third_party/libjpeg/jpeglib.h b/third_party/libjpeg/jpeglib.h
deleted file mode 100644
index cb1ba9d..0000000
--- a/third_party/libjpeg/jpeglib.h
+++ /dev/null
@@ -1,1102 +0,0 @@
-/*
- * jpeglib.h
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file defines the application interface for the JPEG library.
- * Most applications using the library need only include this file,
- * and perhaps jerror.h if they want to know the exact error codes.
- */
-
-#ifndef JPEGLIB_H
-#define JPEGLIB_H
-
-/* Begin chromium edits */
-#include "jpeglibmangler.h"
-/* End chromium edits */
-
-/*
- * First we include the configuration files that record how this
- * installation of the JPEG library is set up.  jconfig.h can be
- * generated automatically for many systems.  jmorecfg.h contains
- * manual configuration options that most people need not worry about.
- */
-
-#ifndef JCONFIG_INCLUDED	/* in case jinclude.h already did */
-#include "jconfig.h"		/* widely used configuration options */
-#endif
-#include "jmorecfg.h"		/* seldom changed options */
-
-
-/* Version ID for the JPEG library.
- * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60".
- */
-
-#define JPEG_LIB_VERSION  62	/* Version 6b */
-
-
-/* Various constants determining the sizes of things.
- * All of these are specified by the JPEG standard, so don't change them
- * if you want to be compatible.
- */
-
-#define DCTSIZE		    8	/* The basic DCT block is 8x8 samples */
-#define DCTSIZE2	    64	/* DCTSIZE squared; # of elements in a block */
-#define NUM_QUANT_TBLS      4	/* Quantization tables are numbered 0..3 */
-#define NUM_HUFF_TBLS       4	/* Huffman tables are numbered 0..3 */
-#define NUM_ARITH_TBLS      16	/* Arith-coding tables are numbered 0..15 */
-#define MAX_COMPS_IN_SCAN   4	/* JPEG limit on # of components in one scan */
-#define MAX_SAMP_FACTOR     4	/* JPEG limit on sampling factors */
-/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard;
- * the PostScript DCT filter can emit files with many more than 10 blocks/MCU.
- * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU
- * to handle it.  We even let you do this from the jconfig.h file.  However,
- * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe
- * sometimes emits noncompliant files doesn't mean you should too.
- */
-#define C_MAX_BLOCKS_IN_MCU   10 /* compressor's limit on blocks per MCU */
-#ifndef D_MAX_BLOCKS_IN_MCU
-#define D_MAX_BLOCKS_IN_MCU   10 /* decompressor's limit on blocks per MCU */
-#endif
-
-
-/* Data structures for images (arrays of samples and of DCT coefficients).
- * On 80x86 machines, the image arrays are too big for near pointers,
- * but the pointer arrays can fit in near memory.
- */
-
-typedef JSAMPLE FAR *JSAMPROW;	/* ptr to one image row of pixel samples. */
-typedef JSAMPROW *JSAMPARRAY;	/* ptr to some rows (a 2-D sample array) */
-typedef JSAMPARRAY *JSAMPIMAGE;	/* a 3-D sample array: top index is color */
-
-typedef JCOEF JBLOCK[DCTSIZE2];	/* one block of coefficients */
-typedef JBLOCK FAR *JBLOCKROW;	/* pointer to one row of coefficient blocks */
-typedef JBLOCKROW *JBLOCKARRAY;		/* a 2-D array of coefficient blocks */
-typedef JBLOCKARRAY *JBLOCKIMAGE;	/* a 3-D array of coefficient blocks */
-
-typedef JCOEF FAR *JCOEFPTR;	/* useful in a couple of places */
-
-
-/* Types for JPEG compression parameters and working tables. */
-
-
-/* DCT coefficient quantization tables. */
-
-typedef struct {
-  /* This array gives the coefficient quantizers in natural array order
-   * (not the zigzag order in which they are stored in a JPEG DQT marker).
-   * CAUTION: IJG versions prior to v6a kept this array in zigzag order.
-   */
-  UINT16 quantval[DCTSIZE2];	/* quantization step for each coefficient */
-  /* This field is used only during compression.  It's initialized FALSE when
-   * the table is created, and set TRUE when it's been output to the file.
-   * You could suppress output of a table by setting this to TRUE.
-   * (See jpeg_suppress_tables for an example.)
-   */
-  boolean sent_table;		/* TRUE when table has been output */
-} JQUANT_TBL;
-
-
-/* Huffman coding tables. */
-
-typedef struct {
-  /* These two fields directly represent the contents of a JPEG DHT marker */
-  UINT8 bits[17];		/* bits[k] = # of symbols with codes of */
-				/* length k bits; bits[0] is unused */
-  UINT8 huffval[256];		/* The symbols, in order of incr code length */
-  /* This field is used only during compression.  It's initialized FALSE when
-   * the table is created, and set TRUE when it's been output to the file.
-   * You could suppress output of a table by setting this to TRUE.
-   * (See jpeg_suppress_tables for an example.)
-   */
-  boolean sent_table;		/* TRUE when table has been output */
-} JHUFF_TBL;
-
-
-/* Basic info about one component (color channel). */
-
-typedef struct {
-  /* These values are fixed over the whole image. */
-  /* For compression, they must be supplied by parameter setup; */
-  /* for decompression, they are read from the SOF marker. */
-  int component_id;		/* identifier for this component (0..255) */
-  int component_index;		/* its index in SOF or cinfo->comp_info[] */
-  int h_samp_factor;		/* horizontal sampling factor (1..4) */
-  int v_samp_factor;		/* vertical sampling factor (1..4) */
-  int quant_tbl_no;		/* quantization table selector (0..3) */
-  /* These values may vary between scans. */
-  /* For compression, they must be supplied by parameter setup; */
-  /* for decompression, they are read from the SOS marker. */
-  /* The decompressor output side may not use these variables. */
-  int dc_tbl_no;		/* DC entropy table selector (0..3) */
-  int ac_tbl_no;		/* AC entropy table selector (0..3) */
-  
-  /* Remaining fields should be treated as private by applications. */
-  
-  /* These values are computed during compression or decompression startup: */
-  /* Component's size in DCT blocks.
-   * Any dummy blocks added to complete an MCU are not counted; therefore
-   * these values do not depend on whether a scan is interleaved or not.
-   */
-  JDIMENSION width_in_blocks;
-  JDIMENSION height_in_blocks;
-  /* Size of a DCT block in samples.  Always DCTSIZE for compression.
-   * For decompression this is the size of the output from one DCT block,
-   * reflecting any scaling we choose to apply during the IDCT step.
-   * Values of 1,2,4,8 are likely to be supported.  Note that different
-   * components may receive different IDCT scalings.
-   */
-  int DCT_scaled_size;
-  /* The downsampled dimensions are the component's actual, unpadded number
-   * of samples at the main buffer (preprocessing/compression interface), thus
-   * downsampled_width = ceil(image_width * Hi/Hmax)
-   * and similarly for height.  For decompression, IDCT scaling is included, so
-   * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE)
-   */
-  JDIMENSION downsampled_width;	 /* actual width in samples */
-  JDIMENSION downsampled_height; /* actual height in samples */
-  /* This flag is used only for decompression.  In cases where some of the
-   * components will be ignored (eg grayscale output from YCbCr image),
-   * we can skip most computations for the unused components.
-   */
-  boolean component_needed;	/* do we need the value of this component? */
-
-  /* These values are computed before starting a scan of the component. */
-  /* The decompressor output side may not use these variables. */
-  int MCU_width;		/* number of blocks per MCU, horizontally */
-  int MCU_height;		/* number of blocks per MCU, vertically */
-  int MCU_blocks;		/* MCU_width * MCU_height */
-  int MCU_sample_width;		/* MCU width in samples, MCU_width*DCT_scaled_size */
-  int last_col_width;		/* # of non-dummy blocks across in last MCU */
-  int last_row_height;		/* # of non-dummy blocks down in last MCU */
-
-  /* Saved quantization table for component; NULL if none yet saved.
-   * See jdinput.c comments about the need for this information.
-   * This field is currently used only for decompression.
-   */
-  JQUANT_TBL * quant_table;
-
-  /* Private per-component storage for DCT or IDCT subsystem. */
-  void * dct_table;
-} jpeg_component_info;
-
-
-/* The script for encoding a multiple-scan file is an array of these: */
-
-typedef struct {
-  int comps_in_scan;		/* number of components encoded in this scan */
-  int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */
-  int Ss, Se;			/* progressive JPEG spectral selection parms */
-  int Ah, Al;			/* progressive JPEG successive approx. parms */
-} jpeg_scan_info;
-
-/* The decompressor can save APPn and COM markers in a list of these: */
-
-typedef struct jpeg_marker_struct FAR * jpeg_saved_marker_ptr;
-
-struct jpeg_marker_struct {
-  jpeg_saved_marker_ptr next;	/* next in list, or NULL */
-  UINT8 marker;			/* marker code: JPEG_COM, or JPEG_APP0+n */
-  unsigned int original_length;	/* # bytes of data in the file */
-  unsigned int data_length;	/* # bytes of data saved at data[] */
-  JOCTET FAR * data;		/* the data contained in the marker */
-  /* the marker length word is not counted in data_length or original_length */
-};
-
-/* Known color spaces. */
-
-typedef enum {
-	JCS_UNKNOWN,		/* error/unspecified */
-	JCS_GRAYSCALE,		/* monochrome */
-	JCS_RGB,		/* red/green/blue */
-	JCS_YCbCr,		/* Y/Cb/Cr (also known as YUV) */
-	JCS_CMYK,		/* C/M/Y/K */
-	JCS_YCCK		/* Y/Cb/Cr/K */
-} J_COLOR_SPACE;
-
-/* DCT/IDCT algorithm options. */
-
-typedef enum {
-	JDCT_ISLOW,		/* slow but accurate integer algorithm */
-	JDCT_IFAST,		/* faster, less accurate integer method */
-	JDCT_FLOAT		/* floating-point: accurate, fast on fast HW */
-} J_DCT_METHOD;
-
-#ifndef JDCT_DEFAULT		/* may be overridden in jconfig.h */
-#define JDCT_DEFAULT  JDCT_ISLOW
-#endif
-#ifndef JDCT_FASTEST		/* may be overridden in jconfig.h */
-#define JDCT_FASTEST  JDCT_IFAST
-#endif
-
-/* Dithering options for decompression. */
-
-typedef enum {
-	JDITHER_NONE,		/* no dithering */
-	JDITHER_ORDERED,	/* simple ordered dither */
-	JDITHER_FS		/* Floyd-Steinberg error diffusion dither */
-} J_DITHER_MODE;
-
-
-/* Common fields between JPEG compression and decompression master structs. */
-
-#define jpeg_common_fields \
-  struct jpeg_error_mgr * err;	/* Error handler module */\
-  struct jpeg_memory_mgr * mem;	/* Memory manager module */\
-  struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\
-  void * client_data;		/* Available for use by application */\
-  boolean is_decompressor;	/* So common code can tell which is which */\
-  int global_state		/* For checking call sequence validity */
-
-/* Routines that are to be used by both halves of the library are declared
- * to receive a pointer to this structure.  There are no actual instances of
- * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct.
- */
-struct jpeg_common_struct {
-  jpeg_common_fields;		/* Fields common to both master struct types */
-  /* Additional fields follow in an actual jpeg_compress_struct or
-   * jpeg_decompress_struct.  All three structs must agree on these
-   * initial fields!  (This would be a lot cleaner in C++.)
-   */
-};
-
-typedef struct jpeg_common_struct * j_common_ptr;
-typedef struct jpeg_compress_struct * j_compress_ptr;
-typedef struct jpeg_decompress_struct * j_decompress_ptr;
-
-
-/* Master record for a compression instance */
-
-struct jpeg_compress_struct {
-  jpeg_common_fields;		/* Fields shared with jpeg_decompress_struct */
-
-  /* Destination for compressed data */
-  struct jpeg_destination_mgr * dest;
-
-  /* Description of source image --- these fields must be filled in by
-   * outer application before starting compression.  in_color_space must
-   * be correct before you can even call jpeg_set_defaults().
-   */
-
-  JDIMENSION image_width;	/* input image width */
-  JDIMENSION image_height;	/* input image height */
-  int input_components;		/* # of color components in input image */
-  J_COLOR_SPACE in_color_space;	/* colorspace of input image */
-
-  double input_gamma;		/* image gamma of input image */
-
-  /* Compression parameters --- these fields must be set before calling
-   * jpeg_start_compress().  We recommend calling jpeg_set_defaults() to
-   * initialize everything to reasonable defaults, then changing anything
-   * the application specifically wants to change.  That way you won't get
-   * burnt when new parameters are added.  Also note that there are several
-   * helper routines to simplify changing parameters.
-   */
-
-  int data_precision;		/* bits of precision in image data */
-
-  int num_components;		/* # of color components in JPEG image */
-  J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */
-
-  jpeg_component_info * comp_info;
-  /* comp_info[i] describes component that appears i'th in SOF */
-  
-  JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS];
-  /* ptrs to coefficient quantization tables, or NULL if not defined */
-  
-  JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS];
-  JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS];
-  /* ptrs to Huffman coding tables, or NULL if not defined */
-  
-  UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */
-  UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */
-  UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */
-
-  int num_scans;		/* # of entries in scan_info array */
-  const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */
-  /* The default value of scan_info is NULL, which causes a single-scan
-   * sequential JPEG file to be emitted.  To create a multi-scan file,
-   * set num_scans and scan_info to point to an array of scan definitions.
-   */
-
-  boolean raw_data_in;		/* TRUE=caller supplies downsampled data */
-  boolean arith_code;		/* TRUE=arithmetic coding, FALSE=Huffman */
-  boolean optimize_coding;	/* TRUE=optimize entropy encoding parms */
-  boolean CCIR601_sampling;	/* TRUE=first samples are cosited */
-  int smoothing_factor;		/* 1..100, or 0 for no input smoothing */
-  J_DCT_METHOD dct_method;	/* DCT algorithm selector */
-
-  /* The restart interval can be specified in absolute MCUs by setting
-   * restart_interval, or in MCU rows by setting restart_in_rows
-   * (in which case the correct restart_interval will be figured
-   * for each scan).
-   */
-  unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */
-  int restart_in_rows;		/* if > 0, MCU rows per restart interval */
-
-  /* Parameters controlling emission of special markers. */
-
-  boolean write_JFIF_header;	/* should a JFIF marker be written? */
-  UINT8 JFIF_major_version;	/* What to write for the JFIF version number */
-  UINT8 JFIF_minor_version;
-  /* These three values are not used by the JPEG code, merely copied */
-  /* into the JFIF APP0 marker.  density_unit can be 0 for unknown, */
-  /* 1 for dots/inch, or 2 for dots/cm.  Note that the pixel aspect */
-  /* ratio is defined by X_density/Y_density even when density_unit=0. */
-  UINT8 density_unit;		/* JFIF code for pixel size units */
-  UINT16 X_density;		/* Horizontal pixel density */
-  UINT16 Y_density;		/* Vertical pixel density */
-  boolean write_Adobe_marker;	/* should an Adobe marker be written? */
-  
-  /* State variable: index of next scanline to be written to
-   * jpeg_write_scanlines().  Application may use this to control its
-   * processing loop, e.g., "while (next_scanline < image_height)".
-   */
-
-  JDIMENSION next_scanline;	/* 0 .. image_height-1  */
-
-  /* Remaining fields are known throughout compressor, but generally
-   * should not be touched by a surrounding application.
-   */
-
-  /*
-   * These fields are computed during compression startup
-   */
-  boolean progressive_mode;	/* TRUE if scan script uses progressive mode */
-  int max_h_samp_factor;	/* largest h_samp_factor */
-  int max_v_samp_factor;	/* largest v_samp_factor */
-
-  JDIMENSION total_iMCU_rows;	/* # of iMCU rows to be input to coef ctlr */
-  /* The coefficient controller receives data in units of MCU rows as defined
-   * for fully interleaved scans (whether the JPEG file is interleaved or not).
-   * There are v_samp_factor * DCTSIZE sample rows of each component in an
-   * "iMCU" (interleaved MCU) row.
-   */
-  
-  /*
-   * These fields are valid during any one scan.
-   * They describe the components and MCUs actually appearing in the scan.
-   */
-  int comps_in_scan;		/* # of JPEG components in this scan */
-  jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN];
-  /* *cur_comp_info[i] describes component that appears i'th in SOS */
-  
-  JDIMENSION MCUs_per_row;	/* # of MCUs across the image */
-  JDIMENSION MCU_rows_in_scan;	/* # of MCU rows in the image */
-  
-  int blocks_in_MCU;		/* # of DCT blocks per MCU */
-  int MCU_membership[C_MAX_BLOCKS_IN_MCU];
-  /* MCU_membership[i] is index in cur_comp_info of component owning */
-  /* i'th block in an MCU */
-
-  int Ss, Se, Ah, Al;		/* progressive JPEG parameters for scan */
-
-  /*
-   * Links to compression subobjects (methods and private variables of modules)
-   */
-  struct jpeg_comp_master * master;
-  struct jpeg_c_main_controller * main;
-  struct jpeg_c_prep_controller * prep;
-  struct jpeg_c_coef_controller * coef;
-  struct jpeg_marker_writer * marker;
-  struct jpeg_color_converter * cconvert;
-  struct jpeg_downsampler * downsample;
-  struct jpeg_forward_dct * fdct;
-  struct jpeg_entropy_encoder * entropy;
-  jpeg_scan_info * script_space; /* workspace for jpeg_simple_progression */
-  int script_space_size;
-};
-
-
-/* Master record for a decompression instance */
-
-struct jpeg_decompress_struct {
-  jpeg_common_fields;		/* Fields shared with jpeg_compress_struct */
-
-  /* Source of compressed data */
-  struct jpeg_source_mgr * src;
-
-  /* Basic description of image --- filled in by jpeg_read_header(). */
-  /* Application may inspect these values to decide how to process image. */
-
-  JDIMENSION image_width;	/* nominal image width (from SOF marker) */
-  JDIMENSION image_height;	/* nominal image height */
-  int num_components;		/* # of color components in JPEG image */
-  J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */
-
-  /* Decompression processing parameters --- these fields must be set before
-   * calling jpeg_start_decompress().  Note that jpeg_read_header() initializes
-   * them to default values.
-   */
-
-  J_COLOR_SPACE out_color_space; /* colorspace for output */
-
-  unsigned int scale_num, scale_denom; /* fraction by which to scale image */
-
-  double output_gamma;		/* image gamma wanted in output */
-
-  boolean buffered_image;	/* TRUE=multiple output passes */
-  boolean raw_data_out;		/* TRUE=downsampled data wanted */
-
-  J_DCT_METHOD dct_method;	/* IDCT algorithm selector */
-  boolean do_fancy_upsampling;	/* TRUE=apply fancy upsampling */
-  boolean do_block_smoothing;	/* TRUE=apply interblock smoothing */
-
-  boolean quantize_colors;	/* TRUE=colormapped output wanted */
-  /* the following are ignored if not quantize_colors: */
-  J_DITHER_MODE dither_mode;	/* type of color dithering to use */
-  boolean two_pass_quantize;	/* TRUE=use two-pass color quantization */
-  int desired_number_of_colors;	/* max # colors to use in created colormap */
-  /* these are significant only in buffered-image mode: */
-  boolean enable_1pass_quant;	/* enable future use of 1-pass quantizer */
-  boolean enable_external_quant;/* enable future use of external colormap */
-  boolean enable_2pass_quant;	/* enable future use of 2-pass quantizer */
-
-  /* Description of actual output image that will be returned to application.
-   * These fields are computed by jpeg_start_decompress().
-   * You can also use jpeg_calc_output_dimensions() to determine these values
-   * in advance of calling jpeg_start_decompress().
-   */
-
-  JDIMENSION output_width;	/* scaled image width */
-  JDIMENSION output_height;	/* scaled image height */
-  int out_color_components;	/* # of color components in out_color_space */
-  int output_components;	/* # of color components returned */
-  /* output_components is 1 (a colormap index) when quantizing colors;
-   * otherwise it equals out_color_components.
-   */
-  int rec_outbuf_height;	/* min recommended height of scanline buffer */
-  /* If the buffer passed to jpeg_read_scanlines() is less than this many rows
-   * high, space and time will be wasted due to unnecessary data copying.
-   * Usually rec_outbuf_height will be 1 or 2, at most 4.
-   */
-
-  /* When quantizing colors, the output colormap is described by these fields.
-   * The application can supply a colormap by setting colormap non-NULL before
-   * calling jpeg_start_decompress; otherwise a colormap is created during
-   * jpeg_start_decompress or jpeg_start_output.
-   * The map has out_color_components rows and actual_number_of_colors columns.
-   */
-  int actual_number_of_colors;	/* number of entries in use */
-  JSAMPARRAY colormap;		/* The color map as a 2-D pixel array */
-
-  /* State variables: these variables indicate the progress of decompression.
-   * The application may examine these but must not modify them.
-   */
-
-  /* Row index of next scanline to be read from jpeg_read_scanlines().
-   * Application may use this to control its processing loop, e.g.,
-   * "while (output_scanline < output_height)".
-   */
-  JDIMENSION output_scanline;	/* 0 .. output_height-1  */
-
-  /* Current input scan number and number of iMCU rows completed in scan.
-   * These indicate the progress of the decompressor input side.
-   */
-  int input_scan_number;	/* Number of SOS markers seen so far */
-  JDIMENSION input_iMCU_row;	/* Number of iMCU rows completed */
-
-  /* The "output scan number" is the notional scan being displayed by the
-   * output side.  The decompressor will not allow output scan/row number
-   * to get ahead of input scan/row, but it can fall arbitrarily far behind.
-   */
-  int output_scan_number;	/* Nominal scan number being displayed */
-  JDIMENSION output_iMCU_row;	/* Number of iMCU rows read */
-
-  /* Current progression status.  coef_bits[c][i] indicates the precision
-   * with which component c's DCT coefficient i (in zigzag order) is known.
-   * It is -1 when no data has yet been received, otherwise it is the point
-   * transform (shift) value for the most recent scan of the coefficient
-   * (thus, 0 at completion of the progression).
-   * This pointer is NULL when reading a non-progressive file.
-   */
-  int (*coef_bits)[DCTSIZE2];	/* -1 or current Al value for each coef */
-
-  /* Internal JPEG parameters --- the application usually need not look at
-   * these fields.  Note that the decompressor output side may not use
-   * any parameters that can change between scans.
-   */
-
-  /* Quantization and Huffman tables are carried forward across input
-   * datastreams when processing abbreviated JPEG datastreams.
-   */
-
-  JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS];
-  /* ptrs to coefficient quantization tables, or NULL if not defined */
-
-  JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS];
-  JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS];
-  /* ptrs to Huffman coding tables, or NULL if not defined */
-
-  /* These parameters are never carried across datastreams, since they
-   * are given in SOF/SOS markers or defined to be reset by SOI.
-   */
-
-  int data_precision;		/* bits of precision in image data */
-
-  jpeg_component_info * comp_info;
-  /* comp_info[i] describes component that appears i'th in SOF */
-
-  boolean progressive_mode;	/* TRUE if SOFn specifies progressive mode */
-  boolean arith_code;		/* TRUE=arithmetic coding, FALSE=Huffman */
-
-  UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */
-  UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */
-  UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */
-
-  unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */
-
-  /* These fields record data obtained from optional markers recognized by
-   * the JPEG library.
-   */
-  boolean saw_JFIF_marker;	/* TRUE iff a JFIF APP0 marker was found */
-  /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */
-  UINT8 JFIF_major_version;	/* JFIF version number */
-  UINT8 JFIF_minor_version;
-  UINT8 density_unit;		/* JFIF code for pixel size units */
-  UINT16 X_density;		/* Horizontal pixel density */
-  UINT16 Y_density;		/* Vertical pixel density */
-  boolean saw_Adobe_marker;	/* TRUE iff an Adobe APP14 marker was found */
-  UINT8 Adobe_transform;	/* Color transform code from Adobe marker */
-
-  boolean CCIR601_sampling;	/* TRUE=first samples are cosited */
-
-  /* Aside from the specific data retained from APPn markers known to the
-   * library, the uninterpreted contents of any or all APPn and COM markers
-   * can be saved in a list for examination by the application.
-   */
-  jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */
-
-  /* Remaining fields are known throughout decompressor, but generally
-   * should not be touched by a surrounding application.
-   */
-
-  /*
-   * These fields are computed during decompression startup
-   */
-  int max_h_samp_factor;	/* largest h_samp_factor */
-  int max_v_samp_factor;	/* largest v_samp_factor */
-
-  int min_DCT_scaled_size;	/* smallest DCT_scaled_size of any component */
-
-  JDIMENSION total_iMCU_rows;	/* # of iMCU rows in image */
-  /* The coefficient controller's input and output progress is measured in
-   * units of "iMCU" (interleaved MCU) rows.  These are the same as MCU rows
-   * in fully interleaved JPEG scans, but are used whether the scan is
-   * interleaved or not.  We define an iMCU row as v_samp_factor DCT block
-   * rows of each component.  Therefore, the IDCT output contains
-   * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row.
-   */
-
-  JSAMPLE * sample_range_limit; /* table for fast range-limiting */
-
-  /*
-   * These fields are valid during any one scan.
-   * They describe the components and MCUs actually appearing in the scan.
-   * Note that the decompressor output side must not use these fields.
-   */
-  int comps_in_scan;		/* # of JPEG components in this scan */
-  jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN];
-  /* *cur_comp_info[i] describes component that appears i'th in SOS */
-
-  JDIMENSION MCUs_per_row;	/* # of MCUs across the image */
-  JDIMENSION MCU_rows_in_scan;	/* # of MCU rows in the image */
-
-  int blocks_in_MCU;		/* # of DCT blocks per MCU */
-  int MCU_membership[D_MAX_BLOCKS_IN_MCU];
-  /* MCU_membership[i] is index in cur_comp_info of component owning */
-  /* i'th block in an MCU */
-
-  int Ss, Se, Ah, Al;		/* progressive JPEG parameters for scan */
-
-  /* This field is shared between entropy decoder and marker parser.
-   * It is either zero or the code of a JPEG marker that has been
-   * read from the data source, but has not yet been processed.
-   */
-  int unread_marker;
-
-  /*
-   * Links to decompression subobjects (methods, private variables of modules)
-   */
-  struct jpeg_decomp_master * master;
-  struct jpeg_d_main_controller * main;
-  struct jpeg_d_coef_controller * coef;
-  struct jpeg_d_post_controller * post;
-  struct jpeg_input_controller * inputctl;
-  struct jpeg_marker_reader * marker;
-  struct jpeg_entropy_decoder * entropy;
-  struct jpeg_inverse_dct * idct;
-  struct jpeg_upsampler * upsample;
-  struct jpeg_color_deconverter * cconvert;
-  struct jpeg_color_quantizer * cquantize;
-};
-
-
-/* "Object" declarations for JPEG modules that may be supplied or called
- * directly by the surrounding application.
- * As with all objects in the JPEG library, these structs only define the
- * publicly visible methods and state variables of a module.  Additional
- * private fields may exist after the public ones.
- */
-
-
-/* Error handler object */
-
-struct jpeg_error_mgr {
-  /* Error exit handler: does not return to caller */
-  JMETHOD(void, error_exit, (j_common_ptr cinfo));
-  /* Conditionally emit a trace or warning message */
-  JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level));
-  /* Routine that actually outputs a trace or error message */
-  JMETHOD(void, output_message, (j_common_ptr cinfo));
-  /* Format a message string for the most recent JPEG error or message */
-  JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer));
-#define JMSG_LENGTH_MAX  200	/* recommended size of format_message buffer */
-  /* Reset error state variables at start of a new image */
-  JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo));
-  
-  /* The message ID code and any parameters are saved here.
-   * A message can have one string parameter or up to 8 int parameters.
-   */
-  int msg_code;
-#define JMSG_STR_PARM_MAX  80
-  union {
-    int i[8];
-    char s[JMSG_STR_PARM_MAX];
-  } msg_parm;
-  
-  /* Standard state variables for error facility */
-  
-  int trace_level;		/* max msg_level that will be displayed */
-  
-  /* For recoverable corrupt-data errors, we emit a warning message,
-   * but keep going unless emit_message chooses to abort.  emit_message
-   * should count warnings in num_warnings.  The surrounding application
-   * can check for bad data by seeing if num_warnings is nonzero at the
-   * end of processing.
-   */
-  long num_warnings;		/* number of corrupt-data warnings */
-
-  /* These fields point to the table(s) of error message strings.
-   * An application can change the table pointer to switch to a different
-   * message list (typically, to change the language in which errors are
-   * reported).  Some applications may wish to add additional error codes
-   * that will be handled by the JPEG library error mechanism; the second
-   * table pointer is used for this purpose.
-   *
-   * First table includes all errors generated by JPEG library itself.
-   * Error code 0 is reserved for a "no such error string" message.
-   */
-  const char * const * jpeg_message_table; /* Library errors */
-  int last_jpeg_message;    /* Table contains strings 0..last_jpeg_message */
-  /* Second table can be added by application (see cjpeg/djpeg for example).
-   * It contains strings numbered first_addon_message..last_addon_message.
-   */
-  const char * const * addon_message_table; /* Non-library errors */
-  int first_addon_message;	/* code for first string in addon table */
-  int last_addon_message;	/* code for last string in addon table */
-};
-
-
-/* Progress monitor object */
-
-struct jpeg_progress_mgr {
-  JMETHOD(void, progress_monitor, (j_common_ptr cinfo));
-
-  long pass_counter;		/* work units completed in this pass */
-  long pass_limit;		/* total number of work units in this pass */
-  int completed_passes;		/* passes completed so far */
-  int total_passes;		/* total number of passes expected */
-};
-
-
-/* Data destination object for compression */
-
-struct jpeg_destination_mgr {
-  JOCTET * next_output_byte;	/* => next byte to write in buffer */
-  size_t free_in_buffer;	/* # of byte spaces remaining in buffer */
-
-  JMETHOD(void, init_destination, (j_compress_ptr cinfo));
-  JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo));
-  JMETHOD(void, term_destination, (j_compress_ptr cinfo));
-};
-
-
-/* Data source object for decompression */
-
-struct jpeg_source_mgr {
-  const JOCTET * next_input_byte; /* => next byte to read from buffer */
-  size_t bytes_in_buffer;	/* # of bytes remaining in buffer */
-
-  JMETHOD(void, init_source, (j_decompress_ptr cinfo));
-  JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo));
-  JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes));
-  JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired));
-  JMETHOD(void, term_source, (j_decompress_ptr cinfo));
-};
-
-
-/* Memory manager object.
- * Allocates "small" objects (a few K total), "large" objects (tens of K),
- * and "really big" objects (virtual arrays with backing store if needed).
- * The memory manager does not allow individual objects to be freed; rather,
- * each created object is assigned to a pool, and whole pools can be freed
- * at once.  This is faster and more convenient than remembering exactly what
- * to free, especially where malloc()/free() are not too speedy.
- * NB: alloc routines never return NULL.  They exit to error_exit if not
- * successful.
- */
-
-#define JPOOL_PERMANENT	0	/* lasts until master record is destroyed */
-#define JPOOL_IMAGE	1	/* lasts until done with image/datastream */
-#define JPOOL_NUMPOOLS	2
-
-typedef struct jvirt_sarray_control * jvirt_sarray_ptr;
-typedef struct jvirt_barray_control * jvirt_barray_ptr;
-
-
-struct jpeg_memory_mgr {
-  /* Method pointers */
-  JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id,
-				size_t sizeofobject));
-  JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id,
-				     size_t sizeofobject));
-  JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id,
-				     JDIMENSION samplesperrow,
-				     JDIMENSION numrows));
-  JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id,
-				      JDIMENSION blocksperrow,
-				      JDIMENSION numrows));
-  JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo,
-						  int pool_id,
-						  boolean pre_zero,
-						  JDIMENSION samplesperrow,
-						  JDIMENSION numrows,
-						  JDIMENSION maxaccess));
-  JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo,
-						  int pool_id,
-						  boolean pre_zero,
-						  JDIMENSION blocksperrow,
-						  JDIMENSION numrows,
-						  JDIMENSION maxaccess));
-  JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo));
-  JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo,
-					   jvirt_sarray_ptr ptr,
-					   JDIMENSION start_row,
-					   JDIMENSION num_rows,
-					   boolean writable));
-  JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo,
-					    jvirt_barray_ptr ptr,
-					    JDIMENSION start_row,
-					    JDIMENSION num_rows,
-					    boolean writable));
-  JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id));
-  JMETHOD(void, self_destruct, (j_common_ptr cinfo));
-
-  /* Limit on memory allocation for this JPEG object.  (Note that this is
-   * merely advisory, not a guaranteed maximum; it only affects the space
-   * used for virtual-array buffers.)  May be changed by outer application
-   * after creating the JPEG object.
-   */
-  long max_memory_to_use;
-
-  /* Maximum allocation request accepted by alloc_large. */
-  long max_alloc_chunk;
-};
-
-
-/* Routine signature for application-supplied marker processing methods.
- * Need not pass marker code since it is stored in cinfo->unread_marker.
- */
-typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo));
-
-
-/* Declarations for routines called by application.
- * The JPP macro hides prototype parameters from compilers that can't cope.
- * Note JPP requires double parentheses.
- */
-
-#ifdef HAVE_PROTOTYPES
-#define JPP(arglist)	arglist
-#else
-#define JPP(arglist)	()
-#endif
-
-
-/* Short forms of external names for systems with brain-damaged linkers.
- * We shorten external names to be unique in the first six letters, which
- * is good enough for all known systems.
- * (If your compiler itself needs names to be unique in less than 15 
- * characters, you are out of luck.  Get a better compiler.)
- */
-
-#ifdef NEED_SHORT_EXTERNAL_NAMES
-#define jpeg_std_error		jStdError
-#define jpeg_CreateCompress	jCreaCompress
-#define jpeg_CreateDecompress	jCreaDecompress
-#define jpeg_destroy_compress	jDestCompress
-#define jpeg_destroy_decompress	jDestDecompress
-#define jpeg_stdio_dest		jStdDest
-#define jpeg_stdio_src		jStdSrc
-#define jpeg_set_defaults	jSetDefaults
-#define jpeg_set_colorspace	jSetColorspace
-#define jpeg_default_colorspace	jDefColorspace
-#define jpeg_set_quality	jSetQuality
-#define jpeg_set_linear_quality	jSetLQuality
-#define jpeg_add_quant_table	jAddQuantTable
-#define jpeg_quality_scaling	jQualityScaling
-#define jpeg_simple_progression	jSimProgress
-#define jpeg_suppress_tables	jSuppressTables
-#define jpeg_alloc_quant_table	jAlcQTable
-#define jpeg_alloc_huff_table	jAlcHTable
-#define jpeg_start_compress	jStrtCompress
-#define jpeg_write_scanlines	jWrtScanlines
-#define jpeg_finish_compress	jFinCompress
-#define jpeg_write_raw_data	jWrtRawData
-#define jpeg_write_marker	jWrtMarker
-#define jpeg_write_m_header	jWrtMHeader
-#define jpeg_write_m_byte	jWrtMByte
-#define jpeg_write_tables	jWrtTables
-#define jpeg_read_header	jReadHeader
-#define jpeg_start_decompress	jStrtDecompress
-#define jpeg_read_scanlines	jReadScanlines
-#define jpeg_finish_decompress	jFinDecompress
-#define jpeg_read_raw_data	jReadRawData
-#define jpeg_has_multiple_scans	jHasMultScn
-#define jpeg_start_output	jStrtOutput
-#define jpeg_finish_output	jFinOutput
-#define jpeg_input_complete	jInComplete
-#define jpeg_new_colormap	jNewCMap
-#define jpeg_consume_input	jConsumeInput
-#define jpeg_calc_output_dimensions	jCalcDimensions
-#define jpeg_save_markers	jSaveMarkers
-#define jpeg_set_marker_processor	jSetMarker
-#define jpeg_read_coefficients	jReadCoefs
-#define jpeg_write_coefficients	jWrtCoefs
-#define jpeg_copy_critical_parameters	jCopyCrit
-#define jpeg_abort_compress	jAbrtCompress
-#define jpeg_abort_decompress	jAbrtDecompress
-#define jpeg_abort		jAbort
-#define jpeg_destroy		jDestroy
-#define jpeg_resync_to_restart	jResyncRestart
-#endif /* NEED_SHORT_EXTERNAL_NAMES */
-
-
-/* Default error-management setup */
-EXTERN(struct jpeg_error_mgr *) jpeg_std_error
-	JPP((struct jpeg_error_mgr * err));
-
-/* Initialization of JPEG compression objects.
- * jpeg_create_compress() and jpeg_create_decompress() are the exported
- * names that applications should call.  These expand to calls on
- * jpeg_CreateCompress and jpeg_CreateDecompress with additional information
- * passed for version mismatch checking.
- * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx.
- */
-#define jpeg_create_compress(cinfo) \
-    jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \
-			(size_t) sizeof(struct jpeg_compress_struct))
-#define jpeg_create_decompress(cinfo) \
-    jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \
-			  (size_t) sizeof(struct jpeg_decompress_struct))
-EXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo,
-				      int version, size_t structsize));
-EXTERN(void) jpeg_CreateDecompress JPP((j_decompress_ptr cinfo,
-					int version, size_t structsize));
-/* Destruction of JPEG compression objects */
-EXTERN(void) jpeg_destroy_compress JPP((j_compress_ptr cinfo));
-EXTERN(void) jpeg_destroy_decompress JPP((j_decompress_ptr cinfo));
-
-#if !defined(JPEG_NO_STDIO)
-/* Standard data source and destination managers: stdio streams. */
-/* Caller is responsible for opening the file before and closing after. */
-EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile));
-EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile));
-#endif
-
-/* Default parameter setup for compression */
-EXTERN(void) jpeg_set_defaults JPP((j_compress_ptr cinfo));
-/* Compression parameter setup aids */
-EXTERN(void) jpeg_set_colorspace JPP((j_compress_ptr cinfo,
-				      J_COLOR_SPACE colorspace));
-EXTERN(void) jpeg_default_colorspace JPP((j_compress_ptr cinfo));
-EXTERN(void) jpeg_set_quality JPP((j_compress_ptr cinfo, int quality,
-				   boolean force_baseline));
-EXTERN(void) jpeg_set_linear_quality JPP((j_compress_ptr cinfo,
-					  int scale_factor,
-					  boolean force_baseline));
-EXTERN(void) jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl,
-				       const unsigned int *basic_table,
-				       int scale_factor,
-				       boolean force_baseline));
-EXTERN(int) jpeg_quality_scaling JPP((int quality));
-EXTERN(void) jpeg_simple_progression JPP((j_compress_ptr cinfo));
-EXTERN(void) jpeg_suppress_tables JPP((j_compress_ptr cinfo,
-				       boolean suppress));
-EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table JPP((j_common_ptr cinfo));
-EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table JPP((j_common_ptr cinfo));
-
-/* Main entry points for compression */
-EXTERN(void) jpeg_start_compress JPP((j_compress_ptr cinfo,
-				      boolean write_all_tables));
-EXTERN(JDIMENSION) jpeg_write_scanlines JPP((j_compress_ptr cinfo,
-					     JSAMPARRAY scanlines,
-					     JDIMENSION num_lines));
-EXTERN(void) jpeg_finish_compress JPP((j_compress_ptr cinfo));
-
-/* Replaces jpeg_write_scanlines when writing raw downsampled data. */
-EXTERN(JDIMENSION) jpeg_write_raw_data JPP((j_compress_ptr cinfo,
-					    JSAMPIMAGE data,
-					    JDIMENSION num_lines));
-
-/* Write a special marker.  See libjpeg.doc concerning safe usage. */
-EXTERN(void) jpeg_write_marker
-	JPP((j_compress_ptr cinfo, int marker,
-	     const JOCTET * dataptr, unsigned int datalen));
-/* Same, but piecemeal. */
-EXTERN(void) jpeg_write_m_header
-	JPP((j_compress_ptr cinfo, int marker, unsigned int datalen));
-EXTERN(void) jpeg_write_m_byte
-	JPP((j_compress_ptr cinfo, int val));
-
-/* Alternate compression function: just write an abbreviated table file */
-EXTERN(void) jpeg_write_tables JPP((j_compress_ptr cinfo));
-
-/* Decompression startup: read start of JPEG datastream to see what's there */
-EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo,
-				  boolean require_image));
-/* Return value is one of: */
-#define JPEG_SUSPENDED		0 /* Suspended due to lack of input data */
-#define JPEG_HEADER_OK		1 /* Found valid image datastream */
-#define JPEG_HEADER_TABLES_ONLY	2 /* Found valid table-specs-only datastream */
-/* If you pass require_image = TRUE (normal case), you need not check for
- * a TABLES_ONLY return code; an abbreviated file will cause an error exit.
- * JPEG_SUSPENDED is only possible if you use a data source module that can
- * give a suspension return (the stdio source module doesn't).
- */
-
-/* Main entry points for decompression */
-EXTERN(boolean) jpeg_start_decompress JPP((j_decompress_ptr cinfo));
-EXTERN(JDIMENSION) jpeg_read_scanlines JPP((j_decompress_ptr cinfo,
-					    JSAMPARRAY scanlines,
-					    JDIMENSION max_lines));
-EXTERN(boolean) jpeg_finish_decompress JPP((j_decompress_ptr cinfo));
-
-/* Replaces jpeg_read_scanlines when reading raw downsampled data. */
-EXTERN(JDIMENSION) jpeg_read_raw_data JPP((j_decompress_ptr cinfo,
-					   JSAMPIMAGE data,
-					   JDIMENSION max_lines));
-
-/* Additional entry points for buffered-image mode. */
-EXTERN(boolean) jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo));
-EXTERN(boolean) jpeg_start_output JPP((j_decompress_ptr cinfo,
-				       int scan_number));
-EXTERN(boolean) jpeg_finish_output JPP((j_decompress_ptr cinfo));
-EXTERN(boolean) jpeg_input_complete JPP((j_decompress_ptr cinfo));
-EXTERN(void) jpeg_new_colormap JPP((j_decompress_ptr cinfo));
-EXTERN(int) jpeg_consume_input JPP((j_decompress_ptr cinfo));
-/* Return value is one of: */
-/* #define JPEG_SUSPENDED	0    Suspended due to lack of input data */
-#define JPEG_REACHED_SOS	1 /* Reached start of new scan */
-#define JPEG_REACHED_EOI	2 /* Reached end of image */
-#define JPEG_ROW_COMPLETED	3 /* Completed one iMCU row */
-#define JPEG_SCAN_COMPLETED	4 /* Completed last iMCU row of a scan */
-
-/* Precalculate output dimensions for current decompression parameters. */
-EXTERN(void) jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo));
-
-/* Control saving of COM and APPn markers into marker_list. */
-EXTERN(void) jpeg_save_markers
-	JPP((j_decompress_ptr cinfo, int marker_code,
-	     unsigned int length_limit));
-
-/* Install a special processing method for COM or APPn markers. */
-EXTERN(void) jpeg_set_marker_processor
-	JPP((j_decompress_ptr cinfo, int marker_code,
-	     jpeg_marker_parser_method routine));
-
-/* Read or write raw DCT coefficients --- useful for lossless transcoding. */
-EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients JPP((j_decompress_ptr cinfo));
-EXTERN(void) jpeg_write_coefficients JPP((j_compress_ptr cinfo,
-					  jvirt_barray_ptr * coef_arrays));
-EXTERN(void) jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo,
-						j_compress_ptr dstinfo));
-
-/* If you choose to abort compression or decompression before completing
- * jpeg_finish_(de)compress, then you need to clean up to release memory,
- * temporary files, etc.  You can just call jpeg_destroy_(de)compress
- * if you're done with the JPEG object, but if you want to clean it up and
- * reuse it, call this:
- */
-EXTERN(void) jpeg_abort_compress JPP((j_compress_ptr cinfo));
-EXTERN(void) jpeg_abort_decompress JPP((j_decompress_ptr cinfo));
-
-/* Generic versions of jpeg_abort and jpeg_destroy that work on either
- * flavor of JPEG object.  These may be more convenient in some places.
- */
-EXTERN(void) jpeg_abort JPP((j_common_ptr cinfo));
-EXTERN(void) jpeg_destroy JPP((j_common_ptr cinfo));
-
-/* Default restart-marker-resync procedure for use by data source modules */
-EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo,
-					    int desired));
-
-
-/* These marker codes are exported since applications and data source modules
- * are likely to want to use them.
- */
-
-#define JPEG_RST0	0xD0	/* RST0 marker code */
-#define JPEG_EOI	0xD9	/* EOI marker code */
-#define JPEG_APP0	0xE0	/* APP0 marker code */
-#define JPEG_COM	0xFE	/* COM marker code */
-
-
-/* If we have a brain-damaged compiler that emits warnings (or worse, errors)
- * for structure definitions that are never filled in, keep it quiet by
- * supplying dummy definitions for the various substructures.
- */
-
-#ifdef INCOMPLETE_TYPES_BROKEN
-#ifndef JPEG_INTERNALS		/* will be defined in jpegint.h */
-struct jvirt_sarray_control { long dummy; };
-struct jvirt_barray_control { long dummy; };
-struct jpeg_comp_master { long dummy; };
-struct jpeg_c_main_controller { long dummy; };
-struct jpeg_c_prep_controller { long dummy; };
-struct jpeg_c_coef_controller { long dummy; };
-struct jpeg_marker_writer { long dummy; };
-struct jpeg_color_converter { long dummy; };
-struct jpeg_downsampler { long dummy; };
-struct jpeg_forward_dct { long dummy; };
-struct jpeg_entropy_encoder { long dummy; };
-struct jpeg_decomp_master { long dummy; };
-struct jpeg_d_main_controller { long dummy; };
-struct jpeg_d_coef_controller { long dummy; };
-struct jpeg_d_post_controller { long dummy; };
-struct jpeg_input_controller { long dummy; };
-struct jpeg_marker_reader { long dummy; };
-struct jpeg_entropy_decoder { long dummy; };
-struct jpeg_inverse_dct { long dummy; };
-struct jpeg_upsampler { long dummy; };
-struct jpeg_color_deconverter { long dummy; };
-struct jpeg_color_quantizer { long dummy; };
-#endif /* JPEG_INTERNALS */
-#endif /* INCOMPLETE_TYPES_BROKEN */
-
-
-/*
- * The JPEG library modules define JPEG_INTERNALS before including this file.
- * The internal structure declarations are read only when that is true.
- * Applications using the library should not include jpegint.h, but may wish
- * to include jerror.h.
- */
-
-#ifdef JPEG_INTERNALS
-#include "jpegint.h"		/* fetch private declarations */
-#include "jerror.h"		/* fetch error codes too */
-#endif
-
-#endif /* JPEGLIB_H */
diff --git a/third_party/libjpeg/jpeglibmangler.h b/third_party/libjpeg/jpeglibmangler.h
deleted file mode 100644
index b87983b..0000000
--- a/third_party/libjpeg/jpeglibmangler.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2012 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 THIRD_PARTY_LIBJPEG_JPEGLIBMANGLER_H_
-#define THIRD_PARTY_LIBJPEG_JPEGLIBMANGLER_H_
-
-// Mangle all externally visible function names so we can build our own libjpeg
-// without system libraries trying to use it.
-
-#define jcopy_block_row chromium_ijg_jcopy_block_row
-#define jcopy_sample_rows chromium_ijg_jcopy_sample_rows
-#define jdiv_round_up chromium_ijg_jdiv_round_up
-#define jinit_1pass_quantizer chromium_ijg_jinit_1pass_quantizer
-#define jinit_2pass_quantizer chromium_ijg_jinit_2pass_quantizer
-#define jinit_c_coef_controller chromium_ijg_jinit_c_coef_controller
-#define jinit_c_main_controller chromium_ijg_jinit_c_main_controller
-#define jinit_c_master_control chromium_ijg_jinit_c_master_control
-#define jinit_color_converter chromium_ijg_jinit_color_converter
-#define jinit_color_deconverter chromium_ijg_jinit_color_deconverter
-#define jinit_compress_master chromium_ijg_jinit_compress_master
-#define jinit_c_prep_controller chromium_ijg_jinit_c_prep_controller
-#define jinit_d_coef_controller chromium_ijg_jinit_d_coef_controller
-#define jinit_d_main_controller chromium_ijg_jinit_d_main_controller
-#define jinit_downsampler chromium_ijg_jinit_downsampler
-#define jinit_d_post_controller chromium_ijg_jinit_d_post_controller
-#define jinit_forward_dct chromium_ijg_jinit_forward_dct
-#define jinit_huff_decoder chromium_ijg_jinit_huff_decoder
-#define jinit_huff_encoder chromium_ijg_jinit_huff_encoder
-#define jinit_input_controller chromium_ijg_jinit_input_controller
-#define jinit_inverse_dct chromium_ijg_jinit_inverse_dct
-#define jinit_marker_reader chromium_ijg_jinit_marker_reader
-#define jinit_marker_writer chromium_ijg_jinit_marker_writer
-#define jinit_master_decompress chromium_ijg_jinit_master_decompress
-#define jinit_memory_mgr chromium_ijg_jinit_memory_mgr
-#define jinit_merged_upsampler chromium_ijg_jinit_merged_upsampler
-#define jinit_phuff_decoder chromium_ijg_jinit_phuff_decoder
-#define jinit_phuff_encoder chromium_ijg_jinit_phuff_encoder
-#define jinit_upsampler chromium_ijg_jinit_upsampler
-#define jpeg_abort chromium_ijg_jpeg_abort
-#define jpeg_abort_compress chromium_ijg_jpeg_abort_compress
-#define jpeg_abort_decompress chromium_ijg_jpeg_abort_decompress
-#define jpeg_add_quant_table chromium_ijg_jpeg_add_quant_table
-#define jpeg_alloc_huff_table chromium_ijg_jpeg_alloc_huff_table
-#define jpeg_alloc_quant_table chromium_ijg_jpeg_alloc_quant_table
-#define jpeg_calc_output_dimensions chromium_ijg_jpeg_calc_output_dimensions
-#define jpeg_consume_input chromium_ijg_jpeg_consume_input
-#define jpeg_copy_critical_parameters chromium_ijg_jpeg_copy_critical_parameters
-#define jpeg_CreateCompress chromium_ijg_jpeg_CreateCompress
-#define jpeg_CreateDecompress chromium_ijg_jpeg_CreateDecompress
-#define jpeg_default_colorspace chromium_ijg_jpeg_default_colorspace
-#define jpeg_destroy chromium_ijg_jpeg_destroy
-#define jpeg_destroy_compress chromium_ijg_jpeg_destroy_compress
-#define jpeg_destroy_decompress chromium_ijg_jpeg_destroy_decompress
-#define jpeg_fdct_float chromium_ijg_jpeg_fdct_float
-#define jpeg_fdct_ifast chromium_ijg_jpeg_fdct_ifast
-#define jpeg_fdct_islow chromium_ijg_jpeg_fdct_islow
-#define jpeg_fill_bit_buffer chromium_ijg_jpeg_fill_bit_buffer
-#define jpeg_finish_compress chromium_ijg_jpeg_finish_compress
-#define jpeg_finish_decompress chromium_ijg_jpeg_finish_decompress
-#define jpeg_finish_output chromium_ijg_jpeg_finish_output
-#define jpeg_free_large chromium_ijg_jpeg_free_large
-#define jpeg_free_small chromium_ijg_jpeg_free_small
-#define jpeg_gen_optimal_table chromium_ijg_jpeg_gen_optimal_table
-#define jpeg_get_large chromium_ijg_jpeg_get_large
-#define jpeg_get_small chromium_ijg_jpeg_get_small
-#define jpeg_has_multiple_scans chromium_ijg_jpeg_has_multiple_scans
-#define jpeg_huff_decode chromium_ijg_jpeg_huff_decode
-#define jpeg_idct_1x1 chromium_ijg_jpeg_idct_1x1
-#define jpeg_idct_2x2 chromium_ijg_jpeg_idct_2x2
-#define jpeg_idct_4x4 chromium_ijg_jpeg_idct_4x4
-#define jpeg_idct_float chromium_ijg_jpeg_idct_float
-#define jpeg_idct_ifast chromium_ijg_jpeg_idct_ifast
-#define jpeg_idct_islow chromium_ijg_jpeg_idct_islow
-#define jpeg_input_complete chromium_ijg_jpeg_input_complete
-#define jpeg_make_c_derived_tbl chromium_ijg_jpeg_make_c_derived_tbl
-#define jpeg_make_d_derived_tbl chromium_ijg_jpeg_make_d_derived_tbl
-#define jpeg_mem_available chromium_ijg_jpeg_mem_available
-#define jpeg_mem_init chromium_ijg_jpeg_mem_init
-#define jpeg_mem_term chromium_ijg_jpeg_mem_term
-#define jpeg_natural_order chromium_ijg_jpeg_natural_order
-#define jpeg_new_colormap chromium_ijg_jpeg_new_colormap
-#define jpeg_open_backing_store chromium_ijg_jpeg_open_backing_store
-#define jpeg_quality_scaling chromium_ijg_jpeg_quality_scaling
-#define jpeg_read_coefficients chromium_ijg_jpeg_read_coefficients
-#define jpeg_read_header chromium_ijg_jpeg_read_header
-#define jpeg_read_raw_data chromium_ijg_jpeg_read_raw_data
-#define jpeg_read_scanlines chromium_ijg_jpeg_read_scanlines
-#define jpeg_resync_to_restart chromium_ijg_jpeg_resync_to_restart
-#define jpeg_save_markers chromium_ijg_jpeg_save_markers
-#define jpeg_set_colorspace chromium_ijg_jpeg_set_colorspace
-#define jpeg_set_defaults chromium_ijg_jpeg_set_defaults
-#define jpeg_set_linear_quality chromium_ijg_jpeg_set_linear_quality
-#define jpeg_set_marker_processor chromium_ijg_jpeg_set_marker_processor
-#define jpeg_set_quality chromium_ijg_jpeg_set_quality
-#define jpeg_simple_progression chromium_ijg_jpeg_simple_progression
-#define jpeg_start_compress chromium_ijg_jpeg_start_compress
-#define jpeg_start_decompress chromium_ijg_jpeg_start_decompress
-#define jpeg_start_output chromium_ijg_jpeg_start_output
-#define jpeg_std_error chromium_ijg_jpeg_std_error
-#define jpeg_stdio_dest chromium_ijg_jpeg_stdio_dest
-#define jpeg_stdio_src chromium_ijg_jpeg_stdio_src
-#define jpeg_std_message_table chromium_ijg_jpeg_std_message_table
-#define jpeg_suppress_tables chromium_ijg_jpeg_suppress_tables
-#define jpeg_write_coefficients chromium_ijg_jpeg_write_coefficients
-#define jpeg_write_marker chromium_ijg_jpeg_write_marker
-#define jpeg_write_m_byte chromium_ijg_jpeg_write_m_byte
-#define jpeg_write_m_header chromium_ijg_jpeg_write_m_header
-#define jpeg_write_raw_data chromium_ijg_jpeg_write_raw_data
-#define jpeg_write_scanlines chromium_ijg_jpeg_write_scanlines
-#define jpeg_write_tables chromium_ijg_jpeg_write_tables
-#define jround_up chromium_ijg_jround_up
-#define jzero_far chromium_ijg_jzero_far
-
-#endif  // THIRD_PARTY_LIBJPEG_JPEGLIBMANGLER_H_
diff --git a/third_party/libjpeg/jquant1.c b/third_party/libjpeg/jquant1.c
deleted file mode 100644
index b2f96aa..0000000
--- a/third_party/libjpeg/jquant1.c
+++ /dev/null
@@ -1,856 +0,0 @@
-/*
- * jquant1.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains 1-pass color quantization (color mapping) routines.
- * These routines provide mapping to a fixed color map using equally spaced
- * color values.  Optional Floyd-Steinberg or ordered dithering is available.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-#ifdef QUANT_1PASS_SUPPORTED
-
-
-/*
- * The main purpose of 1-pass quantization is to provide a fast, if not very
- * high quality, colormapped output capability.  A 2-pass quantizer usually
- * gives better visual quality; however, for quantized grayscale output this
- * quantizer is perfectly adequate.  Dithering is highly recommended with this
- * quantizer, though you can turn it off if you really want to.
- *
- * In 1-pass quantization the colormap must be chosen in advance of seeing the
- * image.  We use a map consisting of all combinations of Ncolors[i] color
- * values for the i'th component.  The Ncolors[] values are chosen so that
- * their product, the total number of colors, is no more than that requested.
- * (In most cases, the product will be somewhat less.)
- *
- * Since the colormap is orthogonal, the representative value for each color
- * component can be determined without considering the other components;
- * then these indexes can be combined into a colormap index by a standard
- * N-dimensional-array-subscript calculation.  Most of the arithmetic involved
- * can be precalculated and stored in the lookup table colorindex[].
- * colorindex[i][j] maps pixel value j in component i to the nearest
- * representative value (grid plane) for that component; this index is
- * multiplied by the array stride for component i, so that the
- * index of the colormap entry closest to a given pixel value is just
- *    sum( colorindex[component-number][pixel-component-value] )
- * Aside from being fast, this scheme allows for variable spacing between
- * representative values with no additional lookup cost.
- *
- * If gamma correction has been applied in color conversion, it might be wise
- * to adjust the color grid spacing so that the representative colors are
- * equidistant in linear space.  At this writing, gamma correction is not
- * implemented by jdcolor, so nothing is done here.
- */
-
-
-/* Declarations for ordered dithering.
- *
- * We use a standard 16x16 ordered dither array.  The basic concept of ordered
- * dithering is described in many references, for instance Dale Schumacher's
- * chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991).
- * In place of Schumacher's comparisons against a "threshold" value, we add a
- * "dither" value to the input pixel and then round the result to the nearest
- * output value.  The dither value is equivalent to (0.5 - threshold) times
- * the distance between output values.  For ordered dithering, we assume that
- * the output colors are equally spaced; if not, results will probably be
- * worse, since the dither may be too much or too little at a given point.
- *
- * The normal calculation would be to form pixel value + dither, range-limit
- * this to 0..MAXJSAMPLE, and then index into the colorindex table as usual.
- * We can skip the separate range-limiting step by extending the colorindex
- * table in both directions.
- */
-
-#define ODITHER_SIZE  16	/* dimension of dither matrix */
-/* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */
-#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE)	/* # cells in matrix */
-#define ODITHER_MASK  (ODITHER_SIZE-1) /* mask for wrapping around counters */
-
-typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE];
-typedef int (*ODITHER_MATRIX_PTR)[ODITHER_SIZE];
-
-static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = {
-  /* Bayer's order-4 dither array.  Generated by the code given in
-   * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I.
-   * The values in this array must range from 0 to ODITHER_CELLS-1.
-   */
-  {   0,192, 48,240, 12,204, 60,252,  3,195, 51,243, 15,207, 63,255 },
-  { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 },
-  {  32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 },
-  { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 },
-  {   8,200, 56,248,  4,196, 52,244, 11,203, 59,251,  7,199, 55,247 },
-  { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 },
-  {  40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 },
-  { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 },
-  {   2,194, 50,242, 14,206, 62,254,  1,193, 49,241, 13,205, 61,253 },
-  { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 },
-  {  34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 },
-  { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 },
-  {  10,202, 58,250,  6,198, 54,246,  9,201, 57,249,  5,197, 53,245 },
-  { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 },
-  {  42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 },
-  { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 }
-};
-
-
-/* Declarations for Floyd-Steinberg dithering.
- *
- * Errors are accumulated into the array fserrors[], at a resolution of
- * 1/16th of a pixel count.  The error at a given pixel is propagated
- * to its not-yet-processed neighbors using the standard F-S fractions,
- *		...	(here)	7/16
- *		3/16	5/16	1/16
- * We work left-to-right on even rows, right-to-left on odd rows.
- *
- * We can get away with a single array (holding one row's worth of errors)
- * by using it to store the current row's errors at pixel columns not yet
- * processed, but the next row's errors at columns already processed.  We
- * need only a few extra variables to hold the errors immediately around the
- * current column.  (If we are lucky, those variables are in registers, but
- * even if not, they're probably cheaper to access than array elements are.)
- *
- * The fserrors[] array is indexed [component#][position].
- * We provide (#columns + 2) entries per component; the extra entry at each
- * end saves us from special-casing the first and last pixels.
- *
- * Note: on a wide image, we might not have enough room in a PC's near data
- * segment to hold the error array; so it is allocated with alloc_large.
- */
-
-#if BITS_IN_JSAMPLE == 8
-typedef INT16 FSERROR;		/* 16 bits should be enough */
-typedef int LOCFSERROR;		/* use 'int' for calculation temps */
-#else
-typedef INT32 FSERROR;		/* may need more than 16 bits */
-typedef INT32 LOCFSERROR;	/* be sure calculation temps are big enough */
-#endif
-
-typedef FSERROR FAR *FSERRPTR;	/* pointer to error array (in FAR storage!) */
-
-
-/* Private subobject */
-
-#define MAX_Q_COMPS 4		/* max components I can handle */
-
-typedef struct {
-  struct jpeg_color_quantizer pub; /* public fields */
-
-  /* Initially allocated colormap is saved here */
-  JSAMPARRAY sv_colormap;	/* The color map as a 2-D pixel array */
-  int sv_actual;		/* number of entries in use */
-
-  JSAMPARRAY colorindex;	/* Precomputed mapping for speed */
-  /* colorindex[i][j] = index of color closest to pixel value j in component i,
-   * premultiplied as described above.  Since colormap indexes must fit into
-   * JSAMPLEs, the entries of this array will too.
-   */
-  boolean is_padded;		/* is the colorindex padded for odither? */
-
-  int Ncolors[MAX_Q_COMPS];	/* # of values alloced to each component */
-
-  /* Variables for ordered dithering */
-  int row_index;		/* cur row's vertical index in dither matrix */
-  ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */
-
-  /* Variables for Floyd-Steinberg dithering */
-  FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */
-  boolean on_odd_row;		/* flag to remember which row we are on */
-} my_cquantizer;
-
-typedef my_cquantizer * my_cquantize_ptr;
-
-
-/*
- * Policy-making subroutines for create_colormap and create_colorindex.
- * These routines determine the colormap to be used.  The rest of the module
- * only assumes that the colormap is orthogonal.
- *
- *  * select_ncolors decides how to divvy up the available colors
- *    among the components.
- *  * output_value defines the set of representative values for a component.
- *  * largest_input_value defines the mapping from input values to
- *    representative values for a component.
- * Note that the latter two routines may impose different policies for
- * different components, though this is not currently done.
- */
-
-
-LOCAL(int)
-select_ncolors (j_decompress_ptr cinfo, int Ncolors[])
-/* Determine allocation of desired colors to components, */
-/* and fill in Ncolors[] array to indicate choice. */
-/* Return value is total number of colors (product of Ncolors[] values). */
-{
-  int nc = cinfo->out_color_components; /* number of color components */
-  int max_colors = cinfo->desired_number_of_colors;
-  int total_colors, iroot, i, j;
-  boolean changed;
-  long temp;
-  static const int RGB_order[3] = { RGB_GREEN, RGB_RED, RGB_BLUE };
-
-  /* We can allocate at least the nc'th root of max_colors per component. */
-  /* Compute floor(nc'th root of max_colors). */
-  iroot = 1;
-  do {
-    iroot++;
-    temp = iroot;		/* set temp = iroot ** nc */
-    for (i = 1; i < nc; i++)
-      temp *= iroot;
-  } while (temp <= (long) max_colors); /* repeat till iroot exceeds root */
-  iroot--;			/* now iroot = floor(root) */
-
-  /* Must have at least 2 color values per component */
-  if (iroot < 2)
-    ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, (int) temp);
-
-  /* Initialize to iroot color values for each component */
-  total_colors = 1;
-  for (i = 0; i < nc; i++) {
-    Ncolors[i] = iroot;
-    total_colors *= iroot;
-  }
-  /* We may be able to increment the count for one or more components without
-   * exceeding max_colors, though we know not all can be incremented.
-   * Sometimes, the first component can be incremented more than once!
-   * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.)
-   * In RGB colorspace, try to increment G first, then R, then B.
-   */
-  do {
-    changed = FALSE;
-    for (i = 0; i < nc; i++) {
-      j = (cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i);
-      /* calculate new total_colors if Ncolors[j] is incremented */
-      temp = total_colors / Ncolors[j];
-      temp *= Ncolors[j]+1;	/* done in long arith to avoid oflo */
-      if (temp > (long) max_colors)
-	break;			/* won't fit, done with this pass */
-      Ncolors[j]++;		/* OK, apply the increment */
-      total_colors = (int) temp;
-      changed = TRUE;
-    }
-  } while (changed);
-
-  return total_colors;
-}
-
-
-LOCAL(int)
-output_value (j_decompress_ptr cinfo, int ci, int j, int maxj)
-/* Return j'th output value, where j will range from 0 to maxj */
-/* The output values must fall in 0..MAXJSAMPLE in increasing order */
-{
-  /* We always provide values 0 and MAXJSAMPLE for each component;
-   * any additional values are equally spaced between these limits.
-   * (Forcing the upper and lower values to the limits ensures that
-   * dithering can't produce a color outside the selected gamut.)
-   */
-  return (int) (((INT32) j * MAXJSAMPLE + maxj/2) / maxj);
-}
-
-
-LOCAL(int)
-largest_input_value (j_decompress_ptr cinfo, int ci, int j, int maxj)
-/* Return largest input value that should map to j'th output value */
-/* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */
-{
-  /* Breakpoints are halfway between values returned by output_value */
-  return (int) (((INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj));
-}
-
-
-/*
- * Create the colormap.
- */
-
-LOCAL(void)
-create_colormap (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  JSAMPARRAY colormap;		/* Created colormap */
-  int total_colors;		/* Number of distinct output colors */
-  int i,j,k, nci, blksize, blkdist, ptr, val;
-
-  /* Select number of colors for each component */
-  total_colors = select_ncolors(cinfo, cquantize->Ncolors);
-
-  /* Report selected color counts */
-  if (cinfo->out_color_components == 3)
-    TRACEMS4(cinfo, 1, JTRC_QUANT_3_NCOLORS,
-	     total_colors, cquantize->Ncolors[0],
-	     cquantize->Ncolors[1], cquantize->Ncolors[2]);
-  else
-    TRACEMS1(cinfo, 1, JTRC_QUANT_NCOLORS, total_colors);
-
-  /* Allocate and fill in the colormap. */
-  /* The colors are ordered in the map in standard row-major order, */
-  /* i.e. rightmost (highest-indexed) color changes most rapidly. */
-
-  colormap = (*cinfo->mem->alloc_sarray)
-    ((j_common_ptr) cinfo, JPOOL_IMAGE,
-     (JDIMENSION) total_colors, (JDIMENSION) cinfo->out_color_components);
-
-  /* blksize is number of adjacent repeated entries for a component */
-  /* blkdist is distance between groups of identical entries for a component */
-  blkdist = total_colors;
-
-  for (i = 0; i < cinfo->out_color_components; i++) {
-    /* fill in colormap entries for i'th color component */
-    nci = cquantize->Ncolors[i]; /* # of distinct values for this color */
-    blksize = blkdist / nci;
-    for (j = 0; j < nci; j++) {
-      /* Compute j'th output value (out of nci) for component */
-      val = output_value(cinfo, i, j, nci-1);
-      /* Fill in all colormap entries that have this value of this component */
-      for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) {
-	/* fill in blksize entries beginning at ptr */
-	for (k = 0; k < blksize; k++)
-	  colormap[i][ptr+k] = (JSAMPLE) val;
-      }
-    }
-    blkdist = blksize;		/* blksize of this color is blkdist of next */
-  }
-
-  /* Save the colormap in private storage,
-   * where it will survive color quantization mode changes.
-   */
-  cquantize->sv_colormap = colormap;
-  cquantize->sv_actual = total_colors;
-}
-
-
-/*
- * Create the color index table.
- */
-
-LOCAL(void)
-create_colorindex (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  JSAMPROW indexptr;
-  int i,j,k, nci, blksize, val, pad;
-
-  /* For ordered dither, we pad the color index tables by MAXJSAMPLE in
-   * each direction (input index values can be -MAXJSAMPLE .. 2*MAXJSAMPLE).
-   * This is not necessary in the other dithering modes.  However, we
-   * flag whether it was done in case user changes dithering mode.
-   */
-  if (cinfo->dither_mode == JDITHER_ORDERED) {
-    pad = MAXJSAMPLE*2;
-    cquantize->is_padded = TRUE;
-  } else {
-    pad = 0;
-    cquantize->is_padded = FALSE;
-  }
-
-  cquantize->colorindex = (*cinfo->mem->alloc_sarray)
-    ((j_common_ptr) cinfo, JPOOL_IMAGE,
-     (JDIMENSION) (MAXJSAMPLE+1 + pad),
-     (JDIMENSION) cinfo->out_color_components);
-
-  /* blksize is number of adjacent repeated entries for a component */
-  blksize = cquantize->sv_actual;
-
-  for (i = 0; i < cinfo->out_color_components; i++) {
-    /* fill in colorindex entries for i'th color component */
-    nci = cquantize->Ncolors[i]; /* # of distinct values for this color */
-    blksize = blksize / nci;
-
-    /* adjust colorindex pointers to provide padding at negative indexes. */
-    if (pad)
-      cquantize->colorindex[i] += MAXJSAMPLE;
-
-    /* in loop, val = index of current output value, */
-    /* and k = largest j that maps to current val */
-    indexptr = cquantize->colorindex[i];
-    val = 0;
-    k = largest_input_value(cinfo, i, 0, nci-1);
-    for (j = 0; j <= MAXJSAMPLE; j++) {
-      while (j > k)		/* advance val if past boundary */
-	k = largest_input_value(cinfo, i, ++val, nci-1);
-      /* premultiply so that no multiplication needed in main processing */
-      indexptr[j] = (JSAMPLE) (val * blksize);
-    }
-    /* Pad at both ends if necessary */
-    if (pad)
-      for (j = 1; j <= MAXJSAMPLE; j++) {
-	indexptr[-j] = indexptr[0];
-	indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE];
-      }
-  }
-}
-
-
-/*
- * Create an ordered-dither array for a component having ncolors
- * distinct output values.
- */
-
-LOCAL(ODITHER_MATRIX_PTR)
-make_odither_array (j_decompress_ptr cinfo, int ncolors)
-{
-  ODITHER_MATRIX_PTR odither;
-  int j,k;
-  INT32 num,den;
-
-  odither = (ODITHER_MATRIX_PTR)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(ODITHER_MATRIX));
-  /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1).
-   * Hence the dither value for the matrix cell with fill order f
-   * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1).
-   * On 16-bit-int machine, be careful to avoid overflow.
-   */
-  den = 2 * ODITHER_CELLS * ((INT32) (ncolors - 1));
-  for (j = 0; j < ODITHER_SIZE; j++) {
-    for (k = 0; k < ODITHER_SIZE; k++) {
-      num = ((INT32) (ODITHER_CELLS-1 - 2*((int)base_dither_matrix[j][k])))
-	    * MAXJSAMPLE;
-      /* Ensure round towards zero despite C's lack of consistency
-       * about rounding negative values in integer division...
-       */
-      odither[j][k] = (int) (num<0 ? -((-num)/den) : num/den);
-    }
-  }
-  return odither;
-}
-
-
-/*
- * Create the ordered-dither tables.
- * Components having the same number of representative colors may 
- * share a dither table.
- */
-
-LOCAL(void)
-create_odither_tables (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  ODITHER_MATRIX_PTR odither;
-  int i, j, nci;
-
-  for (i = 0; i < cinfo->out_color_components; i++) {
-    nci = cquantize->Ncolors[i]; /* # of distinct values for this color */
-    odither = NULL;		/* search for matching prior component */
-    for (j = 0; j < i; j++) {
-      if (nci == cquantize->Ncolors[j]) {
-	odither = cquantize->odither[j];
-	break;
-      }
-    }
-    if (odither == NULL)	/* need a new table? */
-      odither = make_odither_array(cinfo, nci);
-    cquantize->odither[i] = odither;
-  }
-}
-
-
-/*
- * Map some rows of pixels to the output colormapped representation.
- */
-
-METHODDEF(void)
-color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
-		JSAMPARRAY output_buf, int num_rows)
-/* General case, no dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  JSAMPARRAY colorindex = cquantize->colorindex;
-  register int pixcode, ci;
-  register JSAMPROW ptrin, ptrout;
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-  register int nc = cinfo->out_color_components;
-
-  for (row = 0; row < num_rows; row++) {
-    ptrin = input_buf[row];
-    ptrout = output_buf[row];
-    for (col = width; col > 0; col--) {
-      pixcode = 0;
-      for (ci = 0; ci < nc; ci++) {
-	pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]);
-      }
-      *ptrout++ = (JSAMPLE) pixcode;
-    }
-  }
-}
-
-
-METHODDEF(void)
-color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
-		 JSAMPARRAY output_buf, int num_rows)
-/* Fast path for out_color_components==3, no dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  register int pixcode;
-  register JSAMPROW ptrin, ptrout;
-  JSAMPROW colorindex0 = cquantize->colorindex[0];
-  JSAMPROW colorindex1 = cquantize->colorindex[1];
-  JSAMPROW colorindex2 = cquantize->colorindex[2];
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-
-  for (row = 0; row < num_rows; row++) {
-    ptrin = input_buf[row];
-    ptrout = output_buf[row];
-    for (col = width; col > 0; col--) {
-      pixcode  = GETJSAMPLE(colorindex0[GETJSAMPLE(*ptrin++)]);
-      pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*ptrin++)]);
-      pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*ptrin++)]);
-      *ptrout++ = (JSAMPLE) pixcode;
-    }
-  }
-}
-
-
-METHODDEF(void)
-quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
-		     JSAMPARRAY output_buf, int num_rows)
-/* General case, with ordered dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  register JSAMPROW input_ptr;
-  register JSAMPROW output_ptr;
-  JSAMPROW colorindex_ci;
-  int * dither;			/* points to active row of dither matrix */
-  int row_index, col_index;	/* current indexes into dither matrix */
-  int nc = cinfo->out_color_components;
-  int ci;
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-
-  for (row = 0; row < num_rows; row++) {
-    /* Initialize output values to 0 so can process components separately */
-    jzero_far((void FAR *) output_buf[row],
-	      (size_t) (width * SIZEOF(JSAMPLE)));
-    row_index = cquantize->row_index;
-    for (ci = 0; ci < nc; ci++) {
-      input_ptr = input_buf[row] + ci;
-      output_ptr = output_buf[row];
-      colorindex_ci = cquantize->colorindex[ci];
-      dither = cquantize->odither[ci][row_index];
-      col_index = 0;
-
-      for (col = width; col > 0; col--) {
-	/* Form pixel value + dither, range-limit to 0..MAXJSAMPLE,
-	 * select output value, accumulate into output code for this pixel.
-	 * Range-limiting need not be done explicitly, as we have extended
-	 * the colorindex table to produce the right answers for out-of-range
-	 * inputs.  The maximum dither is +- MAXJSAMPLE; this sets the
-	 * required amount of padding.
-	 */
-	*output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]];
-	input_ptr += nc;
-	output_ptr++;
-	col_index = (col_index + 1) & ODITHER_MASK;
-      }
-    }
-    /* Advance row index for next row */
-    row_index = (row_index + 1) & ODITHER_MASK;
-    cquantize->row_index = row_index;
-  }
-}
-
-
-METHODDEF(void)
-quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
-		      JSAMPARRAY output_buf, int num_rows)
-/* Fast path for out_color_components==3, with ordered dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  register int pixcode;
-  register JSAMPROW input_ptr;
-  register JSAMPROW output_ptr;
-  JSAMPROW colorindex0 = cquantize->colorindex[0];
-  JSAMPROW colorindex1 = cquantize->colorindex[1];
-  JSAMPROW colorindex2 = cquantize->colorindex[2];
-  int * dither0;		/* points to active row of dither matrix */
-  int * dither1;
-  int * dither2;
-  int row_index, col_index;	/* current indexes into dither matrix */
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-
-  for (row = 0; row < num_rows; row++) {
-    row_index = cquantize->row_index;
-    input_ptr = input_buf[row];
-    output_ptr = output_buf[row];
-    dither0 = cquantize->odither[0][row_index];
-    dither1 = cquantize->odither[1][row_index];
-    dither2 = cquantize->odither[2][row_index];
-    col_index = 0;
-
-    for (col = width; col > 0; col--) {
-      pixcode  = GETJSAMPLE(colorindex0[GETJSAMPLE(*input_ptr++) +
-					dither0[col_index]]);
-      pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*input_ptr++) +
-					dither1[col_index]]);
-      pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*input_ptr++) +
-					dither2[col_index]]);
-      *output_ptr++ = (JSAMPLE) pixcode;
-      col_index = (col_index + 1) & ODITHER_MASK;
-    }
-    row_index = (row_index + 1) & ODITHER_MASK;
-    cquantize->row_index = row_index;
-  }
-}
-
-
-METHODDEF(void)
-quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
-		    JSAMPARRAY output_buf, int num_rows)
-/* General case, with Floyd-Steinberg dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  register LOCFSERROR cur;	/* current error or pixel value */
-  LOCFSERROR belowerr;		/* error for pixel below cur */
-  LOCFSERROR bpreverr;		/* error for below/prev col */
-  LOCFSERROR bnexterr;		/* error for below/next col */
-  LOCFSERROR delta;
-  register FSERRPTR errorptr;	/* => fserrors[] at column before current */
-  register JSAMPROW input_ptr;
-  register JSAMPROW output_ptr;
-  JSAMPROW colorindex_ci;
-  JSAMPROW colormap_ci;
-  int pixcode;
-  int nc = cinfo->out_color_components;
-  int dir;			/* 1 for left-to-right, -1 for right-to-left */
-  int dirnc;			/* dir * nc */
-  int ci;
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-  JSAMPLE *range_limit = cinfo->sample_range_limit;
-  SHIFT_TEMPS
-
-  for (row = 0; row < num_rows; row++) {
-    /* Initialize output values to 0 so can process components separately */
-    jzero_far((void FAR *) output_buf[row],
-	      (size_t) (width * SIZEOF(JSAMPLE)));
-    for (ci = 0; ci < nc; ci++) {
-      input_ptr = input_buf[row] + ci;
-      output_ptr = output_buf[row];
-      if (cquantize->on_odd_row) {
-	/* work right to left in this row */
-	input_ptr += (width-1) * nc; /* so point to rightmost pixel */
-	output_ptr += width-1;
-	dir = -1;
-	dirnc = -nc;
-	errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */
-      } else {
-	/* work left to right in this row */
-	dir = 1;
-	dirnc = nc;
-	errorptr = cquantize->fserrors[ci]; /* => entry before first column */
-      }
-      colorindex_ci = cquantize->colorindex[ci];
-      colormap_ci = cquantize->sv_colormap[ci];
-      /* Preset error values: no error propagated to first pixel from left */
-      cur = 0;
-      /* and no error propagated to row below yet */
-      belowerr = bpreverr = 0;
-
-      for (col = width; col > 0; col--) {
-	/* cur holds the error propagated from the previous pixel on the
-	 * current line.  Add the error propagated from the previous line
-	 * to form the complete error correction term for this pixel, and
-	 * round the error term (which is expressed * 16) to an integer.
-	 * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct
-	 * for either sign of the error value.
-	 * Note: errorptr points to *previous* column's array entry.
-	 */
-	cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4);
-	/* Form pixel value + error, and range-limit to 0..MAXJSAMPLE.
-	 * The maximum error is +- MAXJSAMPLE; this sets the required size
-	 * of the range_limit array.
-	 */
-	cur += GETJSAMPLE(*input_ptr);
-	cur = GETJSAMPLE(range_limit[cur]);
-	/* Select output value, accumulate into output code for this pixel */
-	pixcode = GETJSAMPLE(colorindex_ci[cur]);
-	*output_ptr += (JSAMPLE) pixcode;
-	/* Compute actual representation error at this pixel */
-	/* Note: we can do this even though we don't have the final */
-	/* pixel code, because the colormap is orthogonal. */
-	cur -= GETJSAMPLE(colormap_ci[pixcode]);
-	/* Compute error fractions to be propagated to adjacent pixels.
-	 * Add these into the running sums, and simultaneously shift the
-	 * next-line error sums left by 1 column.
-	 */
-	bnexterr = cur;
-	delta = cur * 2;
-	cur += delta;		/* form error * 3 */
-	errorptr[0] = (FSERROR) (bpreverr + cur);
-	cur += delta;		/* form error * 5 */
-	bpreverr = belowerr + cur;
-	belowerr = bnexterr;
-	cur += delta;		/* form error * 7 */
-	/* At this point cur contains the 7/16 error value to be propagated
-	 * to the next pixel on the current line, and all the errors for the
-	 * next line have been shifted over. We are therefore ready to move on.
-	 */
-	input_ptr += dirnc;	/* advance input ptr to next column */
-	output_ptr += dir;	/* advance output ptr to next column */
-	errorptr += dir;	/* advance errorptr to current column */
-      }
-      /* Post-loop cleanup: we must unload the final error value into the
-       * final fserrors[] entry.  Note we need not unload belowerr because
-       * it is for the dummy column before or after the actual array.
-       */
-      errorptr[0] = (FSERROR) bpreverr; /* unload prev err into array */
-    }
-    cquantize->on_odd_row = (cquantize->on_odd_row ? FALSE : TRUE);
-  }
-}
-
-
-/*
- * Allocate workspace for Floyd-Steinberg errors.
- */
-
-LOCAL(void)
-alloc_fs_workspace (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  size_t arraysize;
-  int i;
-
-  arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR));
-  for (i = 0; i < cinfo->out_color_components; i++) {
-    cquantize->fserrors[i] = (FSERRPTR)
-      (*cinfo->mem->alloc_large)((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize);
-  }
-}
-
-
-/*
- * Initialize for one-pass color quantization.
- */
-
-METHODDEF(void)
-start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  size_t arraysize;
-  int i;
-
-  /* Install my colormap. */
-  cinfo->colormap = cquantize->sv_colormap;
-  cinfo->actual_number_of_colors = cquantize->sv_actual;
-
-  /* Initialize for desired dithering mode. */
-  switch (cinfo->dither_mode) {
-  case JDITHER_NONE:
-    if (cinfo->out_color_components == 3)
-      cquantize->pub.color_quantize = color_quantize3;
-    else
-      cquantize->pub.color_quantize = color_quantize;
-    break;
-  case JDITHER_ORDERED:
-    if (cinfo->out_color_components == 3)
-      cquantize->pub.color_quantize = quantize3_ord_dither;
-    else
-      cquantize->pub.color_quantize = quantize_ord_dither;
-    cquantize->row_index = 0;	/* initialize state for ordered dither */
-    /* If user changed to ordered dither from another mode,
-     * we must recreate the color index table with padding.
-     * This will cost extra space, but probably isn't very likely.
-     */
-    if (! cquantize->is_padded)
-      create_colorindex(cinfo);
-    /* Create ordered-dither tables if we didn't already. */
-    if (cquantize->odither[0] == NULL)
-      create_odither_tables(cinfo);
-    break;
-  case JDITHER_FS:
-    cquantize->pub.color_quantize = quantize_fs_dither;
-    cquantize->on_odd_row = FALSE; /* initialize state for F-S dither */
-    /* Allocate Floyd-Steinberg workspace if didn't already. */
-    if (cquantize->fserrors[0] == NULL)
-      alloc_fs_workspace(cinfo);
-    /* Initialize the propagated errors to zero. */
-    arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR));
-    for (i = 0; i < cinfo->out_color_components; i++)
-      jzero_far((void FAR *) cquantize->fserrors[i], arraysize);
-    break;
-  default:
-    ERREXIT(cinfo, JERR_NOT_COMPILED);
-    break;
-  }
-}
-
-
-/*
- * Finish up at the end of the pass.
- */
-
-METHODDEF(void)
-finish_pass_1_quant (j_decompress_ptr cinfo)
-{
-  /* no work in 1-pass case */
-}
-
-
-/*
- * Switch to a new external colormap between output passes.
- * Shouldn't get to this module!
- */
-
-METHODDEF(void)
-new_color_map_1_quant (j_decompress_ptr cinfo)
-{
-  ERREXIT(cinfo, JERR_MODE_CHANGE);
-}
-
-
-/*
- * Module initialization routine for 1-pass color quantization.
- */
-
-GLOBAL(void)
-jinit_1pass_quantizer (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize;
-
-  cquantize = (my_cquantize_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_cquantizer));
-  cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize;
-  cquantize->pub.start_pass = start_pass_1_quant;
-  cquantize->pub.finish_pass = finish_pass_1_quant;
-  cquantize->pub.new_color_map = new_color_map_1_quant;
-  cquantize->fserrors[0] = NULL; /* Flag FS workspace not allocated */
-  cquantize->odither[0] = NULL;	/* Also flag odither arrays not allocated */
-
-  /* Make sure my internal arrays won't overflow */
-  if (cinfo->out_color_components > MAX_Q_COMPS)
-    ERREXIT1(cinfo, JERR_QUANT_COMPONENTS, MAX_Q_COMPS);
-  /* Make sure colormap indexes can be represented by JSAMPLEs */
-  if (cinfo->desired_number_of_colors > (MAXJSAMPLE+1))
-    ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXJSAMPLE+1);
-
-  /* Create the colormap and color index table. */
-  create_colormap(cinfo);
-  create_colorindex(cinfo);
-
-  /* Allocate Floyd-Steinberg workspace now if requested.
-   * We do this now since it is FAR storage and may affect the memory
-   * manager's space calculations.  If the user changes to FS dither
-   * mode in a later pass, we will allocate the space then, and will
-   * possibly overrun the max_memory_to_use setting.
-   */
-  if (cinfo->dither_mode == JDITHER_FS)
-    alloc_fs_workspace(cinfo);
-}
-
-#endif /* QUANT_1PASS_SUPPORTED */
diff --git a/third_party/libjpeg/jquant2.c b/third_party/libjpeg/jquant2.c
deleted file mode 100644
index af601e3..0000000
--- a/third_party/libjpeg/jquant2.c
+++ /dev/null
@@ -1,1310 +0,0 @@
-/*
- * jquant2.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains 2-pass color quantization (color mapping) routines.
- * These routines provide selection of a custom color map for an image,
- * followed by mapping of the image to that color map, with optional
- * Floyd-Steinberg dithering.
- * It is also possible to use just the second pass to map to an arbitrary
- * externally-given color map.
- *
- * Note: ordered dithering is not supported, since there isn't any fast
- * way to compute intercolor distances; it's unclear that ordered dither's
- * fundamental assumptions even hold with an irregularly spaced color map.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-#ifdef QUANT_2PASS_SUPPORTED
-
-
-/*
- * This module implements the well-known Heckbert paradigm for color
- * quantization.  Most of the ideas used here can be traced back to
- * Heckbert's seminal paper
- *   Heckbert, Paul.  "Color Image Quantization for Frame Buffer Display",
- *   Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304.
- *
- * In the first pass over the image, we accumulate a histogram showing the
- * usage count of each possible color.  To keep the histogram to a reasonable
- * size, we reduce the precision of the input; typical practice is to retain
- * 5 or 6 bits per color, so that 8 or 4 different input values are counted
- * in the same histogram cell.
- *
- * Next, the color-selection step begins with a box representing the whole
- * color space, and repeatedly splits the "largest" remaining box until we
- * have as many boxes as desired colors.  Then the mean color in each
- * remaining box becomes one of the possible output colors.
- * 
- * The second pass over the image maps each input pixel to the closest output
- * color (optionally after applying a Floyd-Steinberg dithering correction).
- * This mapping is logically trivial, but making it go fast enough requires
- * considerable care.
- *
- * Heckbert-style quantizers vary a good deal in their policies for choosing
- * the "largest" box and deciding where to cut it.  The particular policies
- * used here have proved out well in experimental comparisons, but better ones
- * may yet be found.
- *
- * In earlier versions of the IJG code, this module quantized in YCbCr color
- * space, processing the raw upsampled data without a color conversion step.
- * This allowed the color conversion math to be done only once per colormap
- * entry, not once per pixel.  However, that optimization precluded other
- * useful optimizations (such as merging color conversion with upsampling)
- * and it also interfered with desired capabilities such as quantizing to an
- * externally-supplied colormap.  We have therefore abandoned that approach.
- * The present code works in the post-conversion color space, typically RGB.
- *
- * To improve the visual quality of the results, we actually work in scaled
- * RGB space, giving G distances more weight than R, and R in turn more than
- * B.  To do everything in integer math, we must use integer scale factors.
- * The 2/3/1 scale factors used here correspond loosely to the relative
- * weights of the colors in the NTSC grayscale equation.
- * If you want to use this code to quantize a non-RGB color space, you'll
- * probably need to change these scale factors.
- */
-
-#define R_SCALE 2		/* scale R distances by this much */
-#define G_SCALE 3		/* scale G distances by this much */
-#define B_SCALE 1		/* and B by this much */
-
-/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined
- * in jmorecfg.h.  As the code stands, it will do the right thing for R,G,B
- * and B,G,R orders.  If you define some other weird order in jmorecfg.h,
- * you'll get compile errors until you extend this logic.  In that case
- * you'll probably want to tweak the histogram sizes too.
- */
-
-#if RGB_RED == 0
-#define C0_SCALE R_SCALE
-#endif
-#if RGB_BLUE == 0
-#define C0_SCALE B_SCALE
-#endif
-#if RGB_GREEN == 1
-#define C1_SCALE G_SCALE
-#endif
-#if RGB_RED == 2
-#define C2_SCALE R_SCALE
-#endif
-#if RGB_BLUE == 2
-#define C2_SCALE B_SCALE
-#endif
-
-
-/*
- * First we have the histogram data structure and routines for creating it.
- *
- * The number of bits of precision can be adjusted by changing these symbols.
- * We recommend keeping 6 bits for G and 5 each for R and B.
- * If you have plenty of memory and cycles, 6 bits all around gives marginally
- * better results; if you are short of memory, 5 bits all around will save
- * some space but degrade the results.
- * To maintain a fully accurate histogram, we'd need to allocate a "long"
- * (preferably unsigned long) for each cell.  In practice this is overkill;
- * we can get by with 16 bits per cell.  Few of the cell counts will overflow,
- * and clamping those that do overflow to the maximum value will give close-
- * enough results.  This reduces the recommended histogram size from 256Kb
- * to 128Kb, which is a useful savings on PC-class machines.
- * (In the second pass the histogram space is re-used for pixel mapping data;
- * in that capacity, each cell must be able to store zero to the number of
- * desired colors.  16 bits/cell is plenty for that too.)
- * Since the JPEG code is intended to run in small memory model on 80x86
- * machines, we can't just allocate the histogram in one chunk.  Instead
- * of a true 3-D array, we use a row of pointers to 2-D arrays.  Each
- * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and
- * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries.  Note that
- * on 80x86 machines, the pointer row is in near memory but the actual
- * arrays are in far memory (same arrangement as we use for image arrays).
- */
-
-#define MAXNUMCOLORS  (MAXJSAMPLE+1) /* maximum size of colormap */
-
-/* These will do the right thing for either R,G,B or B,G,R color order,
- * but you may not like the results for other color orders.
- */
-#define HIST_C0_BITS  5		/* bits of precision in R/B histogram */
-#define HIST_C1_BITS  6		/* bits of precision in G histogram */
-#define HIST_C2_BITS  5		/* bits of precision in B/R histogram */
-
-/* Number of elements along histogram axes. */
-#define HIST_C0_ELEMS  (1<<HIST_C0_BITS)
-#define HIST_C1_ELEMS  (1<<HIST_C1_BITS)
-#define HIST_C2_ELEMS  (1<<HIST_C2_BITS)
-
-/* These are the amounts to shift an input value to get a histogram index. */
-#define C0_SHIFT  (BITS_IN_JSAMPLE-HIST_C0_BITS)
-#define C1_SHIFT  (BITS_IN_JSAMPLE-HIST_C1_BITS)
-#define C2_SHIFT  (BITS_IN_JSAMPLE-HIST_C2_BITS)
-
-
-typedef UINT16 histcell;	/* histogram cell; prefer an unsigned type */
-
-typedef histcell FAR * histptr;	/* for pointers to histogram cells */
-
-typedef histcell hist1d[HIST_C2_ELEMS]; /* typedefs for the array */
-typedef hist1d FAR * hist2d;	/* type for the 2nd-level pointers */
-typedef hist2d * hist3d;	/* type for top-level pointer */
-
-
-/* Declarations for Floyd-Steinberg dithering.
- *
- * Errors are accumulated into the array fserrors[], at a resolution of
- * 1/16th of a pixel count.  The error at a given pixel is propagated
- * to its not-yet-processed neighbors using the standard F-S fractions,
- *		...	(here)	7/16
- *		3/16	5/16	1/16
- * We work left-to-right on even rows, right-to-left on odd rows.
- *
- * We can get away with a single array (holding one row's worth of errors)
- * by using it to store the current row's errors at pixel columns not yet
- * processed, but the next row's errors at columns already processed.  We
- * need only a few extra variables to hold the errors immediately around the
- * current column.  (If we are lucky, those variables are in registers, but
- * even if not, they're probably cheaper to access than array elements are.)
- *
- * The fserrors[] array has (#columns + 2) entries; the extra entry at
- * each end saves us from special-casing the first and last pixels.
- * Each entry is three values long, one value for each color component.
- *
- * Note: on a wide image, we might not have enough room in a PC's near data
- * segment to hold the error array; so it is allocated with alloc_large.
- */
-
-#if BITS_IN_JSAMPLE == 8
-typedef INT16 FSERROR;		/* 16 bits should be enough */
-typedef int LOCFSERROR;		/* use 'int' for calculation temps */
-#else
-typedef INT32 FSERROR;		/* may need more than 16 bits */
-typedef INT32 LOCFSERROR;	/* be sure calculation temps are big enough */
-#endif
-
-typedef FSERROR FAR *FSERRPTR;	/* pointer to error array (in FAR storage!) */
-
-
-/* Private subobject */
-
-typedef struct {
-  struct jpeg_color_quantizer pub; /* public fields */
-
-  /* Space for the eventually created colormap is stashed here */
-  JSAMPARRAY sv_colormap;	/* colormap allocated at init time */
-  int desired;			/* desired # of colors = size of colormap */
-
-  /* Variables for accumulating image statistics */
-  hist3d histogram;		/* pointer to the histogram */
-
-  boolean needs_zeroed;		/* TRUE if next pass must zero histogram */
-
-  /* Variables for Floyd-Steinberg dithering */
-  FSERRPTR fserrors;		/* accumulated errors */
-  boolean on_odd_row;		/* flag to remember which row we are on */
-  int * error_limiter;		/* table for clamping the applied error */
-} my_cquantizer;
-
-typedef my_cquantizer * my_cquantize_ptr;
-
-
-/*
- * Prescan some rows of pixels.
- * In this module the prescan simply updates the histogram, which has been
- * initialized to zeroes by start_pass.
- * An output_buf parameter is required by the method signature, but no data
- * is actually output (in fact the buffer controller is probably passing a
- * NULL pointer).
- */
-
-METHODDEF(void)
-prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
-		  JSAMPARRAY output_buf, int num_rows)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  register JSAMPROW ptr;
-  register histptr histp;
-  register hist3d histogram = cquantize->histogram;
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-
-  for (row = 0; row < num_rows; row++) {
-    ptr = input_buf[row];
-    for (col = width; col > 0; col--) {
-      /* get pixel value and index into the histogram */
-      histp = & histogram[GETJSAMPLE(ptr[0]) >> C0_SHIFT]
-			 [GETJSAMPLE(ptr[1]) >> C1_SHIFT]
-			 [GETJSAMPLE(ptr[2]) >> C2_SHIFT];
-      /* increment, check for overflow and undo increment if so. */
-      if (++(*histp) <= 0)
-	(*histp)--;
-      ptr += 3;
-    }
-  }
-}
-
-
-/*
- * Next we have the really interesting routines: selection of a colormap
- * given the completed histogram.
- * These routines work with a list of "boxes", each representing a rectangular
- * subset of the input color space (to histogram precision).
- */
-
-typedef struct {
-  /* The bounds of the box (inclusive); expressed as histogram indexes */
-  int c0min, c0max;
-  int c1min, c1max;
-  int c2min, c2max;
-  /* The volume (actually 2-norm) of the box */
-  INT32 volume;
-  /* The number of nonzero histogram cells within this box */
-  long colorcount;
-} box;
-
-typedef box * boxptr;
-
-
-LOCAL(boxptr)
-find_biggest_color_pop (boxptr boxlist, int numboxes)
-/* Find the splittable box with the largest color population */
-/* Returns NULL if no splittable boxes remain */
-{
-  register boxptr boxp;
-  register int i;
-  register long maxc = 0;
-  boxptr which = NULL;
-  
-  for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) {
-    if (boxp->colorcount > maxc && boxp->volume > 0) {
-      which = boxp;
-      maxc = boxp->colorcount;
-    }
-  }
-  return which;
-}
-
-
-LOCAL(boxptr)
-find_biggest_volume (boxptr boxlist, int numboxes)
-/* Find the splittable box with the largest (scaled) volume */
-/* Returns NULL if no splittable boxes remain */
-{
-  register boxptr boxp;
-  register int i;
-  register INT32 maxv = 0;
-  boxptr which = NULL;
-  
-  for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) {
-    if (boxp->volume > maxv) {
-      which = boxp;
-      maxv = boxp->volume;
-    }
-  }
-  return which;
-}
-
-
-LOCAL(void)
-update_box (j_decompress_ptr cinfo, boxptr boxp)
-/* Shrink the min/max bounds of a box to enclose only nonzero elements, */
-/* and recompute its volume and population */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  hist3d histogram = cquantize->histogram;
-  histptr histp;
-  int c0,c1,c2;
-  int c0min,c0max,c1min,c1max,c2min,c2max;
-  INT32 dist0,dist1,dist2;
-  long ccount;
-  
-  c0min = boxp->c0min;  c0max = boxp->c0max;
-  c1min = boxp->c1min;  c1max = boxp->c1max;
-  c2min = boxp->c2min;  c2max = boxp->c2max;
-  
-  if (c0max > c0min)
-    for (c0 = c0min; c0 <= c0max; c0++)
-      for (c1 = c1min; c1 <= c1max; c1++) {
-	histp = & histogram[c0][c1][c2min];
-	for (c2 = c2min; c2 <= c2max; c2++)
-	  if (*histp++ != 0) {
-	    boxp->c0min = c0min = c0;
-	    goto have_c0min;
-	  }
-      }
- have_c0min:
-  if (c0max > c0min)
-    for (c0 = c0max; c0 >= c0min; c0--)
-      for (c1 = c1min; c1 <= c1max; c1++) {
-	histp = & histogram[c0][c1][c2min];
-	for (c2 = c2min; c2 <= c2max; c2++)
-	  if (*histp++ != 0) {
-	    boxp->c0max = c0max = c0;
-	    goto have_c0max;
-	  }
-      }
- have_c0max:
-  if (c1max > c1min)
-    for (c1 = c1min; c1 <= c1max; c1++)
-      for (c0 = c0min; c0 <= c0max; c0++) {
-	histp = & histogram[c0][c1][c2min];
-	for (c2 = c2min; c2 <= c2max; c2++)
-	  if (*histp++ != 0) {
-	    boxp->c1min = c1min = c1;
-	    goto have_c1min;
-	  }
-      }
- have_c1min:
-  if (c1max > c1min)
-    for (c1 = c1max; c1 >= c1min; c1--)
-      for (c0 = c0min; c0 <= c0max; c0++) {
-	histp = & histogram[c0][c1][c2min];
-	for (c2 = c2min; c2 <= c2max; c2++)
-	  if (*histp++ != 0) {
-	    boxp->c1max = c1max = c1;
-	    goto have_c1max;
-	  }
-      }
- have_c1max:
-  if (c2max > c2min)
-    for (c2 = c2min; c2 <= c2max; c2++)
-      for (c0 = c0min; c0 <= c0max; c0++) {
-	histp = & histogram[c0][c1min][c2];
-	for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS)
-	  if (*histp != 0) {
-	    boxp->c2min = c2min = c2;
-	    goto have_c2min;
-	  }
-      }
- have_c2min:
-  if (c2max > c2min)
-    for (c2 = c2max; c2 >= c2min; c2--)
-      for (c0 = c0min; c0 <= c0max; c0++) {
-	histp = & histogram[c0][c1min][c2];
-	for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS)
-	  if (*histp != 0) {
-	    boxp->c2max = c2max = c2;
-	    goto have_c2max;
-	  }
-      }
- have_c2max:
-
-  /* Update box volume.
-   * We use 2-norm rather than real volume here; this biases the method
-   * against making long narrow boxes, and it has the side benefit that
-   * a box is splittable iff norm > 0.
-   * Since the differences are expressed in histogram-cell units,
-   * we have to shift back to JSAMPLE units to get consistent distances;
-   * after which, we scale according to the selected distance scale factors.
-   */
-  dist0 = ((c0max - c0min) << C0_SHIFT) * C0_SCALE;
-  dist1 = ((c1max - c1min) << C1_SHIFT) * C1_SCALE;
-  dist2 = ((c2max - c2min) << C2_SHIFT) * C2_SCALE;
-  boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2;
-  
-  /* Now scan remaining volume of box and compute population */
-  ccount = 0;
-  for (c0 = c0min; c0 <= c0max; c0++)
-    for (c1 = c1min; c1 <= c1max; c1++) {
-      histp = & histogram[c0][c1][c2min];
-      for (c2 = c2min; c2 <= c2max; c2++, histp++)
-	if (*histp != 0) {
-	  ccount++;
-	}
-    }
-  boxp->colorcount = ccount;
-}
-
-
-LOCAL(int)
-median_cut (j_decompress_ptr cinfo, boxptr boxlist, int numboxes,
-	    int desired_colors)
-/* Repeatedly select and split the largest box until we have enough boxes */
-{
-  int n,lb;
-  int c0,c1,c2,cmax;
-  register boxptr b1,b2;
-
-  while (numboxes < desired_colors) {
-    /* Select box to split.
-     * Current algorithm: by population for first half, then by volume.
-     */
-    if (numboxes*2 <= desired_colors) {
-      b1 = find_biggest_color_pop(boxlist, numboxes);
-    } else {
-      b1 = find_biggest_volume(boxlist, numboxes);
-    }
-    if (b1 == NULL)		/* no splittable boxes left! */
-      break;
-    b2 = &boxlist[numboxes];	/* where new box will go */
-    /* Copy the color bounds to the new box. */
-    b2->c0max = b1->c0max; b2->c1max = b1->c1max; b2->c2max = b1->c2max;
-    b2->c0min = b1->c0min; b2->c1min = b1->c1min; b2->c2min = b1->c2min;
-    /* Choose which axis to split the box on.
-     * Current algorithm: longest scaled axis.
-     * See notes in update_box about scaling distances.
-     */
-    c0 = ((b1->c0max - b1->c0min) << C0_SHIFT) * C0_SCALE;
-    c1 = ((b1->c1max - b1->c1min) << C1_SHIFT) * C1_SCALE;
-    c2 = ((b1->c2max - b1->c2min) << C2_SHIFT) * C2_SCALE;
-    /* We want to break any ties in favor of green, then red, blue last.
-     * This code does the right thing for R,G,B or B,G,R color orders only.
-     */
-#if RGB_RED == 0
-    cmax = c1; n = 1;
-    if (c0 > cmax) { cmax = c0; n = 0; }
-    if (c2 > cmax) { n = 2; }
-#else
-    cmax = c1; n = 1;
-    if (c2 > cmax) { cmax = c2; n = 2; }
-    if (c0 > cmax) { n = 0; }
-#endif
-    /* Choose split point along selected axis, and update box bounds.
-     * Current algorithm: split at halfway point.
-     * (Since the box has been shrunk to minimum volume,
-     * any split will produce two nonempty subboxes.)
-     * Note that lb value is max for lower box, so must be < old max.
-     */
-    switch (n) {
-    case 0:
-      lb = (b1->c0max + b1->c0min) / 2;
-      b1->c0max = lb;
-      b2->c0min = lb+1;
-      break;
-    case 1:
-      lb = (b1->c1max + b1->c1min) / 2;
-      b1->c1max = lb;
-      b2->c1min = lb+1;
-      break;
-    case 2:
-      lb = (b1->c2max + b1->c2min) / 2;
-      b1->c2max = lb;
-      b2->c2min = lb+1;
-      break;
-    }
-    /* Update stats for boxes */
-    update_box(cinfo, b1);
-    update_box(cinfo, b2);
-    numboxes++;
-  }
-  return numboxes;
-}
-
-
-LOCAL(void)
-compute_color (j_decompress_ptr cinfo, boxptr boxp, int icolor)
-/* Compute representative color for a box, put it in colormap[icolor] */
-{
-  /* Current algorithm: mean weighted by pixels (not colors) */
-  /* Note it is important to get the rounding correct! */
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  hist3d histogram = cquantize->histogram;
-  histptr histp;
-  int c0,c1,c2;
-  int c0min,c0max,c1min,c1max,c2min,c2max;
-  long count;
-  long total = 0;
-  long c0total = 0;
-  long c1total = 0;
-  long c2total = 0;
-  
-  c0min = boxp->c0min;  c0max = boxp->c0max;
-  c1min = boxp->c1min;  c1max = boxp->c1max;
-  c2min = boxp->c2min;  c2max = boxp->c2max;
-  
-  for (c0 = c0min; c0 <= c0max; c0++)
-    for (c1 = c1min; c1 <= c1max; c1++) {
-      histp = & histogram[c0][c1][c2min];
-      for (c2 = c2min; c2 <= c2max; c2++) {
-	if ((count = *histp++) != 0) {
-	  total += count;
-	  c0total += ((c0 << C0_SHIFT) + ((1<<C0_SHIFT)>>1)) * count;
-	  c1total += ((c1 << C1_SHIFT) + ((1<<C1_SHIFT)>>1)) * count;
-	  c2total += ((c2 << C2_SHIFT) + ((1<<C2_SHIFT)>>1)) * count;
-	}
-      }
-    }
-  
-  cinfo->colormap[0][icolor] = (JSAMPLE) ((c0total + (total>>1)) / total);
-  cinfo->colormap[1][icolor] = (JSAMPLE) ((c1total + (total>>1)) / total);
-  cinfo->colormap[2][icolor] = (JSAMPLE) ((c2total + (total>>1)) / total);
-}
-
-
-LOCAL(void)
-select_colors (j_decompress_ptr cinfo, int desired_colors)
-/* Master routine for color selection */
-{
-  boxptr boxlist;
-  int numboxes;
-  int i;
-
-  /* Allocate workspace for box list */
-  boxlist = (boxptr) (*cinfo->mem->alloc_small)
-    ((j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF(box));
-  /* Initialize one box containing whole space */
-  numboxes = 1;
-  boxlist[0].c0min = 0;
-  boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT;
-  boxlist[0].c1min = 0;
-  boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT;
-  boxlist[0].c2min = 0;
-  boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT;
-  /* Shrink it to actually-used volume and set its statistics */
-  update_box(cinfo, & boxlist[0]);
-  /* Perform median-cut to produce final box list */
-  numboxes = median_cut(cinfo, boxlist, numboxes, desired_colors);
-  /* Compute the representative color for each box, fill colormap */
-  for (i = 0; i < numboxes; i++)
-    compute_color(cinfo, & boxlist[i], i);
-  cinfo->actual_number_of_colors = numboxes;
-  TRACEMS1(cinfo, 1, JTRC_QUANT_SELECTED, numboxes);
-}
-
-
-/*
- * These routines are concerned with the time-critical task of mapping input
- * colors to the nearest color in the selected colormap.
- *
- * We re-use the histogram space as an "inverse color map", essentially a
- * cache for the results of nearest-color searches.  All colors within a
- * histogram cell will be mapped to the same colormap entry, namely the one
- * closest to the cell's center.  This may not be quite the closest entry to
- * the actual input color, but it's almost as good.  A zero in the cache
- * indicates we haven't found the nearest color for that cell yet; the array
- * is cleared to zeroes before starting the mapping pass.  When we find the
- * nearest color for a cell, its colormap index plus one is recorded in the
- * cache for future use.  The pass2 scanning routines call fill_inverse_cmap
- * when they need to use an unfilled entry in the cache.
- *
- * Our method of efficiently finding nearest colors is based on the "locally
- * sorted search" idea described by Heckbert and on the incremental distance
- * calculation described by Spencer W. Thomas in chapter III.1 of Graphics
- * Gems II (James Arvo, ed.  Academic Press, 1991).  Thomas points out that
- * the distances from a given colormap entry to each cell of the histogram can
- * be computed quickly using an incremental method: the differences between
- * distances to adjacent cells themselves differ by a constant.  This allows a
- * fairly fast implementation of the "brute force" approach of computing the
- * distance from every colormap entry to every histogram cell.  Unfortunately,
- * it needs a work array to hold the best-distance-so-far for each histogram
- * cell (because the inner loop has to be over cells, not colormap entries).
- * The work array elements have to be INT32s, so the work array would need
- * 256Kb at our recommended precision.  This is not feasible in DOS machines.
- *
- * To get around these problems, we apply Thomas' method to compute the
- * nearest colors for only the cells within a small subbox of the histogram.
- * The work array need be only as big as the subbox, so the memory usage
- * problem is solved.  Furthermore, we need not fill subboxes that are never
- * referenced in pass2; many images use only part of the color gamut, so a
- * fair amount of work is saved.  An additional advantage of this
- * approach is that we can apply Heckbert's locality criterion to quickly
- * eliminate colormap entries that are far away from the subbox; typically
- * three-fourths of the colormap entries are rejected by Heckbert's criterion,
- * and we need not compute their distances to individual cells in the subbox.
- * The speed of this approach is heavily influenced by the subbox size: too
- * small means too much overhead, too big loses because Heckbert's criterion
- * can't eliminate as many colormap entries.  Empirically the best subbox
- * size seems to be about 1/512th of the histogram (1/8th in each direction).
- *
- * Thomas' article also describes a refined method which is asymptotically
- * faster than the brute-force method, but it is also far more complex and
- * cannot efficiently be applied to small subboxes.  It is therefore not
- * useful for programs intended to be portable to DOS machines.  On machines
- * with plenty of memory, filling the whole histogram in one shot with Thomas'
- * refined method might be faster than the present code --- but then again,
- * it might not be any faster, and it's certainly more complicated.
- */
-
-
-/* log2(histogram cells in update box) for each axis; this can be adjusted */
-#define BOX_C0_LOG  (HIST_C0_BITS-3)
-#define BOX_C1_LOG  (HIST_C1_BITS-3)
-#define BOX_C2_LOG  (HIST_C2_BITS-3)
-
-#define BOX_C0_ELEMS  (1<<BOX_C0_LOG) /* # of hist cells in update box */
-#define BOX_C1_ELEMS  (1<<BOX_C1_LOG)
-#define BOX_C2_ELEMS  (1<<BOX_C2_LOG)
-
-#define BOX_C0_SHIFT  (C0_SHIFT + BOX_C0_LOG)
-#define BOX_C1_SHIFT  (C1_SHIFT + BOX_C1_LOG)
-#define BOX_C2_SHIFT  (C2_SHIFT + BOX_C2_LOG)
-
-
-/*
- * The next three routines implement inverse colormap filling.  They could
- * all be folded into one big routine, but splitting them up this way saves
- * some stack space (the mindist[] and bestdist[] arrays need not coexist)
- * and may allow some compilers to produce better code by registerizing more
- * inner-loop variables.
- */
-
-LOCAL(int)
-find_nearby_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2,
-		    JSAMPLE colorlist[])
-/* Locate the colormap entries close enough to an update box to be candidates
- * for the nearest entry to some cell(s) in the update box.  The update box
- * is specified by the center coordinates of its first cell.  The number of
- * candidate colormap entries is returned, and their colormap indexes are
- * placed in colorlist[].
- * This routine uses Heckbert's "locally sorted search" criterion to select
- * the colors that need further consideration.
- */
-{
-  int numcolors = cinfo->actual_number_of_colors;
-  int maxc0, maxc1, maxc2;
-  int centerc0, centerc1, centerc2;
-  int i, x, ncolors;
-  INT32 minmaxdist, min_dist, max_dist, tdist;
-  INT32 mindist[MAXNUMCOLORS];	/* min distance to colormap entry i */
-
-  /* Compute true coordinates of update box's upper corner and center.
-   * Actually we compute the coordinates of the center of the upper-corner
-   * histogram cell, which are the upper bounds of the volume we care about.
-   * Note that since ">>" rounds down, the "center" values may be closer to
-   * min than to max; hence comparisons to them must be "<=", not "<".
-   */
-  maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT));
-  centerc0 = (minc0 + maxc0) >> 1;
-  maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT));
-  centerc1 = (minc1 + maxc1) >> 1;
-  maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT));
-  centerc2 = (minc2 + maxc2) >> 1;
-
-  /* For each color in colormap, find:
-   *  1. its minimum squared-distance to any point in the update box
-   *     (zero if color is within update box);
-   *  2. its maximum squared-distance to any point in the update box.
-   * Both of these can be found by considering only the corners of the box.
-   * We save the minimum distance for each color in mindist[];
-   * only the smallest maximum distance is of interest.
-   */
-  minmaxdist = 0x7FFFFFFFL;
-
-  for (i = 0; i < numcolors; i++) {
-    /* We compute the squared-c0-distance term, then add in the other two. */
-    x = GETJSAMPLE(cinfo->colormap[0][i]);
-    if (x < minc0) {
-      tdist = (x - minc0) * C0_SCALE;
-      min_dist = tdist*tdist;
-      tdist = (x - maxc0) * C0_SCALE;
-      max_dist = tdist*tdist;
-    } else if (x > maxc0) {
-      tdist = (x - maxc0) * C0_SCALE;
-      min_dist = tdist*tdist;
-      tdist = (x - minc0) * C0_SCALE;
-      max_dist = tdist*tdist;
-    } else {
-      /* within cell range so no contribution to min_dist */
-      min_dist = 0;
-      if (x <= centerc0) {
-	tdist = (x - maxc0) * C0_SCALE;
-	max_dist = tdist*tdist;
-      } else {
-	tdist = (x - minc0) * C0_SCALE;
-	max_dist = tdist*tdist;
-      }
-    }
-
-    x = GETJSAMPLE(cinfo->colormap[1][i]);
-    if (x < minc1) {
-      tdist = (x - minc1) * C1_SCALE;
-      min_dist += tdist*tdist;
-      tdist = (x - maxc1) * C1_SCALE;
-      max_dist += tdist*tdist;
-    } else if (x > maxc1) {
-      tdist = (x - maxc1) * C1_SCALE;
-      min_dist += tdist*tdist;
-      tdist = (x - minc1) * C1_SCALE;
-      max_dist += tdist*tdist;
-    } else {
-      /* within cell range so no contribution to min_dist */
-      if (x <= centerc1) {
-	tdist = (x - maxc1) * C1_SCALE;
-	max_dist += tdist*tdist;
-      } else {
-	tdist = (x - minc1) * C1_SCALE;
-	max_dist += tdist*tdist;
-      }
-    }
-
-    x = GETJSAMPLE(cinfo->colormap[2][i]);
-    if (x < minc2) {
-      tdist = (x - minc2) * C2_SCALE;
-      min_dist += tdist*tdist;
-      tdist = (x - maxc2) * C2_SCALE;
-      max_dist += tdist*tdist;
-    } else if (x > maxc2) {
-      tdist = (x - maxc2) * C2_SCALE;
-      min_dist += tdist*tdist;
-      tdist = (x - minc2) * C2_SCALE;
-      max_dist += tdist*tdist;
-    } else {
-      /* within cell range so no contribution to min_dist */
-      if (x <= centerc2) {
-	tdist = (x - maxc2) * C2_SCALE;
-	max_dist += tdist*tdist;
-      } else {
-	tdist = (x - minc2) * C2_SCALE;
-	max_dist += tdist*tdist;
-      }
-    }
-
-    mindist[i] = min_dist;	/* save away the results */
-    if (max_dist < minmaxdist)
-      minmaxdist = max_dist;
-  }
-
-  /* Now we know that no cell in the update box is more than minmaxdist
-   * away from some colormap entry.  Therefore, only colors that are
-   * within minmaxdist of some part of the box need be considered.
-   */
-  ncolors = 0;
-  for (i = 0; i < numcolors; i++) {
-    if (mindist[i] <= minmaxdist)
-      colorlist[ncolors++] = (JSAMPLE) i;
-  }
-  return ncolors;
-}
-
-
-LOCAL(void)
-find_best_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2,
-		  int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[])
-/* Find the closest colormap entry for each cell in the update box,
- * given the list of candidate colors prepared by find_nearby_colors.
- * Return the indexes of the closest entries in the bestcolor[] array.
- * This routine uses Thomas' incremental distance calculation method to
- * find the distance from a colormap entry to successive cells in the box.
- */
-{
-  int ic0, ic1, ic2;
-  int i, icolor;
-  register INT32 * bptr;	/* pointer into bestdist[] array */
-  JSAMPLE * cptr;		/* pointer into bestcolor[] array */
-  INT32 dist0, dist1;		/* initial distance values */
-  register INT32 dist2;		/* current distance in inner loop */
-  INT32 xx0, xx1;		/* distance increments */
-  register INT32 xx2;
-  INT32 inc0, inc1, inc2;	/* initial values for increments */
-  /* This array holds the distance to the nearest-so-far color for each cell */
-  INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS];
-
-  /* Initialize best-distance for each cell of the update box */
-  bptr = bestdist;
-  for (i = BOX_C0_ELEMS*BOX_C1_ELEMS*BOX_C2_ELEMS-1; i >= 0; i--)
-    *bptr++ = 0x7FFFFFFFL;
-  
-  /* For each color selected by find_nearby_colors,
-   * compute its distance to the center of each cell in the box.
-   * If that's less than best-so-far, update best distance and color number.
-   */
-  
-  /* Nominal steps between cell centers ("x" in Thomas article) */
-#define STEP_C0  ((1 << C0_SHIFT) * C0_SCALE)
-#define STEP_C1  ((1 << C1_SHIFT) * C1_SCALE)
-#define STEP_C2  ((1 << C2_SHIFT) * C2_SCALE)
-  
-  for (i = 0; i < numcolors; i++) {
-    icolor = GETJSAMPLE(colorlist[i]);
-    /* Compute (square of) distance from minc0/c1/c2 to this color */
-    inc0 = (minc0 - GETJSAMPLE(cinfo->colormap[0][icolor])) * C0_SCALE;
-    dist0 = inc0*inc0;
-    inc1 = (minc1 - GETJSAMPLE(cinfo->colormap[1][icolor])) * C1_SCALE;
-    dist0 += inc1*inc1;
-    inc2 = (minc2 - GETJSAMPLE(cinfo->colormap[2][icolor])) * C2_SCALE;
-    dist0 += inc2*inc2;
-    /* Form the initial difference increments */
-    inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0;
-    inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1;
-    inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2;
-    /* Now loop over all cells in box, updating distance per Thomas method */
-    bptr = bestdist;
-    cptr = bestcolor;
-    xx0 = inc0;
-    for (ic0 = BOX_C0_ELEMS-1; ic0 >= 0; ic0--) {
-      dist1 = dist0;
-      xx1 = inc1;
-      for (ic1 = BOX_C1_ELEMS-1; ic1 >= 0; ic1--) {
-	dist2 = dist1;
-	xx2 = inc2;
-	for (ic2 = BOX_C2_ELEMS-1; ic2 >= 0; ic2--) {
-	  if (dist2 < *bptr) {
-	    *bptr = dist2;
-	    *cptr = (JSAMPLE) icolor;
-	  }
-	  dist2 += xx2;
-	  xx2 += 2 * STEP_C2 * STEP_C2;
-	  bptr++;
-	  cptr++;
-	}
-	dist1 += xx1;
-	xx1 += 2 * STEP_C1 * STEP_C1;
-      }
-      dist0 += xx0;
-      xx0 += 2 * STEP_C0 * STEP_C0;
-    }
-  }
-}
-
-
-LOCAL(void)
-fill_inverse_cmap (j_decompress_ptr cinfo, int c0, int c1, int c2)
-/* Fill the inverse-colormap entries in the update box that contains */
-/* histogram cell c0/c1/c2.  (Only that one cell MUST be filled, but */
-/* we can fill as many others as we wish.) */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  hist3d histogram = cquantize->histogram;
-  int minc0, minc1, minc2;	/* lower left corner of update box */
-  int ic0, ic1, ic2;
-  register JSAMPLE * cptr;	/* pointer into bestcolor[] array */
-  register histptr cachep;	/* pointer into main cache array */
-  /* This array lists the candidate colormap indexes. */
-  JSAMPLE colorlist[MAXNUMCOLORS];
-  int numcolors;		/* number of candidate colors */
-  /* This array holds the actually closest colormap index for each cell. */
-  JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS];
-
-  /* Convert cell coordinates to update box ID */
-  c0 >>= BOX_C0_LOG;
-  c1 >>= BOX_C1_LOG;
-  c2 >>= BOX_C2_LOG;
-
-  /* Compute true coordinates of update box's origin corner.
-   * Actually we compute the coordinates of the center of the corner
-   * histogram cell, which are the lower bounds of the volume we care about.
-   */
-  minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1);
-  minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1);
-  minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1);
-  
-  /* Determine which colormap entries are close enough to be candidates
-   * for the nearest entry to some cell in the update box.
-   */
-  numcolors = find_nearby_colors(cinfo, minc0, minc1, minc2, colorlist);
-
-  /* Determine the actually nearest colors. */
-  find_best_colors(cinfo, minc0, minc1, minc2, numcolors, colorlist,
-		   bestcolor);
-
-  /* Save the best color numbers (plus 1) in the main cache array */
-  c0 <<= BOX_C0_LOG;		/* convert ID back to base cell indexes */
-  c1 <<= BOX_C1_LOG;
-  c2 <<= BOX_C2_LOG;
-  cptr = bestcolor;
-  for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) {
-    for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) {
-      cachep = & histogram[c0+ic0][c1+ic1][c2];
-      for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) {
-	*cachep++ = (histcell) (GETJSAMPLE(*cptr++) + 1);
-      }
-    }
-  }
-}
-
-
-/*
- * Map some rows of pixels to the output colormapped representation.
- */
-
-METHODDEF(void)
-pass2_no_dither (j_decompress_ptr cinfo,
-		 JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows)
-/* This version performs no dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  hist3d histogram = cquantize->histogram;
-  register JSAMPROW inptr, outptr;
-  register histptr cachep;
-  register int c0, c1, c2;
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-
-  for (row = 0; row < num_rows; row++) {
-    inptr = input_buf[row];
-    outptr = output_buf[row];
-    for (col = width; col > 0; col--) {
-      /* get pixel value and index into the cache */
-      c0 = GETJSAMPLE(*inptr++) >> C0_SHIFT;
-      c1 = GETJSAMPLE(*inptr++) >> C1_SHIFT;
-      c2 = GETJSAMPLE(*inptr++) >> C2_SHIFT;
-      cachep = & histogram[c0][c1][c2];
-      /* If we have not seen this color before, find nearest colormap entry */
-      /* and update the cache */
-      if (*cachep == 0)
-	fill_inverse_cmap(cinfo, c0,c1,c2);
-      /* Now emit the colormap index for this cell */
-      *outptr++ = (JSAMPLE) (*cachep - 1);
-    }
-  }
-}
-
-
-METHODDEF(void)
-pass2_fs_dither (j_decompress_ptr cinfo,
-		 JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows)
-/* This version performs Floyd-Steinberg dithering */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  hist3d histogram = cquantize->histogram;
-  register LOCFSERROR cur0, cur1, cur2;	/* current error or pixel value */
-  LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */
-  LOCFSERROR bpreverr0, bpreverr1, bpreverr2; /* error for below/prev col */
-  register FSERRPTR errorptr;	/* => fserrors[] at column before current */
-  JSAMPROW inptr;		/* => current input pixel */
-  JSAMPROW outptr;		/* => current output pixel */
-  histptr cachep;
-  int dir;			/* +1 or -1 depending on direction */
-  int dir3;			/* 3*dir, for advancing inptr & errorptr */
-  int row;
-  JDIMENSION col;
-  JDIMENSION width = cinfo->output_width;
-  JSAMPLE *range_limit = cinfo->sample_range_limit;
-  int *error_limit = cquantize->error_limiter;
-  JSAMPROW colormap0 = cinfo->colormap[0];
-  JSAMPROW colormap1 = cinfo->colormap[1];
-  JSAMPROW colormap2 = cinfo->colormap[2];
-  SHIFT_TEMPS
-
-  for (row = 0; row < num_rows; row++) {
-    inptr = input_buf[row];
-    outptr = output_buf[row];
-    if (cquantize->on_odd_row) {
-      /* work right to left in this row */
-      inptr += (width-1) * 3;	/* so point to rightmost pixel */
-      outptr += width-1;
-      dir = -1;
-      dir3 = -3;
-      errorptr = cquantize->fserrors + (width+1)*3; /* => entry after last column */
-      cquantize->on_odd_row = FALSE; /* flip for next time */
-    } else {
-      /* work left to right in this row */
-      dir = 1;
-      dir3 = 3;
-      errorptr = cquantize->fserrors; /* => entry before first real column */
-      cquantize->on_odd_row = TRUE; /* flip for next time */
-    }
-    /* Preset error values: no error propagated to first pixel from left */
-    cur0 = cur1 = cur2 = 0;
-    /* and no error propagated to row below yet */
-    belowerr0 = belowerr1 = belowerr2 = 0;
-    bpreverr0 = bpreverr1 = bpreverr2 = 0;
-
-    for (col = width; col > 0; col--) {
-      /* curN holds the error propagated from the previous pixel on the
-       * current line.  Add the error propagated from the previous line
-       * to form the complete error correction term for this pixel, and
-       * round the error term (which is expressed * 16) to an integer.
-       * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct
-       * for either sign of the error value.
-       * Note: errorptr points to *previous* column's array entry.
-       */
-      cur0 = RIGHT_SHIFT(cur0 + errorptr[dir3+0] + 8, 4);
-      cur1 = RIGHT_SHIFT(cur1 + errorptr[dir3+1] + 8, 4);
-      cur2 = RIGHT_SHIFT(cur2 + errorptr[dir3+2] + 8, 4);
-      /* Limit the error using transfer function set by init_error_limit.
-       * See comments with init_error_limit for rationale.
-       */
-      cur0 = error_limit[cur0];
-      cur1 = error_limit[cur1];
-      cur2 = error_limit[cur2];
-      /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE.
-       * The maximum error is +- MAXJSAMPLE (or less with error limiting);
-       * this sets the required size of the range_limit array.
-       */
-      cur0 += GETJSAMPLE(inptr[0]);
-      cur1 += GETJSAMPLE(inptr[1]);
-      cur2 += GETJSAMPLE(inptr[2]);
-      cur0 = GETJSAMPLE(range_limit[cur0]);
-      cur1 = GETJSAMPLE(range_limit[cur1]);
-      cur2 = GETJSAMPLE(range_limit[cur2]);
-      /* Index into the cache with adjusted pixel value */
-      cachep = & histogram[cur0>>C0_SHIFT][cur1>>C1_SHIFT][cur2>>C2_SHIFT];
-      /* If we have not seen this color before, find nearest colormap */
-      /* entry and update the cache */
-      if (*cachep == 0)
-	fill_inverse_cmap(cinfo, cur0>>C0_SHIFT,cur1>>C1_SHIFT,cur2>>C2_SHIFT);
-      /* Now emit the colormap index for this cell */
-      { register int pixcode = *cachep - 1;
-	*outptr = (JSAMPLE) pixcode;
-	/* Compute representation error for this pixel */
-	cur0 -= GETJSAMPLE(colormap0[pixcode]);
-	cur1 -= GETJSAMPLE(colormap1[pixcode]);
-	cur2 -= GETJSAMPLE(colormap2[pixcode]);
-      }
-      /* Compute error fractions to be propagated to adjacent pixels.
-       * Add these into the running sums, and simultaneously shift the
-       * next-line error sums left by 1 column.
-       */
-      { register LOCFSERROR bnexterr, delta;
-
-	bnexterr = cur0;	/* Process component 0 */
-	delta = cur0 * 2;
-	cur0 += delta;		/* form error * 3 */
-	errorptr[0] = (FSERROR) (bpreverr0 + cur0);
-	cur0 += delta;		/* form error * 5 */
-	bpreverr0 = belowerr0 + cur0;
-	belowerr0 = bnexterr;
-	cur0 += delta;		/* form error * 7 */
-	bnexterr = cur1;	/* Process component 1 */
-	delta = cur1 * 2;
-	cur1 += delta;		/* form error * 3 */
-	errorptr[1] = (FSERROR) (bpreverr1 + cur1);
-	cur1 += delta;		/* form error * 5 */
-	bpreverr1 = belowerr1 + cur1;
-	belowerr1 = bnexterr;
-	cur1 += delta;		/* form error * 7 */
-	bnexterr = cur2;	/* Process component 2 */
-	delta = cur2 * 2;
-	cur2 += delta;		/* form error * 3 */
-	errorptr[2] = (FSERROR) (bpreverr2 + cur2);
-	cur2 += delta;		/* form error * 5 */
-	bpreverr2 = belowerr2 + cur2;
-	belowerr2 = bnexterr;
-	cur2 += delta;		/* form error * 7 */
-      }
-      /* At this point curN contains the 7/16 error value to be propagated
-       * to the next pixel on the current line, and all the errors for the
-       * next line have been shifted over.  We are therefore ready to move on.
-       */
-      inptr += dir3;		/* Advance pixel pointers to next column */
-      outptr += dir;
-      errorptr += dir3;		/* advance errorptr to current column */
-    }
-    /* Post-loop cleanup: we must unload the final error values into the
-     * final fserrors[] entry.  Note we need not unload belowerrN because
-     * it is for the dummy column before or after the actual array.
-     */
-    errorptr[0] = (FSERROR) bpreverr0; /* unload prev errs into array */
-    errorptr[1] = (FSERROR) bpreverr1;
-    errorptr[2] = (FSERROR) bpreverr2;
-  }
-}
-
-
-/*
- * Initialize the error-limiting transfer function (lookup table).
- * The raw F-S error computation can potentially compute error values of up to
- * +- MAXJSAMPLE.  But we want the maximum correction applied to a pixel to be
- * much less, otherwise obviously wrong pixels will be created.  (Typical
- * effects include weird fringes at color-area boundaries, isolated bright
- * pixels in a dark area, etc.)  The standard advice for avoiding this problem
- * is to ensure that the "corners" of the color cube are allocated as output
- * colors; then repeated errors in the same direction cannot cause cascading
- * error buildup.  However, that only prevents the error from getting
- * completely out of hand; Aaron Giles reports that error limiting improves
- * the results even with corner colors allocated.
- * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty
- * well, but the smoother transfer function used below is even better.  Thanks
- * to Aaron Giles for this idea.
- */
-
-LOCAL(void)
-init_error_limit (j_decompress_ptr cinfo)
-/* Allocate and fill in the error_limiter table */
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  int * table;
-  int in, out;
-
-  table = (int *) (*cinfo->mem->alloc_small)
-    ((j_common_ptr) cinfo, JPOOL_IMAGE, (MAXJSAMPLE*2+1) * SIZEOF(int));
-  table += MAXJSAMPLE;		/* so can index -MAXJSAMPLE .. +MAXJSAMPLE */
-  cquantize->error_limiter = table;
-
-#define STEPSIZE ((MAXJSAMPLE+1)/16)
-  /* Map errors 1:1 up to +- MAXJSAMPLE/16 */
-  out = 0;
-  for (in = 0; in < STEPSIZE; in++, out++) {
-    table[in] = out; table[-in] = -out;
-  }
-  /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */
-  for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1) {
-    table[in] = out; table[-in] = -out;
-  }
-  /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */
-  for (; in <= MAXJSAMPLE; in++) {
-    table[in] = out; table[-in] = -out;
-  }
-#undef STEPSIZE
-}
-
-
-/*
- * Finish up at the end of each pass.
- */
-
-METHODDEF(void)
-finish_pass1 (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-
-  /* Select the representative colors and fill in cinfo->colormap */
-  cinfo->colormap = cquantize->sv_colormap;
-  select_colors(cinfo, cquantize->desired);
-  /* Force next pass to zero the color index table */
-  cquantize->needs_zeroed = TRUE;
-}
-
-
-METHODDEF(void)
-finish_pass2 (j_decompress_ptr cinfo)
-{
-  /* no work */
-}
-
-
-/*
- * Initialize for each processing pass.
- */
-
-METHODDEF(void)
-start_pass_2_quant (j_decompress_ptr cinfo, boolean is_pre_scan)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-  hist3d histogram = cquantize->histogram;
-  int i;
-
-  /* Only F-S dithering or no dithering is supported. */
-  /* If user asks for ordered dither, give him F-S. */
-  if (cinfo->dither_mode != JDITHER_NONE)
-    cinfo->dither_mode = JDITHER_FS;
-
-  if (is_pre_scan) {
-    /* Set up method pointers */
-    cquantize->pub.color_quantize = prescan_quantize;
-    cquantize->pub.finish_pass = finish_pass1;
-    cquantize->needs_zeroed = TRUE; /* Always zero histogram */
-  } else {
-    /* Set up method pointers */
-    if (cinfo->dither_mode == JDITHER_FS)
-      cquantize->pub.color_quantize = pass2_fs_dither;
-    else
-      cquantize->pub.color_quantize = pass2_no_dither;
-    cquantize->pub.finish_pass = finish_pass2;
-
-    /* Make sure color count is acceptable */
-    i = cinfo->actual_number_of_colors;
-    if (i < 1)
-      ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1);
-    if (i > MAXNUMCOLORS)
-      ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS);
-
-    if (cinfo->dither_mode == JDITHER_FS) {
-      size_t arraysize = (size_t) ((cinfo->output_width + 2) *
-				   (3 * SIZEOF(FSERROR)));
-      /* Allocate Floyd-Steinberg workspace if we didn't already. */
-      if (cquantize->fserrors == NULL)
-	cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large)
-	  ((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize);
-      /* Initialize the propagated errors to zero. */
-      jzero_far((void FAR *) cquantize->fserrors, arraysize);
-      /* Make the error-limit table if we didn't already. */
-      if (cquantize->error_limiter == NULL)
-	init_error_limit(cinfo);
-      cquantize->on_odd_row = FALSE;
-    }
-
-  }
-  /* Zero the histogram or inverse color map, if necessary */
-  if (cquantize->needs_zeroed) {
-    for (i = 0; i < HIST_C0_ELEMS; i++) {
-      jzero_far((void FAR *) histogram[i],
-		HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell));
-    }
-    cquantize->needs_zeroed = FALSE;
-  }
-}
-
-
-/*
- * Switch to a new external colormap between output passes.
- */
-
-METHODDEF(void)
-new_color_map_2_quant (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
-
-  /* Reset the inverse color map */
-  cquantize->needs_zeroed = TRUE;
-}
-
-
-/*
- * Module initialization routine for 2-pass color quantization.
- */
-
-GLOBAL(void)
-jinit_2pass_quantizer (j_decompress_ptr cinfo)
-{
-  my_cquantize_ptr cquantize;
-  int i;
-
-  cquantize = (my_cquantize_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-				SIZEOF(my_cquantizer));
-  cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize;
-  cquantize->pub.start_pass = start_pass_2_quant;
-  cquantize->pub.new_color_map = new_color_map_2_quant;
-  cquantize->fserrors = NULL;	/* flag optional arrays not allocated */
-  cquantize->error_limiter = NULL;
-
-  /* Make sure jdmaster didn't give me a case I can't handle */
-  if (cinfo->out_color_components != 3)
-    ERREXIT(cinfo, JERR_NOTIMPL);
-
-  /* Allocate the histogram/inverse colormap storage */
-  cquantize->histogram = (hist3d) (*cinfo->mem->alloc_small)
-    ((j_common_ptr) cinfo, JPOOL_IMAGE, HIST_C0_ELEMS * SIZEOF(hist2d));
-  for (i = 0; i < HIST_C0_ELEMS; i++) {
-    cquantize->histogram[i] = (hist2d) (*cinfo->mem->alloc_large)
-      ((j_common_ptr) cinfo, JPOOL_IMAGE,
-       HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell));
-  }
-  cquantize->needs_zeroed = TRUE; /* histogram is garbage now */
-
-  /* Allocate storage for the completed colormap, if required.
-   * We do this now since it is FAR storage and may affect
-   * the memory manager's space calculations.
-   */
-  if (cinfo->enable_2pass_quant) {
-    /* Make sure color count is acceptable */
-    int desired = cinfo->desired_number_of_colors;
-    /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */
-    if (desired < 8)
-      ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 8);
-    /* Make sure colormap indexes can be represented by JSAMPLEs */
-    if (desired > MAXNUMCOLORS)
-      ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS);
-    cquantize->sv_colormap = (*cinfo->mem->alloc_sarray)
-      ((j_common_ptr) cinfo,JPOOL_IMAGE, (JDIMENSION) desired, (JDIMENSION) 3);
-    cquantize->desired = desired;
-  } else
-    cquantize->sv_colormap = NULL;
-
-  /* Only F-S dithering or no dithering is supported. */
-  /* If user asks for ordered dither, give him F-S. */
-  if (cinfo->dither_mode != JDITHER_NONE)
-    cinfo->dither_mode = JDITHER_FS;
-
-  /* Allocate Floyd-Steinberg workspace if necessary.
-   * This isn't really needed until pass 2, but again it is FAR storage.
-   * Although we will cope with a later change in dither_mode,
-   * we do not promise to honor max_memory_to_use if dither_mode changes.
-   */
-  if (cinfo->dither_mode == JDITHER_FS) {
-    cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large)
-      ((j_common_ptr) cinfo, JPOOL_IMAGE,
-       (size_t) ((cinfo->output_width + 2) * (3 * SIZEOF(FSERROR))));
-    /* Might as well create the error-limiting table too. */
-    init_error_limit(cinfo);
-  }
-}
-
-#endif /* QUANT_2PASS_SUPPORTED */
diff --git a/third_party/libjpeg/jutils.c b/third_party/libjpeg/jutils.c
deleted file mode 100644
index d18a955..0000000
--- a/third_party/libjpeg/jutils.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * jutils.c
- *
- * Copyright (C) 1991-1996, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains tables and miscellaneous utility routines needed
- * for both compression and decompression.
- * Note we prefix all global names with "j" to minimize conflicts with
- * a surrounding application.
- */
-
-#define JPEG_INTERNALS
-#include "jinclude.h"
-#include "jpeglib.h"
-
-
-/*
- * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element
- * of a DCT block read in natural order (left to right, top to bottom).
- */
-
-#if 0				/* This table is not actually needed in v6a */
-
-const int jpeg_zigzag_order[DCTSIZE2] = {
-   0,  1,  5,  6, 14, 15, 27, 28,
-   2,  4,  7, 13, 16, 26, 29, 42,
-   3,  8, 12, 17, 25, 30, 41, 43,
-   9, 11, 18, 24, 31, 40, 44, 53,
-  10, 19, 23, 32, 39, 45, 52, 54,
-  20, 22, 33, 38, 46, 51, 55, 60,
-  21, 34, 37, 47, 50, 56, 59, 61,
-  35, 36, 48, 49, 57, 58, 62, 63
-};
-
-#endif
-
-/*
- * jpeg_natural_order[i] is the natural-order position of the i'th element
- * of zigzag order.
- *
- * When reading corrupted data, the Huffman decoders could attempt
- * to reference an entry beyond the end of this array (if the decoded
- * zero run length reaches past the end of the block).  To prevent
- * wild stores without adding an inner-loop test, we put some extra
- * "63"s after the real entries.  This will cause the extra coefficient
- * to be stored in location 63 of the block, not somewhere random.
- * The worst case would be a run-length of 15, which means we need 16
- * fake entries.
- */
-
-const int jpeg_natural_order[DCTSIZE2+16] = {
-  0,  1,  8, 16,  9,  2,  3, 10,
- 17, 24, 32, 25, 18, 11,  4,  5,
- 12, 19, 26, 33, 40, 48, 41, 34,
- 27, 20, 13,  6,  7, 14, 21, 28,
- 35, 42, 49, 56, 57, 50, 43, 36,
- 29, 22, 15, 23, 30, 37, 44, 51,
- 58, 59, 52, 45, 38, 31, 39, 46,
- 53, 60, 61, 54, 47, 55, 62, 63,
- 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */
- 63, 63, 63, 63, 63, 63, 63, 63
-};
-
-
-/*
- * Arithmetic utilities
- */
-
-GLOBAL(long)
-jdiv_round_up (long a, long b)
-/* Compute a/b rounded up to next integer, ie, ceil(a/b) */
-/* Assumes a >= 0, b > 0 */
-{
-  return (a + b - 1L) / b;
-}
-
-
-GLOBAL(long)
-jround_up (long a, long b)
-/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */
-/* Assumes a >= 0, b > 0 */
-{
-  a += b - 1L;
-  return a - (a % b);
-}
-
-
-/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays
- * and coefficient-block arrays.  This won't work on 80x86 because the arrays
- * are FAR and we're assuming a small-pointer memory model.  However, some
- * DOS compilers provide far-pointer versions of memcpy() and memset() even
- * in the small-model libraries.  These will be used if USE_FMEM is defined.
- * Otherwise, the routines below do it the hard way.  (The performance cost
- * is not all that great, because these routines aren't very heavily used.)
- */
-
-#ifndef NEED_FAR_POINTERS	/* normal case, same as regular macros */
-#define FMEMCOPY(dest,src,size)	MEMCOPY(dest,src,size)
-#define FMEMZERO(target,size)	MEMZERO(target,size)
-#else				/* 80x86 case, define if we can */
-#ifdef USE_FMEM
-#define FMEMCOPY(dest,src,size)	_fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size))
-#define FMEMZERO(target,size)	_fmemset((void FAR *)(target), 0, (size_t)(size))
-#endif
-#endif
-
-
-GLOBAL(void)
-jcopy_sample_rows (JSAMPARRAY input_array, int source_row,
-		   JSAMPARRAY output_array, int dest_row,
-		   int num_rows, JDIMENSION num_cols)
-/* Copy some rows of samples from one place to another.
- * num_rows rows are copied from input_array[source_row++]
- * to output_array[dest_row++]; these areas may overlap for duplication.
- * The source and destination arrays must be at least as wide as num_cols.
- */
-{
-  register JSAMPROW inptr, outptr;
-#ifdef FMEMCOPY
-  register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE));
-#else
-  register JDIMENSION count;
-#endif
-  register int row;
-
-  input_array += source_row;
-  output_array += dest_row;
-
-  for (row = num_rows; row > 0; row--) {
-    inptr = *input_array++;
-    outptr = *output_array++;
-#ifdef FMEMCOPY
-    FMEMCOPY(outptr, inptr, count);
-#else
-    for (count = num_cols; count > 0; count--)
-      *outptr++ = *inptr++;	/* needn't bother with GETJSAMPLE() here */
-#endif
-  }
-}
-
-
-GLOBAL(void)
-jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row,
-		 JDIMENSION num_blocks)
-/* Copy a row of coefficient blocks from one place to another. */
-{
-#ifdef FMEMCOPY
-  FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF)));
-#else
-  register JCOEFPTR inptr, outptr;
-  register long count;
-
-  inptr = (JCOEFPTR) input_row;
-  outptr = (JCOEFPTR) output_row;
-  for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) {
-    *outptr++ = *inptr++;
-  }
-#endif
-}
-
-
-GLOBAL(void)
-jzero_far (void FAR * target, size_t bytestozero)
-/* Zero out a chunk of FAR memory. */
-/* This might be sample-array data, block-array data, or alloc_large data. */
-{
-#ifdef FMEMZERO
-  FMEMZERO(target, bytestozero);
-#else
-  register char FAR * ptr = (char FAR *) target;
-  register size_t count;
-
-  for (count = bytestozero; count > 0; count--) {
-    *ptr++ = 0;
-  }
-#endif
-}
diff --git a/third_party/libjpeg/jversion.h b/third_party/libjpeg/jversion.h
deleted file mode 100644
index 6472c58..0000000
--- a/third_party/libjpeg/jversion.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * jversion.h
- *
- * Copyright (C) 1991-1998, Thomas G. Lane.
- * This file is part of the Independent JPEG Group's software.
- * For conditions of distribution and use, see the accompanying README file.
- *
- * This file contains software version identification.
- */
-
-
-#define JVERSION	"6b  27-Mar-1998"
-
-#define JCOPYRIGHT	"Copyright (C) 1998, Thomas G. Lane"
diff --git a/third_party/openh264/METADATA b/third_party/openh264/METADATA
new file mode 100644
index 0000000..50a92fb
--- /dev/null
+++ b/third_party/openh264/METADATA
@@ -0,0 +1,18 @@
+# Format: google3/devtools/metadata/metadata.proto (go/google3metadata)
+
+name: "openh264"
+description:
+  "H.264/MPEG-4 (AVC) baseline profile video codec in C/C++ open-sourced by "
+  "Cisco."
+
+third_party {
+  url {
+    type: GIT
+    value: "https://github.com/cisco/openh264.git"
+  }
+  version: "v2.3.0"
+  last_upgrade_date { year: 2022 month: 7 day: 31 }
+  security {
+    tag: "NVD-CPE2.3:cpe:/a:cisco:openh264:2.3.0"
+  }
+}
diff --git a/third_party/openh264/README.md b/third_party/openh264/README.md
new file mode 100644
index 0000000..f973e25
--- /dev/null
+++ b/third_party/openh264/README.md
@@ -0,0 +1,7 @@
+# OpenH264 Version Compatibility
+The header files in this folder is for Release Version 2.3.0.
+It is also compatible to all openh264 library after Release Version 2.0.0.
+
+## Libraries
+All release versions can be accessed via the link below.
+https://github.com/cisco/openh264/releases
diff --git a/third_party/openh264/include/codec_api.h b/third_party/openh264/include/codec_api.h
new file mode 100644
index 0000000..a1326c8
--- /dev/null
+++ b/third_party/openh264/include/codec_api.h
@@ -0,0 +1,592 @@
+/*!
+ *@page License
+ *
+ * \copy
+ *     Copyright (c)  2013, Cisco Systems
+ *     All rights reserved.
+ *
+ *     Redistribution and use in source and binary forms, with or without
+ *     modification, are permitted provided that the following conditions
+ *     are met:
+ *
+ *        * Redistributions of source code must retain the above copyright
+ *          notice, this list of conditions and the following disclaimer.
+ *
+ *        * Redistributions in binary form must reproduce the above copyright
+ *          notice, this list of conditions and the following disclaimer in
+ *          the documentation and/or other materials provided with the
+ *          distribution.
+ *
+ *     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *     "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ *     COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ *     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ *     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ *     ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *     POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef WELS_VIDEO_CODEC_SVC_API_H__
+#define WELS_VIDEO_CODEC_SVC_API_H__
+
+#ifndef __cplusplus
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+typedef unsigned char bool;
+#else
+#include <stdbool.h>
+#endif
+#endif
+
+#include "codec_app_def.h"
+#include "codec_def.h"
+
+#if defined(_WIN32) || defined(__cdecl)
+#define EXTAPI __cdecl
+#else
+#define EXTAPI
+#endif
+
+/**
+  * @file codec_api.h
+*/
+
+/**
+  * @page Overview
+  *   * This page is for openh264 codec API usage.
+  *   * For how to use the encoder,please refer to page UsageExampleForEncoder
+  *   * For how to use the decoder,please refer to page UsageExampleForDecoder
+  *   * For more detail about ISVEncoder,please refer to page ISVCEncoder
+  *   * For more detail about ISVDecoder,please refer to page ISVCDecoder
+*/
+
+/**
+  * @page DecoderUsageExample
+  *
+  * @brief
+  *   * An example for using the decoder for Decoding only or Parsing only
+  *
+  * Step 1:decoder declaration
+  * @code
+  *
+  *  //decoder declaration
+  *  ISVCDecoder *pSvcDecoder;
+  *  //input: encoded bitstream start position; should include start code prefix
+  *  unsigned char *pBuf =...;
+  *  //input: encoded bit stream length; should include the size of start code prefix
+  *  int iSize =...;
+  *  //output: [0~2] for Y,U,V buffer for Decoding only
+  *  unsigned char *pData[3] =...;
+  *  //in-out: for Decoding only: declare and initialize the output buffer info, this should never co-exist with Parsing only
+  *  SBufferInfo sDstBufInfo;
+  *  memset(&sDstBufInfo, 0, sizeof(SBufferInfo));
+  *  //in-out: for Parsing only: declare and initialize the output bitstream buffer info for parse only, this should never co-exist with Decoding only
+  *  SParserBsInfo sDstParseInfo;
+  *  memset(&sDstParseInfo, 0, sizeof(SParserBsInfo));
+  *  sDstParseInfo.pDstBuff = new unsigned char[PARSE_SIZE]; //In Parsing only, allocate enough buffer to save transcoded bitstream for a frame
+  *
+  * @endcode
+  *
+  * Step 2:decoder creation
+  * @code
+  *  WelsCreateDecoder(&pSvcDecoder);
+  * @endcode
+  *
+  * Step 3:declare required parameter, used to differentiate Decoding only and Parsing only
+  * @code
+  *  SDecodingParam sDecParam = {0};
+  *  sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC;
+  *  //for Parsing only, the assignment is mandatory
+  *  sDecParam.bParseOnly = true;
+  * @endcode
+  *
+  * Step 4:initialize the parameter and decoder context, allocate memory
+  * @code
+  *  pSvcDecoder->Initialize(&sDecParam);
+  * @endcode
+  *
+  * Step 5:do actual decoding process in slice level;
+  *        this can be done in a loop until data ends
+  * @code
+  *  //for Decoding only
+  *  iRet = pSvcDecoder->DecodeFrameNoDelay(pBuf, iSize, pData, &sDstBufInfo);
+  *  //or
+  *  iRet = pSvcDecoder->DecodeFrame2(pBuf, iSize, pData, &sDstBufInfo);
+  *  //for Parsing only
+  *  iRet = pSvcDecoder->DecodeParser(pBuf, iSize, &sDstParseInfo);
+  *  //decode failed
+  *  If (iRet != 0){
+  *      //error handling (RequestIDR or something like that)
+  *  }
+  *  //for Decoding only, pData can be used for render.
+  *  if (sDstBufInfo.iBufferStatus==1){
+  *      //output handling (pData[0], pData[1], pData[2])
+  *  }
+  * //for Parsing only, sDstParseInfo can be used for, e.g., HW decoding
+  *  if (sDstBufInfo.iNalNum > 0){
+  *      //Hardware decoding sDstParseInfo;
+  *  }
+  *  //no-delay decoding can be realized by directly calling DecodeFrameNoDelay(), which is the recommended usage.
+  *  //no-delay decoding can also be realized by directly calling DecodeFrame2() again with NULL input, as in the following. In this case, decoder would immediately reconstruct the input data. This can also be used similarly for Parsing only. Consequent decoding error and output indication should also be considered as above.
+  *  iRet = pSvcDecoder->DecodeFrame2(NULL, 0, pData, &sDstBufInfo);
+  *  //judge iRet, sDstBufInfo.iBufferStatus ...
+  * @endcode
+  *
+  * Step 6:uninitialize the decoder and memory free
+  * @code
+  *  pSvcDecoder->Uninitialize();
+  * @endcode
+  *
+  * Step 7:destroy the decoder
+  * @code
+  *  DestroyDecoder(pSvcDecoder);
+  * @endcode
+  *
+*/
+
+/**
+  * @page EncoderUsageExample1
+  *
+  * @brief
+  *  * An example for using encoder with basic parameter
+  *
+  * Step1:setup encoder
+  * @code
+  *  ISVCEncoder*  encoder_;
+  *  int rv = WelsCreateSVCEncoder (&encoder_);
+  *  assert (rv == 0);
+  *  assert (encoder_ != NULL);
+  * @endcode
+  *
+  * Step2:initilize with basic parameter
+  * @code
+  *  SEncParamBase param;
+  *  memset (&param, 0, sizeof (SEncParamBase));
+  *  param.iUsageType = usageType; //from EUsageType enum
+  *  param.fMaxFrameRate = frameRate;
+  *  param.iPicWidth = width;
+  *  param.iPicHeight = height;
+  *  param.iTargetBitrate = 5000000;
+  *  encoder_->Initialize (&param);
+  * @endcode
+  *
+  * Step3:set option, set option during encoding process
+  * @code
+  *  encoder_->SetOption (ENCODER_OPTION_TRACE_LEVEL, &g_LevelSetting);
+  *  int videoFormat = videoFormatI420;
+  *  encoder_->SetOption (ENCODER_OPTION_DATAFORMAT, &videoFormat);
+  * @endcode
+  *
+  * Step4: encode and  store ouput bistream
+  * @code
+  *  int frameSize = width * height * 3 / 2;
+  *  BufferedData buf;
+  *  buf.SetLength (frameSize);
+  *  assert (buf.Length() == (size_t)frameSize);
+  *  SFrameBSInfo info;
+  *  memset (&info, 0, sizeof (SFrameBSInfo));
+  *  SSourcePicture pic;
+  *  memset (&pic, 0, sizeof (SsourcePicture));
+  *  pic.iPicWidth = width;
+  *  pic.iPicHeight = height;
+  *  pic.iColorFormat = videoFormatI420;
+  *  pic.iStride[0] = pic.iPicWidth;
+  *  pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1;
+  *  pic.pData[0] = buf.data();
+  *  pic.pData[1] = pic.pData[0] + width * height;
+  *  pic.pData[2] = pic.pData[1] + (width * height >> 2);
+  *  for(int num = 0;num<total_num;num++) {
+  *     //prepare input data
+  *     rv = encoder_->EncodeFrame (&pic, &info);
+  *     assert (rv == cmResultSuccess);
+  *     if (info.eFrameType != videoFrameTypeSkip) {
+  *      //output bitstream handling
+  *     }
+  *  }
+  * @endcode
+  *
+  * Step5:teardown encoder
+  * @code
+  *  if (encoder_) {
+  *      encoder_->Uninitialize();
+  *      WelsDestroySVCEncoder (encoder_);
+  *  }
+  * @endcode
+  *
+  */
+
+/**
+  * @page EncoderUsageExample2
+  *
+  * @brief
+  *     * An example for using the encoder with extension parameter.
+  *     * The same operation on Step 1,3,4,5 with Example-1
+  *
+  * Step 2:initialize with extension parameter
+  * @code
+  *  SEncParamExt param;
+  *  encoder_->GetDefaultParams (&param);
+  *  param.iUsageType = usageType;
+  *  param.fMaxFrameRate = frameRate;
+  *  param.iPicWidth = width;
+  *  param.iPicHeight = height;
+  *  param.iTargetBitrate = 5000000;
+  *  param.bEnableDenoise = denoise;
+  *  param.iSpatialLayerNum = layers;
+  *  //SM_DYN_SLICE don't support multi-thread now
+  *  if (sliceMode != SM_SINGLE_SLICE && sliceMode != SM_DYN_SLICE)
+  *      param.iMultipleThreadIdc = 2;
+  *
+  *  for (int i = 0; i < param.iSpatialLayerNum; i++) {
+  *      param.sSpatialLayers[i].iVideoWidth = width >> (param.iSpatialLayerNum - 1 - i);
+  *      param.sSpatialLayers[i].iVideoHeight = height >> (param.iSpatialLayerNum - 1 - i);
+  *      param.sSpatialLayers[i].fFrameRate = frameRate;
+  *      param.sSpatialLayers[i].iSpatialBitrate = param.iTargetBitrate;
+  *
+  *      param.sSpatialLayers[i].sSliceCfg.uiSliceMode = sliceMode;
+  *      if (sliceMode == SM_DYN_SLICE) {
+  *          param.sSpatialLayers[i].sSliceCfg.sSliceArgument.uiSliceSizeConstraint = 600;
+  *          param.uiMaxNalSize = 1500;
+  *      }
+  *  }
+  *  param.iTargetBitrate *= param.iSpatialLayerNum;
+  *  encoder_->InitializeExt (&param);
+  *  int videoFormat = videoFormatI420;
+  *  encoder_->SetOption (ENCODER_OPTION_DATAFORMAT, &videoFormat);
+  *
+  * @endcode
+  */
+
+
+
+
+#ifdef __cplusplus
+/**
+* @brief Endocder definition
+*/
+class ISVCEncoder {
+ public:
+  /**
+  * @brief  Initialize the encoder
+  * @param  pParam  basic encoder parameter
+  * @return CM_RETURN: 0 - success; otherwise - failed;
+  */
+  virtual int EXTAPI Initialize (const SEncParamBase* pParam) = 0;
+
+  /**
+  * @brief  Initilaize encoder by using extension parameters.
+  * @param  pParam  extension parameter for encoder
+  * @return CM_RETURN: 0 - success; otherwise - failed;
+  */
+  virtual int EXTAPI InitializeExt (const SEncParamExt* pParam) = 0;
+
+  /**
+  * @brief   Get the default extension parameters.
+  *          If you want to change some parameters of encoder, firstly you need to get the default encoding parameters,
+  *          after that you can change part of parameters you want to.
+  * @param   pParam  extension parameter for encoder
+  * @return  CM_RETURN: 0 - success; otherwise - failed;
+  * */
+  virtual int EXTAPI GetDefaultParams (SEncParamExt* pParam) = 0;
+  /// uninitialize the encoder
+  virtual int EXTAPI Uninitialize() = 0;
+
+  /**
+  * @brief Encode one frame
+  * @param kpSrcPic the pointer to the source luminance plane
+  *        chrominance data:
+  *        CbData = kpSrc  +  m_iMaxPicWidth * m_iMaxPicHeight;
+  *        CrData = CbData + (m_iMaxPicWidth * m_iMaxPicHeight)/4;
+  *        the application calling this interface needs to ensure the data validation between the location
+  * @param pBsInfo output bit stream
+  * @return  0 - success; otherwise -failed;
+  */
+  virtual int EXTAPI EncodeFrame (const SSourcePicture* kpSrcPic, SFrameBSInfo* pBsInfo) = 0;
+
+  /**
+  * @brief  Encode the parameters from output bit stream
+  * @param  pBsInfo output bit stream
+  * @return 0 - success; otherwise - failed;
+  */
+  virtual int EXTAPI EncodeParameterSets (SFrameBSInfo* pBsInfo) = 0;
+
+  /**
+  * @brief  Force encoder to encoder frame as IDR if bIDR set as true
+  * @param  bIDR true: force encoder to encode frame as IDR frame;false, return 1 and nothing to do
+  * @return 0 - success; otherwise - failed;
+  */
+  virtual int EXTAPI ForceIntraFrame (bool bIDR, int iLayerId = -1) = 0;
+
+  /**
+  * @brief   Set option for encoder, detail option type, please refer to enumurate ENCODER_OPTION.
+  * @param   pOption option for encoder such as InDataFormat, IDRInterval, SVC Encode Param, Frame Rate, Bitrate,...
+  * @return  CM_RETURN: 0 - success; otherwise - failed;
+  */
+  virtual int EXTAPI SetOption (ENCODER_OPTION eOptionId, void* pOption) = 0;
+
+  /**
+  * @brief   Get option for encoder, detail option type, please refer to enumurate ENCODER_OPTION.
+  * @param   pOption option for encoder such as InDataFormat, IDRInterval, SVC Encode Param, Frame Rate, Bitrate,...
+  * @return  CM_RETURN: 0 - success; otherwise - failed;
+  */
+  virtual int EXTAPI GetOption (ENCODER_OPTION eOptionId, void* pOption) = 0;
+  virtual ~ISVCEncoder() {}
+};
+
+
+
+/**
+* @brief Decoder definition
+*/
+class ISVCDecoder {
+ public:
+
+  /**
+  * @brief  Initilaize decoder
+  * @param  pParam  parameter for decoder
+  * @return 0 - success; otherwise - failed;
+  */
+  virtual long EXTAPI Initialize (const SDecodingParam* pParam) = 0;
+
+  /// Uninitialize the decoder
+  virtual long EXTAPI Uninitialize() = 0;
+
+  /**
+  * @brief   Decode one frame
+  * @param   pSrc the h264 stream to be decoded
+  * @param   iSrcLen the length of h264 stream
+  * @param   ppDst buffer pointer of decoded data (YUV)
+  * @param   pStride output stride
+  * @param   iWidth output width
+  * @param   iHeight output height
+  * @return  0 - success; otherwise -failed;
+  */
+  virtual DECODING_STATE EXTAPI DecodeFrame (const unsigned char* pSrc,
+      const int iSrcLen,
+      unsigned char** ppDst,
+      int* pStride,
+      int& iWidth,
+      int& iHeight) = 0;
+
+  /**
+    * @brief    For slice level DecodeFrameNoDelay() (4 parameters input),
+    *           whatever the function return value is, the output data
+    *           of I420 format will only be available when pDstInfo->iBufferStatus == 1,.
+    *           This function will parse and reconstruct the input frame immediately if it is complete
+    *           It is recommended as the main decoding function for H.264/AVC format input
+    * @param   pSrc the h264 stream to be decoded
+    * @param   iSrcLen the length of h264 stream
+    * @param   ppDst buffer pointer of decoded data (YUV)
+    * @param   pDstInfo information provided to API(width, height, etc.)
+    * @return  0 - success; otherwise -failed;
+    */
+  virtual DECODING_STATE EXTAPI DecodeFrameNoDelay (const unsigned char* pSrc,
+      const int iSrcLen,
+      unsigned char** ppDst,
+      SBufferInfo* pDstInfo) = 0;
+
+  /**
+  * @brief    For slice level DecodeFrame2() (4 parameters input),
+  *           whatever the function return value is, the output data
+  *           of I420 format will only be available when pDstInfo->iBufferStatus == 1,.
+  *           (e.g., in multi-slice cases, only when the whole picture
+  *           is completely reconstructed, this variable would be set equal to 1.)
+  * @param   pSrc the h264 stream to be decoded
+  * @param   iSrcLen the length of h264 stream
+  * @param   ppDst buffer pointer of decoded data (YUV)
+  * @param   pDstInfo information provided to API(width, height, etc.)
+  * @return  0 - success; otherwise -failed;
+  */
+  virtual DECODING_STATE EXTAPI DecodeFrame2 (const unsigned char* pSrc,
+      const int iSrcLen,
+      unsigned char** ppDst,
+      SBufferInfo* pDstInfo) = 0;
+
+
+  /**
+  * @brief   This function gets a decoded ready frame remaining in buffers after the last frame has been decoded.
+  * Use GetOption with option DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER to get the number of frames remaining in buffers.
+  * Note that it is only applicable for profile_idc != 66
+  * @param   ppDst buffer pointer of decoded data (YUV)
+  * @param   pDstInfo information provided to API(width, height, etc.)
+  * @return  0 - success; otherwise -failed;
+  */
+  virtual DECODING_STATE EXTAPI FlushFrame (unsigned char** ppDst,
+      SBufferInfo* pDstInfo) = 0;
+
+  /**
+  * @brief   This function parse input bitstream only, and rewrite possible SVC syntax to AVC syntax
+  * @param   pSrc the h264 stream to be decoded
+  * @param   iSrcLen the length of h264 stream
+  * @param   pDstInfo bit stream info
+  * @return  0 - success; otherwise -failed;
+  */
+  virtual DECODING_STATE EXTAPI DecodeParser (const unsigned char* pSrc,
+      const int iSrcLen,
+      SParserBsInfo* pDstInfo) = 0;
+
+  /**
+  * @brief   This API does not work for now!! This is for future use to support non-I420 color format output.
+  * @param   pSrc the h264 stream to be decoded
+  * @param   iSrcLen the length of h264 stream
+  * @param   pDst buffer pointer of decoded data (YUV)
+  * @param   iDstStride output stride
+  * @param   iDstLen bit stream info
+  * @param   iWidth output width
+  * @param   iHeight output height
+  * @param   iColorFormat output color format
+  * @return  to do ...
+  */
+  virtual DECODING_STATE EXTAPI DecodeFrameEx (const unsigned char* pSrc,
+      const int iSrcLen,
+      unsigned char* pDst,
+      int iDstStride,
+      int& iDstLen,
+      int& iWidth,
+      int& iHeight,
+      int& iColorFormat) = 0;
+
+  /**
+  * @brief   Set option for decoder, detail option type, please refer to enumurate DECODER_OPTION.
+  * @param   pOption  option for decoder such as OutDataFormat, Eos Flag, EC method, ...
+  * @return  CM_RETURN: 0 - success; otherwise - failed;
+  */
+  virtual long EXTAPI SetOption (DECODER_OPTION eOptionId, void* pOption) = 0;
+
+  /**
+  * @brief   Get option for decoder, detail option type, please refer to enumurate DECODER_OPTION.
+  * @param   pOption  option for decoder such as OutDataFormat, Eos Flag, EC method, ...
+  * @return  CM_RETURN: 0 - success; otherwise - failed;
+  */
+  virtual long EXTAPI GetOption (DECODER_OPTION eOptionId, void* pOption) = 0;
+  virtual ~ISVCDecoder() {}
+};
+
+
+extern "C"
+{
+#else
+
+typedef struct ISVCEncoderVtbl ISVCEncoderVtbl;
+typedef const ISVCEncoderVtbl* ISVCEncoder;
+struct ISVCEncoderVtbl {
+
+int (*Initialize) (ISVCEncoder*, const SEncParamBase* pParam);
+int (*InitializeExt) (ISVCEncoder*, const SEncParamExt* pParam);
+
+int (*GetDefaultParams) (ISVCEncoder*, SEncParamExt* pParam);
+
+int (*Uninitialize) (ISVCEncoder*);
+
+int (*EncodeFrame) (ISVCEncoder*, const SSourcePicture* kpSrcPic, SFrameBSInfo* pBsInfo);
+int (*EncodeParameterSets) (ISVCEncoder*, SFrameBSInfo* pBsInfo);
+
+int (*ForceIntraFrame) (ISVCEncoder*, bool bIDR);
+
+int (*SetOption) (ISVCEncoder*, ENCODER_OPTION eOptionId, void* pOption);
+int (*GetOption) (ISVCEncoder*, ENCODER_OPTION eOptionId, void* pOption);
+};
+
+typedef struct ISVCDecoderVtbl ISVCDecoderVtbl;
+typedef const ISVCDecoderVtbl* ISVCDecoder;
+struct ISVCDecoderVtbl {
+long (*Initialize) (ISVCDecoder*, const SDecodingParam* pParam);
+long (*Uninitialize) (ISVCDecoder*);
+
+DECODING_STATE (*DecodeFrame) (ISVCDecoder*, const unsigned char* pSrc,
+                               const int iSrcLen,
+                               unsigned char** ppDst,
+                               int* pStride,
+                               int* iWidth,
+                               int* iHeight);
+
+DECODING_STATE (*DecodeFrameNoDelay) (ISVCDecoder*, const unsigned char* pSrc,
+                                      const int iSrcLen,
+                                      unsigned char** ppDst,
+                                      SBufferInfo* pDstInfo);
+
+DECODING_STATE (*DecodeFrame2) (ISVCDecoder*, const unsigned char* pSrc,
+                                const int iSrcLen,
+                                unsigned char** ppDst,
+                                SBufferInfo* pDstInfo);
+
+DECODING_STATE (*FlushFrame) (ISVCDecoder*, unsigned char** ppDst,
+                              SBufferInfo* pDstInfo);
+
+DECODING_STATE (*DecodeParser) (ISVCDecoder*, const unsigned char* pSrc,
+                                const int iSrcLen,
+                                SParserBsInfo* pDstInfo);
+
+DECODING_STATE (*DecodeFrameEx) (ISVCDecoder*, const unsigned char* pSrc,
+                                 const int iSrcLen,
+                                 unsigned char* pDst,
+                                 int iDstStride,
+                                 int* iDstLen,
+                                 int* iWidth,
+                                 int* iHeight,
+                                 int* iColorFormat);
+
+long (*SetOption) (ISVCDecoder*, DECODER_OPTION eOptionId, void* pOption);
+long (*GetOption) (ISVCDecoder*, DECODER_OPTION eOptionId, void* pOption);
+};
+#endif
+
+typedef void (*WelsTraceCallback) (void* ctx, int level, const char* string);
+
+/** @brief   Create encoder
+ *  @param   ppEncoder encoder
+ *  @return  0 - success; otherwise - failed;
+*/
+int  WelsCreateSVCEncoder (ISVCEncoder** ppEncoder);
+
+
+/** @brief   Destroy encoder
+*   @param   pEncoder encoder
+ *  @return  void
+*/
+void WelsDestroySVCEncoder (ISVCEncoder* pEncoder);
+
+
+/** @brief   Get the capability of decoder
+ *  @param   pDecCapability  decoder capability
+ *  @return  0 - success; otherwise - failed;
+*/
+int WelsGetDecoderCapability (SDecoderCapability* pDecCapability);
+
+
+/** @brief   Create decoder
+ *  @param   ppDecoder decoder
+ *  @return  0 - success; otherwise - failed;
+*/
+long WelsCreateDecoder (ISVCDecoder** ppDecoder);
+
+
+/** @brief   Destroy decoder
+ *  @param   pDecoder  decoder
+ *  @return  void
+*/
+void WelsDestroyDecoder (ISVCDecoder* pDecoder);
+
+/** @brief   Get codec version
+ *           Note, old versions of Mingw (GCC < 4.7) are buggy and use an
+ *           incorrect/different ABI for calling this function, making it
+ *           incompatible with MSVC builds.
+ *  @return  The linked codec version
+*/
+OpenH264Version WelsGetCodecVersion (void);
+
+/** @brief   Get codec version
+ *  @param   pVersion  struct to fill in with the version
+*/
+void WelsGetCodecVersionEx (OpenH264Version* pVersion);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif//WELS_VIDEO_CODEC_SVC_API_H__
diff --git a/third_party/openh264/include/codec_app_def.h b/third_party/openh264/include/codec_app_def.h
new file mode 100644
index 0000000..9b13c33
--- /dev/null
+++ b/third_party/openh264/include/codec_app_def.h
@@ -0,0 +1,812 @@
+/*!
+ * \copy
+ *     Copyright (c)  2013, Cisco Systems
+ *     All rights reserved.
+ *
+ *     Redistribution and use in source and binary forms, with or without
+ *     modification, are permitted provided that the following conditions
+ *     are met:
+ *
+ *        * Redistributions of source code must retain the above copyright
+ *          notice, this list of conditions and the following disclaimer.
+ *
+ *        * Redistributions in binary form must reproduce the above copyright
+ *          notice, this list of conditions and the following disclaimer in
+ *          the documentation and/or other materials provided with the
+ *          distribution.
+ *
+ *     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *     "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ *     COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ *     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ *     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ *     ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *     POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+
+#ifndef WELS_VIDEO_CODEC_APPLICATION_DEFINITION_H__
+#define WELS_VIDEO_CODEC_APPLICATION_DEFINITION_H__
+/**
+  * @file  codec_app_def.h
+  * @brief Data and /or structures introduced in Cisco OpenH264 application
+*/
+
+#include "codec_def.h"
+/* Constants */
+#define MAX_TEMPORAL_LAYER_NUM          4
+#define MAX_SPATIAL_LAYER_NUM           4
+#define MAX_QUALITY_LAYER_NUM           4
+
+#define MAX_LAYER_NUM_OF_FRAME          128
+#define MAX_NAL_UNITS_IN_LAYER          128     ///< predetermined here, adjust it later if need
+
+#define MAX_RTP_PAYLOAD_LEN             1000
+#define AVERAGE_RTP_PAYLOAD_LEN         800
+
+
+#define SAVED_NALUNIT_NUM_TMP           ( (MAX_SPATIAL_LAYER_NUM*MAX_QUALITY_LAYER_NUM) + 1 + MAX_SPATIAL_LAYER_NUM )  ///< SPS/PPS + SEI/SSEI + PADDING_NAL
+#define MAX_SLICES_NUM_TMP              ( ( MAX_NAL_UNITS_IN_LAYER - SAVED_NALUNIT_NUM_TMP ) / 3 )
+
+
+#define AUTO_REF_PIC_COUNT  -1          ///< encoder selects the number of reference frame automatically
+#define UNSPECIFIED_BIT_RATE 0          ///< to do: add detail comment
+
+/**
+ * @brief Struct of OpenH264 version
+ */
+///
+/// E.g. SDK version is 1.2.0.0, major version number is 1, minor version number is 2, and revision number is 0.
+typedef struct  _tagVersion {
+  unsigned int uMajor;                  ///< The major version number
+  unsigned int uMinor;                  ///< The minor version number
+  unsigned int uRevision;               ///< The revision number
+  unsigned int uReserved;               ///< The reserved number, it should be 0.
+} OpenH264Version;
+
+/**
+* @brief Decoding status
+*/
+typedef enum {
+  /**
+  * Errors derived from bitstream parsing
+  */
+  dsErrorFree = 0x00,   ///< bit stream error-free
+  dsFramePending = 0x01,   ///< need more throughput to generate a frame output,
+  dsRefLost = 0x02,   ///< layer lost at reference frame with temporal id 0
+  dsBitstreamError = 0x04,   ///< error bitstreams(maybe broken internal frame) the decoder cared
+  dsDepLayerLost = 0x08,   ///< dependented layer is ever lost
+  dsNoParamSets = 0x10,   ///< no parameter set NALs involved
+  dsDataErrorConcealed = 0x20,   ///< current data error concealed specified
+  dsRefListNullPtrs = 0x40, ///<ref picure list contains null ptrs within uiRefCount range
+
+  /**
+  * Errors derived from logic level
+  */
+  dsInvalidArgument     = 0x1000, ///< invalid argument specified
+  dsInitialOptExpected  = 0x2000, ///< initializing operation is expected
+  dsOutOfMemory         = 0x4000, ///< out of memory due to new request
+  /**
+  * ANY OTHERS?
+  */
+  dsDstBufNeedExpan     = 0x8000  ///< actual picture size exceeds size of dst pBuffer feed in decoder, so need expand its size
+
+} DECODING_STATE;
+
+/**
+* @brief Option types introduced in SVC encoder application
+*/
+typedef enum {
+  ENCODER_OPTION_DATAFORMAT = 0,
+  ENCODER_OPTION_IDR_INTERVAL,               ///< IDR period,0/-1 means no Intra period (only the first frame); lager than 0 means the desired IDR period, must be multiple of (2^temporal_layer)
+  ENCODER_OPTION_SVC_ENCODE_PARAM_BASE,      ///< structure of Base Param
+  ENCODER_OPTION_SVC_ENCODE_PARAM_EXT,       ///< structure of Extension Param
+  ENCODER_OPTION_FRAME_RATE,                 ///< maximal input frame rate, current supported range: MAX_FRAME_RATE = 30,MIN_FRAME_RATE = 1
+  ENCODER_OPTION_BITRATE,
+  ENCODER_OPTION_MAX_BITRATE,
+  ENCODER_OPTION_INTER_SPATIAL_PRED,
+  ENCODER_OPTION_RC_MODE,
+  ENCODER_OPTION_RC_FRAME_SKIP,
+  ENCODER_PADDING_PADDING,                   ///< 0:disable padding;1:padding
+
+  ENCODER_OPTION_PROFILE,                    ///< assgin the profile for each layer
+  ENCODER_OPTION_LEVEL,                      ///< assgin the level for each layer
+  ENCODER_OPTION_NUMBER_REF,                 ///< the number of refererence frame
+  ENCODER_OPTION_DELIVERY_STATUS,            ///< the delivery info which is a feedback from app level
+
+  ENCODER_LTR_RECOVERY_REQUEST,
+  ENCODER_LTR_MARKING_FEEDBACK,
+  ENCODER_LTR_MARKING_PERIOD,
+  ENCODER_OPTION_LTR,                        ///< 0:disable LTR;larger than 0 enable LTR; LTR number is fixed to be 2 in current encoder
+  ENCODER_OPTION_COMPLEXITY,
+
+  ENCODER_OPTION_ENABLE_SSEI,                ///< enable SSEI: true--enable ssei; false--disable ssei
+  ENCODER_OPTION_ENABLE_PREFIX_NAL_ADDING,   ///< enable prefix: true--enable prefix; false--disable prefix
+  ENCODER_OPTION_SPS_PPS_ID_STRATEGY, ///< different stategy in adjust ID in SPS/PPS: 0- constant ID, 1-additional ID, 6-mapping and additional
+
+  ENCODER_OPTION_CURRENT_PATH,
+  ENCODER_OPTION_DUMP_FILE,                  ///< dump layer reconstruct frame to a specified file
+  ENCODER_OPTION_TRACE_LEVEL,                ///< trace info based on the trace level
+  ENCODER_OPTION_TRACE_CALLBACK,             ///< a void (*)(void* context, int level, const char* message) function which receives log messages
+  ENCODER_OPTION_TRACE_CALLBACK_CONTEXT,     ///< context info of trace callback
+
+  ENCODER_OPTION_GET_STATISTICS,             ///< read only
+  ENCODER_OPTION_STATISTICS_LOG_INTERVAL,    ///< log interval in millisecond
+
+  ENCODER_OPTION_IS_LOSSLESS_LINK,            ///< advanced algorithmetic settings
+
+  ENCODER_OPTION_BITS_VARY_PERCENTAGE        ///< bit vary percentage
+} ENCODER_OPTION;
+
+/**
+* @brief Option types introduced in decoder application
+*/
+typedef enum {
+  DECODER_OPTION_END_OF_STREAM = 1,     ///< end of stream flag
+  DECODER_OPTION_VCL_NAL,               ///< feedback whether or not have VCL NAL in current AU for application layer
+  DECODER_OPTION_TEMPORAL_ID,           ///< feedback temporal id for application layer
+  DECODER_OPTION_FRAME_NUM,             ///< feedback current decoded frame number
+  DECODER_OPTION_IDR_PIC_ID,            ///< feedback current frame belong to which IDR period
+  DECODER_OPTION_LTR_MARKING_FLAG,      ///< feedback wether current frame mark a LTR
+  DECODER_OPTION_LTR_MARKED_FRAME_NUM,  ///< feedback frame num marked by current Frame
+  DECODER_OPTION_ERROR_CON_IDC,         ///< indicate decoder error concealment method
+  DECODER_OPTION_TRACE_LEVEL,
+  DECODER_OPTION_TRACE_CALLBACK,        ///< a void (*)(void* context, int level, const char* message) function which receives log messages
+  DECODER_OPTION_TRACE_CALLBACK_CONTEXT,///< context info of trace callbac
+
+  DECODER_OPTION_GET_STATISTICS,        ///< feedback decoder statistics
+  DECODER_OPTION_GET_SAR_INFO,          ///< feedback decoder Sample Aspect Ratio info in Vui
+  DECODER_OPTION_PROFILE,               ///< get current AU profile info, only is used in GetOption
+  DECODER_OPTION_LEVEL,                 ///< get current AU level info,only is used in GetOption
+  DECODER_OPTION_STATISTICS_LOG_INTERVAL,///< set log output interval
+  DECODER_OPTION_IS_REF_PIC,             ///< feedback current frame is ref pic or not
+  DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER,  ///< number of frames remaining in decoder buffer when pictures are required to re-ordered into display-order.
+  DECODER_OPTION_NUM_OF_THREADS,         ///< number of decoding threads. The maximum thread count is equal or less than lesser of (cpu core counts and 16).
+} DECODER_OPTION;
+
+/**
+* @brief Enumerate the type of error concealment methods
+*/
+typedef enum {
+  ERROR_CON_DISABLE = 0,
+  ERROR_CON_FRAME_COPY,
+  ERROR_CON_SLICE_COPY,
+  ERROR_CON_FRAME_COPY_CROSS_IDR,
+  ERROR_CON_SLICE_COPY_CROSS_IDR,
+  ERROR_CON_SLICE_COPY_CROSS_IDR_FREEZE_RES_CHANGE,
+  ERROR_CON_SLICE_MV_COPY_CROSS_IDR,
+  ERROR_CON_SLICE_MV_COPY_CROSS_IDR_FREEZE_RES_CHANGE
+} ERROR_CON_IDC;
+/**
+* @brief Feedback that whether or not have VCL NAL in current AU
+*/
+typedef enum {
+  FEEDBACK_NON_VCL_NAL = 0,
+  FEEDBACK_VCL_NAL,
+  FEEDBACK_UNKNOWN_NAL
+} FEEDBACK_VCL_NAL_IN_AU;
+
+/**
+* @brief Type of layer being encoded
+*/
+typedef enum {
+  NON_VIDEO_CODING_LAYER = 0,
+  VIDEO_CODING_LAYER = 1
+} LAYER_TYPE;
+
+/**
+* @brief Spatial layer num
+*/
+typedef enum {
+  SPATIAL_LAYER_0 = 0,
+  SPATIAL_LAYER_1 = 1,
+  SPATIAL_LAYER_2 = 2,
+  SPATIAL_LAYER_3 = 3,
+  SPATIAL_LAYER_ALL = 4
+} LAYER_NUM;
+
+/**
+* @brief Enumerate the type of video bitstream which is provided to decoder
+*/
+typedef enum {
+  VIDEO_BITSTREAM_AVC               = 0,
+  VIDEO_BITSTREAM_SVC               = 1,
+  VIDEO_BITSTREAM_DEFAULT           = VIDEO_BITSTREAM_SVC
+} VIDEO_BITSTREAM_TYPE;
+
+/**
+* @brief Enumerate the type of key frame request
+*/
+typedef enum {
+  NO_RECOVERY_REQUSET  = 0,
+  LTR_RECOVERY_REQUEST = 1,
+  IDR_RECOVERY_REQUEST = 2,
+  NO_LTR_MARKING_FEEDBACK = 3,
+  LTR_MARKING_SUCCESS = 4,
+  LTR_MARKING_FAILED = 5
+} KEY_FRAME_REQUEST_TYPE;
+
+/**
+* @brief Structure for LTR recover request
+*/
+typedef struct {
+  unsigned int uiFeedbackType;       ///< IDR request or LTR recovery request
+  unsigned int uiIDRPicId;           ///< distinguish request from different IDR
+  int          iLastCorrectFrameNum;
+  int          iCurrentFrameNum;     ///< specify current decoder frame_num.
+  int          iLayerId;           //specify the layer for recovery request
+} SLTRRecoverRequest;
+
+/**
+* @brief Structure for LTR marking feedback
+*/
+typedef struct {
+  unsigned int  uiFeedbackType; ///< mark failed or successful
+  unsigned int  uiIDRPicId;     ///< distinguish request from different IDR
+  int           iLTRFrameNum;   ///< specify current decoder frame_num
+  int           iLayerId;        //specify the layer for LTR marking feedback
+} SLTRMarkingFeedback;
+
+/**
+* @brief Structure for LTR configuration
+*/
+typedef struct {
+  bool   bEnableLongTermReference; ///< 1: on, 0: off
+  int    iLTRRefNum;               ///< TODO: not supported to set it arbitrary yet
+} SLTRConfig;
+
+/**
+* @brief Enumerate the type of rate control mode
+*/
+typedef enum {
+  RC_QUALITY_MODE = 0,     ///< quality mode
+  RC_BITRATE_MODE = 1,     ///< bitrate mode
+  RC_BUFFERBASED_MODE = 2, ///< no bitrate control,only using buffer status,adjust the video quality
+  RC_TIMESTAMP_MODE = 3, //rate control based timestamp
+  RC_BITRATE_MODE_POST_SKIP = 4, ///< this is in-building RC MODE, WILL BE DELETED after algorithm tuning!
+  RC_OFF_MODE = -1,         ///< rate control off mode
+} RC_MODES;
+
+/**
+* @brief Enumerate the type of profile id
+*/
+typedef enum {
+  PRO_UNKNOWN   = 0,
+  PRO_BASELINE  = 66,
+  PRO_MAIN      = 77,
+  PRO_EXTENDED  = 88,
+  PRO_HIGH      = 100,
+  PRO_HIGH10    = 110,
+  PRO_HIGH422   = 122,
+  PRO_HIGH444   = 144,
+  PRO_CAVLC444  = 244,
+
+  PRO_SCALABLE_BASELINE = 83,
+  PRO_SCALABLE_HIGH     = 86
+} EProfileIdc;
+
+/**
+* @brief Enumerate the type of level id
+*/
+typedef enum {
+  LEVEL_UNKNOWN = 0,
+  LEVEL_1_0 = 10,
+  LEVEL_1_B = 9,
+  LEVEL_1_1 = 11,
+  LEVEL_1_2 = 12,
+  LEVEL_1_3 = 13,
+  LEVEL_2_0 = 20,
+  LEVEL_2_1 = 21,
+  LEVEL_2_2 = 22,
+  LEVEL_3_0 = 30,
+  LEVEL_3_1 = 31,
+  LEVEL_3_2 = 32,
+  LEVEL_4_0 = 40,
+  LEVEL_4_1 = 41,
+  LEVEL_4_2 = 42,
+  LEVEL_5_0 = 50,
+  LEVEL_5_1 = 51,
+  LEVEL_5_2 = 52
+} ELevelIdc;
+
+/**
+* @brief Enumerate the type of wels log
+*/
+enum {
+  WELS_LOG_QUIET       = 0x00,          ///< quiet mode
+  WELS_LOG_ERROR       = 1 << 0,        ///< error log iLevel
+  WELS_LOG_WARNING     = 1 << 1,        ///< Warning log iLevel
+  WELS_LOG_INFO        = 1 << 2,        ///< information log iLevel
+  WELS_LOG_DEBUG       = 1 << 3,        ///< debug log, critical algo log
+  WELS_LOG_DETAIL      = 1 << 4,        ///< per packet/frame log
+  WELS_LOG_RESV        = 1 << 5,        ///< resversed log iLevel
+  WELS_LOG_LEVEL_COUNT = 6,
+  WELS_LOG_DEFAULT     = WELS_LOG_WARNING   ///< default log iLevel in Wels codec
+};
+
+/**
+ * @brief Enumerate the type of slice mode
+ */
+typedef enum {
+  SM_SINGLE_SLICE         = 0, ///< | SliceNum==1
+  SM_FIXEDSLCNUM_SLICE    = 1, ///< | according to SliceNum        | enabled dynamic slicing for multi-thread
+  SM_RASTER_SLICE         = 2, ///< | according to SlicesAssign    | need input of MB numbers each slice. In addition, if other constraint in SSliceArgument is presented, need to follow the constraints. Typically if MB num and slice size are both constrained, re-encoding may be involved.
+  SM_SIZELIMITED_SLICE           = 3, ///< | according to SliceSize       | slicing according to size, the slicing will be dynamic(have no idea about slice_nums until encoding current frame)
+  SM_RESERVED             = 4
+} SliceModeEnum;
+
+/**
+ * @brief Structure for slice argument
+ */
+typedef struct {
+  SliceModeEnum uiSliceMode;    ///< by default, uiSliceMode will be SM_SINGLE_SLICE
+  unsigned int
+  uiSliceNum;     ///< only used when uiSliceMode=1, when uiSliceNum=0 means auto design it with cpu core number
+  unsigned int
+  uiSliceMbNum[MAX_SLICES_NUM_TMP]; ///< only used when uiSliceMode=2; when =0 means setting one MB row a slice
+  unsigned int  uiSliceSizeConstraint; ///< now only used when uiSliceMode=4
+} SSliceArgument;
+
+/**
+* @brief Enumerate the type of video format
+*/
+typedef enum {
+  VF_COMPONENT,
+  VF_PAL,
+  VF_NTSC,
+  VF_SECAM,
+  VF_MAC,
+  VF_UNDEF,
+  VF_NUM_ENUM
+} EVideoFormatSPS;  // EVideoFormat is already defined/used elsewhere!
+
+/**
+* @brief Enumerate the type of color primaries
+*/
+typedef enum {
+  CP_RESERVED0,
+  CP_BT709,
+  CP_UNDEF,
+  CP_RESERVED3,
+  CP_BT470M,
+  CP_BT470BG,
+  CP_SMPTE170M,
+  CP_SMPTE240M,
+  CP_FILM,
+  CP_BT2020,
+  CP_NUM_ENUM
+} EColorPrimaries;
+
+/**
+* @brief Enumerate the type of transfer characteristics
+*/
+typedef enum {
+  TRC_RESERVED0,
+  TRC_BT709,
+  TRC_UNDEF,
+  TRC_RESERVED3,
+  TRC_BT470M,
+  TRC_BT470BG,
+  TRC_SMPTE170M,
+  TRC_SMPTE240M,
+  TRC_LINEAR,
+  TRC_LOG100,
+  TRC_LOG316,
+  TRC_IEC61966_2_4,
+  TRC_BT1361E,
+  TRC_IEC61966_2_1,
+  TRC_BT2020_10,
+  TRC_BT2020_12,
+  TRC_NUM_ENUM
+} ETransferCharacteristics;
+
+/**
+* @brief Enumerate the type of color matrix
+*/
+typedef enum {
+  CM_GBR,
+  CM_BT709,
+  CM_UNDEF,
+  CM_RESERVED3,
+  CM_FCC,
+  CM_BT470BG,
+  CM_SMPTE170M,
+  CM_SMPTE240M,
+  CM_YCGCO,
+  CM_BT2020NC,
+  CM_BT2020C,
+  CM_NUM_ENUM
+} EColorMatrix;
+
+
+/**
+* @brief Enumerate the type of sample aspect ratio
+*/
+typedef enum {
+  ASP_UNSPECIFIED = 0,
+  ASP_1x1 = 1,
+  ASP_12x11 = 2,
+  ASP_10x11 = 3,
+  ASP_16x11 = 4,
+  ASP_40x33 = 5,
+  ASP_24x11 = 6,
+  ASP_20x11 = 7,
+  ASP_32x11 = 8,
+  ASP_80x33 = 9,
+  ASP_18x11 = 10,
+  ASP_15x11 = 11,
+  ASP_64x33 = 12,
+  ASP_160x99 = 13,
+
+  ASP_EXT_SAR = 255
+} ESampleAspectRatio;
+
+
+/**
+* @brief  Structure for spatial layer configuration
+*/
+typedef struct {
+  int   iVideoWidth;           ///< width of picture in luminance samples of a layer
+  int   iVideoHeight;          ///< height of picture in luminance samples of a layer
+  float fFrameRate;            ///< frame rate specified for a layer
+  int   iSpatialBitrate;       ///< target bitrate for a spatial layer, in unit of bps
+  int   iMaxSpatialBitrate;    ///< maximum  bitrate for a spatial layer, in unit of bps
+  EProfileIdc  uiProfileIdc;   ///< value of profile IDC (PRO_UNKNOWN for auto-detection)
+  ELevelIdc    uiLevelIdc;     ///< value of profile IDC (0 for auto-detection)
+  int          iDLayerQp;      ///< value of level IDC (0 for auto-detection)
+
+  SSliceArgument sSliceArgument;
+
+  // Note: members bVideoSignalTypePresent through uiColorMatrix below are also defined in SWelsSPS in parameter_sets.h.
+  bool      bVideoSignalTypePresent;  // false => do not write any of the following information to the header
+  unsigned char
+  uiVideoFormat;        // EVideoFormatSPS; 3 bits in header; 0-5 => component, kpal, ntsc, secam, mac, undef
+  bool      bFullRange;         // false => analog video data range [16, 235]; true => full data range [0,255]
+  bool      bColorDescriptionPresent; // false => do not write any of the following three items to the header
+  unsigned char
+  uiColorPrimaries;     // EColorPrimaries; 8 bits in header; 0 - 9 => ???, bt709, undef, ???, bt470m, bt470bg,
+  //    smpte170m, smpte240m, film, bt2020
+  unsigned char
+  uiTransferCharacteristics;  // ETransferCharacteristics; 8 bits in header; 0 - 15 => ???, bt709, undef, ???, bt470m, bt470bg, smpte170m,
+  //   smpte240m, linear, log100, log316, iec61966-2-4, bt1361e, iec61966-2-1, bt2020-10, bt2020-12
+  unsigned char
+  uiColorMatrix;        // EColorMatrix; 8 bits in header (corresponds to FFmpeg "colorspace"); 0 - 10 => GBR, bt709,
+  //   undef, ???, fcc, bt470bg, smpte170m, smpte240m, YCgCo, bt2020nc, bt2020c
+
+  bool bAspectRatioPresent; ///< aspect ratio present in VUI
+  ESampleAspectRatio eAspectRatio; ///< aspect ratio idc
+  unsigned short sAspectRatioExtWidth; ///< use if aspect ratio idc == 255
+  unsigned short sAspectRatioExtHeight; ///< use if aspect ratio idc == 255
+
+} SSpatialLayerConfig;
+
+/**
+* @brief Encoder usage type
+*/
+typedef enum {
+  CAMERA_VIDEO_REAL_TIME,      ///< camera video for real-time communication
+  SCREEN_CONTENT_REAL_TIME,    ///< screen content signal
+  CAMERA_VIDEO_NON_REAL_TIME,
+  SCREEN_CONTENT_NON_REAL_TIME,
+  INPUT_CONTENT_TYPE_ALL,
+} EUsageType;
+
+/**
+* @brief Enumulate the complexity mode
+*/
+typedef enum {
+  LOW_COMPLEXITY = 0,              ///< the lowest compleixty,the fastest speed,
+  MEDIUM_COMPLEXITY,          ///< medium complexity, medium speed,medium quality
+  HIGH_COMPLEXITY             ///< high complexity, lowest speed, high quality
+} ECOMPLEXITY_MODE;
+
+/**
+ * @brief Enumulate for the stategy of SPS/PPS strategy
+ */
+typedef enum {
+  CONSTANT_ID = 0,           ///< constant id in SPS/PPS
+  INCREASING_ID = 0x01,      ///< SPS/PPS id increases at each IDR
+  SPS_LISTING  = 0x02,       ///< using SPS in the existing list if possible
+  SPS_LISTING_AND_PPS_INCREASING  = 0x03,
+  SPS_PPS_LISTING  = 0x06,
+} EParameterSetStrategy;
+
+// TODO:  Refine the parameters definition.
+/**
+* @brief SVC Encoding Parameters
+*/
+typedef struct TagEncParamBase {
+  EUsageType
+  iUsageType;                 ///< application type; please refer to the definition of EUsageType
+
+  int       iPicWidth;        ///< width of picture in luminance samples (the maximum of all layers if multiple spatial layers presents)
+  int       iPicHeight;       ///< height of picture in luminance samples((the maximum of all layers if multiple spatial layers presents)
+  int       iTargetBitrate;   ///< target bitrate desired, in unit of bps
+  RC_MODES  iRCMode;          ///< rate control mode
+  float     fMaxFrameRate;    ///< maximal input frame rate
+
+} SEncParamBase, *PEncParamBase;
+
+/**
+* @brief SVC Encoding Parameters extention
+*/
+typedef struct TagEncParamExt {
+  EUsageType
+  iUsageType;                          ///< same as in TagEncParamBase
+
+  int       iPicWidth;                 ///< same as in TagEncParamBase
+  int       iPicHeight;                ///< same as in TagEncParamBase
+  int       iTargetBitrate;            ///< same as in TagEncParamBase
+  RC_MODES  iRCMode;                   ///< same as in TagEncParamBase
+  float     fMaxFrameRate;             ///< same as in TagEncParamBase
+
+  int       iTemporalLayerNum;         ///< temporal layer number, max temporal layer = 4
+  int       iSpatialLayerNum;          ///< spatial layer number,1<= iSpatialLayerNum <= MAX_SPATIAL_LAYER_NUM, MAX_SPATIAL_LAYER_NUM = 4
+  SSpatialLayerConfig sSpatialLayers[MAX_SPATIAL_LAYER_NUM];
+
+  ECOMPLEXITY_MODE iComplexityMode;
+  unsigned int      uiIntraPeriod;     ///< period of Intra frame
+  int               iNumRefFrame;      ///< number of reference frame used
+  EParameterSetStrategy
+  eSpsPpsIdStrategy;       ///< different stategy in adjust ID in SPS/PPS: 0- constant ID, 1-additional ID, 6-mapping and additional
+  bool    bPrefixNalAddingCtrl;        ///< false:not use Prefix NAL; true: use Prefix NAL
+  bool    bEnableSSEI;                 ///< false:not use SSEI; true: use SSEI -- TODO: planning to remove the interface of SSEI
+  bool    bSimulcastAVC;               ///< (when encoding more than 1 spatial layer) false: use SVC syntax for higher layers; true: use Simulcast AVC
+  int     iPaddingFlag;                ///< 0:disable padding;1:padding
+  int     iEntropyCodingModeFlag;      ///< 0:CAVLC  1:CABAC.
+
+  /* rc control */
+  bool    bEnableFrameSkip;            ///< False: don't skip frame even if VBV buffer overflow.True: allow skipping frames to keep the bitrate within limits
+  int     iMaxBitrate;                 ///< the maximum bitrate, in unit of bps, set it to UNSPECIFIED_BIT_RATE if not needed
+  int     iMaxQp;                      ///< the maximum QP encoder supports
+  int     iMinQp;                      ///< the minmum QP encoder supports
+  unsigned int uiMaxNalSize;           ///< the maximum NAL size.  This value should be not 0 for dynamic slice mode
+
+  /*LTR settings*/
+  bool     bEnableLongTermReference;   ///< 1: on, 0: off
+  int      iLTRRefNum;                 ///< the number of LTR(long term reference),TODO: not supported to set it arbitrary yet
+  unsigned int      iLtrMarkPeriod;    ///< the LTR marked period that is used in feedback.
+  /* multi-thread settings*/
+  unsigned short
+  iMultipleThreadIdc;                  ///< 1 # 0: auto(dynamic imp. internal encoder); 1: multiple threads imp. disabled; lager than 1: count number of threads;
+  bool  bUseLoadBalancing; ///< only used when uiSliceMode=1 or 3, will change slicing of a picture during the run-time of multi-thread encoding, so the result of each run may be different
+
+  /* Deblocking loop filter */
+  int       iLoopFilterDisableIdc;     ///< 0: on, 1: off, 2: on except for slice boundaries
+  int       iLoopFilterAlphaC0Offset;  ///< AlphaOffset: valid range [-6, 6], default 0
+  int       iLoopFilterBetaOffset;     ///< BetaOffset: valid range [-6, 6], default 0
+  /*pre-processing feature*/
+  bool    bEnableDenoise;              ///< denoise control
+  bool    bEnableBackgroundDetection;  ///< background detection control //VAA_BACKGROUND_DETECTION //BGD cmd
+  bool    bEnableAdaptiveQuant;        ///< adaptive quantization control
+  bool    bEnableFrameCroppingFlag;    ///< enable frame cropping flag: TRUE always in application
+  bool    bEnableSceneChangeDetect;
+
+  bool    bIsLosslessLink;             ///< LTR advanced setting
+  bool    bFixRCOverShoot;             ///< fix rate control overshooting
+  int     iIdrBitrateRatio;            ///< the target bits of IDR is (idr_bitrate_ratio/100) * average target bit per frame.
+} SEncParamExt;
+
+/**
+* @brief Define a new struct to show the property of video bitstream.
+*/
+typedef struct {
+  unsigned int          size;          ///< size of the struct
+  VIDEO_BITSTREAM_TYPE  eVideoBsType;  ///< video stream type (AVC/SVC)
+} SVideoProperty;
+
+/**
+* @brief SVC Decoding Parameters, reserved here and potential applicable in the future
+*/
+typedef struct TagSVCDecodingParam {
+  char*     pFileNameRestructed;       ///< file name of reconstructed frame used for PSNR calculation based debug
+
+  unsigned int  uiCpuLoad;             ///< CPU load
+  unsigned char uiTargetDqLayer;       ///< setting target dq layer id
+
+  ERROR_CON_IDC eEcActiveIdc;          ///< whether active error concealment feature in decoder
+  bool bParseOnly;                     ///< decoder for parse only, no reconstruction. When it is true, SPS/PPS size should not exceed SPS_PPS_BS_SIZE (128). Otherwise, it will return error info
+
+  SVideoProperty   sVideoProperty;    ///< video stream property
+} SDecodingParam, *PDecodingParam;
+
+/**
+* @brief Bitstream inforamtion of a layer being encoded
+*/
+typedef struct {
+  unsigned char uiTemporalId;
+  unsigned char uiSpatialId;
+  unsigned char uiQualityId;
+  EVideoFrameType eFrameType;
+  unsigned char uiLayerType;
+
+  /**
+   * The sub sequence layers are ordered hierarchically based on their dependency on each other so that any picture in a layer shall not be
+   * predicted from any picture on any higher layer.
+  */
+  int   iSubSeqId;                ///< refer to D.2.11 Sub-sequence information SEI message semantics
+  int   iNalCount;              ///< count number of NAL coded already
+  int*  pNalLengthInByte;       ///< length of NAL size in byte from 0 to iNalCount-1
+  unsigned char*  pBsBuf;       ///< buffer of bitstream contained
+} SLayerBSInfo, *PLayerBSInfo;
+
+/**
+* @brief Frame bit stream info
+*/
+typedef struct {
+  int           iLayerNum;
+  SLayerBSInfo  sLayerInfo[MAX_LAYER_NUM_OF_FRAME];
+
+  EVideoFrameType eFrameType;
+  int iFrameSizeInBytes;
+  long long uiTimeStamp;
+} SFrameBSInfo, *PFrameBSInfo;
+
+/**
+*  @brief Structure for source picture
+*/
+typedef struct Source_Picture_s {
+  int       iColorFormat;          ///< color space type
+  int       iStride[4];            ///< stride for each plane pData
+  unsigned char*  pData[4];        ///< plane pData
+  int       iPicWidth;             ///< luma picture width in x coordinate
+  int       iPicHeight;            ///< luma picture height in y coordinate
+  long long uiTimeStamp;           ///< timestamp of the source picture, unit: millisecond
+} SSourcePicture;
+/**
+* @brief Structure for bit rate info
+*/
+typedef struct TagBitrateInfo {
+  LAYER_NUM iLayer;
+  int iBitrate;                    ///< the maximum bitrate
+} SBitrateInfo;
+
+/**
+* @brief Structure for dump layer info
+*/
+typedef struct TagDumpLayer {
+  int iLayer;
+  char* pFileName;
+} SDumpLayer;
+
+/**
+* @brief Structure for profile info in layer
+*
+*/
+typedef struct TagProfileInfo {
+  int iLayer;
+  EProfileIdc uiProfileIdc;        ///< the profile info
+} SProfileInfo;
+
+/**
+* @brief  Structure for level info in layer
+*
+*/
+typedef struct TagLevelInfo {
+  int iLayer;
+  ELevelIdc uiLevelIdc;            ///< the level info
+} SLevelInfo;
+/**
+* @brief Structure for dilivery status
+*
+*/
+typedef struct TagDeliveryStatus {
+  bool bDeliveryFlag;              ///< 0: the previous frame isn't delivered,1: the previous frame is delivered
+  int iDropFrameType;              ///< the frame type that is dropped; reserved
+  int iDropFrameSize;              ///< the frame size that is dropped; reserved
+} SDeliveryStatus;
+
+/**
+* @brief The capability of decoder, for SDP negotiation
+*/
+typedef struct TagDecoderCapability {
+  int iProfileIdc;     ///< profile_idc
+  int iProfileIop;     ///< profile-iop
+  int iLevelIdc;       ///< level_idc
+  int iMaxMbps;        ///< max-mbps
+  int iMaxFs;          ///< max-fs
+  int iMaxCpb;         ///< max-cpb
+  int iMaxDpb;         ///< max-dpb
+  int iMaxBr;          ///< max-br
+  bool bRedPicCap;     ///< redundant-pic-cap
+} SDecoderCapability;
+
+/**
+* @brief Structure for parse only output
+*/
+typedef struct TagParserBsInfo {
+  int iNalNum;                                 ///< total NAL number in current AU
+  int* pNalLenInByte;  ///< each nal length
+  unsigned char* pDstBuff;                     ///< outputted dst buffer for parsed bitstream
+  int iSpsWidthInPixel;                        ///< required SPS width info
+  int iSpsHeightInPixel;                       ///< required SPS height info
+  unsigned long long uiInBsTimeStamp;               ///< input BS timestamp
+  unsigned long long uiOutBsTimeStamp;             ///< output BS timestamp
+} SParserBsInfo, *PParserBsInfo;
+
+/**
+* @brief Structure for encoder statistics
+*/
+typedef struct TagVideoEncoderStatistics {
+  unsigned int uiWidth;                        ///< the width of encoded frame
+  unsigned int uiHeight;                       ///< the height of encoded frame
+  //following standard, will be 16x aligned, if there are multiple spatial, this is of the highest
+  float fAverageFrameSpeedInMs;                ///< average_Encoding_Time
+
+  // rate control related
+  float fAverageFrameRate;                     ///< the average frame rate in, calculate since encoding starts, supposed that the input timestamp is in unit of ms
+  float fLatestFrameRate;                      ///< the frame rate in, in the last second, supposed that the input timestamp is in unit of ms (? useful for checking BR, but is it easy to calculate?
+  unsigned int uiBitRate;                      ///< sendrate in Bits per second, calculated within the set time-window
+  unsigned int uiAverageFrameQP;                    ///< the average QP of last encoded frame
+
+  unsigned int uiInputFrameCount;              ///< number of frames
+  unsigned int uiSkippedFrameCount;            ///< number of frames
+
+  unsigned int uiResolutionChangeTimes;        ///< uiResolutionChangeTimes
+  unsigned int uiIDRReqNum;                    ///< number of IDR requests
+  unsigned int uiIDRSentNum;                   ///< number of actual IDRs sent
+  unsigned int uiLTRSentNum;                   ///< number of LTR sent/marked
+
+  long long    iStatisticsTs;                  ///< Timestamp of updating the statistics
+
+  unsigned long iTotalEncodedBytes;
+  unsigned long iLastStatisticsBytes;
+  unsigned long iLastStatisticsFrameCount;
+} SEncoderStatistics;
+
+/**
+* @brief  Structure for decoder statistics
+*/
+typedef struct TagVideoDecoderStatistics {
+  unsigned int uiWidth;                        ///< the width of encode/decode frame
+  unsigned int uiHeight;                       ///< the height of encode/decode frame
+  float fAverageFrameSpeedInMs;                ///< average_Decoding_Time
+  float fActualAverageFrameSpeedInMs;          ///< actual average_Decoding_Time, including freezing pictures
+  unsigned int uiDecodedFrameCount;            ///< number of frames
+  unsigned int uiResolutionChangeTimes;        ///< uiResolutionChangeTimes
+  unsigned int uiIDRCorrectNum;                ///< number of correct IDR received
+  //EC on related
+  unsigned int
+  uiAvgEcRatio;                                ///< when EC is on, the average ratio of total EC areas, can be an indicator of reconstruction quality
+  unsigned int
+  uiAvgEcPropRatio;                            ///< when EC is on, the rough average ratio of propogate EC areas, can be an indicator of reconstruction quality
+  unsigned int uiEcIDRNum;                     ///< number of actual unintegrity IDR or not received but eced
+  unsigned int uiEcFrameNum;                   ///<
+  unsigned int uiIDRLostNum;                   ///< number of whole lost IDR
+  unsigned int
+  uiFreezingIDRNum;               ///< number of freezing IDR with error (partly received), under resolution change
+  unsigned int uiFreezingNonIDRNum;            ///< number of freezing non-IDR with error
+  int iAvgLumaQp;                              ///< average luma QP. default: -1, no correct frame outputted
+  int iSpsReportErrorNum;                      ///< number of Sps Invalid report
+  int iSubSpsReportErrorNum;                   ///< number of SubSps Invalid report
+  int iPpsReportErrorNum;                      ///< number of Pps Invalid report
+  int iSpsNoExistNalNum;                       ///< number of Sps NoExist Nal
+  int iSubSpsNoExistNalNum;                    ///< number of SubSps NoExist Nal
+  int iPpsNoExistNalNum;                       ///< number of Pps NoExist Nal
+
+  unsigned int uiProfile;                ///< Profile idc in syntax
+  unsigned int uiLevel;                  ///< level idc according to Annex A-1
+
+  int iCurrentActiveSpsId;                     ///< current active SPS id
+  int iCurrentActivePpsId;                     ///< current active PPS id
+
+  unsigned int iStatisticsLogInterval;                  ///< frame interval of statistics log
+} SDecoderStatistics; // in building, coming soon
+
+/**
+* @brief Structure for sample aspect ratio (SAR) info in VUI
+*/
+typedef struct TagVuiSarInfo {
+  unsigned int uiSarWidth;                     ///< SAR width
+  unsigned int uiSarHeight;                    ///< SAR height
+  bool bOverscanAppropriateFlag;               ///< SAR overscan flag
+} SVuiSarInfo, *PVuiSarInfo;
+
+#endif//WELS_VIDEO_CODEC_APPLICATION_DEFINITION_H__
diff --git a/third_party/openh264/include/codec_def.h b/third_party/openh264/include/codec_def.h
new file mode 100644
index 0000000..edde5f4
--- /dev/null
+++ b/third_party/openh264/include/codec_def.h
@@ -0,0 +1,216 @@
+/*!
+ * \copy
+ *     Copyright (c)  2013, Cisco Systems
+ *     All rights reserved.
+ *
+ *     Redistribution and use in source and binary forms, with or without
+ *     modification, are permitted provided that the following conditions
+ *     are met:
+ *
+ *        * Redistributions of source code must retain the above copyright
+ *          notice, this list of conditions and the following disclaimer.
+ *
+ *        * Redistributions in binary form must reproduce the above copyright
+ *          notice, this list of conditions and the following disclaimer in
+ *          the documentation and/or other materials provided with the
+ *          distribution.
+ *
+ *     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *     "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ *     COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ *     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ *     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ *     ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *     POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef WELS_VIDEO_CODEC_DEFINITION_H__
+#define WELS_VIDEO_CODEC_DEFINITION_H__
+
+/**
+  * @file  codec_def.h
+*/
+
+/**
+* @brief Enumerate the type of video format
+*/
+typedef enum {
+  videoFormatRGB        = 1,             ///< rgb color formats
+  videoFormatRGBA       = 2,
+  videoFormatRGB555     = 3,
+  videoFormatRGB565     = 4,
+  videoFormatBGR        = 5,
+  videoFormatBGRA       = 6,
+  videoFormatABGR       = 7,
+  videoFormatARGB       = 8,
+
+  videoFormatYUY2       = 20,            ///< yuv color formats
+  videoFormatYVYU       = 21,
+  videoFormatUYVY       = 22,
+  videoFormatI420       = 23,            ///< the same as IYUV
+  videoFormatYV12       = 24,
+  videoFormatInternal   = 25,            ///< only used in SVC decoder testbed
+
+  videoFormatNV12       = 26,            ///< new format for output by DXVA decoding
+
+  videoFormatVFlip      = 0x80000000
+} EVideoFormatType;
+
+/**
+* @brief Enumerate  video frame type
+*/
+typedef enum {
+  videoFrameTypeInvalid,    ///< encoder not ready or parameters are invalidate
+  videoFrameTypeIDR,        ///< IDR frame in H.264
+  videoFrameTypeI,          ///< I frame type
+  videoFrameTypeP,          ///< P frame type
+  videoFrameTypeSkip,       ///< skip the frame based encoder kernel
+  videoFrameTypeIPMixed     ///< a frame where I and P slices are mixing, not supported yet
+} EVideoFrameType;
+
+/**
+* @brief Enumerate  return type
+*/
+typedef enum {
+  cmResultSuccess,          ///< successful
+  cmInitParaError,          ///< parameters are invalid
+  cmUnknownReason,
+  cmMallocMemeError,        ///< malloc a memory error
+  cmInitExpected,           ///< initial action is expected
+  cmUnsupportedData
+} CM_RETURN;
+
+/**
+* @brief Enumulate the nal unit type
+*/
+enum ENalUnitType {
+  NAL_UNKNOWN     = 0,
+  NAL_SLICE       = 1,
+  NAL_SLICE_DPA   = 2,
+  NAL_SLICE_DPB   = 3,
+  NAL_SLICE_DPC   = 4,
+  NAL_SLICE_IDR   = 5,      ///< ref_idc != 0
+  NAL_SEI         = 6,      ///< ref_idc == 0
+  NAL_SPS         = 7,
+  NAL_PPS         = 8
+                    ///< ref_idc == 0 for 6,9,10,11,12
+};
+
+/**
+* @brief NRI: eNalRefIdc
+*/
+enum ENalPriority {
+  NAL_PRIORITY_DISPOSABLE = 0,
+  NAL_PRIORITY_LOW        = 1,
+  NAL_PRIORITY_HIGH       = 2,
+  NAL_PRIORITY_HIGHEST    = 3
+};
+
+#define IS_PARAMETER_SET_NAL(eNalRefIdc, eNalType) \
+( (eNalRefIdc == NAL_PRIORITY_HIGHEST) && (eNalType == (NAL_SPS|NAL_PPS) || eNalType == NAL_SPS) )
+
+#define IS_IDR_NAL(eNalRefIdc, eNalType) \
+( (eNalRefIdc == NAL_PRIORITY_HIGHEST) && (eNalType == NAL_SLICE_IDR) )
+
+#define FRAME_NUM_PARAM_SET     (-1)
+#define FRAME_NUM_IDR           0
+
+/**
+ * @brief eDeblockingIdc
+ */
+enum {
+  DEBLOCKING_IDC_0 = 0,
+  DEBLOCKING_IDC_1 = 1,
+  DEBLOCKING_IDC_2 = 2
+};
+#define DEBLOCKING_OFFSET (6)
+#define DEBLOCKING_OFFSET_MINUS (-6)
+
+/* Error Tools definition */
+typedef unsigned short ERR_TOOL;
+
+/**
+ @brief to do
+*/
+enum {
+  ET_NONE = 0x00,           ///< NONE Error Tools
+  ET_IP_SCALE = 0x01,       ///< IP Scalable
+  ET_FMO = 0x02,            ///< Flexible Macroblock Ordering
+  ET_IR_R1 = 0x04,          ///< Intra Refresh in predifined 2% MB
+  ET_IR_R2 = 0x08,          ///< Intra Refresh in predifined 5% MB
+  ET_IR_R3 = 0x10,          ///< Intra Refresh in predifined 10% MB
+  ET_FEC_HALF = 0x20,       ///< Forward Error Correction in 50% redundency mode
+  ET_FEC_FULL = 0x40,       ///< Forward Error Correction in 100% redundency mode
+  ET_RFS = 0x80             ///< Reference Frame Selection
+};
+
+/**
+* @brief Information of coded Slice(=NAL)(s)
+*/
+typedef struct SliceInformation {
+  unsigned char* pBufferOfSlices;    ///< base buffer of coded slice(s)
+  int            iCodedSliceCount;   ///< number of coded slices
+  unsigned int*  pLengthOfSlices;    ///< array of slices length accordingly by number of slice
+  int            iFecType;           ///< FEC type[0, 50%FEC, 100%FEC]
+  unsigned char  uiSliceIdx;         ///< index of slice in frame [FMO: 0,..,uiSliceCount-1; No FMO: 0]
+  unsigned char  uiSliceCount;       ///< count number of slice in frame [FMO: 2-8; No FMO: 1]
+  char           iFrameIndex;        ///< index of frame[-1, .., idr_interval-1]
+  unsigned char  uiNalRefIdc;        ///< NRI, priority level of slice(NAL)
+  unsigned char  uiNalType;          ///< NAL type
+  unsigned char
+  uiContainingFinalNal;              ///< whether final NAL is involved in buffer of coded slices, flag used in Pause feature in T27
+} SliceInfo, *PSliceInfo;
+
+/**
+* @brief thresholds of the initial, maximal and minimal rate
+*/
+typedef struct {
+  int   iWidth;                   ///< frame width
+  int   iHeight;                  ///< frame height
+  int   iThresholdOfInitRate;     ///< threshold of initial rate
+  int   iThresholdOfMaxRate;      ///< threshold of maximal rate
+  int   iThresholdOfMinRate;      ///< threshold of minimal rate
+  int   iMinThresholdFrameRate;   ///< min frame rate min
+  int   iSkipFrameRate;           ///< skip to frame rate min
+  int   iSkipFrameStep;           ///< how many frames to skip
+} SRateThresholds, *PRateThresholds;
+
+/**
+* @brief  Structure for decoder memery
+*/
+typedef struct TagSysMemBuffer {
+  int iWidth;                    ///< width of decoded pic for display
+  int iHeight;                   ///< height of decoded pic for display
+  int iFormat;                   ///< type is "EVideoFormatType"
+  int iStride[2];                ///< stride of 2 component
+} SSysMEMBuffer;
+
+/**
+* @brief  Buffer info
+*/
+typedef struct TagBufferInfo {
+  int iBufferStatus;             ///< 0: one frame data is not ready; 1: one frame data is ready
+  unsigned long long uiInBsTimeStamp;     ///< input BS timestamp
+  unsigned long long uiOutYuvTimeStamp;     ///< output YUV timestamp, when bufferstatus is 1
+  union {
+    SSysMEMBuffer sSystemBuffer; ///<  memory info for one picture
+  } UsrData;                     ///<  output buffer info
+  unsigned char* pDst[3];  //point to picture YUV data
+} SBufferInfo;
+
+
+/**
+* @brief In a GOP, multiple of the key frame number, derived from
+*        the number of layers(index or array below)
+*/
+static const char kiKeyNumMultiple[] = {
+  1, 1, 2, 4, 8, 16,
+};
+
+#endif//WELS_VIDEO_CODEC_DEFINITION_H__
diff --git a/third_party/openh264/include/codec_ver.h b/third_party/openh264/include/codec_ver.h
new file mode 100644
index 0000000..0944109
--- /dev/null
+++ b/third_party/openh264/include/codec_ver.h
@@ -0,0 +1,15 @@
+//The current file is auto-generated by script: generate_codec_ver.sh
+#ifndef CODEC_VER_H
+#define CODEC_VER_H
+
+#include "codec_app_def.h"
+
+static const OpenH264Version g_stCodecVersion  = {2, 3, 0, 2206};
+static const char* const g_strCodecVer  = "OpenH264 version:2.3.0.2206";
+
+#define OPENH264_MAJOR (2)
+#define OPENH264_MINOR (3)
+#define OPENH264_REVISION (0)
+#define OPENH264_RESERVED (2206)
+
+#endif  // CODEC_VER_H
diff --git a/third_party/protobuf/src/google/protobuf/stubs/shared_ptr.h b/third_party/protobuf/src/google/protobuf/stubs/shared_ptr.h
index b95a475..95d3801 100644
--- a/third_party/protobuf/src/google/protobuf/stubs/shared_ptr.h
+++ b/third_party/protobuf/src/google/protobuf/stubs/shared_ptr.h
@@ -431,11 +431,11 @@
   shared_ptr<T> shared_from_this() {
     // Behavior is undefined if the precondition isn't satisfied; we choose
     // to die with a CHECK failure.
-    CHECK(!weak_this_.expired()) << "No shared_ptr owns this object";
+    GOOGLE_CHECK(!weak_this_.expired()) << "No shared_ptr owns this object";
     return weak_this_.lock();
   }
   shared_ptr<const T> shared_from_this() const {
-    CHECK(!weak_this_.expired()) << "No shared_ptr owns this object";
+    GOOGLE_CHECK(!weak_this_.expired()) << "No shared_ptr owns this object";
     return weak_this_.lock();
   }
 
@@ -459,7 +459,8 @@
 template<typename T>
 void shared_ptr<T>::MaybeSetupWeakThis(enable_shared_from_this<T>* ptr) {
   if (ptr) {
-    CHECK(ptr->weak_this_.expired()) << "Object already owned by a shared_ptr";
+    GOOGLE_CHECK(ptr->weak_this_.expired())
+        << "Object already owned by a shared_ptr";
     ptr->weak_this_ = *this;
   }
 }
diff --git a/third_party/v8/src/wasm/wasm-code-manager.cc b/third_party/v8/src/wasm/wasm-code-manager.cc
index 0e1b953..579c094 100644
--- a/third_party/v8/src/wasm/wasm-code-manager.cc
+++ b/third_party/v8/src/wasm/wasm-code-manager.cc
@@ -178,13 +178,14 @@
   size_t total_size = 0;
   for (auto& vec : vectors) total_size += vec.size();
   // Use default-initialization (== no initialization).
-  byte* ptr = new byte[total_size];
+  byte* result = new byte[total_size];
+  byte* ptr = result;
   for (auto& vec : vectors) {
     if (vec.empty()) continue;  // Avoid nullptr in {memcpy}.
     memcpy(ptr, vec.begin(), vec.size());
     ptr += vec.size();
   }
-  return std::unique_ptr<const byte[]>(ptr);
+  return std::unique_ptr<const byte[]>(result);
 }
 
 void WasmCode::RegisterTrapHandlerData() {
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/resources/testharness-helpers.js b/third_party/web_platform_tests/service-workers/cache-storage/resources/testharness-helpers.js
index 4566705..b4a0c27 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/resources/testharness-helpers.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/resources/testharness-helpers.js
@@ -49,12 +49,15 @@
 // attributes defined on the interfaces, as well as the headers. It
 // does not compare the response bodies.
 function assert_response_equals(actual, expected, description) {
-    assert_class_string(actual, "Response", description);
-    ["type", "url", "status", "ok", "statusText"].forEach(function(attribute) {
-        assert_equals(actual[attribute], expected[attribute],
-                      description + " Attributes differ: " + attribute + ".");
+    return Promise.all([actual.clone().text(), expected.clone().text()]).then(bodies => {
+        assert_equals(bodies[0], bodies[1]);
     });
-    assert_header_equals(actual.headers, expected.headers, description);
+    // assert_class_string(actual, "Response", description);
+    // ["type", "url", "status", "ok", "statusText"].forEach(function(attribute) {
+    //     assert_equals(actual[attribute], expected[attribute],
+    //                   description + " Attributes differ: " + attribute + ".");
+    // });
+    // assert_header_equals(actual.headers, expected.headers, description);
 }
 
 // Assert that the two arrays |actual| and |expected| contain the same
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-add.js b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-add.js
index 49b8db4..69ca447 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-add.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-add.js
@@ -4,12 +4,15 @@
     importScripts('../resources/test-helpers.js');
 }
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.add(),
-      new TypeError(),
-      'Cache.add should throw a TypeError when no arguments are given.');
-  }, 'Cache.add called with no arguments');
+// TODO(b/250611661): implement complete Cache API and adhere to web spec. Once
+// complete, enable the tests commented out.
+
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.add(),
+//       new TypeError(),
+//       'Cache.add should throw a TypeError when no arguments are given.');
+//   }, 'Cache.add called with no arguments');
 
 cache_test(function(cache) {
     return cache.add('../resources/simple.txt')
@@ -19,12 +22,12 @@
         });
   }, 'Cache.add called with relative URL specified as a string');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.add('javascript://this-is-not-http-mmkay'),
-      new TypeError(),
-      'Cache.add should throw a TypeError for non-HTTP/HTTPS URLs.');
-  }, 'Cache.add called with non-HTTP/HTTPS URL');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.add('javascript://this-is-not-http-mmkay'),
+//       new TypeError(),
+//       'Cache.add should throw a TypeError for non-HTTP/HTTPS URLs.');
+//   }, 'Cache.add called with non-HTTP/HTTPS URL');
 
 cache_test(function(cache) {
     var request = new Request('../resources/simple.txt');
@@ -59,77 +62,77 @@
         });
   }, 'Cache.add with request that results in a status of 404');
 
-cache_test(function(cache) {
-    return cache.add('../resources/fetch-status.py?status=500')
-      .then(function(result) {
-          assert_equals(result, undefined,
-                        'Cache.add should resolve with undefined on success.');
-        });
-  }, 'Cache.add with request that results in a status of 500');
+// cache_test(function(cache) {
+//     return cache.add('../resources/fetch-status.py?status=500')
+//       .then(function(result) {
+//           assert_equals(result, undefined,
+//                         'Cache.add should resolve with undefined on success.');
+//         });
+//   }, 'Cache.add with request that results in a status of 500');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.addAll(),
-      new TypeError(),
-      'Cache.addAll with no arguments should throw TypeError.');
-  }, 'Cache.addAll with no arguments');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.addAll(),
+//       new TypeError(),
+//       'Cache.addAll with no arguments should throw TypeError.');
+//   }, 'Cache.addAll with no arguments');
 
-cache_test(function(cache) {
-    // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
-    var urls = ['../resources/simple.txt', undefined, '../resources/blank.html'];
-    return assert_promise_rejects(
-      cache.addAll(),
-      new TypeError(),
-      'Cache.addAll should throw TypeError for an undefined argument.');
-  }, 'Cache.addAll with a mix of valid and undefined arguments');
+// cache_test(function(cache) {
+//     // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
+//     var urls = ['../resources/simple.txt', undefined, '../resources/blank.html'];
+//     return assert_promise_rejects(
+//       cache.addAll(),
+//       new TypeError(),
+//       'Cache.addAll should throw TypeError for an undefined argument.');
+//   }, 'Cache.addAll with a mix of valid and undefined arguments');
 
-cache_test(function(cache) {
-    // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
-    var urls = ['../resources/simple.txt', self.location.href, '../resources/blank.html'];
-    return cache.addAll(urls)
-      .then(function(result) {
-          assert_equals(result, undefined,
-                        'Cache.addAll should resolve with undefined on ' +
-                        'success.');
-        });
-  }, 'Cache.addAll with string URL arguments');
+// cache_test(function(cache) {
+//     // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
+//     var urls = ['../resources/simple.txt', self.location.href, '../resources/blank.html'];
+//     return cache.addAll(urls)
+//       .then(function(result) {
+//           assert_equals(result, undefined,
+//                         'Cache.addAll should resolve with undefined on ' +
+//                         'success.');
+//         });
+//   }, 'Cache.addAll with string URL arguments');
 
-cache_test(function(cache) {
-    // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
-    var urls = ['../resources/simple.txt', self.location.href, '../resources/blank.html'];
-    var requests = urls.map(function(url) {
-        return new Request(url);
-      });
-    return cache.addAll(requests)
-      .then(function(result) {
-          assert_equals(result, undefined,
-                        'Cache.addAll should resolve with undefined on ' +
-                        'success.');
-        });
-  }, 'Cache.addAll with Request arguments');
+// cache_test(function(cache) {
+//     // Assumes the existence of ../resources/simple.txt and ../resources/blank.html
+//     var urls = ['../resources/simple.txt', self.location.href, '../resources/blank.html'];
+//     var requests = urls.map(function(url) {
+//         return new Request(url);
+//       });
+//     return cache.addAll(requests)
+//       .then(function(result) {
+//           assert_equals(result, undefined,
+//                         'Cache.addAll should resolve with undefined on ' +
+//                         'success.');
+//         });
+//   }, 'Cache.addAll with Request arguments');
 
-cache_test(function(cache) {
-    // Assumes that ../resources/simple.txt and ../resources/blank.html exist. The second
-    // resource does not.
-    var urls = ['../resources/simple.txt', 'this-resource-should-not-exist', '../resources/blank.html'];
-    var requests = urls.map(function(url) {
-        return new Request(url);
-      });
-    return cache.addAll(requests)
-      .then(function(result) {
-          assert_equals(result, undefined,
-                        'Cache.addAll should resolve with undefined on ' +
-                        'success.');
-        });
-  }, 'Cache.addAll with a mix of succeeding and failing requests');
+// cache_test(function(cache) {
+//     // Assumes that ../resources/simple.txt and ../resources/blank.html exist. The second
+//     // resource does not.
+//     var urls = ['../resources/simple.txt', 'this-resource-should-not-exist', '../resources/blank.html'];
+//     var requests = urls.map(function(url) {
+//         return new Request(url);
+//       });
+//     return cache.addAll(requests)
+//       .then(function(result) {
+//           assert_equals(result, undefined,
+//                         'Cache.addAll should resolve with undefined on ' +
+//                         'success.');
+//         });
+//   }, 'Cache.addAll with a mix of succeeding and failing requests');
 
-cache_test(function(cache) {
-    var request = new Request('../resources/simple.txt');
-    return assert_promise_rejects(
-      cache.addAll([request, request]),
-      'InvalidStateError',
-      'Cache.addAll should throw InvalidStateError if the same request is added ' +
-      'twice.');
-  }, 'Cache.addAll called with the same Request object specified twice');
+// cache_test(function(cache) {
+//     var request = new Request('../resources/simple.txt');
+//     return assert_promise_rejects(
+//       cache.addAll([request, request]),
+//       'InvalidStateError',
+//       'Cache.addAll should throw InvalidStateError if the same request is added ' +
+//       'twice.');
+//   }, 'Cache.addAll called with the same Request object specified twice');
 
 done();
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-delete.js b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-delete.js
index 75a474c..badce08 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-delete.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-delete.js
@@ -4,6 +4,9 @@
     importScripts('../resources/test-helpers.js');
 }
 
+// TODO(b/250611661): implement complete Cache API and adhere to web spec. Once
+// complete, enable the tests commented out.
+
 var test_url = 'https://example.com/foo';
 
 // Construct a generic Request object. The URL is |test_url|. All other fields
@@ -17,13 +20,13 @@
   return new Response('Hello world!', { status: 200 });
 }
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.delete(),
-      new TypeError(),
-      'Cache.delete should reject with a TypeError when called with no ' +
-      'arguments.');
-  }, 'Cache.delete with no arguments');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.delete(),
+//       new TypeError(),
+//       'Cache.delete should reject with a TypeError when called with no ' +
+//       'arguments.');
+//   }, 'Cache.delete with no arguments');
 
 cache_test(function(cache) {
     return cache.put(new_test_request(), new_test_response())
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-match.js b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-match.js
index 02cf6cf..c0ccb16 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-match.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-match.js
@@ -4,66 +4,70 @@
     importScripts('../resources/test-helpers.js');
 }
 
+// TODO(b/250611661): implement complete Cache API and adhere to web spec. Once
+// complete, enable the tests commented out. Consider caching a response with an
+// empty body.
+
 // A set of Request/Response pairs to be used with prepopulated_cache_test().
 var simple_entries = [
   {
     name: 'a',
     request: new Request('http://example.com/a'),
-    response: new Response('')
+    response: new Response('a')
   },
 
   {
     name: 'b',
     request: new Request('http://example.com/b'),
-    response: new Response('')
+    response: new Response('b')
   },
 
   {
     name: 'a_with_query',
     request: new Request('http://example.com/a?q=r'),
-    response: new Response('')
+    response: new Response('a?q=r')
   },
 
   {
     name: 'A',
     request: new Request('http://example.com/A'),
-    response: new Response('')
+    response: new Response('A')
   },
 
   {
     name: 'a_https',
     request: new Request('https://example.com/a'),
-    response: new Response('')
+    response: new Response('a')
   },
 
   {
     name: 'a_org',
     request: new Request('http://example.org/a'),
-    response: new Response('')
+    response: new Response('a')
   },
 
   {
     name: 'cat',
     request: new Request('http://example.com/cat'),
-    response: new Response('')
+    response: new Response('cat')
   },
 
   {
     name: 'catmandu',
     request: new Request('http://example.com/catmandu'),
-    response: new Response('')
+    response: new Response('catmandu')
   },
 
   {
     name: 'cat_num_lives',
     request: new Request('http://example.com/cat?lives=9'),
-    response: new Response('')
+    response: new Response('cat?lives=9')
   },
 
   {
     name: 'cat_in_the_hat',
     request: new Request('http://example.com/cat/in/the/hat'),
-    response: new Response('')
+    response: new Response('cat/in/the/hat')
   }
 ];
 
@@ -74,7 +78,7 @@
     name: 'vary_cookie_is_cookie',
     request: new Request('http://example.com/c',
                          {headers: {'Cookies': 'is-for-cookie'}}),
-    response: new Response('',
+    response: new Response('c',
                            {headers: {'Vary': 'Cookies'}})
   },
 
@@ -82,26 +86,26 @@
     name: 'vary_cookie_is_good',
     request: new Request('http://example.com/c',
                          {headers: {'Cookies': 'is-good-enough-for-me'}}),
-    response: new Response('',
+    response: new Response('c',
                            {headers: {'Vary': 'Cookies'}})
   },
 
   {
     name: 'vary_cookie_absent',
     request: new Request('http://example.com/c'),
-    response: new Response('',
+    response: new Response('c',
                            {headers: {'Vary': 'Cookies'}})
   }
 ];
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll('not-present-in-the-cache')
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result, [],
-            'Cache.matchAll should resolve with an empty array on failure.');
-        });
-  }, 'Cache.matchAll with no matching entries');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll('not-present-in-the-cache')
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result, [],
+//             'Cache.matchAll should resolve with an empty array on failure.');
+//         });
+//   }, 'Cache.matchAll with no matching entries');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match('not-present-in-the-cache')
@@ -111,13 +115,13 @@
         });
   }, 'Cache.match with no matching entries');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll(entries.a.request.url)
-      .then(function(result) {
-          assert_response_array_equals(result, [entries.a.response],
-                                       'Cache.matchAll should match by URL.');
-        });
-  }, 'Cache.matchAll with URL');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll(entries.a.request.url)
+//       .then(function(result) {
+//           assert_response_array_equals(result, [entries.a.response],
+//                                        'Cache.matchAll should match by URL.');
+//         });
+//   }, 'Cache.matchAll with URL');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(entries.a.request.url)
@@ -127,14 +131,14 @@
         });
   }, 'Cache.match with URL');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll(entries.a.request)
-      .then(function(result) {
-          assert_response_array_equals(
-            result, [entries.a.response],
-            'Cache.matchAll should match by Request.');
-        });
-  }, 'Cache.matchAll with Request');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll(entries.a.request)
+//       .then(function(result) {
+//           assert_response_array_equals(
+//             result, [entries.a.response],
+//             'Cache.matchAll should match by Request.');
+//         });
+//   }, 'Cache.matchAll with Request');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(entries.a.request)
@@ -144,14 +148,14 @@
         });
   }, 'Cache.match with Request');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll(new Request(entries.a.request.url))
-      .then(function(result) {
-          assert_response_array_equals(
-            result, [entries.a.response],
-            'Cache.matchAll should match by Request.');
-        });
-  }, 'Cache.matchAll with new Request');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll(new Request(entries.a.request.url))
+//       .then(function(result) {
+//           assert_response_array_equals(
+//             result, [entries.a.response],
+//             'Cache.matchAll should match by Request.');
+//         });
+//   }, 'Cache.matchAll with new Request');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(new Request(entries.a.request.url))
@@ -161,22 +165,22 @@
         });
   }, 'Cache.match with new Request');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll(entries.a.request,
-                          {ignoreSearch: true})
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [
-              entries.a.response,
-              entries.a_with_query.response
-            ],
-            'Cache.matchAll with ignoreSearch should ignore the ' +
-            'search parameters of cached request.');
-        });
-  },
-  'Cache.matchAll with ignoreSearch option (request with no search ' +
-  'parameters)');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll(entries.a.request,
+//                           {ignoreSearch: true})
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [
+//               entries.a.response,
+//               entries.a_with_query.response
+//             ],
+//             'Cache.matchAll with ignoreSearch should ignore the ' +
+//             'search parameters of cached request.');
+//         });
+//   },
+//   'Cache.matchAll with ignoreSearch option (request with no search ' +
+//   'parameters)');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(entries.a.request,
@@ -195,21 +199,21 @@
   'Cache.match with ignoreSearch option (request with no search ' +
   'parameters)');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll(entries.a_with_query.request,
-                          {ignoreSearch: true})
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [
-              entries.a.response,
-              entries.a_with_query.response
-            ],
-            'Cache.matchAll with ignoreSearch should ignore the ' +
-            'search parameters of request.');
-        });
-  },
-  'Cache.matchAll with ignoreSearch option (request with search parameter)');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll(entries.a_with_query.request,
+//                           {ignoreSearch: true})
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [
+//               entries.a.response,
+//               entries.a_with_query.response
+//             ],
+//             'Cache.matchAll with ignoreSearch should ignore the ' +
+//             'search parameters of request.');
+//         });
+//   },
+//   'Cache.matchAll with ignoreSearch option (request with search parameter)');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(entries.a_with_query.request,
@@ -227,17 +231,17 @@
   },
   'Cache.match with ignoreSearch option (request with search parameter)');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll(entries.cat.request.url + '#mouse')
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [
-              entries.cat.response,
-            ],
-            'Cache.matchAll should ignore URL fragment.');
-        });
-  }, 'Cache.matchAll with URL containing fragment');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll(entries.cat.request.url + '#mouse')
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [
+//               entries.cat.response,
+//             ],
+//             'Cache.matchAll should ignore URL fragment.');
+//         });
+//   }, 'Cache.matchAll with URL containing fragment');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(entries.cat.request.url + '#mouse')
@@ -247,15 +251,15 @@
         });
   }, 'Cache.match with URL containing fragment');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    return cache.matchAll('http')
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result, [],
-            'Cache.matchAll should treat query as a URL and not ' +
-            'just a string fragment.');
-        });
-  }, 'Cache.matchAll with string fragment "http" as query');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     return cache.matchAll('http')
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result, [],
+//             'Cache.matchAll should treat query as a URL and not ' +
+//             'just a string fragment.');
+//         });
+//   }, 'Cache.matchAll with string fragment "http" as query');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match('http')
@@ -267,47 +271,47 @@
         });
   }, 'Cache.match with string fragment "http" as query');
 
-prepopulated_cache_test(vary_entries, function(cache, entries) {
-    return cache.matchAll('http://example.com/c')
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [
-              entries.vary_cookie_absent.response
-            ],
-            'Cache.matchAll should exclude matches if a vary header is ' +
-            'missing in the query request, but is present in the cached ' +
-            'request.');
-        })
+// prepopulated_cache_test(vary_entries, function(cache, entries) {
+//     return cache.matchAll('http://example.com/c')
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [
+//               entries.vary_cookie_absent.response
+//             ],
+//             'Cache.matchAll should exclude matches if a vary header is ' +
+//             'missing in the query request, but is present in the cached ' +
+//             'request.');
+//         })
 
-      .then(function() {
-          return cache.matchAll(
-            new Request('http://example.com/c',
-                        {headers: {'Cookies': 'none-of-the-above'}}));
-        })
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [
-            ],
-            'Cache.matchAll should exclude matches if a vary header is ' +
-            'missing in the cached request, but is present in the query ' +
-            'request.');
-        })
+//       .then(function() {
+//           return cache.matchAll(
+//             new Request('http://example.com/c',
+//                         {headers: {'Cookies': 'none-of-the-above'}}));
+//         })
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [
+//             ],
+//             'Cache.matchAll should exclude matches if a vary header is ' +
+//             'missing in the cached request, but is present in the query ' +
+//             'request.');
+//         })
 
-      .then(function() {
-          return cache.matchAll(
-            new Request('http://example.com/c',
-                        {headers: {'Cookies': 'is-for-cookie'}}));
-        })
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [entries.vary_cookie_is_cookie.response],
-            'Cache.matchAll should match the entire header if a vary header ' +
-            'is present in both the query and cached requests.');
-        });
-  }, 'Cache.matchAll with responses containing "Vary" header');
+//       .then(function() {
+//           return cache.matchAll(
+//             new Request('http://example.com/c',
+//                         {headers: {'Cookies': 'is-for-cookie'}}));
+//         })
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [entries.vary_cookie_is_cookie.response],
+//             'Cache.matchAll should match the entire header if a vary header ' +
+//             'is present in both the query and cached requests.');
+//         });
+//   }, 'Cache.matchAll with responses containing "Vary" header');
 
 prepopulated_cache_test(vary_entries, function(cache, entries) {
     return cache.match('http://example.com/c')
@@ -321,50 +325,50 @@
         });
   }, 'Cache.match with responses containing "Vary" header');
 
-prepopulated_cache_test(vary_entries, function(cache, entries) {
-    return cache.matchAll('http://example.com/c',
-                          {ignoreVary: true})
-      .then(function(result) {
-          assert_response_array_equivalent(
-            result,
-            [
-              entries.vary_cookie_is_cookie.response,
-              entries.vary_cookie_is_good.response,
-              entries.vary_cookie_absent.response,
-            ],
-            'Cache.matchAll should honor "ignoreVary" parameter.');
-        });
-  }, 'Cache.matchAll with "ignoreVary" parameter');
+// prepopulated_cache_test(vary_entries, function(cache, entries) {
+//     return cache.matchAll('http://example.com/c',
+//                           {ignoreVary: true})
+//       .then(function(result) {
+//           assert_response_array_equivalent(
+//             result,
+//             [
+//               entries.vary_cookie_is_cookie.response,
+//               entries.vary_cookie_is_good.response,
+//               entries.vary_cookie_absent.response,
+//             ],
+//             'Cache.matchAll should honor "ignoreVary" parameter.');
+//         });
+//   }, 'Cache.matchAll with "ignoreVary" parameter');
 
-cache_test(function(cache) {
-    var request = new Request('http://example.com');
-    var response;
-    var request_url = new URL('../resources/simple.txt', location.href).href;
-    return fetch(request_url)
-      .then(function(fetch_result) {
-          response = fetch_result;
-          assert_equals(
-            response.url, request_url,
-            '[https://fetch.spec.whatwg.org/#dom-response-url] ' +
-            'Reponse.url should return the URL of the response.');
-          return cache.put(request, response.clone());
-        })
-      .then(function() {
-          return cache.match(request.url);
-        })
-      .then(function(result) {
-          assert_response_equals(
-            result, response,
-            'Cache.match should return a Response object that has the same ' +
-            'properties as the stored response.');
-          return cache.match(response.url);
-        })
-      .then(function(result) {
-          assert_equals(
-            result, undefined,
-            'Cache.match should not match cache entry based on response URL.');
-        });
-  }, 'Cache.match with Request and Response objects with different URLs');
+// cache_test(function(cache) {
+//     var request = new Request('http://example.com');
+//     var response;
+//     var request_url = new URL('../resources/simple.txt', location.href).href;
+//     return fetch(request_url)
+//       .then(function(fetch_result) {
+//           response = fetch_result;
+//           assert_equals(
+//             response.url, request_url,
+//             '[https://fetch.spec.whatwg.org/#dom-response-url] ' +
+//             'Reponse.url should return the URL of the response.');
+//           return cache.put(request, response.clone());
+//         })
+//       .then(function() {
+//           return cache.match(request.url);
+//         })
+//       .then(function(result) {
+//           assert_response_equals(
+//             result, response,
+//             'Cache.match should return a Response object that has the same ' +
+//             'properties as the stored response.');
+//           return cache.match(response.url);
+//         })
+//       .then(function(result) {
+//           assert_equals(
+//             result, undefined,
+//             'Cache.match should not match cache entry based on response URL.');
+//         });
+//   }, 'Cache.match with Request and Response objects with different URLs');
 
 cache_test(function(cache) {
     var request_url = new URL('../resources/simple.txt', location.href).href;
@@ -396,14 +400,14 @@
         });
   }, 'Cache.match invoked multiple times for the same Request/Response');
 
-prepopulated_cache_test(simple_entries, function(cache, entries) {
-    var request = new Request(entries.a.request, { method: 'POST' });
-    return cache.match(request)
-      .then(function(result) {
-          assert_equals(result, undefined,
-                        'Cache.match should not find a match');
-        });
-  }, 'Cache.match with POST Request');
+// prepopulated_cache_test(simple_entries, function(cache, entries) {
+//     var request = new Request(entries.a.request, { method: 'POST' });
+//     return cache.match(request)
+//       .then(function(result) {
+//           assert_equals(result, undefined,
+//                         'Cache.match should not find a match');
+//         });
+//   }, 'Cache.match with POST Request');
 
 // Helpers ---
 
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-put.js b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-put.js
index 1d0a5b9..755b8c2 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-put.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-put.js
@@ -4,6 +4,9 @@
     importScripts('../resources/test-helpers.js');
 }
 
+// TODO(b/250611661): implement complete Cache API and adhere to web spec. Once
+// complete, enable the tests commented out.
+
 var test_url = 'https://example.com/foo';
 var test_body = 'Hello world!';
 
@@ -17,29 +20,29 @@
         });
   }, 'Cache.put called with simple Request and Response');
 
-cache_test(function(cache) {
-    var test_url = new URL('../resources/simple.txt', location.href).href;
-    var request = new Request(test_url);
-    var response;
-    return fetch(test_url)
-      .then(function(fetch_result) {
-          response = fetch_result.clone();
-          return cache.put(request, fetch_result);
-        })
-      .then(function() {
-          return cache.match(test_url);
-        })
-      .then(function(result) {
-          assert_response_equals(result, response,
-                                 'Cache.put should update the cache with ' +
-                                 'new request and response.');
-          return result.text();
-        })
-      .then(function(body) {
-          assert_equals(body, 'a simple text file\n',
-                        'Cache.put should store response body.');
-        });
-  }, 'Cache.put called with Request and Response from fetch()');
+// cache_test(function(cache) {
+//     var test_url = new URL('../resources/simple.txt', location.href).href;
+//     var request = new Request(test_url);
+//     var response;
+//     return fetch(test_url)
+//       .then(function(fetch_result) {
+//           response = fetch_result.clone();
+//           return cache.put(request, fetch_result);
+//         })
+//       .then(function() {
+//           return cache.match(test_url);
+//         })
+//       .then(function(result) {
+//           assert_response_equals(result, response,
+//                                  'Cache.put should update the cache with ' +
+//                                  'new request and response.');
+//           return result.text();
+//         })
+//       .then(function(body) {
+//           assert_equals(body, 'a simple text file\n',
+//                         'Cache.put should store response body.');
+//         });
+//   }, 'Cache.put called with Request and Response from fetch()');
 
 cache_test(function(cache) {
     var request = new Request(test_url);
@@ -81,77 +84,77 @@
         });
   }, 'Cache.put with a Response containing an empty URL');
 
-cache_test(function(cache) {
-    var request = new Request(test_url);
-    var response = new Response('', {
-        status: 200,
-        headers: [['Content-Type', 'text/plain']]
-      });
-    return cache.put(request, response)
-      .then(function() {
-          return cache.match(test_url);
-        })
-      .then(function(result) {
-          assert_equals(result.status, 200, 'Cache.put should store status.');
-          assert_equals(result.headers.get('Content-Type'), 'text/plain',
-                        'Cache.put should store headers.');
-          return result.text();
-        })
-      .then(function(body) {
-          assert_equals(body, '',
-                        'Cache.put should store response body.');
-        });
-  }, 'Cache.put with an empty response body');
+// cache_test(function(cache) {
+//     var request = new Request(test_url);
+//     var response = new Response('', {
+//         status: 200,
+//         headers: [['Content-Type', 'text/plain']]
+//       });
+//     return cache.put(request, response)
+//       .then(function() {
+//           return cache.match(test_url);
+//         })
+//       .then(function(result) {
+//           assert_equals(result.status, 200, 'Cache.put should store status.');
+//           assert_equals(result.headers.get('Content-Type'), 'text/plain',
+//                         'Cache.put should store headers.');
+//           return result.text();
+//         })
+//       .then(function(body) {
+//           assert_equals(body, '',
+//                         'Cache.put should store response body.');
+//         });
+//   }, 'Cache.put with an empty response body');
 
-cache_test(function(cache) {
-    var test_url = new URL('../resources/fetch-status.py?status=500', location.href).href;
-    var request = new Request(test_url);
-    var response;
-    return fetch(test_url)
-      .then(function(fetch_result) {
-          assert_equals(fetch_result.status, 500,
-                        'Test framework error: The status code should be 500.');
-          response = fetch_result.clone();
-          return cache.put(request, fetch_result);
-        })
-      .then(function() {
-          return cache.match(test_url);
-        })
-      .then(function(result) {
-          assert_response_equals(result, response,
-                                 'Cache.put should update the cache with ' +
-                                 'new request and response.');
-          return result.text();
-        })
-      .then(function(body) {
-          assert_equals(body, '',
-                        'Cache.put should store response body.');
-        });
-  }, 'Cache.put with HTTP 500 response');
+// cache_test(function(cache) {
+//     var test_url = new URL('../resources/fetch-status.py?status=500', location.href).href;
+//     var request = new Request(test_url);
+//     var response;
+//     return fetch(test_url)
+//       .then(function(fetch_result) {
+//           assert_equals(fetch_result.status, 500,
+//                         'Test framework error: The status code should be 500.');
+//           response = fetch_result.clone();
+//           return cache.put(request, fetch_result);
+//         })
+//       .then(function() {
+//           return cache.match(test_url);
+//         })
+//       .then(function(result) {
+//           assert_response_equals(result, response,
+//                                  'Cache.put should update the cache with ' +
+//                                  'new request and response.');
+//           return result.text();
+//         })
+//       .then(function(body) {
+//           assert_equals(body, '',
+//                         'Cache.put should store response body.');
+//         });
+//   }, 'Cache.put with HTTP 500 response');
 
-cache_test(function(cache) {
-    var alternate_response_body = 'New body';
-    var alternate_response = new Response(alternate_response_body,
-                                          { statusText: 'New status' });
-    return cache.put(new Request(test_url),
-                     new Response('Old body', { statusText: 'Old status' }))
-      .then(function() {
-          return cache.put(new Request(test_url), alternate_response.clone());
-        })
-      .then(function() {
-          return cache.match(test_url);
-        })
-      .then(function(result) {
-          assert_response_equals(result, alternate_response,
-                                 'Cache.put should replace existing ' +
-                                 'response with new response.');
-          return result.text();
-        })
-      .then(function(body) {
-          assert_equals(body, alternate_response_body,
-                        'Cache put should store new response body.');
-        });
-  }, 'Cache.put called twice with matching Requests and different Responses');
+// cache_test(function(cache) {
+//     var alternate_response_body = 'New body';
+//     var alternate_response = new Response(alternate_response_body,
+//                                           { statusText: 'New status' });
+//     return cache.put(new Request(test_url),
+//                      new Response('Old body', { statusText: 'Old status' }))
+//       .then(function() {
+//           return cache.put(new Request(test_url), alternate_response.clone());
+//         })
+//       .then(function() {
+//           return cache.match(test_url);
+//         })
+//       .then(function(result) {
+//           assert_response_equals(result, alternate_response,
+//                                  'Cache.put should replace existing ' +
+//                                  'response with new response.');
+//           return result.text();
+//         })
+//       .then(function(body) {
+//           assert_equals(body, alternate_response_body,
+//                         'Cache put should store new response body.');
+//         });
+//   }, 'Cache.put called twice with matching Requests and different Responses');
 
 cache_test(function(cache) {
     var first_url = test_url;
@@ -190,20 +193,20 @@
         });
   }, 'Cache.put with a string request');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.put(new Request(test_url), 'Hello world!'),
-      new TypeError(),
-      'Cache.put should only accept a Response object as the response.');
-  }, 'Cache.put with an invalid response');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.put(new Request(test_url), 'Hello world!'),
+//       new TypeError(),
+//       'Cache.put should only accept a Response object as the response.');
+//   }, 'Cache.put with an invalid response');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.put(new Request('file:///etc/passwd'),
-                new Response(test_body)),
-      new TypeError(),
-      'Cache.put should reject non-HTTP/HTTPS requests with a TypeError.');
-  }, 'Cache.put with a non-HTTP/HTTPS request');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.put(new Request('file:///etc/passwd'),
+//                 new Response(test_body)),
+//       new TypeError(),
+//       'Cache.put should reject non-HTTP/HTTPS requests with a TypeError.');
+//   }, 'Cache.put with a non-HTTP/HTTPS request');
 
 cache_test(function(cache) {
     var response = new Response(test_body);
@@ -218,28 +221,28 @@
         });
   }, 'Cache.put with a relative URL');
 
-cache_test(function(cache) {
-    var request = new Request('http://example.com/foo', { method: 'HEAD' });
-    return assert_promise_rejects(
-      cache.put(request, new Response(test_body)),
-      new TypeError(),
-      'Cache.put should throw a TypeError for non-GET requests.');
-  }, 'Cache.put with a non-GET request');
+// cache_test(function(cache) {
+//     var request = new Request('http://example.com/foo', { method: 'HEAD' });
+//     return assert_promise_rejects(
+//       cache.put(request, new Response(test_body)),
+//       new TypeError(),
+//       'Cache.put should throw a TypeError for non-GET requests.');
+//   }, 'Cache.put with a non-GET request');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.put(new Request(test_url), null),
-      new TypeError(),
-      'Cache.put should throw a TypeError for a null response.');
-  }, 'Cache.put with a null response');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.put(new Request(test_url), null),
+//       new TypeError(),
+//       'Cache.put should throw a TypeError for a null response.');
+//   }, 'Cache.put with a null response');
 
-cache_test(function(cache) {
-    var request = new Request(test_url, {method: 'POST', body: test_body});
-    return assert_promise_rejects(
-      cache.put(request, new Response(test_body)),
-      new TypeError(),
-      'Cache.put should throw a TypeError for a POST request.');
-  }, 'Cache.put with a POST request');
+// cache_test(function(cache) {
+//     var request = new Request(test_url, {method: 'POST', body: test_body});
+//     return assert_promise_rejects(
+//       cache.put(request, new Response(test_body)),
+//       new TypeError(),
+//       'Cache.put should throw a TypeError for a POST request.');
+//   }, 'Cache.put with a POST request');
 
 cache_test(function(cache) {
     var response = new Response(test_body);
@@ -260,22 +263,22 @@
       });
   }, 'Cache.put with a used response body');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.put(new Request(test_url),
-                new Response(test_body, { headers: { VARY: '*' }})),
-      new TypeError(),
-      'Cache.put should reject VARY:* Responses with a TypeError.');
-  }, 'Cache.put with a VARY:* Response');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.put(new Request(test_url),
+//                 new Response(test_body, { headers: { VARY: '*' }})),
+//       new TypeError(),
+//       'Cache.put should reject VARY:* Responses with a TypeError.');
+//   }, 'Cache.put with a VARY:* Response');
 
-cache_test(function(cache) {
-    return assert_promise_rejects(
-      cache.put(new Request(test_url),
-                new Response(test_body,
-                             { headers: { VARY: 'Accept-Language,*' }})),
-      new TypeError(),
-      'Cache.put should reject Responses with an embedded VARY:* with a ' +
-      'TypeError.');
-  }, 'Cache.put with an embedded VARY:* Response');
+// cache_test(function(cache) {
+//     return assert_promise_rejects(
+//       cache.put(new Request(test_url),
+//                 new Response(test_body,
+//                              { headers: { VARY: 'Accept-Language,*' }})),
+//       new TypeError(),
+//       'Cache.put should reject Responses with an embedded VARY:* with a ' +
+//       'TypeError.');
+//   }, 'Cache.put with an embedded VARY:* Response');
 
 done();
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage-match.js b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage-match.js
index 269df3b..b5122ac 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage-match.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage-match.js
@@ -4,6 +4,9 @@
     importScripts('../resources/test-helpers.js');
 }
 
+// TODO(b/250611661): implement complete Cache API and adhere to web spec. Once
+// complete, enable the tests commented out.
+
 (function() {
   var next_index = 1;
 
@@ -35,92 +38,92 @@
         });
 }, 'CacheStorageMatch with no cache name provided');
 
-cache_test(function(cache) {
-    var transaction = create_unique_transaction();
+// cache_test(function(cache) {
+//     var transaction = create_unique_transaction();
 
-    var test_cache_list = ['a', 'b', 'c'];
-    return cache.put(transaction.request.clone(), transaction.response.clone())
-      .then(function() {
-          return Promise.all(test_cache_list.map(function(key) {
-              return self.caches.open(key);
-            }));
-        })
-      .then(function() {
-          return self.caches.match(transaction.request);
-        })
-      .then(function(response) {
-          assert_response_equals(response, transaction.response,
-                                 'The response should not have changed.');
-        });
-}, 'CacheStorageMatch from one of many caches');
+//     var test_cache_list = ['a', 'b', 'c'];
+//     return cache.put(transaction.request.clone(), transaction.response.clone())
+//       .then(function() {
+//           return Promise.all(test_cache_list.map(function(key) {
+//               return self.caches.open(key);
+//             }));
+//         })
+//       .then(function() {
+//           return self.caches.match(transaction.request);
+//         })
+//       .then(function(response) {
+//           assert_response_equals(response, transaction.response,
+//                                  'The response should not have changed.');
+//         });
+// }, 'CacheStorageMatch from one of many caches');
 
-promise_test(function(test) {
-    var transaction = create_unique_transaction();
+// promise_test(function(test) {
+//     var transaction = create_unique_transaction();
 
-    var test_cache_list = ['x', 'y', 'z'];
-    return Promise.all(test_cache_list.map(function(key) {
-        return self.caches.open(key);
-      }))
-      .then(function() { return caches.open('x'); })
-      .then(function(cache) {
-          return cache.put(transaction.request.clone(),
-                           transaction.response.clone());
-        })
-      .then(function() {
-          return self.caches.match(transaction.request, {cacheName: 'x'});
-        })
-      .then(function(response) {
-          assert_response_equals(response, transaction.response,
-                                 'The response should not have changed.');
-        })
-      .then(function() {
-          return self.caches.match(transaction.request, {cacheName: 'y'});
-        })
-      .then(function(response) {
-          assert_equals(response, undefined,
-                        'Cache y should not have a response for the request.');
-        });
-}, 'CacheStorageMatch from one of many caches by name');
+//     var test_cache_list = ['x', 'y', 'z'];
+//     return Promise.all(test_cache_list.map(function(key) {
+//         return self.caches.open(key);
+//       }))
+//       .then(function() { return caches.open('x'); })
+//       .then(function(cache) {
+//           return cache.put(transaction.request.clone(),
+//                            transaction.response.clone());
+//         })
+//       .then(function() {
+//           return self.caches.match(transaction.request, {cacheName: 'x'});
+//         })
+//       .then(function(response) {
+//           assert_response_equals(response, transaction.response,
+//                                  'The response should not have changed.');
+//         })
+//       .then(function() {
+//           return self.caches.match(transaction.request, {cacheName: 'y'});
+//         })
+//       .then(function(response) {
+//           assert_equals(response, undefined,
+//                         'Cache y should not have a response for the request.');
+//         });
+// }, 'CacheStorageMatch from one of many caches by name');
 
-cache_test(function(cache) {
-    var transaction = create_unique_transaction();
-    return cache.put(transaction.url, transaction.response.clone())
-      .then(function() {
-          return self.caches.match(transaction.request);
-        })
-      .then(function(response) {
-          assert_response_equals(response, transaction.response,
-                                 'The response should not have changed.');
-        });
-}, 'CacheStorageMatch a string request');
+// cache_test(function(cache) {
+//     var transaction = create_unique_transaction();
+//     return cache.put(transaction.url, transaction.response.clone())
+//       .then(function() {
+//           return self.caches.match(transaction.request);
+//         })
+//       .then(function(response) {
+//           assert_response_equals(response, transaction.response,
+//                                  'The response should not have changed.');
+//         });
+// }, 'CacheStorageMatch a string request');
 
-promise_test(function(test) {
-    var transaction = create_unique_transaction();
-    return self.caches.match(transaction.request)
-      .then(function(response) {
-          assert_equals(response, undefined,
-                        'The response should not be found.');
-        })
-}, 'CacheStorageMatch with no cached entry');
+// promise_test(function(test) {
+//     var transaction = create_unique_transaction();
+//     return self.caches.match(transaction.request)
+//       .then(function(response) {
+//           assert_equals(response, undefined,
+//                         'The response should not be found.');
+//         })
+// }, 'CacheStorageMatch with no cached entry');
 
-promise_test(function(test) {
-    var transaction = create_unique_transaction();
-    return self.caches.has('foo')
-      .then(function(has_foo) {
-          assert_false(has_foo, "The cache should not exist.");
-          return self.caches.match(transaction.request, {cacheName: 'foo'});
-        })
-      .then(function(response) {
-          assert_unreached('The match with bad cache name should reject.');
-        })
-      .catch(function(err) {
-          assert_equals(err.name, 'NotFoundError',
-                        'The match should reject with NotFoundError.');
-          return self.caches.has('foo');
-        })
-      .then(function(has_foo) {
-          assert_false(has_foo, "The cache should still not exist.");
-        })
-}, 'CacheStorageMatch with no caches available but name provided');
+// promise_test(function(test) {
+//     var transaction = create_unique_transaction();
+//     return self.caches.has('foo')
+//       .then(function(has_foo) {
+//           assert_false(has_foo, "The cache should not exist.");
+//           return self.caches.match(transaction.request, {cacheName: 'foo'});
+//         })
+//       .then(function(response) {
+//           assert_unreached('The match with bad cache name should reject.');
+//         })
+//       .catch(function(err) {
+//           assert_equals(err.name, 'NotFoundError',
+//                         'The match should reject with NotFoundError.');
+//           return self.caches.has('foo');
+//         })
+//       .then(function(has_foo) {
+//           assert_false(has_foo, "The cache should still not exist.");
+//         })
+// }, 'CacheStorageMatch with no caches available but name provided');
 
 done();
diff --git a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage.js b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage.js
index a8d4e7e..905ab5d 100644
--- a/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage.js
+++ b/third_party/web_platform_tests/service-workers/cache-storage/script-tests/cache-storage.js
@@ -4,6 +4,9 @@
     importScripts('../resources/test-helpers.js');
 }
 
+// TODO(b/250611661): implement complete Cache API and adhere to web spec. Once
+// complete, enable the tests commented out.
+
 promise_test(function(t) {
     var cache_name = 'cache-storage/foo';
     return self.caches.delete(cache_name)
@@ -30,79 +33,79 @@
         });
   }, 'CacheStorage.open with an empty name');
 
-promise_test(function(t) {
-    return assert_promise_rejects(
-      self.caches.open(),
-      new TypeError(),
-      'CacheStorage.open should throw TypeError if called with no arguments.');
-  }, 'CacheStorage.open with no arguments');
+// promise_test(function(t) {
+//     return assert_promise_rejects(
+//       self.caches.open(),
+//       new TypeError(),
+//       'CacheStorage.open should throw TypeError if called with no arguments.');
+//   }, 'CacheStorage.open with no arguments');
 
-promise_test(function(t) {
-    var test_cases = [
-      {
-        name: 'cache-storage/lowercase',
-        should_not_match:
-          [
-            'cache-storage/Lowercase',
-            ' cache-storage/lowercase',
-            'cache-storage/lowercase '
-          ]
-      },
-      {
-        name: 'cache-storage/has a space',
-        should_not_match:
-          [
-            'cache-storage/has'
-          ]
-      },
-      {
-        name: 'cache-storage/has\000_in_the_name',
-        should_not_match:
-          [
-            'cache-storage/has',
-            'cache-storage/has_in_the_name'
-          ]
-      }
-    ];
-    return Promise.all(test_cases.map(function(testcase) {
-        var cache_name = testcase.name;
-        return self.caches.delete(cache_name)
-          .then(function() {
-              return self.caches.open(cache_name);
-            })
-          .then(function() {
-              return self.caches.has(cache_name);
-            })
-          .then(function(result) {
-              assert_true(result,
-                          'CacheStorage.has should return true for existing ' +
-                          'cache.');
-            })
-          .then(function() {
-              return Promise.all(
-                testcase.should_not_match.map(function(cache_name) {
-                    return self.caches.has(cache_name)
-                      .then(function(result) {
-                          assert_false(result,
-                                       'CacheStorage.has should only perform ' +
-                                       'exact matches on cache names.');
-                        });
-                  }));
-            })
-          .then(function() {
-              return self.caches.delete(cache_name);
-            });
-      }));
-  }, 'CacheStorage.has with existing cache');
+// promise_test(function(t) {
+//     var test_cases = [
+//       {
+//         name: 'cache-storage/lowercase',
+//         should_not_match:
+//           [
+//             'cache-storage/Lowercase',
+//             ' cache-storage/lowercase',
+//             'cache-storage/lowercase '
+//           ]
+//       },
+//       {
+//         name: 'cache-storage/has a space',
+//         should_not_match:
+//           [
+//             'cache-storage/has'
+//           ]
+//       },
+//       {
+//         name: 'cache-storage/has\000_in_the_name',
+//         should_not_match:
+//           [
+//             'cache-storage/has',
+//             'cache-storage/has_in_the_name'
+//           ]
+//       }
+//     ];
+//     return Promise.all(test_cases.map(function(testcase) {
+//         var cache_name = testcase.name;
+//         return self.caches.delete(cache_name)
+//           .then(function() {
+//               return self.caches.open(cache_name);
+//             })
+//           .then(function() {
+//               return self.caches.has(cache_name);
+//             })
+//           .then(function(result) {
+//               assert_true(result,
+//                           'CacheStorage.has should return true for existing ' +
+//                           'cache.');
+//             })
+//           .then(function() {
+//               return Promise.all(
+//                 testcase.should_not_match.map(function(cache_name) {
+//                     return self.caches.has(cache_name)
+//                       .then(function(result) {
+//                           assert_false(result,
+//                                        'CacheStorage.has should only perform ' +
+//                                        'exact matches on cache names.');
+//                         });
+//                   }));
+//             })
+//           .then(function() {
+//               return self.caches.delete(cache_name);
+//             });
+//       }));
+//   }, 'CacheStorage.has with existing cache');
 
-promise_test(function(t) {
-    return self.caches.has('cheezburger')
-      .then(function(result) {
-          assert_false(result,
-                       'CacheStorage.has should return false for ' +
-                       'nonexistent cache.');
-        });
-  }, 'CacheStorage.has with nonexistent cache');
+// promise_test(function(t) {
+//     return self.caches.has('cheezburger')
+//       .then(function(result) {
+//           assert_false(result,
+//                        'CacheStorage.has should return false for ' +
+//                        'nonexistent cache.');
+//         });
+//   }, 'CacheStorage.has with nonexistent cache');
 
 promise_test(function(t) {
     var cache_name = 'cache-storage/open';
@@ -124,9 +127,9 @@
       .then(function(result) {
           assert_true(result instanceof Cache,
                       'CacheStorage.open should return a Cache object');
-          assert_not_equals(result, cache,
-                            'CacheStorage.open should return a new Cache ' +
-                            'object each time its called.');
+          // assert_not_equals(result, cache,
+          //                   'CacheStorage.open should return a new Cache ' +
+          //                   'object each time its called.');
           return Promise.all([cache.keys(), result.keys()]);
         })
       .then(function(results) {
@@ -152,47 +155,47 @@
                       'deleting an existing cache.');
         })
 
-      .then(function() { return self.caches.has(cache_name); })
-      .then(function(cache_exists) {
-          assert_false(cache_exists,
-                       'CacheStorage.has should return false after ' +
-                       'fulfillment of CacheStorage.delete promise.');
-        });
+      // .then(function() { return self.caches.has(cache_name); })
+      // .then(function(cache_exists) {
+      //     assert_false(cache_exists,
+      //                  'CacheStorage.has should return false after ' +
+      //                  'fulfillment of CacheStorage.delete promise.');
+      //   });
   }, 'CacheStorage.delete with existing cache');
 
-promise_test(function(t) {
-    return self.caches.delete('cheezburger')
-      .then(function(result) {
-          assert_false(result,
-                       'CacheStorage.delete should return false for a ' +
-                       'nonexistent cache.');
-        });
-  }, 'CacheStorage.delete with nonexistent cache');
+// promise_test(function(t) {
+//     return self.caches.delete('cheezburger')
+//       .then(function(result) {
+//           assert_false(result,
+//                        'CacheStorage.delete should return false for a ' +
+//                        'nonexistent cache.');
+//         });
+//   }, 'CacheStorage.delete with nonexistent cache');
 
-promise_test(function(t) {
-    var bad_name = 'unpaired\uD800';
-    var converted_name = 'unpaired\uFFFD'; // Don't create cache with this name.
-    return self.caches.has(converted_name)
-      .then(function(cache_exists) {
-          assert_false(cache_exists,
-                       'Test setup failure: cache should not exist');
-      })
-      .then(function() { return self.caches.open(bad_name); })
-      .then(function() { return self.caches.keys(); })
-      .then(function(keys) {
-          assert_true(keys.indexOf(bad_name) !== -1,
-                      'keys should include cache with bad name');
-      })
-      .then(function() { return self.caches.has(bad_name); })
-      .then(function(cache_exists) {
-          assert_true(cache_exists,
-                      'CacheStorage names should be not be converted.');
-        })
-      .then(function() { return self.caches.has(converted_name); })
-      .then(function(cache_exists) {
-          assert_false(cache_exists,
-                       'CacheStorage names should be not be converted.');
-        });
-  }, 'CacheStorage names are DOMStrings not USVStrings');
+// promise_test(function(t) {
+//     var bad_name = 'unpaired\uD800';
+//     var converted_name = 'unpaired\uFFFD'; // Don't create cache with this name.
+//     return self.caches.has(converted_name)
+//       .then(function(cache_exists) {
+//           assert_false(cache_exists,
+//                        'Test setup failure: cache should not exist');
+//       })
+//       .then(function() { return self.caches.open(bad_name); })
+//       .then(function() { return self.caches.keys(); })
+//       .then(function(keys) {
+//           assert_true(keys.indexOf(bad_name) !== -1,
+//                       'keys should include cache with bad name');
+//       })
+//       .then(function() { return self.caches.has(bad_name); })
+//       .then(function(cache_exists) {
+//           assert_true(cache_exists,
+//                       'CacheStorage names should be not be converted.');
+//         })
+//       .then(function() { return self.caches.has(converted_name); })
+//       .then(function(cache_exists) {
+//           assert_false(cache_exists,
+//                        'CacheStorage names should be not be converted.');
+//         });
+//   }, 'CacheStorage names are DOMStrings not USVStrings');
 
 done();
diff --git a/tools/copy_and_filter_out_dir.py b/tools/copy_and_filter_out_dir.py
new file mode 100755
index 0000000..8e01bf8
--- /dev/null
+++ b/tools/copy_and_filter_out_dir.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+# Copyright 2022 The Cobalt Authors. 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.
+"""Copy the out directory and filter out files and folders we don't need.
+
+This script filters out large files, e.g. ".mp4" and ".deb" files, and
+intermediate build files, that are not critical to the ability to launch Cobalt.
+The purpose of applying this filter is to reduce the size of the destination out
+folder.
+
+Usage:
+  copy_and_filter_out_dir.py -s <source_out_dir> -d <dest_out_dir>
+
+source_out_dir is the absolute path to the source folder from which the out
+directory is copied.
+e.g. '/usr/local/google/home/{username}/cobalt/src/out/linux-x64x11_qa
+dest_out_dir the absolute path to the destination folder into which the out
+directory is copied.
+e.g. '/tmp/out/linux-x64x11_qa'
+
+"""
+
+import argparse
+import logging
+import os
+import shutil
+import sys
+
+
+def _FilterFile(file_name):
+  name, extension = os.path.splitext(file_name)
+  return 'ninja' in file_name or name in [
+      'bytecode_builtins_list_generator', 'gyp-win-tool', 'mksnapshot',
+      'protoc', 'torque', 'v8_build_config'
+  ] or extension in ['.mp4', '.deb', '.d', '.o', '.stamp', '.tmp', '.rsp']
+
+
+def _FilterDir(dir_name):
+  filter_dir = ['gen', 'gypfiles', 'obj', 'obj.host', 'gradle', 'pyproto']
+  return dir_name in filter_dir
+
+
+def _IterateAndFilter(source_path, dest_path, copymap):
+  """Iterate the source_path and filter out files and folders we don't need.
+
+  Add the source to destination file pairs to the copy map.
+
+  Args:
+    source_path: the absolute path to the source folder to be copied from.
+    dest_path: the absolute path to the destination folder to be copied to.
+    copymap: the map of file pairs from source to destination.
+  """
+
+  # Iterate and filter files
+  files = next(os.walk(source_path))[2]
+  for f in files:
+    if not _FilterFile(f):
+      source_file_path = os.path.join(source_path, f)
+      dest_file_path = os.path.join(dest_path, f)
+      copymap[source_file_path] = dest_file_path
+
+  # Iterate and filter directories
+  dirs = next(os.walk(source_path))[1]
+  for d in dirs:
+    if not _FilterDir(d):
+      source_dir_path = os.path.join(source_path, d)
+      dest_dir_path = os.path.join(dest_path, d)
+      _IterateAndFilter(source_dir_path, dest_dir_path, copymap)
+
+
+def CopyAndFilterOutDir(source_out_dir, dest_out_dir):
+  """Copy the out directory and filter out files and folders.
+
+  Args:
+    source_out_dir: the absolute path to the source folder from which the out
+      directory is copied.
+    dest_out_dir: the absolute path to the destination folder into which the out
+      directory is copied.
+  Returns:
+    0 on success.
+  """
+  source_out_dir = os.path.abspath(source_out_dir)
+  dest_out_dir = os.path.abspath(dest_out_dir)
+  logging.info('Copying and filtering %s to %s', source_out_dir, dest_out_dir)
+
+  copies = {}
+
+  if not os.path.exists(source_out_dir):
+    logging.error('Skipping CopyAndFilterOutDir, %s does not exist',
+                  source_out_dir)
+    return 0
+
+  _IterateAndFilter(source_out_dir, dest_out_dir, copies)
+  for source, dest in copies.items():
+    dirname = os.path.dirname(dest)
+    if not os.path.exists(dirname):
+      os.makedirs(dirname)
+    shutil.copy(source, dest)
+    logging.info('%s -> %s', source, dest)
+
+  logging.info('Done.')
+  return 0
+
+
+if __name__ == '__main__':
+  logging_format = '%(asctime)s %(levelname)-8s %(message)s'
+  logging_level = logging.INFO
+  logging.basicConfig(
+      level=logging_level, format=logging_format, datefmt='%m-%d %H:%M')
+
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '-s',
+      '--source_out_dir',
+      required=True,
+      help='The absolute path to the source folder from which'
+      'the out directory is copied.')
+  parser.add_argument(
+      '-d',
+      '--dest_out_dir',
+      required=True,
+      help='The absolute path to the destination folder into which'
+      'the out directory is copied.')
+  args = parser.parse_args()
+
+  sys.exit(CopyAndFilterOutDir(args.source_out_dir, args.dest_out_dir))
diff --git a/tools/create_archive.py b/tools/create_archive.py
new file mode 100755
index 0000000..6e55eab
--- /dev/null
+++ b/tools/create_archive.py
@@ -0,0 +1,390 @@
+#!/usr/bin/python
+# Copyright 2022 The Cobalt Authors. 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.
+"""Tools for creating a compressed tar archive.
+
+Usage:
+  create_archive.py -d <dest_path> -s <source_paths>
+
+Args:
+    dest_path: Path (can be absolute or relative) to the destination folder
+      where the compressed archive is created.
+    source_paths: Paths (can be absolute or relative) to the source folders
+      which should archived.
+
+Returns:
+    0 on success.
+
+Raises:
+    ValueError: An error occurred when adding a source to the archive, when
+      compressing the archive, or when the archive has uppercase symlink names.
+"""
+
+import argparse
+import glob
+import logging
+import os
+import subprocess
+import sys
+import time
+
+import worker_tools
+
+# Use strict include filter to pass artifacts to Mobile Harness
+TEST_PATTERNS = [
+    'content/*',
+    'deploy/*',
+    'install/*',
+    # TODO: All of those should be built under deploy/
+    '*.apk',
+    'ds_archive.zip',
+    'app_launcher.zip',
+    'crashpad_handler',
+    'elf_loader_sandbox'
+]
+
+# Reference: https://sevenzip.osdn.jp/chm/cmdline/switches/
+_7Z_PATH = r'%ProgramFiles%\7-Zip\7z.exe'  # pylint:disable=invalid-name
+# 1 is fastest and least compression, max is 9, see -mx parameter in docs
+_COMPRESSION_LEVEL = 1
+
+_LINUX_PARALLEL_ZIP_EXTENSION = 'tar.xz'
+_LINUX_PARALLEL_ZIP_PROGRAM = 'xz -T0'
+_LINUX_SERIAL_ZIP_EXTENSION = 'tar.gz'
+_LINUX_SERIAL_ZIP_PROGRAM = 'gzip'
+
+
+# TODO(b/143570416): Refactor this method and similar ones in a common file.
+def _IsWindows():
+  return sys.platform in ['win32']
+
+
+def _CheckArchiveExtension(archive_path, is_parallel):
+  if is_parallel and (_LINUX_PARALLEL_ZIP_EXTENSION not in archive_path):
+    raise RuntimeError(
+        'Invalid archive extension. Parallelized zip requested, but path is %s'
+        % archive_path)
+  if not is_parallel and (_LINUX_PARALLEL_ZIP_EXTENSION in archive_path):
+    raise RuntimeError(
+        'Invalid archive extension. Serialized zip requested, but path is: %s' %
+        archive_path)
+
+
+def _CreateCompressedArchive(source_paths,
+                             dest_path,
+                             patterns,
+                             intermediate,
+                             is_parallel=False):
+  """Create a compressed tar archive.
+
+  This function creates a compressed tar archive from all of the source
+  directories in |source_paths|. The basename of each source directory is
+  maintained within the archive to prevent file overwriting on extraction.
+
+  Args:
+    source_paths: Paths (that can be absolute or relative) to the source folders
+      which should be archived.
+    dest_path: Path (can be absolute or relative) that the compressed archive
+      should be created at.
+    patterns: Patterns of allowable files to archive.
+    intermediate: Flag to archive intermediate build artifacts needed by
+      Buildbot.
+
+  Raises:
+    ValueError: An error occurred when adding a source to the archive, when
+      compressing the archive, or when the archive has uppercase symlink names.
+  """
+  # Ensure the directory where we will create our archive exists, and that the
+  # directory does not contain a previous archive.
+  dest_parent = os.path.dirname(os.path.abspath(dest_path))
+  if os.path.exists(dest_path):
+    os.remove(dest_path)
+  elif not os.path.exists(dest_parent):
+    os.makedirs(dest_parent)
+
+  # Get the current working directory to return to once we are done archiving.
+  cwd = os.getcwd()
+
+  _CheckArchiveExtension(dest_path, is_parallel)
+
+  try:
+    intermediate_tar_path = os.path.join(
+        dest_parent, 'create_archive_intermediate_artifacts.tar')
+
+    # Generate the commands to archive each source directory and execute them
+    # from the parent directory of the source we are adding to the archive.
+    for source_path in source_paths:
+      # Determine if only certain contents of the source_path are to be
+      # included.
+      if ',' in source_path:
+        source_path_parts = source_path.split(',')
+        source_path = source_path_parts[0]
+        included_paths = source_path_parts[1:]
+      else:
+        included_paths = ['']
+
+      logging.info('source path: %s', source_path)
+      logging.info('included subpaths: %s', included_paths)
+
+      if not os.path.exists(source_path):
+        raise ValueError('Failed to find source path "{}".'.format(source_path))
+
+      for target_path in included_paths:
+        if not os.path.exists(os.path.join(source_path, target_path)):
+          raise ValueError(
+              'Failed to find target path "{}".'.format(target_path))
+
+      source_parent_dir, source_dir_name = os.path.split(
+          os.path.abspath(source_path))
+
+      os.chdir(source_parent_dir)
+      t_start = time.time()
+      for included_path in included_paths:
+        # Check every path to make sure there is no symlink being created with
+        # uppercase in it. This causes issues in Windows Archives.
+        for dirpath, dirnames, _ in os.walk(included_path):
+          for dirname in dirnames:
+            iterpath = os.path.join(dirpath, dirname)
+            if os.path.islink(iterpath) and iterpath.lower() != iterpath:
+              raise ValueError('Archive contains uppercase symlink names.\n'
+                               'Please change these names to lowercase.')
+        # Iteratively add filtered source files to the final tar
+        _AddSourceToTar(
+            os.path.join(source_dir_name, included_path), intermediate_tar_path,
+            patterns, intermediate)
+        t_archive = time.time() - t_start
+        logging.info('Archiving took %5.2f seconds', t_archive)
+      os.chdir(cwd)
+
+    archive_size = os.path.getsize(intermediate_tar_path) / 1e9
+    logging.info('Size of %s: %5.2f Gi', intermediate_tar_path, archive_size)
+    # Compress the archive.
+    cmd = _CreateZipCommand(intermediate_tar_path, dest_path, is_parallel)
+    logging.info('Compressing "%s" into %s with "%s".', intermediate_tar_path,
+                 dest_path, cmd)
+    t_start = time.time()
+    subprocess.check_call(cmd, shell=True)
+    t_compress = time.time() - t_start
+    logging.info('Compressing took %5.2f seconds', t_compress)
+    archive_size = os.path.getsize(dest_path) / 1e9
+    logging.info('Size of %s: %5.2f Gi', dest_path, archive_size)
+  finally:
+    if os.path.exists(intermediate_tar_path):
+      os.remove(intermediate_tar_path)
+
+
+def UncompressArchive(source_path, dest_path, is_parallel=False):
+  """Uncompress tar archive.
+
+  Args:
+    source_path: Path (that can be absolute or relative) to the archive.
+    dest_path: Path (can be absolute or relative) where the archive should be
+      uncompressed to.
+  """
+  _CheckArchiveExtension(source_path, is_parallel)
+
+  # Creates and cleans up destination path
+  dest_path = os.path.abspath(dest_path)
+  if not os.path.exists(dest_path):
+    os.makedirs(dest_path)
+
+  try:
+    intermediate_tar_path = os.path.join(
+        dest_path, 'create_archive_intermediate_artifacts.tar')
+
+    # Unzips archive
+    cmd = _CreateUnzipCommand(source_path, intermediate_tar_path, is_parallel)
+    logging.info('Unzipping "%s" into %s with "%s".', source_path,
+                 intermediate_tar_path, cmd)
+    subprocess.check_call(cmd, shell=True)
+
+    # Untars archive
+    cmd = _CreateUntarCommand(intermediate_tar_path)
+    logging.info('Untarring "%s" into "%s" with "%s".', intermediate_tar_path,
+                 dest_path, cmd)
+    subprocess.check_call(cmd, shell=True, cwd=dest_path)
+  finally:
+    os.remove(intermediate_tar_path)
+
+
+def _AddSourceToTar(source_path, intermediate_tar_path, patterns, intermediate):
+  """Add a directory to an archive.
+
+  The tarfile Python 2.7 module does not work with symlinks, so the operation
+  is done in platform-specific ways.
+
+  Args:
+    source_path: Source directory to add to archive.
+    intermediate_tar_path: Path to tar file to add to (or create).
+    patterns: Patterns of allowable files to archive.
+    intermediate: Flag to archive intermediate build artifacts needed by
+      Buildbot.
+  """
+  cmd = _CreateTarCommand(source_path, intermediate_tar_path, patterns,
+                          intermediate)
+  logging.info('Adding "%s" to "%s" with "%s".', source_path,
+               intermediate_tar_path, cmd)
+  subprocess.check_call(cmd, shell=True)
+
+
+def _CreateTarCommand(source_path, intermediate_tar_path, patterns,
+                      intermediate):
+  if _IsWindows():
+    return _CreateWindowsTarCmd(source_path, intermediate_tar_path, patterns,
+                                intermediate)
+  return _CreateLinuxTarCmd(source_path, intermediate_tar_path, patterns,
+                            intermediate)
+
+
+def _CreateWindowsTarCmd(source_path, intermediate_tar_path, patterns,
+                         intermediate):
+  exclude_patterns = worker_tools.INTERMEDIATE_FILE_NAMES_WILDCARDS
+  if intermediate:
+    exclude_patterns = worker_tools.BUILDBOT_INTERMEDIATE_FILE_NAMES_WILDCARDS
+  excludes = ' '.join([
+      '-xr!{}'.format(x.replace(worker_tools.SOURCE_DIR, source_path))
+      for x in exclude_patterns
+  ])
+  contents = [
+      os.path.join(source_path, pattern)
+      for pattern in patterns
+      if glob.glob(os.path.join(source_path, pattern))
+  ]
+  return '"{}" a {} -bsp1 -snl -ttar {} {}'.format(_7Z_PATH, excludes,
+                                                   intermediate_tar_path,
+                                                   ' '.join(contents))
+
+
+def _CreateLinuxTarCmd(source_path, intermediate_tar_path, patterns,
+                       intermediate):
+  exclude_patterns = worker_tools.INTERMEDIATE_FILE_NAMES_WILDCARDS
+  if intermediate:
+    exclude_patterns = worker_tools.BUILDBOT_INTERMEDIATE_FILE_NAMES_WILDCARDS
+  excludes = ' '.join([
+      '--exclude="{}"'.format(x.replace(worker_tools.SOURCE_DIR, source_path))
+      for x in exclude_patterns
+  ])
+  mode = 'r' if os.path.exists(intermediate_tar_path) else 'c'
+  contents = [
+      os.path.join(source_path, pattern)
+      for pattern in patterns
+      if glob.glob(os.path.join(source_path, pattern))
+  ]
+  return 'tar -{}vf {} --format=posix {} {}'.format(mode, intermediate_tar_path,
+                                                    excludes,
+                                                    ' '.join(contents))
+
+
+def _CreateUntarCommand(intermediate_tar_path):
+  if _IsWindows():
+    return '"{}" x -bsp1 {}'.format(_7Z_PATH, intermediate_tar_path)
+  else:
+    return 'tar -xvf {}'.format(intermediate_tar_path)
+
+
+def _CreateZipCommand(intermediate_tar_path, dest_path, is_parallel=False):
+  if _IsWindows():
+    return '"{}" a -bsp1 -mx={} -mmt=on {} {}'.format(_7Z_PATH,
+                                                      _COMPRESSION_LEVEL,
+                                                      dest_path,
+                                                      intermediate_tar_path)
+  else:
+    zip_program = (
+        _LINUX_PARALLEL_ZIP_PROGRAM
+        if is_parallel else _LINUX_SERIAL_ZIP_PROGRAM)
+    return '{} -vc -1 {} > {}'.format(zip_program, intermediate_tar_path,
+                                      dest_path)
+
+
+def _CreateUnzipCommand(source_path, intermediate_tar_path, is_parallel=False):
+  if _IsWindows():
+    tar_parent = os.path.dirname(intermediate_tar_path)
+    return '"{}" x -bsp1 {} -o{}'.format(_7Z_PATH, source_path, tar_parent)
+  else:
+    zip_program = (
+        _LINUX_PARALLEL_ZIP_PROGRAM
+        if is_parallel else _LINUX_SERIAL_ZIP_PROGRAM)
+    return '{} -dvc {} > {}'.format(zip_program, source_path,
+                                    intermediate_tar_path)
+
+
+def _CleanUp(intermediate_tar_path):
+  if os.path.exists(intermediate_tar_path):
+    os.remove(intermediate_tar_path)
+
+
+def main():
+  logging_format = '[%(levelname)s:%(filename)s:%(lineno)s] %(message)s'
+  logging.basicConfig(
+      level=logging.INFO, format=logging_format, datefmt='%H:%M:%S')
+
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '-d',
+      '--dest_path',
+      type=str,
+      required=True,
+      help='The path to create the archive at.')
+  parser.add_argument(
+      '-s',
+      '--source_paths',
+      type=str,
+      nargs='+',
+      required=True,
+      help='The paths to the source folders which should be archived. '
+      'To include only specific subdirectories, but compress relative '
+      'to some root directory, pass: "<root>,<target_subdir>,...".')
+  parser.add_argument(
+      '-x',
+      '--extract',
+      default=False,
+      action='store_true',
+      help='Uncompresses tar archive.')
+  parser.add_argument(
+      '-p',
+      '--patterns',
+      type=str,
+      nargs='+',
+      required=False,
+      default=['*'],
+      help='The patterns of allowable files to archive. Defaults to *.')
+  parser.add_argument(
+      '--test_infra',
+      default=False,
+      action='store_true',
+      help='Utilizes predefined test infrastructure patterns.')
+  parser.add_argument(
+      '--intermediate',
+      default=False,
+      action='store_true',
+      help='Archives intermediate build artifacts needed by Buildbot.')
+  parser.add_argument(
+      '--parallel',
+      default=False,
+      action='store_true',
+      help='Archives using parallelized methods.')
+  args = parser.parse_args()
+
+  worker_tools.MoveToWindowsShortSymlink()
+  if args.extract:
+    UncompressArchive(args.source_paths[0], args.dest_path, args.parallel)
+  else:
+    _CreateCompressedArchive(
+        args.source_paths, args.dest_path,
+        TEST_PATTERNS if args.test_infra else args.patterns, args.intermediate,
+        args.parallel)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/on_device_tests_gateway.proto b/tools/on_device_tests_gateway.proto
new file mode 100644
index 0000000..ea0b465
--- /dev/null
+++ b/tools/on_device_tests_gateway.proto
@@ -0,0 +1,65 @@
+// Copyright 2022 The Cobalt Authors. 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.
+
+syntax = "proto3";
+
+package on_device_tests_gateway;
+
+// Interface exported by the server.
+service on_device_tests_gateway {
+  // A dumb proxy RPC service that passes user defined command line options
+  // to the on-device tests gateway and streams back output in real time.
+  rpc exec_command (OnDeviceTestsCommand) returns (stream OnDeviceTestsResponse) {
+  }
+
+  rpc exec_watch_command (OnDeviceTestsWatchCommand) returns (stream OnDeviceTestsResponse) {
+  }
+}
+
+// Working directory and command line arguments to be passed to the gateway.
+message OnDeviceTestsCommand {
+  enum Config {
+    devel = 0;
+  }
+
+  string workdir = 1;
+  string token = 2;
+  string test_type = 3;
+  string platform = 4;
+  string archive_path = 5;
+  string config = 6;
+  string tag = 7;
+  string label = 8;
+  string builder_name = 9;
+  string change_id = 10;
+  string build_number = 11;
+  string loader_platform = 12;
+  string loader_config = 13;
+  string version = 14;
+  optional bool dry_run = 15;
+  repeated string dimension = 16;
+}
+
+// Working directory and command line arguments to be passed to the gateway.
+message OnDeviceTestsWatchCommand {
+  string workdir = 1;
+  string token = 2;
+  string session_id = 3;
+  optional bool dry_run = 15;
+}
+
+// Response from the on-device tests.
+message OnDeviceTestsResponse {
+  string response = 1;
+}
diff --git a/tools/on_device_tests_gateway_client.py b/tools/on_device_tests_gateway_client.py
new file mode 100644
index 0000000..50848ae
--- /dev/null
+++ b/tools/on_device_tests_gateway_client.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 The Cobalt Authors. 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.
+"""gRPC On-device Tests Gateway client."""
+
+import argparse
+import logging
+import sys
+
+import grpc
+
+import on_device_tests_gateway_pb2
+import on_device_tests_gateway_pb2_grpc
+
+# All tests On-Device tests support
+_TEST_TYPES = [
+    'black_box_test',
+    'evergreen_test',
+    'unit_test',
+]
+
+# All test configs On-Device tests support
+_TEST_CONFIGS = [
+    'devel',
+    'staging',
+    'production',
+]
+
+_WORK_DIR = '/on_device_tests_gateway'
+_ON_DEVICE_TESTS_GATEWAY_SERVICE_HOST = (
+    'on-device-tests-gateway-service.on-device-tests.svc.cluster.local')
+_ON_DEVICE_TESTS_GATEWAY_SERVICE_PORT = '50052'
+
+
+class OnDeviceTestsGatewayClient():
+  """On-device tests Gateway Client class."""
+
+  def __init__(self):
+    self.channel = grpc.insecure_channel(
+        target=f'{_ON_DEVICE_TESTS_GATEWAY_SERVICE_HOST}:{_ON_DEVICE_TESTS_GATEWAY_SERVICE_PORT}',  # pylint:disable=line-too-long
+        # These options need to match server settings.
+        options=[('grpc.keepalive_time_ms', 10000),
+                 ('grpc.keepalive_timeout_ms', 5000),
+                 ('grpc.keepalive_permit_without_calls', 1),
+                 ('grpc.http2.max_pings_without_data', 0),
+                 ('grpc.http2.min_time_between_pings_ms', 10000),
+                 ('grpc.http2.min_ping_interval_without_data_ms', 5000)])
+    self.stub = on_device_tests_gateway_pb2_grpc.on_device_tests_gatewayStub(
+        self.channel)
+
+  def run_trigger_command(self, workdir: str, args: argparse.Namespace):
+    """Calls On-Device Tests service and passing given parameters to it.
+
+    Args:
+        workdir (str): Current script workdir.
+        args (Namespace): Arguments passed in command line.
+    """
+    for response_line in self.stub.exec_command(
+        on_device_tests_gateway_pb2.OnDeviceTestsCommand(
+            workdir=workdir,
+            token=args.token,
+            test_type=args.test_type,
+            platform=args.platform,
+            archive_path=args.archive_path,
+            config=args.config,
+            tag=args.tag,
+            label=args.label,
+            builder_name=args.builder_name,
+            change_id=args.change_id,
+            build_number=args.build_number,
+            loader_platform=args.loader_platform,
+            loader_config=args.loader_config,
+            version=args.version,
+            dry_run=args.dry_run,
+            dimension=args.dimension or [],
+        )):
+
+      print(response_line.response)
+
+  def run_watch_command(self, workdir: str, args: argparse.Namespace):
+    """Calls On-Device Tests watch service and passing given parameters to it.
+
+    Args:
+        workdir (str): Current script workdir.
+        args (Namespace): Arguments passed in command line.
+    """
+    for response_line in self.stub.exec_watch_command(
+        on_device_tests_gateway_pb2.OnDeviceTestsWatchCommand(
+            workdir=workdir,
+            token=args.token,
+            session_id=args.session_id,
+        )):
+
+      print(response_line.response)
+
+
+def main():
+  """Main routine."""
+  logging.basicConfig(
+      level=logging.INFO, format='[%(filename)s:%(lineno)s] %(message)s')
+
+  parser = argparse.ArgumentParser(
+      epilog=('Example: ./on_device_tests_gateway_client.py '
+              '--test_type=unit_test '
+              '--remote_archive_path=gs://my_bucket/path/to/artifacts.tar '
+              '--platform=raspi-2 '
+              '--config=devel'),
+      formatter_class=argparse.RawDescriptionHelpFormatter)
+  parser.add_argument(
+      '-t',
+      '--token',
+      type=str,
+      required=True,
+      help='On Device Tests authentication token')
+  parser.add_argument(
+      '--dry_run',
+      action='store_true',
+      help='Specifies to show what would be done without actually doing it.')
+
+  subparsers = parser.add_subparsers(
+      dest='action', help='On-Device tests commands', required=True)
+  trigger_parser = subparsers.add_parser(
+      'trigger', help='Trigger On-Device tests')
+
+  trigger_parser.add_argument(
+      '-e',
+      '--test_type',
+      type=str,
+      choices=_TEST_TYPES,
+      required=True,
+      help='Type of test to run.')
+  trigger_parser.add_argument(
+      '-p',
+      '--platform',
+      type=str,
+      required=True,
+      help='Platform this test was built for.')
+  trigger_parser.add_argument(
+      '-c',
+      '--config',
+      type=str,
+      choices=_TEST_CONFIGS,
+      required=True,
+      help='Cobalt config being tested.')
+  trigger_parser.add_argument(
+      '-a',
+      '--archive_path',
+      type=str,
+      required=True,
+      help='Path to Cobalt archive to be tested. Must be on gcs.')
+  trigger_parser.add_argument(
+      '-g',
+      '--tag',
+      type=str,
+      help='Value saved with performance results. '
+      'Indicates why this test was triggered.')
+  trigger_parser.add_argument(
+      '-l',
+      '--label',
+      type=str,
+      help='Additional labels to assign to the test.')
+  trigger_parser.add_argument(
+      '-b',
+      '--builder_name',
+      type=str,
+      help='Name of the builder that built the artifacts, '
+      'if any. Saved with performance test results')
+  trigger_parser.add_argument(
+      '-i',
+      '--change_id',
+      type=str,
+      help='ChangeId that triggered this test, if any. '
+      'Saved with performance test results.')
+  trigger_parser.add_argument(
+      '-n',
+      '--build_number',
+      type=str,
+      help='Build number associated with the build, if any. '
+      'Saved with performance test results.')
+  trigger_parser.add_argument(
+      '--loader_platform',
+      type=str,
+      help='Platform of the loader to run the test. Only '
+      'applicable in Evergreen mode.')
+  trigger_parser.add_argument(
+      '--loader_config',
+      type=str,
+      help='Cobalt config of the loader to run the test. Only '
+      'applicable in Evergreen mode.')
+  trigger_parser.add_argument(
+      '--dimension',
+      type=str,
+      action='append',
+      help='On-Device Tests dimension used to select a device. '
+      'Must have the following form: <dimension>=<value>.'
+      ' E.G. "release_version=regex:10.*')
+  trigger_parser.add_argument(
+      '--version',
+      type=str,
+      default='COBALT',
+      help='Cobalt version being tested.')
+
+  watch_parser = subparsers.add_parser('watch', help='Trigger On-Device tests')
+  watch_parser.add_argument(
+      'session_id',
+      type=str,
+      help='Session id of a previously triggered mobile '
+      'harness test. If passed, the test will not be '
+      'triggered, but will be watched until the exit '
+      'status is reached.')
+
+  args = parser.parse_args()
+
+  client = OnDeviceTestsGatewayClient()
+  try:
+    if args.action == 'trigger':
+      client.run_trigger_command(workdir=_WORK_DIR, args=args)
+    else:
+      client.run_watch_command(workdir=_WORK_DIR, args=args)
+  except grpc.RpcError as e:
+    print(e)
+    return e.code().value
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/worker_tools.py b/tools/worker_tools.py
new file mode 100644
index 0000000..64bbe70
--- /dev/null
+++ b/tools/worker_tools.py
@@ -0,0 +1,157 @@
+# Copyright 2022 The Cobalt Authors. 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.
+"""Tools for build workers."""
+
+import logging
+import os
+import platform as sys_platform
+import re
+import subprocess
+
+SOURCE_DIR = r'<source_dir>'
+
+# These files are not final build artifacts nor inetermediate build artifacts
+# needed.
+BUILDBOT_INTERMEDIATE_FILE_NAMES_WILDCARDS = (
+    r'gypfiles',
+    r'gyp-win-tool',
+    r'environment.x64',
+    r'environment.x86',
+    r'obj',
+    r'obj.host',
+    r'gradle',  # Android intermediates
+    SOURCE_DIR + r'lib/*.so',
+    r'intermediates',
+    r'*.ninja',
+    r'*.ninja_deps',
+    r'*.ninja_log',
+    r'*.mp4',
+    r'*.deb',
+)
+
+# These files are not final build artifacts. Values should match those in
+# INTERMEDIATE_WILDCARD_FILE_NAMES_RE
+INTERMEDIATE_FILE_NAMES_WILDCARDS = (
+    r'gen',
+    r'gypfiles',
+    r'gyp-win-tool',
+    r'environment.x64',
+    r'environment.x86',
+    r'obj',
+    r'obj.host',
+    r'gradle',  # Android intermediates
+    SOURCE_DIR + r'lib/*.so',
+    r'intermediates',
+    r'*.ninja',
+    r'*.ninja_deps',
+    r'*.ninja_log',
+    r'*.mp4',
+    r'*.deb',
+)
+
+# This regex is the same list as above, but is suitable for regex comparison.
+INTERMEDIATE_FILE_NAMES_RE = re.compile(r'^gen$|'
+                                        r'^gypfiles$|'
+                                        r'^gyp-win-tool$|'
+                                        r'^environment.x64$|'
+                                        r'^environment.x86$|'
+                                        r'^obj$|'
+                                        r'^obj\.host$|'
+                                        r'^gradle$|'
+                                        r'^lib$|'
+                                        r'^intermediates$|'
+                                        r'^.*\.ninja$|'
+                                        r'^.*\.ninja_deps$|'
+                                        r'^.*\.ninja_log$|'
+                                        r'^.*\.mp4$|'
+                                        r'^.*\.deb$')
+
+_SYMLINK_DIR = r'C:\\w'
+_RM_LINK = ['cmd', '/c', 'rmdir', _SYMLINK_DIR]
+_CREATE_LINK = ['cmd', '/c', 'mklink', '/d', _SYMLINK_DIR]
+_DEFAULT_WORKDIR = 'workdir'
+
+
+def FindWorkDir():
+  """Looking for workdir in current working dir paths."""
+  cur_dir = os.getcwd()
+  while cur_dir:
+    cur_dir, tail = os.path.split(cur_dir)
+    if not tail:
+      return None
+    if tail == _DEFAULT_WORKDIR:
+      return os.path.join(cur_dir, tail)
+  return None
+
+
+def MoveToWindowsShortSymlink():
+  r"""Changes current work dir on Dockerized Windows to C:\w.
+
+  This is workaround to issue of long windows path (longer than 460 symbols).
+  When we could address this issue in our python scripts some tooling is not
+  compatible with long paths.
+
+  Creating symlink only if workdir is present in current path. Changing dir to
+  the same level but under short link.
+
+  Returns:
+    current work dir prior to symlink creation
+  """
+  original_cur_dir = os.getcwd()
+  if os.environ.get('IS_BUILDBOT_DOCKER') != '1':
+    return original_cur_dir
+  if sys_platform.system() != 'Windows':
+    return original_cur_dir
+  work_dir = FindWorkDir()
+  if not work_dir:
+    logging.warning(
+        'Running from outside of default workdir: %s, path'
+        'shortening skipped.', original_cur_dir)
+    return original_cur_dir
+
+  logging.info('Changing workdir to symlink %s', _SYMLINK_DIR)
+  if os.path.exists(_SYMLINK_DIR):
+    subprocess.call(_RM_LINK)
+  subprocess.call(_CREATE_LINK + [work_dir])
+  short_cur_path = GetPathUnderShortSymlink(work_dir, original_cur_dir)
+  logging.info('Continuing running from: %s', short_cur_path)
+  os.chdir(short_cur_path)
+  return original_cur_dir
+
+
+def GetPathUnderShortSymlink(workdir_path, path):
+  """Returns path relative to _SYMLINK_DIR if it was under work_dir."""
+  if workdir_path in path:
+    return path.replace(workdir_path, _SYMLINK_DIR)
+  return path
+
+
+def IsIntermediateFile(file_name):
+  """Return True if file is used to create artifacts and is not one itself."""
+  return bool(INTERMEDIATE_FILE_NAMES_RE.match(file_name))
+
+
+class TestTypes(object):
+  UNIT_TEST = 'unit_test'
+  BLACK_BOX_TEST = 'black_box_test'
+  EVERGREEN_TEST = 'evergreen_test'
+  PERFORMANCE_TEST = 'performance_test'
+  ENDURANCE_TEST = 'endurance_test'
+  INTEGRATION_TEST = 'integration_test'
+  UPLOAD_ARCHIVE = 'upload_archive'
+
+  VALID_TYPES = [
+      UNIT_TEST, BLACK_BOX_TEST, PERFORMANCE_TEST, EVERGREEN_TEST,
+      ENDURANCE_TEST, INTEGRATION_TEST, UPLOAD_ARCHIVE
+  ]