diff --git a/BUILD.gn b/BUILD.gn
index 4b4ed6de..d93cc2d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -728,6 +728,7 @@
     deps += [
       "//media/mojo:media_mojo_unittests",
       "//mojo/common:mojo_common_perftests",
+      "//services:service_unittests",
       "//services/video_capture:video_capture_unittests",
     ]
   }
diff --git a/chrome/android/java/res/OWNERS b/chrome/android/java/res/OWNERS
index 1d38848..2ea0d4cbc 100644
--- a/chrome/android/java/res/OWNERS
+++ b/chrome/android/java/res/OWNERS
@@ -1,7 +1,6 @@
 set noparent
 dfalcantara@chromium.org
 dtrainor@chromium.org
-ianwen@chromium.org
 tedchoc@chromium.org
 twellington@chromium.org
 yusufo@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/banners/SwipableOverlayView.java b/chrome/android/java/src/org/chromium/chrome/browser/banners/SwipableOverlayView.java
index babdce5..78b3703e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/banners/SwipableOverlayView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/banners/SwipableOverlayView.java
@@ -18,6 +18,7 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import org.chromium.chrome.browser.widget.animation.CancelAwareAnimatorListener;
 import org.chromium.content.browser.ContentViewCore;
 import org.chromium.content_public.browser.GestureStateListener;
 
@@ -275,6 +276,7 @@
                         + computeScrollDifference(scrollOffsetY, scrollExtentY);
                 translation = Math.max(0.0f, Math.min(mTotalHeight, translation));
                 setTranslationY(translation);
+                updateVisibility();
             }
         };
     }
@@ -314,9 +316,23 @@
         mCurrentAnimation.setDuration(duration);
         mCurrentAnimation.addListener(mAnimatorListener);
         mCurrentAnimation.setInterpolator(mInterpolator);
+        mCurrentAnimation.addListener(new CancelAwareAnimatorListener() {
+            @Override
+            public void onEnd(Animator animator) {
+                updateVisibility();
+            }
+        });
         mCurrentAnimation.start();
     }
 
+    private void updateVisibility() {
+        if (getTranslationY() >= getHeight()) {
+            if (getVisibility() != GONE) setVisibility(GONE);
+        } else {
+            if (getVisibility() != VISIBLE) setVisibility(VISIBLE);
+        }
+    }
+
     private int computeScrollDifference(int scrollOffsetY, int scrollExtentY) {
         return scrollOffsetY + scrollExtentY - mInitialOffsetY;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/OWNERS
index 8646c78..05d32ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/OWNERS
@@ -1,2 +1 @@
-kkimlabs@chromium.org
-ianwen@chromium.org
\ No newline at end of file
+twellington@chromium.org
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/OWNERS
deleted file mode 100644
index fc6a6ef..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ianwen@chromium.org
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/download/OWNERS
index d305eb8..efafd02 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/OWNERS
@@ -1,5 +1,4 @@
 qinmin@chromium.org
 
 per-file DownloadActivity.java=dfalcantara@chromium.org
-per-file DownloadActivity.java=ianwen@chromium.org
 per-file DownloadActivity.java=twellington@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OWNERS
index 5a46910..92e42be0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OWNERS
@@ -1,3 +1,2 @@
 dfalcantara@chromium.org
 twellington@chromium.org
-ianwen@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/historyreport/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/historyreport/OWNERS
index 5816e5fe..047e38a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/historyreport/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/historyreport/OWNERS
@@ -1 +1 @@
-ianwen@chromium.org
+nyquist@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS
deleted file mode 100644
index fc6a6ef..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ianwen@chromium.org
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java
index 32572c75..38881c2f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java
@@ -36,7 +36,6 @@
 import org.chromium.chrome.browser.preferences.website.SingleWebsitePreferences;
 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
-import org.chromium.chrome.browser.search_engines.TemplateUrlService.LoadListener;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrl;
 import org.chromium.components.location.LocationUtils;
 import org.chromium.ui.text.SpanApplier;
@@ -48,7 +47,9 @@
 /**
 * A custom adapter for listing search engines.
 */
-public class SearchEngineAdapter extends BaseAdapter implements LoadListener, OnClickListener {
+public class SearchEngineAdapter extends BaseAdapter
+        implements TemplateUrlService.LoadListener, TemplateUrlService.TemplateUrlServiceObserver,
+                OnClickListener {
     private static final int VIEW_TYPE_ITEM = 0;
     private static final int VIEW_TYPE_DIVIDER = 1;
     private static final int VIEW_TYPE_COUNT = 2;
@@ -75,6 +76,8 @@
     /** The position of the default search engine before user's action. */
     private int mInitialEnginePosition = -1;
 
+    private boolean mHasLoadObserver;
+
     /**
      * Construct a SearchEngineAdapter.
      * @param context The current context.
@@ -83,8 +86,25 @@
         mContext = context;
         mLayoutInflater = (LayoutInflater) mContext.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
+    }
 
-        initEntries();
+    /**
+     * Start the adapter to gather the available search engines and listen for updates.
+     */
+    public void start() {
+        refreshData();
+        TemplateUrlService.getInstance().addObserver(this);
+    }
+
+    /**
+     * Stop the adapter from listening for future search engine updates.
+     */
+    public void stop() {
+        if (mHasLoadObserver) {
+            TemplateUrlService.getInstance().unregisterLoadListener(this);
+            mHasLoadObserver = false;
+        }
+        TemplateUrlService.getInstance().removeObserver(this);
     }
 
     @VisibleForTesting
@@ -102,19 +122,12 @@
         return toKeyword(index);
     }
 
-    /**
-     * Initialize the search engine list.
-     */
-    private void initEntries() {
-        TemplateUrlService templateUrlService = TemplateUrlService.getInstance();
-        if (!templateUrlService.isLoaded()) {
-            templateUrlService.registerLoadListener(this);
-            templateUrlService.load();
-            return;  // Flow continues in onTemplateUrlServiceLoaded below.
-        }
+    private void initializeSearchEngineGroups(List<TemplateUrl> templateUrls) {
+        mPrepopulatedSearchEngines = new ArrayList<>();
+        mRecentSearchEngines = new ArrayList<>();
 
-        int defaultSearchEngineIndex = templateUrlService.getDefaultSearchEngineIndex();
-        for (TemplateUrl templateUrl : templateUrlService.getSearchEngines()) {
+        for (int i = 0; i < templateUrls.size(); i++) {
+            TemplateUrl templateUrl = templateUrls.get(i);
             if (templateUrl.getType() == TemplateUrlService.TYPE_PREPOPULATED
                     || templateUrl.getType() == TemplateUrlService.TYPE_DEFAULT) {
                 mPrepopulatedSearchEngines.add(templateUrl);
@@ -122,6 +135,36 @@
                 mRecentSearchEngines.add(templateUrl);
             }
         }
+    }
+
+    /**
+     * Initialize the search engine list.
+     */
+    private void refreshData() {
+        TemplateUrlService templateUrlService = TemplateUrlService.getInstance();
+        if (!templateUrlService.isLoaded()) {
+            mHasLoadObserver = true;
+            templateUrlService.registerLoadListener(this);
+            templateUrlService.load();
+            return;  // Flow continues in onTemplateUrlServiceLoaded below.
+        }
+
+        List<TemplateUrl> templateUrls = templateUrlService.getSearchEngines();
+        boolean searchEnginesChanged = templateUrls.size()
+                != mPrepopulatedSearchEngines.size() + mRecentSearchEngines.size();
+        if (!searchEnginesChanged) {
+            for (int i = 0; i < templateUrls.size(); i++) {
+                TemplateUrl templateUrl = templateUrls.get(i);
+                if (!mPrepopulatedSearchEngines.contains(templateUrl)
+                        && !mRecentSearchEngines.contains(templateUrl)) {
+                    searchEnginesChanged = true;
+                    break;
+                }
+            }
+        }
+        if (searchEnginesChanged) initializeSearchEngineGroups(templateUrls);
+
+        int defaultSearchEngineIndex = templateUrlService.getDefaultSearchEngineIndex();
 
         // Convert the TemplateUrl index into an index of mSearchEngines.
         mSelectedSearchEnginePosition = -1;
@@ -138,9 +181,14 @@
             }
         }
 
+        if (mSelectedSearchEnginePosition == -1) {
+            throw new IllegalStateException(
+                    "Default search engine index did not match any available search engines.");
+        }
+
         mInitialEnginePosition = mSelectedSearchEnginePosition;
 
-        TemplateUrlService.getInstance().setSearchEngine(toKeyword(mSelectedSearchEnginePosition));
+        notifyDataSetChanged();
     }
 
     private String toKeyword(int position) {
@@ -295,8 +343,13 @@
     @Override
     public void onTemplateUrlServiceLoaded() {
         TemplateUrlService.getInstance().unregisterLoadListener(this);
-        initEntries();
-        notifyDataSetChanged();
+        mHasLoadObserver = false;
+        refreshData();
+    }
+
+    @Override
+    public void onTemplateURLServiceChanged() {
+        refreshData();
     }
 
     // OnClickListener:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEnginePreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEnginePreference.java
index 28f9854..2bcbcd4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEnginePreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEnginePreference.java
@@ -56,11 +56,14 @@
     }
 
     @Override
-    public void onResume() {
-        super.onResume();
-        /**
-         * Handle UI update when location setting for a search engine is changed.
-         */
-        mSearchEngineAdapter.notifyDataSetChanged();
+    public void onStart() {
+        super.onStart();
+        mSearchEngineAdapter.start();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mSearchEngineAdapter.stop();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/OWNERS
deleted file mode 100644
index fc6a6ef..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ianwen@chromium.org
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/OWNERS
index 69d7926d..0fa757c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/OWNERS
@@ -1,2 +1 @@
-ianwen@chromium.org
 twellington@chromium.org
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS
index fc6a6ef..05d32ba 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/OWNERS
@@ -1 +1 @@
-ianwen@chromium.org
\ No newline at end of file
+twellington@chromium.org
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java
index ce176cf..0912c41 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java
@@ -91,8 +91,8 @@
         mMediaImageManager.onFinishDownloadImage(
                 REQUEST_ID_1, 200, IMAGE_URL_1, mBitmaps, mOriginalImageSizes);
 
-        verify(mCallback).onImageDownloaded(isNotNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback).onImageDownloaded((Bitmap) isNotNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -115,8 +115,8 @@
                 .downloadImage(eq(IMAGE_URL_1), eq(false),
                         eq(MediaImageManager.MAX_BITMAP_SIZE_FOR_DOWNLOAD), eq(false),
                         eq(mMediaImageManager));
-        verify(mCallback, times(1)).onImageDownloaded(isNotNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback, times(1)).onImageDownloaded((Bitmap) isNotNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -140,7 +140,7 @@
                 .downloadImage(eq(IMAGE_URL_1), eq(false),
                         eq(MediaImageManager.MAX_BITMAP_SIZE_FOR_DOWNLOAD), eq(false),
                         eq(mMediaImageManager));
-        verify(mCallback, times(1)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback, times(1)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -170,8 +170,8 @@
                 .downloadImage(eq(IMAGE_URL_2), eq(false),
                         eq(MediaImageManager.MAX_BITMAP_SIZE_FOR_DOWNLOAD), eq(false),
                         eq(mMediaImageManager));
-        verify(mCallback, times(2)).onImageDownloaded(isNotNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback, times(2)).onImageDownloaded((Bitmap) isNotNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -205,8 +205,8 @@
                         eq(MediaImageManager.MAX_BITMAP_SIZE_FOR_DOWNLOAD), eq(false),
                         eq(mMediaImageManager));
 
-        verify(mCallback, times(1)).onImageDownloaded(isNotNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback, times(1)).onImageDownloaded((Bitmap) isNotNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -217,8 +217,8 @@
         mMediaImageManager.onFinishDownloadImage(
                 REQUEST_ID_1, 200, IMAGE_URL_1, mBitmaps, mOriginalImageSizes);
 
-        verify(mCallback, times(1)).onImageDownloaded(isNotNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback, times(1)).onImageDownloaded((Bitmap) isNotNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -227,8 +227,8 @@
         mMediaImageManager.onFinishDownloadImage(
                 REQUEST_ID_2, 200, IMAGE_URL_1, mBitmaps, mOriginalImageSizes);
 
-        verify(mCallback, times(0)).onImageDownloaded(isNotNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNull(Bitmap.class));
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNotNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNull());
     }
 
     @Test
@@ -242,8 +242,8 @@
         verify(mWebContents, times(0))
                 .downloadImage(anyString(), anyBoolean(), anyInt(), anyBoolean(),
                         any(MediaImageManager.class));
-        verify(mCallback).onImageDownloaded(isNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNotNull(Bitmap.class));
+        verify(mCallback).onImageDownloaded((Bitmap) isNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNotNull());
     }
 
     @Test
@@ -260,8 +260,8 @@
         mMediaImageManager.onFinishDownloadImage(
                 REQUEST_ID_1, 200, IMAGE_URL_1, mBitmaps, mOriginalImageSizes);
 
-        verify(mCallback).onImageDownloaded(isNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNotNull(Bitmap.class));
+        verify(mCallback).onImageDownloaded((Bitmap) isNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNotNull());
     }
 
     @Test
@@ -270,8 +270,8 @@
         mMediaImageManager.onFinishDownloadImage(
                 REQUEST_ID_1, 404, IMAGE_URL_1, new ArrayList<Bitmap>(), new ArrayList<Rect>());
 
-        verify(mCallback).onImageDownloaded(isNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNotNull(Bitmap.class));
+        verify(mCallback).onImageDownloaded((Bitmap) isNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNotNull());
     }
 
     @Test
@@ -282,8 +282,8 @@
         verify(mWebContents, times(0))
                 .downloadImage(anyString(), anyBoolean(), anyInt(), anyBoolean(),
                         any(MediaImageManager.class));
-        verify(mCallback).onImageDownloaded(isNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNotNull(Bitmap.class));
+        verify(mCallback).onImageDownloaded((Bitmap) isNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNotNull());
     }
 
     @Test
@@ -293,7 +293,7 @@
         verify(mWebContents, times(0))
                 .downloadImage(anyString(), anyBoolean(), anyInt(), anyBoolean(),
                         any(MediaImageManager.class));
-        verify(mCallback).onImageDownloaded(isNull(Bitmap.class));
-        verify(mCallback, times(0)).onImageDownloaded(isNotNull(Bitmap.class));
+        verify(mCallback).onImageDownloaded((Bitmap) isNull());
+        verify(mCallback, times(0)).onImageDownloaded((Bitmap) isNotNull());
     }
 }
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java
index 7ca06c3..b0f9295 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java
@@ -11,7 +11,6 @@
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout;
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout.ContentSuggestionsCardLayoutEnum;
 import org.chromium.chrome.browser.ntp.snippets.FakeSuggestionsSource;
-import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder;
@@ -41,15 +40,6 @@
     }
 
     /**
-     * @deprecated The hardcoded category is a common source of bugs. Prefer
-     * {@link #createDummySuggestions(int, int)}
-     */
-    @Deprecated
-    public static List<SnippetArticle> createDummySuggestions(int count) {
-        return createDummySuggestions(count, KnownCategories.BOOKMARKS);
-    }
-
-    /**
      * Registers a category according to the provided category info.
      * @return the suggestions added to the newly registered category.
      */
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index f07229dc..273dd94 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -91,7 +91,7 @@
     @Feature({"Ntp"})
     @EnableFeatures(ChromeFeatureList.NTP_SUGGESTIONS_SECTION_DISMISSAL)
     public void testDismissSibling() {
-        List<SnippetArticle> snippets = createDummySuggestions(3);
+        List<SnippetArticle> snippets = createDummySuggestions(3, TEST_CATEGORY_ID);
         SuggestionsSection section = createSectionWithReloadAction(true);
 
         section.setStatus(CategoryStatus.AVAILABLE);
@@ -117,7 +117,7 @@
     @Feature({"Ntp"})
     @EnableFeatures({})
     public void testDismissSiblingWithSectionDismissalDisabled() {
-        List<SnippetArticle> snippets = createDummySuggestions(3);
+        List<SnippetArticle> snippets = createDummySuggestions(3, TEST_CATEGORY_ID);
         SuggestionsSection section = createSectionWithReloadAction(true);
 
         section.setStatus(CategoryStatus.AVAILABLE);
@@ -143,7 +143,8 @@
     @Feature({"Ntp"})
     public void testAddSuggestionsNotification() {
         final int suggestionCount = 5;
-        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount);
+        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
+                TEST_CATEGORY_ID);
 
         SuggestionsSection section = createSectionWithReloadAction(false);
         // Simulate initialisation by the adapter. Here we don't care about the notifications, since
@@ -163,7 +164,8 @@
     @Feature({"Ntp"})
     public void testSetStatusNotification() {
         final int suggestionCount = 5;
-        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount);
+        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
+                TEST_CATEGORY_ID);
         SuggestionsSection section = createSectionWithReloadAction(false);
 
         // Simulate initialisation by the adapter. Here we don't care about the notifications, since
@@ -200,7 +202,8 @@
     @Feature({"Ntp"})
     public void testRemoveSuggestionNotification() {
         final int suggestionCount = 2;
-        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount);
+        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
+                TEST_CATEGORY_ID);
 
         SuggestionsSection section = createSectionWithReloadAction(false);
         section.setStatus(CategoryStatus.AVAILABLE);
@@ -223,7 +226,8 @@
     @Feature({"Ntp"})
     public void testRemoveSuggestionNotificationWithButton() {
         final int suggestionCount = 2;
-        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount);
+        List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
+                TEST_CATEGORY_ID);
 
         SuggestionsCategoryInfo info =
                 new CategoryInfoBuilder(TEST_CATEGORY_ID)
@@ -270,7 +274,8 @@
     @Feature({"Ntp"})
     public void testOfflineStatus() {
         final int suggestionCount = 3;
-        final List<SnippetArticle> snippets = createDummySuggestions(suggestionCount);
+        final List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
+                TEST_CATEGORY_ID);
         assertNull(snippets.get(0).getOfflinePageOfflineId());
         assertNull(snippets.get(1).getOfflinePageOfflineId());
         assertNull(snippets.get(2).getOfflinePageOfflineId());
@@ -304,7 +309,8 @@
     @Feature({"Ntp"})
     public void testOfflineStatusIgnoredIfDetached() {
         final int suggestionCount = 2;
-        final List<SnippetArticle> suggestions = createDummySuggestions(suggestionCount);
+        final List<SnippetArticle> suggestions = createDummySuggestions(suggestionCount,
+                TEST_CATEGORY_ID);
         assertNull(suggestions.get(0).getOfflinePageOfflineId());
         assertNull(suggestions.get(1).getOfflinePageOfflineId());
 
@@ -348,8 +354,8 @@
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_VIEW_ALL);
 
-        section.setSuggestions(
-                createDummySuggestions(3), CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
+        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_VIEW_ALL);
@@ -373,8 +379,8 @@
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_RELOAD);
 
-        section.setSuggestions(
-                createDummySuggestions(3), CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
+        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_FETCH_MORE);
@@ -393,8 +399,8 @@
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_RELOAD);
 
-        section.setSuggestions(
-                createDummySuggestions(3), CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
+        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         assertFalse(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_NONE);
@@ -413,8 +419,8 @@
         assertFalse(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_NONE);
 
-        section.setSuggestions(
-                createDummySuggestions(3), CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
+        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_FETCH_MORE);
@@ -433,8 +439,8 @@
         assertFalse(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_NONE);
 
-        section.setSuggestions(
-                createDummySuggestions(3), CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
+        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         assertFalse(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_NONE);
@@ -447,8 +453,8 @@
         SuggestionsCategoryInfo info = spy(
                 new CategoryInfoBuilder(TEST_CATEGORY_ID).withMoreAction().showIfEmpty().build());
         SuggestionsSection section = createSection(info);
-        section.setSuggestions(createDummySuggestions(suggestionCount), CategoryStatus.AVAILABLE,
-                /* replaceExisting = */ true);
+        section.setSuggestions(createDummySuggestions(suggestionCount, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
         assertFalse(section.getProgressItemForTesting().isVisible());
 
         // Tap the button
@@ -456,8 +462,8 @@
         assertTrue(section.getProgressItemForTesting().isVisible());
 
         // Simulate receiving suggestions.
-        section.setSuggestions(createDummySuggestions(suggestionCount), CategoryStatus.AVAILABLE,
-                /* replaceExisting = */ false);
+        section.setSuggestions(createDummySuggestions(suggestionCount, TEST_CATEGORY_ID),
+                CategoryStatus.AVAILABLE, /* replaceExisting = */ false);
         assertFalse(section.getProgressItemForTesting().isVisible());
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
index 6ab4d4ac..a4cfab3 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyListOf;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.spy;
@@ -126,9 +125,10 @@
 
         answerNativeGetAllPages(itemCount);
         Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
-
         mBridge.getAllPages(callback);
-        verify(callback, times(1)).onResult(anyListOf(OfflinePageItem.class));
+
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        verify(callback, times(1)).onResult(itemList);
     }
 
     /**
@@ -141,9 +141,12 @@
 
         answerNativeGetAllPages(itemCount);
         Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
-
         mBridge.getAllPages(callback);
-        verify(callback, times(1)).onResult(anyListOf(OfflinePageItem.class));
+
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        verify(callback, times(1)).onResult(itemList);
     }
 
     /**
@@ -153,14 +156,15 @@
     @Feature({"OfflinePages"})
     public void testGetPagesByClientIds_listOfClientIdsEmpty() {
         final int itemCount = 0;
-        answerGetPagesByClientIds(itemCount);
 
+        answerGetPagesByClientIds(itemCount);
         Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
         ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
         List<ClientId> list = new ArrayList<>();
         mBridge.getPagesByClientIds(list, callback);
 
-        verify(callback, times(1)).onResult(anyListOf(OfflinePageItem.class));
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        verify(callback, times(1)).onResult(itemList);
     }
 
     /**
@@ -170,8 +174,8 @@
     @Feature({"OfflinePages"})
     public void testGetPagesByClientIds() {
         final int itemCount = 2;
-        answerGetPagesByClientIds(itemCount);
 
+        answerGetPagesByClientIds(itemCount);
         Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
         ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
         List<ClientId> list = new ArrayList<>();
@@ -179,7 +183,10 @@
         list.add(secondClientId);
         mBridge.getPagesByClientIds(list, callback);
 
-        verify(callback, times(1)).onResult(anyListOf(OfflinePageItem.class));
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        verify(callback, times(1)).onResult(itemList);
     }
 
     /**
@@ -189,8 +196,8 @@
     @Feature({"OfflinePages"})
     public void testDeletePagesByClientIds_listOfClientIdsEmpty() {
         final int itemCount = 0;
-        answerDeletePagesByClientIds(itemCount);
 
+        answerDeletePagesByClientIds(itemCount);
         Callback<Integer> callback = createDeletePageCallback();
         ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
         List<ClientId> list = new ArrayList<>();
@@ -206,8 +213,8 @@
     @Feature({"OfflinePages"})
     public void testDeletePagesByClientIds() {
         final int itemCount = 2;
-        answerDeletePagesByClientIds(itemCount);
 
+        answerDeletePagesByClientIds(itemCount);
         Callback<Integer> callback = createDeletePageCallback();
         ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
         List<ClientId> list = new ArrayList<>();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java
index fedead47..f9bc08c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java
@@ -333,6 +333,6 @@
     }
 
     private static Bitmap createBitmap() {
-        return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_4444);
+        return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
     }
 }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index cedf1277..9289040 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -15645,6 +15645,16 @@
       </message>
     </if>
     </if>
+
+    <if expr="is_win">
+      <!-- Custom draw the Windows 10 titlebar. crbug.com/505013 -->
+      <message name="IDS_FLAGS_WINDOWS10_CUSTOM_TITLEBAR_NAME" desc="Name of the flag that enables custom drawing of the Windows 10 titlebar.">
+        Custom-drawn Windows 10 Titlebar
+      </message>
+      <message name="IDS_FLAGS_WINDOWS10_CUSTOM_TITLEBAR_DESCRIPTION" desc="Description for the flag that enables custom drawing of the Windows 10 titlebar.">
+        If enabled, Chrome will draw the titlebar and caption buttons instead of deferring to Windows.
+      </message>
+    </if>
   </messages>
  </release>
 </grit>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 156667e..9a6f7b0b 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2195,6 +2195,13 @@
      IDS_NATIVE_ANDROID_HISTORY_MANAGER_DESCRIPTION, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kNativeAndroidHistoryManager)},
 #endif  // OS_ANDROID
+
+#if defined(OS_WIN)
+    {"windows10-custom-titlebar", IDS_FLAGS_WINDOWS10_CUSTOM_TITLEBAR_NAME,
+     IDS_FLAGS_WINDOWS10_CUSTOM_TITLEBAR_DESCRIPTION, kOsWin,
+     SINGLE_VALUE_TYPE(switches::kWindows10CustomTitlebar)},
+#endif  // OS_WIN
+
     {"enable-faster-location-reload", IDS_FLAGS_FASTER_LOCATION_RELOAD_NAME,
      IDS_FLAGS_FASTER_LOCATION_RELOAD_DESCRIPTION, kOsAll,
      FEATURE_VALUE_TYPE(features::kFasterLocationReload)},
diff --git a/chrome/browser/android/history_report/OWNERS b/chrome/browser/android/history_report/OWNERS
index 5816e5fe..047e38a 100644
--- a/chrome/browser/android/history_report/OWNERS
+++ b/chrome/browser/android/history_report/OWNERS
@@ -1 +1 @@
-ianwen@chromium.org
+nyquist@chromium.org
diff --git a/chrome/browser/android/locale/OWNERS b/chrome/browser/android/locale/OWNERS
index 5816e5fe..d758086 100644
--- a/chrome/browser/android/locale/OWNERS
+++ b/chrome/browser/android/locale/OWNERS
@@ -1 +1 @@
-ianwen@chromium.org
+mariakhomenko@chromium.org
diff --git a/chrome/browser/android/offline_pages/recent_tab_helper.cc b/chrome/browser/android/offline_pages/recent_tab_helper.cc
index 65871f0..1be59e3 100644
--- a/chrome/browser/android/offline_pages/recent_tab_helper.cc
+++ b/chrome/browser/android/offline_pages/recent_tab_helper.cc
@@ -56,10 +56,33 @@
 
 using PageQuality = SnapshotController::PageQuality;
 
-bool RecentTabHelper::SnapshotProgressInfo::IsForLastN() {
-  // A last_n snapshot always has an invalid request id.
-  return request_id == OfflinePageModel::kInvalidOfflineId;
-}
+// Keeps client_id/request_id that will be used for the offline snapshot.
+struct RecentTabHelper::SnapshotProgressInfo {
+ public:
+  // For a downloads snapshot request, where the |request_id| is defined.
+  SnapshotProgressInfo(const ClientId& client_id, int64_t request_id)
+      : client_id(client_id), request_id(request_id) {}
+
+  // For a last_n snapshot request.
+  explicit SnapshotProgressInfo(const ClientId& client_id)
+      : client_id(client_id) {}
+
+  bool IsForLastN() { return client_id.name_space == kLastNNamespace; }
+
+  // The ClientID to go with the offline page.
+  ClientId client_id;
+
+  // Id of the suspended request in Background Offliner. Used to un-suspend
+  // the request if the capture of the current page was not possible (e.g.
+  // the user navigated to another page before current one was loaded).
+  // 0 if this is a "last_n" info.
+  int64_t request_id = OfflinePageModel::kInvalidOfflineId;
+
+  // Expected snapshot quality should the saving succeed. This value is only
+  // valid for successfully saved snapshots.
+  SnapshotController::PageQuality expected_page_quality =
+      SnapshotController::PageQuality::POOR;
+};
 
 RecentTabHelper::RecentTabHelper(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
@@ -79,30 +102,47 @@
 
 void RecentTabHelper::ObserveAndDownloadCurrentPage(
     const ClientId& client_id, int64_t request_id) {
+  // Note: as this implementation only supports one client namespace, enforce
+  // that the call is from Downloads.
+  DCHECK_EQ(kDownloadNamespace, client_id.name_space);
   auto new_downloads_snapshot_info =
       base::MakeUnique<SnapshotProgressInfo>(client_id, request_id);
 
   // If this tab helper is not enabled, immediately give the job back to
   // RequestCoordinator.
   if (!EnsureInitialized()) {
-    ReportDownloadStatusToRequestCoordinator(new_downloads_snapshot_info.get());
+    ReportDownloadStatusToRequestCoordinator(new_downloads_snapshot_info.get(),
+                                             false);
     return;
   }
 
-  // TODO(carlosk): This is a good moment check if a snapshot is currently being
-  // generated. This would allow the early cancellation of this request (without
-  // incurring in scheduling a background download).
+  // If there is an ongoing snapshot request, completely ignore this one and
+  // cancel the Background Offliner request.
+  // TODO(carlosk): it might be better to make the decision to schedule or not
+  // the background request here. See https://crbug.com/686165.
+  // TODO(carlosk): there is an edge case that happens when the ongoing request
+  // was automatically and transparently scheduled by a navigation event and
+  // this call happens due to the user pressing the download button. The user's
+  // request to download the page will be immediately dismissed. See
+  // https://crbug.com/686283.
+  if (downloads_ongoing_snapshot_info_) {
+    ReportDownloadStatusToRequestCoordinator(new_downloads_snapshot_info.get(),
+                                             true);
+    return;
+  }
 
   // Stores the new snapshot info.
-  downloads_latest_snapshot_info_ = std::move(new_downloads_snapshot_info);
+  downloads_ongoing_snapshot_info_ = std::move(new_downloads_snapshot_info);
 
   // If the page is not yet ready for a snapshot return now as it will be
   // started later, once page loading advances.
-  if (PageQuality::POOR == snapshot_controller_->current_page_quality())
+  if (PageQuality::POOR == snapshot_controller_->current_page_quality()) {
+    downloads_snapshot_on_hold_ = true;
     return;
+  }
 
   // Otherwise start saving the snapshot now.
-  SaveSnapshotForDownloads();
+  SaveSnapshotForDownloads(false);
 }
 
 // Initialize lazily. It needs TabAndroid for initialization, which is also a
@@ -145,16 +185,16 @@
   if (!EnsureInitialized())
     return;
 
-  // We navigated to a different page, lets report progress to Background
-  // Offliner.
-  if (downloads_latest_snapshot_info_)
+  // If there is an ongoing downloads request, lets allow Background Offliner to
+  // continue downloading this page.
+  if (downloads_ongoing_snapshot_info_) {
     ReportDownloadStatusToRequestCoordinator(
-        downloads_latest_snapshot_info_.get());
+        downloads_ongoing_snapshot_info_.get(), false);
+  }
 
   // Cancel any and all in flight snapshot tasks from the previous page.
-  weak_ptr_factory_.InvalidateWeakPtrs();
-  downloads_latest_snapshot_info_.reset();
-  last_n_ongoing_snapshot_info_.reset();
+  CancelInFlightSnapshots();
+  downloads_snapshot_on_hold_ = false;
 
   // New navigation, new snapshot session.
   snapshot_url_ = web_contents()->GetLastCommittedURL();
@@ -187,12 +227,13 @@
 }
 
 void RecentTabHelper::WebContentsDestroyed() {
-  // WebContents (and maybe Tab) is destroyed, report status to Offliner.
-  if (downloads_latest_snapshot_info_)
+  // If there is an ongoing downloads request, lets allow Background Offliner to
+  // continue downloading this page.
+  if (downloads_ongoing_snapshot_info_)
     ReportDownloadStatusToRequestCoordinator(
-        downloads_latest_snapshot_info_.get());
+        downloads_ongoing_snapshot_info_.get(), false);
   // And cancel any ongoing snapshots.
-  weak_ptr_factory_.InvalidateWeakPtrs();
+  CancelInFlightSnapshots();
 }
 
 // TODO(carlosk): this method is also called when the tab is being closed, when
@@ -218,6 +259,7 @@
 
   last_n_ongoing_snapshot_info_ =
       base::MakeUnique<SnapshotProgressInfo>(GetRecentPagesClientId());
+  DCHECK(last_n_ongoing_snapshot_info_->IsForLastN());
   DCHECK(snapshots_enabled_);
   // Remove previously captured pages for this tab.
   page_model_->GetOfflineIdsForClientId(
@@ -232,28 +274,49 @@
 void RecentTabHelper::StartSnapshot() {
   DCHECK_NE(PageQuality::POOR, snapshot_controller_->current_page_quality());
 
-  // This is a navigation based snapshot request so check that snapshots are
-  // both enabled and there is a downloads request for one.
-  // TODO(carlosk): This is a good moment to add the check for an ongoing
-  // snapshot that could trigger the early cancellation of this request.
-  if (snapshots_enabled_ && downloads_latest_snapshot_info_)
-    SaveSnapshotForDownloads();
-  else
-    snapshot_controller_->PendingSnapshotCompleted();
+  // As long as snapshots are enabled for this tab, there are two situations
+  // that allow for a navigation event to start a snapshot:
+  // 1) There is a request on hold waiting for the page to be minimally loaded.
+  if (snapshots_enabled_ && downloads_snapshot_on_hold_) {
+    downloads_snapshot_on_hold_ = false;
+    SaveSnapshotForDownloads(false);
+    return;
+  }
+
+  // 2) There's no ongoing snapshot and a previous one was saved with lower
+  // expected quality than what would be possible now.
+  if (snapshots_enabled_ &&
+      (!downloads_ongoing_snapshot_info_ &&
+       downloads_latest_saved_snapshot_info_ &&
+       downloads_latest_saved_snapshot_info_->expected_page_quality <
+           snapshot_controller_->current_page_quality())) {
+    SaveSnapshotForDownloads(true);
+    return;
+  }
+
+  // Notify the controller that a snapshot was not started.
+  snapshot_controller_->PendingSnapshotCompleted();
 }
 
-// TODO(carlosk): There is still the possibility of overlapping snapshots with
-// some combinations of calls to ObserveAndDownloadCurrentPage and
-// StartSnapshot. It won't cause side effects beyond wasted resources and will
-// be dealt with later.
-void RecentTabHelper::SaveSnapshotForDownloads() {
+void RecentTabHelper::SaveSnapshotForDownloads(bool replace_latest) {
   DCHECK_NE(PageQuality::POOR, snapshot_controller_->current_page_quality());
-  DCHECK(downloads_latest_snapshot_info_);
 
-  // Requests the deletion of a potentially existing previous snapshot of this
-  // page.
-  std::vector<int64_t> ids{downloads_latest_snapshot_info_->request_id};
-  ContinueSnapshotWithIdsToPurge(downloads_latest_snapshot_info_.get(), ids);
+  if (replace_latest) {
+    // Start by requesting the deletion of the existing previous snapshot of
+    // this page.
+    DCHECK(downloads_latest_saved_snapshot_info_);
+    DCHECK(!downloads_ongoing_snapshot_info_);
+    downloads_ongoing_snapshot_info_ = base::MakeUnique<SnapshotProgressInfo>(
+        downloads_latest_saved_snapshot_info_->client_id,
+        downloads_latest_saved_snapshot_info_->request_id);
+    std::vector<int64_t> ids{downloads_latest_saved_snapshot_info_->request_id};
+    ContinueSnapshotWithIdsToPurge(downloads_ongoing_snapshot_info_.get(), ids);
+  } else {
+    // Otherwise go straight to saving the page.
+    DCHECK(downloads_ongoing_snapshot_info_);
+    ContinueSnapshotAfterPurge(downloads_ongoing_snapshot_info_.get(),
+                               OfflinePageModel::DeletePageResult::SUCCESS);
+  }
 }
 
 // This is the 1st step of a sequence of async operations chained through
@@ -263,9 +326,9 @@
 // 3) Snapshot the current web contents.
 // 4) Notify requesters about the final result of the operation.
 //
-// For last_n requests the sequence is started in 1); for downloads it starts in
-// 2). Step 4) might be called anytime during the chain for early termination in
-// case of errors.
+// For last_n requests the sequence is always started in 1). For downloads it
+// starts in either 2) or 3). Step 4) might be called anytime during the chain
+// for early termination in case of errors.
 void RecentTabHelper::ContinueSnapshotWithIdsToPurge(
     SnapshotProgressInfo* snapshot_info,
     const std::vector<int64_t>& page_ids) {
@@ -281,7 +344,7 @@
     OfflinePageModel::DeletePageResult result) {
   DCHECK_EQ(snapshot_url_, web_contents()->GetLastCommittedURL());
   if (result != OfflinePageModel::DeletePageResult::SUCCESS) {
-    ReportSnapshotCompleted(snapshot_info);
+    ReportSnapshotCompleted(snapshot_info, false);
     return;
   }
 
@@ -303,32 +366,40 @@
                                        int64_t offline_id) {
   DCHECK(snapshot_info->IsForLastN() ||
          snapshot_info->request_id == offline_id);
-  snapshot_info->page_snapshot_completed = (result == SavePageResult::SUCCESS);
-  ReportSnapshotCompleted(snapshot_info);
+  ReportSnapshotCompleted(snapshot_info, result == SavePageResult::SUCCESS);
 }
 
 // Note: this is the final step in the chain of callbacks and it's where the
 // behavior is different depending on this being a last_n or downloads snapshot.
 void RecentTabHelper::ReportSnapshotCompleted(
-    SnapshotProgressInfo* snapshot_info) {
+    SnapshotProgressInfo* snapshot_info,
+    bool success) {
   if (snapshot_info->IsForLastN()) {
     DCHECK_EQ(snapshot_info, last_n_ongoing_snapshot_info_.get());
-    if (snapshot_info->page_snapshot_completed)
+    if (success)
       last_n_latest_saved_quality_ = snapshot_info->expected_page_quality;
     last_n_ongoing_snapshot_info_.reset();
     return;
   }
 
+  DCHECK_EQ(snapshot_info, downloads_ongoing_snapshot_info_.get());
   snapshot_controller_->PendingSnapshotCompleted();
   // Tell RequestCoordinator how the request should be processed further.
-  ReportDownloadStatusToRequestCoordinator(snapshot_info);
+  ReportDownloadStatusToRequestCoordinator(snapshot_info, success);
+  if (success) {
+    downloads_latest_saved_snapshot_info_ =
+        std::move(downloads_ongoing_snapshot_info_);
+  } else {
+    downloads_ongoing_snapshot_info_.reset();
+  }
 }
 
 // Note: we cannot assume that snapshot_info == downloads_latest_snapshot_info_
 // because further calls made to ObserveAndDownloadCurrentPage will replace
 // downloads_latest_snapshot_info_ with a new instance.
 void RecentTabHelper::ReportDownloadStatusToRequestCoordinator(
-    SnapshotProgressInfo* snapshot_info) {
+    SnapshotProgressInfo* snapshot_info,
+    bool cancel_background_request) {
   DCHECK(snapshot_info);
   DCHECK(!snapshot_info->IsForLastN());
 
@@ -341,7 +412,7 @@
   // It is OK to call these methods more then once, depending on
   // number of snapshots attempted in this tab helper. If the request_id is not
   // in the list of RequestCoordinator, these calls have no effect.
-  if (snapshot_info->page_snapshot_completed)
+  if (cancel_background_request)
     request_coordinator->MarkRequestCompleted(snapshot_info->request_id);
   else
     request_coordinator->EnableForOffliner(snapshot_info->request_id,
@@ -352,4 +423,11 @@
   return ClientId(kLastNNamespace, tab_id_);
 }
 
+void RecentTabHelper::CancelInFlightSnapshots() {
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  downloads_ongoing_snapshot_info_.reset();
+  downloads_latest_saved_snapshot_info_.reset();
+  last_n_ongoing_snapshot_info_.reset();
+}
+
 }  // namespace offline_pages
diff --git a/chrome/browser/android/offline_pages/recent_tab_helper.h b/chrome/browser/android/offline_pages/recent_tab_helper.h
index 6aa442e..b5f095f 100644
--- a/chrome/browser/android/offline_pages/recent_tab_helper.h
+++ b/chrome/browser/android/offline_pages/recent_tab_helper.h
@@ -57,50 +57,33 @@
   };
   void SetDelegate(std::unique_ptr<RecentTabHelper::Delegate> delegate);
 
-  // Support for Download Page feature. The Download Page button does this:
-  // 1. Creates suspended request for Background Offliner.
-  // 2. Calls this method with properly filled ClientId.
-  // 3. This tab helper observes the page load and captures the page
-  //    if the load progresses far enough, as indicated by SnapshotController.
-  // 4. If capture is successful, this tab helper removes the suspended request.
-  //    Otherwise (navigation to other page, close tab) tab helper un-suspends
-  //    the request so the Background Offliner starts working on it.
-  // 5. If Chrome is killed at any point, next time Background Offliner loads
-  //    it converts all suspended requests from last session into active.
+  // Creates a request to download the current page with a properly filled
+  // |client_id| and valid |request_id| issued by RequestCoordinator from a
+  // suspended request. This method might be called multiple times for the same
+  // page at any point after its navigation commits. There are some important
+  // points about how requests are handled:
+  // a) While there is an ongoing request, new requests are ignored (no
+  //    overlapping snapshots).
+  // b) The page has to be sufficiently loaded to be considerer of minimum
+  //    quality for the request to be started immediately.
+  // c) Any calls made before the page is considered to have minimal quality
+  //    will be scheduled to be executed once that happens. The scheduled
+  //    request is considered "ongoing" for a) purposes.
+  // d) If the save operation is successful the dormant request with
+  //    RequestCoordinator is canceled; otherwise it is resumed. This logic is
+  //    robust to crashes.
+  // e) At the moment the page reaches high quality, if there was a successful
+  //    snapshot saved at a lower quality then a new snapshot is automatically
+  //    requested to replace it.
+  // Note #1: Page quality is determined by SnapshotController and is based on
+  // its assessment of "how much loaded" it is.
+  // Note #2: Currently this method only accepts download requests from the
+  // downloads namespace.
   void ObserveAndDownloadCurrentPage(const ClientId& client_id,
                                      int64_t request_id);
 
  private:
-  // Keeps client_id/request_id that will be used for the offline snapshot.
-  struct SnapshotProgressInfo {
-   public:
-    // For a downloads snapshot request, where the |request_id| is defined.
-    SnapshotProgressInfo(const ClientId& client_id, int64_t request_id)
-        : client_id(client_id), request_id(request_id) {}
-
-    // For a last_n snapshot request.
-    explicit SnapshotProgressInfo(const ClientId& client_id)
-        : client_id(client_id) {}
-
-    bool IsForLastN();
-
-    // The ClientID to go with the offline page.
-    ClientId client_id;
-
-    // Id of the suspended request in Background Offliner. Used to un-suspend
-    // the request if the capture of the current page was not possible (e.g.
-    // the user navigated to another page before current one was loaded).
-    // 0 if this is a "last_n" info.
-    int64_t request_id = OfflinePageModel::kInvalidOfflineId;
-
-    // True if there was at least one snapshot successfully completed.
-    bool page_snapshot_completed = false;
-
-    // Expected snapshot quality should the saving succeed. This value is only
-    // valid if |page_snapshot_completed| is true.
-    SnapshotController::PageQuality expected_page_quality =
-        SnapshotController::PageQuality::POOR;
-  };
+  struct SnapshotProgressInfo;
 
   explicit RecentTabHelper(content::WebContents* web_contents);
   friend class content::WebContentsUserData<RecentTabHelper>;
@@ -113,11 +96,14 @@
   void SavePageCallback(SnapshotProgressInfo* snapshot_info,
                         OfflinePageModel::SavePageResult result,
                         int64_t offline_id);
-  void ReportSnapshotCompleted(SnapshotProgressInfo* snapshot_info);
+  void ReportSnapshotCompleted(SnapshotProgressInfo* snapshot_info,
+                               bool success);
   void ReportDownloadStatusToRequestCoordinator(
-      SnapshotProgressInfo* snapshot_info);
+      SnapshotProgressInfo* snapshot_info,
+      bool cancel_background_request);
   ClientId GetRecentPagesClientId() const;
-  void SaveSnapshotForDownloads();
+  void SaveSnapshotForDownloads(bool replace_latest);
+  void CancelInFlightSnapshots();
 
   // Page model is a service, no ownership. Can be null - for example, in
   // case when tab is in incognito profile.
@@ -127,9 +113,17 @@
   // Not page-specific.
   bool snapshots_enabled_ = false;
 
-  // Snapshot progress information for a downloads triggered request. Null if
-  // downloads is not capturing or hasn't captured the current page.
-  std::unique_ptr<SnapshotProgressInfo> downloads_latest_snapshot_info_;
+  // Snapshot progress information for an ongoing snapshot requested by
+  // downloads. Null if there's no ongoing request.
+  std::unique_ptr<SnapshotProgressInfo> downloads_ongoing_snapshot_info_;
+
+  // This is set to true if the ongoing snapshot for downloads is waiting on the
+  // page to reach a minimal quality level to start.
+  bool downloads_snapshot_on_hold_ = false;
+
+  // Snapshot information for the last successful snapshot requested by
+  // downloads. Null if no successful one has ever completed.
+  std::unique_ptr<SnapshotProgressInfo> downloads_latest_saved_snapshot_info_;
 
   // Snapshot progress information for a last_n triggered request. Null if
   // last_n is not currently capturing the current page.
diff --git a/chrome/browser/android/offline_pages/recent_tab_helper_unittest.cc b/chrome/browser/android/offline_pages/recent_tab_helper_unittest.cc
index c3b1f2f..205db07 100644
--- a/chrome/browser/android/offline_pages/recent_tab_helper_unittest.cc
+++ b/chrome/browser/android/offline_pages/recent_tab_helper_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -97,6 +98,8 @@
   // navigation.
   void NavigateAndCommitTyped(const GURL& url);
 
+  ClientId NewDownloadClientId();
+
   RecentTabHelper* recent_tab_helper() const { return recent_tab_helper_; }
 
   OfflinePageModel* model() const { return model_; }
@@ -254,6 +257,12 @@
   web_contents_tester->CommitPendingNavigation();
 }
 
+ClientId RecentTabHelperTest::NewDownloadClientId() {
+  static int counter = 0;
+  return ClientId(kDownloadNamespace,
+                  std::string("id") + base::IntToString(++counter));
+}
+
 // Checks the test setup.
 TEST_F(RecentTabHelperTest, RecentTabHelperInstanceExists) {
   base::test::ScopedFeatureList scoped_feature_list;
@@ -315,8 +324,8 @@
   recent_tab_helper()->DocumentOnLoadCompletedInMainFrame();
   FastForwardSnapshotController();
   recent_tab_helper()->WasHidden();
-  recent_tab_helper()->ObserveAndDownloadCurrentPage(
-      ClientId("download", "id2"), 123L);
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
+                                                     123L);
   RunUntilIdle();
   EXPECT_TRUE(model()->is_loaded());
   // No page should be captured.
@@ -532,7 +541,7 @@
 
   // First snapshot request by downloads. Two offline pages are expected.
   const int64_t second_offline_id = first_offline_id + 1;
-  const ClientId second_client_id("download", "id2");
+  const ClientId second_client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(second_client_id,
                                                      second_offline_id);
   RunUntilIdle();
@@ -547,7 +556,7 @@
 
   // Second snapshot request by downloads. Three offline pages are expected.
   const int64_t third_offline_id = first_offline_id + 2;
-  const ClientId third_client_id("download", "id2");
+  const ClientId third_client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(third_client_id,
                                                      third_offline_id);
   RunUntilIdle();
@@ -569,8 +578,8 @@
   recent_tab_helper()->DocumentOnLoadCompletedInMainFrame();
   FastForwardSnapshotController();
   recent_tab_helper()->WasHidden();
-  recent_tab_helper()->ObserveAndDownloadCurrentPage(
-      ClientId("download", "id1"), 123L);
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
+                                                     123L);
   RunUntilIdle();
   EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
@@ -590,8 +599,8 @@
   // No page should be captured.
   ASSERT_EQ(0U, GetAllPages().size());
 
-  recent_tab_helper()->ObserveAndDownloadCurrentPage(
-      ClientId("download", "id1"), 123L);
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
+                                                     123L);
   RunUntilIdle();
   // No page should be captured.
   ASSERT_EQ(1U, GetAllPages().size());
@@ -603,7 +612,7 @@
   // Commit the navigation and request the snapshot from downloads. No captures
   // so far.
   NavigateAndCommit(kTestPageUrl);
-  const ClientId client_id("download", "id1");
+  const ClientId client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(client_id, 153L);
   FastForwardSnapshotController();
   RunUntilIdle();
@@ -644,7 +653,7 @@
   EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 
-  const ClientId client_id("download", "id1");
+  const ClientId client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(client_id, 153L);
   RunUntilIdle();
   ASSERT_EQ(1U, GetAllPages().size());
@@ -671,7 +680,7 @@
   EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 
-  const ClientId client_id("download", "id1");
+  const ClientId client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(client_id, 153L);
   RunUntilIdle();
   ASSERT_EQ(1U, GetAllPages().size());
@@ -689,7 +698,7 @@
   FastForwardSnapshotController();
   recent_tab_helper()->WasHidden();
   const int64_t download_offline_id = 153L;
-  const ClientId download_client_id("download", "id1");
+  const ClientId download_client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(download_client_id,
                                                      download_offline_id);
   RunUntilIdle();
@@ -742,4 +751,43 @@
   ASSERT_EQ(1U, GetAllPages().size());
 }
 
+// Simulates multiple download requests and verifies that overlapping requests
+// are ignored.
+TEST_F(RecentTabHelperTest, OverlappingDownloadRequestsAreIgnored) {
+  // Navigates and commits then make two download snapshot requests.
+  NavigateAndCommit(kTestPageUrl);
+  const ClientId client_id_1 = NewDownloadClientId();
+  const int64_t offline_id_1 = 153L;
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(client_id_1, offline_id_1);
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
+                                                     351L);
+
+  // Finish loading the page. Only the first request should be executed.
+  recent_tab_helper()->DocumentOnLoadCompletedInMainFrame();
+  FastForwardSnapshotController();
+  RunUntilIdle();
+  EXPECT_EQ(1U, page_added_count());
+  EXPECT_EQ(0U, model_removed_count());
+  ASSERT_EQ(1U, GetAllPages().size());
+  const OfflinePageItem& fist_page = GetAllPages()[0];
+  EXPECT_EQ(client_id_1, fist_page.client_id);
+  EXPECT_EQ(offline_id_1, fist_page.offline_id);
+
+  // Make two additional download snapshot requests. Again only the first should
+  // generate a snapshot.
+  const ClientId client_id_3 = NewDownloadClientId();
+  const int64_t offline_id_3 = 789L;
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(client_id_3, offline_id_3);
+  recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
+                                                     987L);
+  RunUntilIdle();
+  EXPECT_EQ(2U, page_added_count());
+  EXPECT_EQ(0U, model_removed_count());
+  ASSERT_EQ(2U, GetAllPages().size());
+  const OfflinePageItem* second_page = FindPageForOfflineId(offline_id_3);
+  ASSERT_TRUE(second_page);
+  EXPECT_EQ(client_id_3, second_page->client_id);
+  EXPECT_EQ(offline_id_3, second_page->offline_id);
+}
+
 }  // namespace offline_pages
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 781d198..5576439 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -801,8 +801,6 @@
     "login/screens/network_error.cc",
     "login/screens/network_error.h",
     "login/screens/network_error_view.h",
-    "login/screens/network_model.cc",
-    "login/screens/network_model.h",
     "login/screens/network_screen.cc",
     "login/screens/network_screen.h",
     "login/screens/network_view.h",
diff --git a/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc b/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc
index 01c3c6e..722b2f05 100644
--- a/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc
+++ b/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc
@@ -17,6 +17,7 @@
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/background.h"
+#include "ui/views/bubble/bubble_border.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
 
@@ -31,7 +32,7 @@
 constexpr int kPointMoveDurationInMs = 400;
 constexpr int kPointMoveDurationLongInMs = 500;
 
-const SkColor kExitLabelColor = SkColorSetARGBInline(255, 96, 96, 96);
+const SkColor kExitLabelColor = SkColorSetARGBInline(255, 138, 138, 138);
 constexpr int kExitLabelWidth = 300;
 constexpr int kExitLabelHeight = 20;
 
@@ -70,7 +71,7 @@
 constexpr int kTouchTargetWidth = 64;
 constexpr int kTouchTargetHeight = kTouchTargetWidth + kTouchTargetWidth / 2;
 
-constexpr float kTouchTargetVerticalOffsetFactor = 5.f / 12.f;
+constexpr float kTouchTargetVerticalOffsetFactor = 11.f / 24.f;
 
 const SkColor kTouchTargetInnerCircleColor =
     SkColorSetARGBInline(255, 66, 133, 244);
@@ -278,6 +279,8 @@
   void SetSubLabel(const base::string16& text, const SkColor& color);
 
  private:
+  void UpdateWidth(int updated_width);
+
   base::string16 label_text_;
   base::string16 sublabel_text_;
 
@@ -286,8 +289,14 @@
 
   const int border_radius_;
 
+  int base_border_;
+
+  int arrow_width_;
+
   int horizontal_offset_;
 
+  gfx::Rect rounded_rect_bounds_;
+
   gfx::FontList label_font_list_;
   gfx::FontList sublabel_font_list_;
 
@@ -301,16 +310,34 @@
 
 HintBox::HintBox(const gfx::Rect& bounds, int border_radius)
     : border_radius_(border_radius) {
-  SetBoundsRect(bounds);
+  SetBorder(base::MakeUnique<views::BubbleBorder>(
+      base::i18n::IsRTL() ? views::BubbleBorder::RIGHT_CENTER
+                          : views::BubbleBorder::LEFT_CENTER,
+      views::BubbleBorder::NO_SHADOW_OPAQUE_BORDER, SK_ColorWHITE));
+
+  arrow_width_ = (GetInsets().right() - GetInsets().left()) *
+                 (base::i18n::IsRTL() ? 1 : -1);
+
+  // Border on all sides are the same except on the side of the arrow, in which
+  // case the width of the arrow is additional.
+  base_border_ = base::i18n::IsRTL() ? GetInsets().left() : GetInsets().right();
+
+  SetBounds(bounds.x(), bounds.y() - base_border_,
+            bounds.width() + 2 * base_border_ + arrow_width_,
+            bounds.height() + 2 * base_border_);
+
+  rounded_rect_bounds_ = GetLocalBounds();
+  rounded_rect_bounds_.Inset(GetInsets());
 
   paint_.setColor(SK_ColorWHITE);
   paint_.setStyle(SkPaint::kFill_Style);
   paint_.setFlags(SkPaint::kAntiAlias_Flag);
 
-  horizontal_offset_ = width() * 0.08f;
+  horizontal_offset_ =
+      arrow_width_ + base_border_ + rounded_rect_bounds_.width() * 0.08f;
   int top_offset = horizontal_offset_;
-  int line_gap = height() * 0.018f;
-  int label_height = height() * 0.11f;
+  int line_gap = rounded_rect_bounds_.height() * 0.018f;
+  int label_height = rounded_rect_bounds_.height() * 0.11f;
 
   label_text_bounds_.SetRect(horizontal_offset_, top_offset, 0, label_height);
 
@@ -322,6 +349,12 @@
 
 HintBox::~HintBox() {}
 
+void HintBox::UpdateWidth(int updated_width) {
+  SetSize(gfx::Size(updated_width + 2 * base_border_ + arrow_width_, height()));
+  rounded_rect_bounds_ = GetLocalBounds();
+  rounded_rect_bounds_.Inset(GetInsets());
+}
+
 void HintBox::SetLabel(const base::string16& text, const SkColor& color) {
   label_text_ = text;
   label_color_ = color;
@@ -337,9 +370,10 @@
       gfx::Size(size.width(), label_text_bounds_.height()));
 
   // Check if the width of hint box needs to be updated.
-  int minimum_expected_width = size.width() + 2 * horizontal_offset_;
-  if (minimum_expected_width > width())
-    SetSize(gfx::Size(minimum_expected_width, height()));
+  int minimum_expected_width =
+      size.width() + 2 * horizontal_offset_ - arrow_width_;
+  if (minimum_expected_width > rounded_rect_bounds_.width())
+    UpdateWidth(minimum_expected_width);
 }
 
 void HintBox::SetSubLabel(const base::string16& text, const SkColor& color) {
@@ -357,13 +391,15 @@
       gfx::Size(size.width(), sublabel_text_bounds_.height()));
 
   // Check if the width of hint box needs to be updated.
-  int minimum_expected_width = size.width() + 2 * horizontal_offset_;
-  if (minimum_expected_width > width())
-    SetSize(gfx::Size(minimum_expected_width, height()));
+  int minimum_expected_width =
+      size.width() + 2 * horizontal_offset_ - arrow_width_;
+  if (minimum_expected_width > rounded_rect_bounds_.width())
+    UpdateWidth(minimum_expected_width);
 }
 
 void HintBox::OnPaint(gfx::Canvas* canvas) {
-  canvas->DrawRoundRect(GetLocalBounds(), border_radius_, paint_);
+  views::View::OnPaint(canvas);
+  canvas->DrawRoundRect(rounded_rect_bounds_, border_radius_, paint_);
   canvas->DrawStringRectWithFlags(label_text_, label_font_list_, label_color_,
                                   label_text_bounds_, gfx::Canvas::NO_ELLIPSIS);
   canvas->DrawStringRectWithFlags(sublabel_text_, sublabel_font_list_,
@@ -536,9 +572,10 @@
 
   gfx::Size size(kHintBoxWidth, kHintBoxHeight);
 
-  gfx::Point position(
-      touch_point_view_->x() + tpv_width * 1.2f,
-      touch_point_view_->y() + (tpv_width / 2.f) - (size.height() / 2.f));
+  gfx::Point position(touch_point_view_->x() + tpv_width * 1.2f,
+                      touch_point_view_->y() +
+                          (kThrobberCircleViewWidth / 2.f) -
+                          (size.height() / 2.f));
 
   HintBox* hint_box =
       new HintBox(gfx::Rect(position, size), kHintRectBorderRadius);
@@ -551,8 +588,8 @@
 
   // Initialize the animated hint box throbber view.
   TouchTargetThrobberView* target_view = new TouchTargetThrobberView(
-      gfx::Rect((size.width() - kTouchTargetWidth) / 2,
-                size.height() * kTouchTargetVerticalOffsetFactor,
+      gfx::Rect((hint_box->width() - kTouchTargetWidth) / 2,
+                hint_box->height() * kTouchTargetVerticalOffsetFactor,
                 kTouchTargetWidth, kTouchTargetHeight),
       kTouchTargetInnerCircleColor, kTouchTargetOuterCircleColor,
       kHandIconColor, kCircleAnimationDurationMs);
diff --git a/chrome/browser/chromeos/login/oobe_localization_browsertest.cc b/chrome/browser/chromeos/login/oobe_localization_browsertest.cc
index ab3fad4..8dd3473 100644
--- a/chrome/browser/chromeos/login/oobe_localization_browsertest.cc
+++ b/chrome/browser/chromeos/login/oobe_localization_browsertest.cc
@@ -115,9 +115,7 @@
   }
 
  private:
-  bool LanguageListReady() const {
-    return network_screen_->GetLanguageList();
-  }
+  bool LanguageListReady() const { return network_screen_->language_list(); }
 
   void CheckLanguageList() {
     if (LanguageListReady())
diff --git a/chrome/browser/chromeos/login/screens/mock_network_screen.cc b/chrome/browser/chromeos/login/screens/mock_network_screen.cc
index 3a68252..0d97c64 100644
--- a/chrome/browser/chromeos/login/screens/mock_network_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_network_screen.cc
@@ -23,17 +23,17 @@
 }
 
 MockNetworkView::~MockNetworkView() {
-  if (model_)
-    model_->OnViewDestroyed(this);
+  if (screen_)
+    screen_->OnViewDestroyed(this);
 }
 
-void MockNetworkView::Bind(NetworkModel& model) {
-  model_ = &model;
-  MockBind(model);
+void MockNetworkView::Bind(NetworkScreen* screen) {
+  screen_ = screen;
+  MockBind(screen);
 }
 
 void MockNetworkView::Unbind() {
-  model_ = nullptr;
+  screen_ = nullptr;
   MockUnbind();
 }
 
diff --git a/chrome/browser/chromeos/login/screens/mock_network_screen.h b/chrome/browser/chromeos/login/screens/mock_network_screen.h
index 06bd599..965d6243 100644
--- a/chrome/browser/chromeos/login/screens/mock_network_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_network_screen.h
@@ -28,10 +28,10 @@
   MockNetworkView();
   ~MockNetworkView() override;
 
-  void Bind(NetworkModel& model) override;
+  void Bind(NetworkScreen* screen) override;
   void Unbind() override;
 
-  MOCK_METHOD1(MockBind, void(NetworkModel& model));
+  MOCK_METHOD1(MockBind, void(NetworkScreen* screen));
   MOCK_METHOD0(MockUnbind, void());
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
@@ -44,7 +44,7 @@
   MOCK_METHOD0(ReloadLocalizedContent, void());
 
  private:
-  NetworkModel* model_;
+  NetworkScreen* screen_ = nullptr;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_model.cc b/chrome/browser/chromeos/login/screens/network_model.cc
deleted file mode 100644
index 65c3584..0000000
--- a/chrome/browser/chromeos/login/screens/network_model.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2014 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 "chrome/browser/chromeos/login/screens/network_model.h"
-
-#include "chrome/browser/chromeos/login/wizard_controller.h"
-
-namespace chromeos {
-
-const char NetworkModel::kUserActionContinueButtonClicked[] = "continue";
-const char NetworkModel::kUserActionConnectDebuggingFeaturesClicked[] =
-    "connect-debugging-features";
-const char NetworkModel::kContextKeyLocale[] = "locale";
-const char NetworkModel::kContextKeyInputMethod[] = "input-method";
-const char NetworkModel::kContextKeyTimezone[] = "timezone";
-const char NetworkModel::kContextKeyContinueButtonEnabled[] =
-    "continue-button-enabled";
-
-NetworkModel::NetworkModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_NETWORK) {}
-
-NetworkModel::~NetworkModel() {}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_model.h b/chrome/browser/chromeos/login/screens/network_model.h
deleted file mode 100644
index 66cbfda..0000000
--- a/chrome/browser/chromeos/login/screens/network_model.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2014 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 CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_NETWORK_MODEL_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_NETWORK_MODEL_H_
-
-#include "chrome/browser/chromeos/login/screens/base_screen.h"
-
-namespace base {
-class ListValue;
-}
-
-namespace chromeos {
-
-class BaseScreenDelegate;
-class NetworkView;
-
-class NetworkModel : public BaseScreen {
- public:
-  static const char kUserActionContinueButtonClicked[];
-  static const char kUserActionConnectDebuggingFeaturesClicked[];
-  static const char kContextKeyLocale[];
-  static const char kContextKeyInputMethod[];
-  static const char kContextKeyTimezone[];
-  static const char kContextKeyContinueButtonEnabled[];
-
-  explicit NetworkModel(BaseScreenDelegate* base_screen_delegate);
-  ~NetworkModel() override;
-
-  // This method is called, when view is being destroyed. Note, if model
-  // is destroyed earlier then it has to call Unbind().
-  virtual void OnViewDestroyed(NetworkView* view) = 0;
-
-  virtual std::string GetLanguageListLocale() const = 0;
-
-  virtual const base::ListValue* GetLanguageList() const = 0;
-
-  virtual void UpdateLanguageList() = 0;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_NETWORK_MODEL_H_
diff --git a/chrome/browser/chromeos/login/screens/network_screen.cc b/chrome/browser/chromeos/login/screens/network_screen.cc
index 50c0dc1..4e7e084 100644
--- a/chrome/browser/chromeos/login/screens/network_screen.cc
+++ b/chrome/browser/chromeos/login/screens/network_screen.cc
@@ -37,6 +37,15 @@
 // Time in seconds for connection timeout.
 const int kConnectionTimeoutSec = 40;
 
+constexpr const char kUserActionContinueButtonClicked[] = "continue";
+constexpr const char kUserActionConnectDebuggingFeaturesClicked[] =
+    "connect-debugging-features";
+constexpr const char kContextKeyLocale[] = "locale";
+constexpr const char kContextKeyInputMethod[] = "input-method";
+constexpr const char kContextKeyTimezone[] = "timezone";
+constexpr const char kContextKeyContinueButtonEnabled[] =
+    "continue-button-enabled";
+
 }  // namespace
 
 namespace chromeos {
@@ -53,15 +62,13 @@
 NetworkScreen::NetworkScreen(BaseScreenDelegate* base_screen_delegate,
                              Delegate* delegate,
                              NetworkView* view)
-    : NetworkModel(base_screen_delegate),
-      is_network_subscribed_(false),
-      continue_pressed_(false),
+    : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_NETWORK),
       view_(view),
       delegate_(delegate),
       network_state_helper_(new login::NetworkStateHelper),
       weak_factory_(this) {
   if (view_)
-    view_->Bind(*this);
+    view_->Bind(this);
 
   input_method::InputMethodManager::Get()->AddObserver(this);
   InitializeTimezoneObserver();
@@ -79,32 +86,7 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// NetworkScreen, NetworkModel implementation:
-
-void NetworkScreen::Show() {
-  Refresh();
-
-  // Here we should handle default locales, for which we do not have UI
-  // resources. This would load fallback, but properly show "selected" locale
-  // in the UI.
-  if (selected_language_code_.empty()) {
-    const StartupCustomizationDocument* startup_manifest =
-        StartupCustomizationDocument::GetInstance();
-    SetApplicationLocale(startup_manifest->initial_locale_default());
-  }
-
-  if (!timezone_subscription_)
-    InitializeTimezoneObserver();
-
-  if (view_)
-    view_->Show();
-}
-
-void NetworkScreen::Hide() {
-  timezone_subscription_.reset();
-  if (view_)
-    view_->Hide();
-}
+// NetworkScreen, public API, setters and getters for input method and timezone.
 
 void NetworkScreen::OnViewDestroyed(NetworkView* view) {
   if (view_ == view) {
@@ -116,68 +98,12 @@
   }
 }
 
-void NetworkScreen::OnUserAction(const std::string& action_id) {
-  if (action_id == kUserActionContinueButtonClicked) {
-    OnContinueButtonPressed();
-  } else if (action_id == kUserActionConnectDebuggingFeaturesClicked) {
-    if (delegate_)
-      delegate_->OnEnableDebuggingScreenRequested();
-  } else {
-    BaseScreen::OnUserAction(action_id);
-  }
-}
-
-void NetworkScreen::OnContextKeyUpdated(
-    const ::login::ScreenContext::KeyType& key) {
-  if (key == kContextKeyLocale)
-    SetApplicationLocale(context_.GetString(kContextKeyLocale));
-  else if (key == kContextKeyInputMethod)
-    SetInputMethod(context_.GetString(kContextKeyInputMethod));
-  else if (key == kContextKeyTimezone)
-    SetTimezone(context_.GetString(kContextKeyTimezone));
-  else
-    NetworkModel::OnContextKeyUpdated(key);
-}
-
-std::string NetworkScreen::GetLanguageListLocale() const {
-  return language_list_locale_;
-}
-
-const base::ListValue* NetworkScreen::GetLanguageList() const {
-  return language_list_.get();
-}
 
 void NetworkScreen::UpdateLanguageList() {
   ScheduleResolveLanguageList(
       std::unique_ptr<locale_util::LanguageSwitchResult>());
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// NetworkScreen, NetworkStateHandlerObserver implementation:
-
-void NetworkScreen::NetworkConnectionStateChanged(const NetworkState* network) {
-  UpdateStatus();
-}
-
-void NetworkScreen::DefaultNetworkChanged(const NetworkState* network) {
-  UpdateStatus();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// NetworkScreen, InputMethodManager::Observer implementation:
-
-void NetworkScreen::InputMethodChanged(
-    input_method::InputMethodManager* manager,
-    Profile* /* proflie */,
-    bool /* show_message */) {
-  GetContextEditor().SetString(
-      kContextKeyInputMethod,
-      manager->GetActiveIMEState()->GetCurrentInputMethod().id());
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// NetworkScreen, setters and getters for input method and timezone.
-
 void NetworkScreen::SetApplicationLocaleAndInputMethod(
     const std::string& locale,
     const std::string& input_method) {
@@ -245,6 +171,80 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// BaseScreen implementation:
+
+void NetworkScreen::Show() {
+  Refresh();
+
+  // Here we should handle default locales, for which we do not have UI
+  // resources. This would load fallback, but properly show "selected" locale
+  // in the UI.
+  if (selected_language_code_.empty()) {
+    const StartupCustomizationDocument* startup_manifest =
+        StartupCustomizationDocument::GetInstance();
+    SetApplicationLocale(startup_manifest->initial_locale_default());
+  }
+
+  if (!timezone_subscription_)
+    InitializeTimezoneObserver();
+
+  if (view_)
+    view_->Show();
+}
+
+void NetworkScreen::Hide() {
+  timezone_subscription_.reset();
+  if (view_)
+    view_->Hide();
+}
+
+void NetworkScreen::OnUserAction(const std::string& action_id) {
+  if (action_id == kUserActionContinueButtonClicked) {
+    OnContinueButtonPressed();
+  } else if (action_id == kUserActionConnectDebuggingFeaturesClicked) {
+    if (delegate_)
+      delegate_->OnEnableDebuggingScreenRequested();
+  } else {
+    BaseScreen::OnUserAction(action_id);
+  }
+}
+
+void NetworkScreen::OnContextKeyUpdated(
+    const ::login::ScreenContext::KeyType& key) {
+  if (key == kContextKeyLocale)
+    SetApplicationLocale(context_.GetString(kContextKeyLocale));
+  else if (key == kContextKeyInputMethod)
+    SetInputMethod(context_.GetString(kContextKeyInputMethod));
+  else if (key == kContextKeyTimezone)
+    SetTimezone(context_.GetString(kContextKeyTimezone));
+  else
+    BaseScreen::OnContextKeyUpdated(key);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkScreen, NetworkStateHandlerObserver implementation:
+
+void NetworkScreen::NetworkConnectionStateChanged(const NetworkState* network) {
+  UpdateStatus();
+}
+
+void NetworkScreen::DefaultNetworkChanged(const NetworkState* network) {
+  UpdateStatus();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkScreen, InputMethodManager::Observer implementation:
+
+void NetworkScreen::InputMethodChanged(
+    input_method::InputMethodManager* manager,
+    Profile* /* proflie */,
+    bool /* show_message */) {
+  GetContextEditor().SetString(
+      kContextKeyInputMethod,
+      manager->GetActiveIMEState()->GetCurrentInputMethod().id());
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // NetworkScreen, private:
 
 void NetworkScreen::SetApplicationLocale(const std::string& locale) {
diff --git a/chrome/browser/chromeos/login/screens/network_screen.h b/chrome/browser/chromeos/login/screens/network_screen.h
index 1c8c1a0..5e7d162 100644
--- a/chrome/browser/chromeos/login/screens/network_screen.h
+++ b/chrome/browser/chromeos/login/screens/network_screen.h
@@ -15,7 +15,7 @@
 #include "base/observer_list.h"
 #include "base/strings/string16.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/chromeos/login/screens/network_model.h"
+#include "chrome/browser/chromeos/login/screens/base_screen.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chromeos/network/network_state_handler_observer.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
@@ -34,7 +34,7 @@
 class NetworkStateHelper;
 }
 
-class NetworkScreen : public NetworkModel,
+class NetworkScreen : public BaseScreen,
                       public NetworkStateHandlerObserver,
                       public input_method::InputMethodManager::Observer {
  public:
@@ -61,24 +61,16 @@
 
   static NetworkScreen* Get(ScreenManager* manager);
 
-  // NetworkModel implementation:
-  void Show() override;
-  void Hide() override;
-  void OnViewDestroyed(NetworkView* view) override;
-  void OnUserAction(const std::string& action_id) override;
-  void OnContextKeyUpdated(const ::login::ScreenContext::KeyType& key) override;
-  std::string GetLanguageListLocale() const override;
-  const base::ListValue* GetLanguageList() const override;
-  void UpdateLanguageList() override;
+  // Called when |view| has been destroyed. If this instance is destroyed before
+  // the |view| it should call view->Unbind().
+  void OnViewDestroyed(NetworkView* view);
 
-  // NetworkStateHandlerObserver implementation:
-  void NetworkConnectionStateChanged(const NetworkState* network) override;
-  void DefaultNetworkChanged(const NetworkState* network) override;
+  const std::string& language_list_locale() const {
+    return language_list_locale_;
+  }
+  const base::ListValue* language_list() const { return language_list_.get(); }
 
-  // InputMethodManager::Observer implementation:
-  void InputMethodChanged(input_method::InputMethodManager* manager,
-                          Profile* profile,
-                          bool show_message) override;
+  void UpdateLanguageList();
 
   // Set locale and input method. If |locale| is empty or doesn't change, set
   // the |input_method| directly. If |input_method| is empty or ineligible, we
@@ -107,6 +99,21 @@
   FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, CanConnect);
   FRIEND_TEST_ALL_PREFIXES(HandsOffNetworkScreenTest, RequiresNoInput);
 
+  // BaseScreen implementation:
+  void Show() override;
+  void Hide() override;
+  void OnUserAction(const std::string& action_id) override;
+  void OnContextKeyUpdated(const ::login::ScreenContext::KeyType& key) override;
+
+  // NetworkStateHandlerObserver implementation:
+  void NetworkConnectionStateChanged(const NetworkState* network) override;
+  void DefaultNetworkChanged(const NetworkState* network) override;
+
+  // InputMethodManager::Observer implementation:
+  void InputMethodChanged(input_method::InputMethodManager* manager,
+                          Profile* profile,
+                          bool show_message) override;
+
   void SetApplicationLocale(const std::string& locale);
   void SetInputMethod(const std::string& input_method);
 
@@ -166,22 +173,22 @@
   void OnSystemTimezoneChanged();
 
   // True if subscribed to network change notification.
-  bool is_network_subscribed_;
+  bool is_network_subscribed_ = false;
 
   // ID of the the network that we are waiting for.
   base::string16 network_id_;
 
   // True if user pressed continue button so we should proceed with OOBE
   // as soon as we are connected.
-  bool continue_pressed_;
+  bool continue_pressed_ = false;
 
   // Timer for connection timeout.
   base::OneShotTimer connection_timer_;
 
   std::unique_ptr<CrosSettings::ObserverSubscription> timezone_subscription_;
 
-  NetworkView* view_;
-  Delegate* delegate_;
+  NetworkView* view_ = nullptr;
+  Delegate* delegate_ = nullptr;
   std::unique_ptr<login::NetworkStateHelper> network_state_helper_;
 
   std::string input_method_;
diff --git a/chrome/browser/chromeos/login/screens/network_view.h b/chrome/browser/chromeos/login/screens/network_view.h
index 37bcac8a..a27c969b5 100644
--- a/chrome/browser/chromeos/login/screens/network_view.h
+++ b/chrome/browser/chromeos/login/screens/network_view.h
@@ -9,7 +9,7 @@
 
 namespace chromeos {
 
-class NetworkModel;
+class NetworkScreen;
 
 // Interface for dependency injection between NetworkScreen and its actual
 // representation, either views based or WebUI. Owned by NetworkScreen.
@@ -23,8 +23,8 @@
   // Hides the contents of the screen.
   virtual void Hide() = 0;
 
-  // Binds |model| to the view.
-  virtual void Bind(NetworkModel& model) = 0;
+  // Binds |screen| to the view.
+  virtual void Bind(NetworkScreen* screen) = 0;
 
   // Unbinds model from the view.
   virtual void Unbind() = 0;
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
index 369beee..e8474db 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
@@ -253,7 +253,8 @@
   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options));
   DCHECK(options);
 
-  origin_type_mask_ = ParseOriginTypeMask(*options);
+  EXTENSION_FUNCTION_VALIDATE(
+      ParseOriginTypeMask(*options, &origin_type_mask_));
 
   // If |ms_since_epoch| isn't set, default it to 0.
   double ms_since_epoch;
@@ -323,44 +324,55 @@
       removal_mask_, origin_type_mask_, this);
 }
 
-int BrowsingDataRemoverFunction::ParseOriginTypeMask(
-    const base::DictionaryValue& options) {
+bool BrowsingDataRemoverFunction::ParseOriginTypeMask(
+    const base::DictionaryValue& options,
+    int* origin_type_mask) {
   // Parse the |options| dictionary to generate the origin set mask. Default to
   // UNPROTECTED_WEB if the developer doesn't specify anything.
-  int mask = BrowsingDataHelper::UNPROTECTED_WEB;
+  *origin_type_mask = BrowsingDataHelper::UNPROTECTED_WEB;
 
   const base::DictionaryValue* d = NULL;
   if (options.HasKey(extension_browsing_data_api_constants::kOriginTypesKey)) {
-    EXTENSION_FUNCTION_VALIDATE(options.GetDictionary(
-        extension_browsing_data_api_constants::kOriginTypesKey, &d));
+    if (!options.GetDictionary(
+            extension_browsing_data_api_constants::kOriginTypesKey, &d)) {
+      return false;
+    }
     bool value;
 
     // The developer specified something! Reset to 0 and parse the dictionary.
-    mask = 0;
+    *origin_type_mask = 0;
 
     // Unprotected web.
     if (d->HasKey(extension_browsing_data_api_constants::kUnprotectedWebKey)) {
-      EXTENSION_FUNCTION_VALIDATE(d->GetBoolean(
-          extension_browsing_data_api_constants::kUnprotectedWebKey, &value));
-      mask |= value ? BrowsingDataHelper::UNPROTECTED_WEB : 0;
+      if (!d->GetBoolean(
+              extension_browsing_data_api_constants::kUnprotectedWebKey,
+              &value)) {
+        return false;
+      }
+      *origin_type_mask |= value ? BrowsingDataHelper::UNPROTECTED_WEB : 0;
     }
 
     // Protected web.
     if (d->HasKey(extension_browsing_data_api_constants::kProtectedWebKey)) {
-      EXTENSION_FUNCTION_VALIDATE(d->GetBoolean(
-          extension_browsing_data_api_constants::kProtectedWebKey, &value));
-      mask |= value ? BrowsingDataHelper::PROTECTED_WEB : 0;
+      if (!d->GetBoolean(
+              extension_browsing_data_api_constants::kProtectedWebKey,
+              &value)) {
+        return false;
+      }
+      *origin_type_mask |= value ? BrowsingDataHelper::PROTECTED_WEB : 0;
     }
 
     // Extensions.
     if (d->HasKey(extension_browsing_data_api_constants::kExtensionsKey)) {
-      EXTENSION_FUNCTION_VALIDATE(d->GetBoolean(
-          extension_browsing_data_api_constants::kExtensionsKey, &value));
-      mask |= value ? BrowsingDataHelper::EXTENSION : 0;
+      if (!d->GetBoolean(extension_browsing_data_api_constants::kExtensionsKey,
+                         &value)) {
+        return false;
+      }
+      *origin_type_mask |= value ? BrowsingDataHelper::EXTENSION : 0;
     }
   }
 
-  return mask;
+  return true;
 }
 
 // Parses the |dataToRemove| argument to generate the removal mask.
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_api.h b/chrome/browser/extensions/api/browsing_data/browsing_data_api.h
index 0b6f231..8ffab0f2 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_api.h
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_api.h
@@ -110,9 +110,11 @@
   void CheckRemovingPluginDataSupported(
       scoped_refptr<PluginPrefs> plugin_prefs);
 
-  // Parse the developer-provided |origin_types| object into an origin_type_mask
+  // Parse the developer-provided |origin_types| object into |origin_type_mask|
   // that can be used with the BrowsingDataRemover.
-  int ParseOriginTypeMask(const base::DictionaryValue& options);
+  // Returns true if parsing was successful.
+  bool ParseOriginTypeMask(const base::DictionaryValue& options,
+                           int* origin_type_mask);
 
   // Called when we're ready to start removing data.
   void StartRemoving();
diff --git a/chrome/browser/media/android/router/media_router_android.cc b/chrome/browser/media/android/router/media_router_android.cc
index 9fa7aa6..62e132b 100644
--- a/chrome/browser/media/android/router/media_router_android.cc
+++ b/chrome/browser/media/android/router/media_router_android.cc
@@ -75,20 +75,12 @@
 void MediaRouterAndroid::CreateRoute(
     const MediaSource::Id& source_id,
     const MediaSink::Id& sink_id,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
     bool incognito) {
   // TODO(avayvod): Implement timeouts (crbug.com/583036).
-  if (!origin.is_valid()) {
-    std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
-        "Invalid origin", RouteRequestResult::INVALID_ORIGIN);
-    for (const MediaRouteResponseCallback& callback : callbacks)
-      callback.Run(*result);
-    return;
-  }
-
   std::string presentation_id = MediaRouterBase::CreatePresentationId();
 
   int tab_id = -1;
@@ -111,8 +103,10 @@
           base::android::ConvertUTF8ToJavaString(env, sink_id);
   ScopedJavaLocalRef<jstring> jpresentation_id =
           base::android::ConvertUTF8ToJavaString(env, presentation_id);
+  // TODO(crbug.com/685358): Unique origins should not be considered
+  // same-origin.
   ScopedJavaLocalRef<jstring> jorigin =
-          base::android::ConvertUTF8ToJavaString(env, origin.spec());
+      base::android::ConvertUTF8ToJavaString(env, origin.GetURL().spec());
 
   Java_ChromeMediaRouter_createRoute(env, java_media_router_, jsource_id,
                                      jsink_id, jpresentation_id, jorigin,
@@ -122,7 +116,7 @@
 void MediaRouterAndroid::ConnectRouteByRouteId(
     const MediaSource::Id& source,
     const MediaRoute::Id& route_id,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
@@ -133,20 +127,12 @@
 void MediaRouterAndroid::JoinRoute(
     const MediaSource::Id& source_id,
     const std::string& presentation_id,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
     bool incognito) {
   // TODO(avayvod): Implement timeouts (crbug.com/583036).
-  if (!origin.is_valid()) {
-    std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
-        "Invalid origin", RouteRequestResult::INVALID_ORIGIN);
-    for (const MediaRouteResponseCallback& callback : callbacks)
-      callback.Run(*result);
-    return;
-  }
-
   int tab_id = -1;
   TabAndroid* tab = web_contents
       ? TabAndroid::FromWebContents(web_contents) : nullptr;
@@ -154,7 +140,7 @@
     tab_id = tab->GetAndroidId();
 
   DVLOG(2) << "JoinRoute: " << source_id << ", " << presentation_id << ", "
-           << origin.spec() << ", " << tab_id;
+           << origin.GetURL().spec() << ", " << tab_id;
 
   int request_id = route_requests_.Add(base::MakeUnique<MediaRouteRequest>(
       MediaSource(source_id), presentation_id, callbacks));
@@ -165,7 +151,7 @@
   ScopedJavaLocalRef<jstring> jpresentation_id =
           base::android::ConvertUTF8ToJavaString(env, presentation_id);
   ScopedJavaLocalRef<jstring> jorigin =
-          base::android::ConvertUTF8ToJavaString(env, origin.spec());
+      base::android::ConvertUTF8ToJavaString(env, origin.GetURL().spec());
 
   Java_ChromeMediaRouter_joinRoute(env, java_media_router_, jsource_id,
                                    jpresentation_id, jorigin, tab_id,
@@ -348,7 +334,7 @@
   if (it != sinks_observers_.end()) {
     // TODO(imcheng): Pass origins to OnSinksUpdated (crbug.com/594858).
     for (auto& observer : *it->second)
-      observer.OnSinksUpdated(sinks_converted, std::vector<GURL>());
+      observer.OnSinksUpdated(sinks_converted, std::vector<url::Origin>());
   }
 }
 
diff --git a/chrome/browser/media/android/router/media_router_android.h b/chrome/browser/media/android/router/media_router_android.h
index 592c953..b30b4cc 100644
--- a/chrome/browser/media/android/router/media_router_android.h
+++ b/chrome/browser/media/android/router/media_router_android.h
@@ -35,14 +35,14 @@
   // MediaRouter implementation.
   void CreateRoute(const MediaSource::Id& source_id,
                    const MediaSink::Id& sink_id,
-                   const GURL& origin,
+                   const url::Origin& origin,
                    content::WebContents* web_contents,
                    const std::vector<MediaRouteResponseCallback>& callbacks,
                    base::TimeDelta timeout,
                    bool incognito) override;
   void JoinRoute(const MediaSource::Id& source,
                  const std::string& presentation_id,
-                 const GURL& origin,
+                 const url::Origin& origin,
                  content::WebContents* web_contents,
                  const std::vector<MediaRouteResponseCallback>& callbacks,
                  base::TimeDelta timeout,
@@ -50,7 +50,7 @@
   void ConnectRouteByRouteId(
       const MediaSource::Id& source,
       const MediaRoute::Id& route_id,
-      const GURL& origin,
+      const url::Origin& origin,
       content::WebContents* web_contents,
       const std::vector<MediaRouteResponseCallback>& callbacks,
       base::TimeDelta timeout,
diff --git a/chrome/browser/media/android/router/media_router_dialog_controller_android.cc b/chrome/browser/media/android/router/media_router_dialog_controller_android.cc
index 44296df5..9da67346 100644
--- a/chrome/browser/media/android/router/media_router_dialog_controller_android.cc
+++ b/chrome/browser/media/android/router/media_router_dialog_controller_android.cc
@@ -53,7 +53,7 @@
   // TODO(crbug.com/627655): Support multiple URLs.
   const MediaSource::Id source_id =
       presentation_request.GetMediaSources()[0].id();
-  const GURL origin = presentation_request.frame_url().GetOrigin();
+  const auto& origin = presentation_request.frame_origin();
 
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn
index 5de0f25..a9c4bfb 100644
--- a/chrome/browser/media/router/BUILD.gn
+++ b/chrome/browser/media/router/BUILD.gn
@@ -102,6 +102,7 @@
 
   public_deps = [
     "//mojo/common:common_custom_types",
+    "//url/mojo:url_mojom_origin",
   ]
 }
 
diff --git a/chrome/browser/media/router/create_presentation_connection_request.cc b/chrome/browser/media/router/create_presentation_connection_request.cc
index 9dd49c9..49eb3ed 100644
--- a/chrome/browser/media/router/create_presentation_connection_request.cc
+++ b/chrome/browser/media/router/create_presentation_connection_request.cc
@@ -6,7 +6,7 @@
 
 #include "chrome/browser/media/router/media_source_helper.h"
 #include "chrome/browser/media/router/route_request_result.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 using content::PresentationSessionInfo;
 using content::PresentationError;
@@ -16,10 +16,12 @@
 CreatePresentationConnectionRequest::CreatePresentationConnectionRequest(
     const RenderFrameHostId& render_frame_host_id,
     const std::vector<GURL>& presentation_urls,
-    const GURL& frame_url,
+    const url::Origin& frame_origin,
     const PresentationSessionSuccessCallback& success_cb,
     const PresentationSessionErrorCallback& error_cb)
-    : presentation_request_(render_frame_host_id, presentation_urls, frame_url),
+    : presentation_request_(render_frame_host_id,
+                            presentation_urls,
+                            frame_origin),
       success_cb_(success_cb),
       error_cb_(error_cb),
       cb_invoked_(false) {
diff --git a/chrome/browser/media/router/create_presentation_connection_request.h b/chrome/browser/media/router/create_presentation_connection_request.h
index 41e5da49..dc702af 100644
--- a/chrome/browser/media/router/create_presentation_connection_request.h
+++ b/chrome/browser/media/router/create_presentation_connection_request.h
@@ -16,13 +16,15 @@
 #include "chrome/browser/media/router/render_frame_host_id.h"
 #include "content/public/browser/presentation_service_delegate.h"
 
-class GURL;
-
 namespace content {
 struct PresentationError;
 struct PresentationSessionInfo;
 }  // namespace content
 
+namespace url {
+class Origin;
+}  // namespace url
+
 namespace media_router {
 
 class RouteRequestResult;
@@ -42,13 +44,14 @@
       content::PresentationSessionErrorCallback;
   // |presentation_url|: The presentation URL of the request. Must be a valid
   //                     URL.
-  // |frame_url|: The URL of the frame that initiated the presentation request.
+  // |frame_origin|: The origin of the frame that initiated the presentation
+  // request.
   // |success_cb|: Callback to invoke when the request succeeds. Must be valid.
   // |erorr_cb|: Callback to invoke when the request fails. Must be valid.
   CreatePresentationConnectionRequest(
       const RenderFrameHostId& render_frame_host_id,
       const std::vector<GURL>& presentation_urls,
-      const GURL& frame_url,
+      const url::Origin& frame_origin,
       const PresentationSessionSuccessCallback& success_cb,
       const PresentationSessionErrorCallback& error_cb);
   ~CreatePresentationConnectionRequest();
diff --git a/chrome/browser/media/router/create_presentation_connection_request_unittest.cc b/chrome/browser/media/router/create_presentation_connection_request_unittest.cc
index 8e71ecb4..6c373b9f 100644
--- a/chrome/browser/media/router/create_presentation_connection_request_unittest.cc
+++ b/chrome/browser/media/router/create_presentation_connection_request_unittest.cc
@@ -68,14 +68,14 @@
   content::PresentationError error(content::PRESENTATION_ERROR_UNKNOWN,
                                    "Unknown error.");
   CreatePresentationConnectionRequest request(
-      render_frame_host_id_, presentation_urls_, GURL(kFrameUrl),
+      render_frame_host_id_, presentation_urls_, url::Origin(GURL(kFrameUrl)),
       base::Bind(&CreatePresentationConnectionRequestTest::FailOnSuccess,
                  base::Unretained(this)),
       base::Bind(&CreatePresentationConnectionRequestTest::OnError,
                  base::Unretained(this), error));
 
-  PresentationRequest presentation_request(render_frame_host_id_,
-                                           presentation_urls_, GURL(kFrameUrl));
+  PresentationRequest presentation_request(
+      render_frame_host_id_, presentation_urls_, url::Origin(GURL(kFrameUrl)));
   EXPECT_TRUE(request.presentation_request().Equals(presentation_request));
   // Since we didn't explicitly call Invoke*, the error callback will be
   // invoked when |request| is destroyed.
@@ -85,7 +85,7 @@
   content::PresentationSessionInfo session_info(presentation_url_,
                                                 kPresentationId);
   CreatePresentationConnectionRequest request(
-      render_frame_host_id_, {presentation_url_}, GURL(kFrameUrl),
+      render_frame_host_id_, {presentation_url_}, url::Origin(GURL(kFrameUrl)),
       base::Bind(&CreatePresentationConnectionRequestTest::OnSuccess,
                  base::Unretained(this), session_info),
       base::Bind(&CreatePresentationConnectionRequestTest::FailOnError,
@@ -101,7 +101,7 @@
       content::PRESENTATION_ERROR_SESSION_REQUEST_CANCELLED,
       "This is an error message");
   CreatePresentationConnectionRequest request(
-      render_frame_host_id_, presentation_urls_, GURL(kFrameUrl),
+      render_frame_host_id_, presentation_urls_, url::Origin(GURL(kFrameUrl)),
       base::Bind(&CreatePresentationConnectionRequestTest::FailOnSuccess,
                  base::Unretained(this)),
       base::Bind(&CreatePresentationConnectionRequestTest::OnError,
diff --git a/chrome/browser/media/router/media_router.h b/chrome/browser/media/router/media_router.h
index 92d6d068..0f8fd4b 100644
--- a/chrome/browser/media/router/media_router.h
+++ b/chrome/browser/media/router/media_router.h
@@ -26,6 +26,10 @@
 class WebContents;
 }
 
+namespace url {
+class Origin;
+}  // namespace url
+
 namespace media_router {
 
 class IssuesObserver;
@@ -78,7 +82,7 @@
   virtual void CreateRoute(
       const MediaSource::Id& source_id,
       const MediaSink::Id& sink_id,
-      const GURL& origin,
+      const url::Origin& origin,
       content::WebContents* web_contents,
       const std::vector<MediaRouteResponseCallback>& callbacks,
       base::TimeDelta timeout,
@@ -100,7 +104,7 @@
   virtual void ConnectRouteByRouteId(
       const MediaSource::Id& source_id,
       const MediaRoute::Id& route_id,
-      const GURL& origin,
+      const url::Origin& origin,
       content::WebContents* web_contents,
       const std::vector<MediaRouteResponseCallback>& callbacks,
       base::TimeDelta timeout,
@@ -120,7 +124,7 @@
   virtual void JoinRoute(
       const MediaSource::Id& source,
       const std::string& presentation_id,
-      const GURL& origin,
+      const url::Origin& origin,
       content::WebContents* web_contents,
       const std::vector<MediaRouteResponseCallback>& callbacks,
       base::TimeDelta timeout,
diff --git a/chrome/browser/media/router/media_router_dialog_controller_unittest.cc b/chrome/browser/media/router/media_router_dialog_controller_unittest.cc
index a8f3d0e7..1baa12e 100644
--- a/chrome/browser/media/router/media_router_dialog_controller_unittest.cc
+++ b/chrome/browser/media/router/media_router_dialog_controller_unittest.cc
@@ -70,7 +70,7 @@
         new CreatePresentationConnectionRequest(
             RenderFrameHostId(1, 2),
             {GURL("http://example.com"), GURL("http://example2.com")},
-            GURL("http://google.com"),
+            url::Origin(GURL("http://google.com")),
             base::Bind(&MediaRouterDialogControllerTest::RequestSuccess,
                        base::Unretained(this)),
             base::Bind(&MediaRouterDialogControllerTest::RequestError,
diff --git a/chrome/browser/media/router/media_sinks_observer.cc b/chrome/browser/media/router/media_sinks_observer.cc
index 61bb1096f..e7276fe5 100644
--- a/chrome/browser/media/router/media_sinks_observer.cc
+++ b/chrome/browser/media/router/media_sinks_observer.cc
@@ -16,7 +16,7 @@
 
 MediaSinksObserver::MediaSinksObserver(MediaRouter* router,
                                        const MediaSource& source,
-                                       const GURL& origin)
+                                       const url::Origin& origin)
     : source_(source), origin_(origin), router_(router), initialized_(false) {
   DCHECK(router_);
 }
@@ -38,8 +38,9 @@
   return initialized_;
 }
 
-void MediaSinksObserver::OnSinksUpdated(const std::vector<MediaSink>& sinks,
-                                        const std::vector<GURL>& origins) {
+void MediaSinksObserver::OnSinksUpdated(
+    const std::vector<MediaSink>& sinks,
+    const std::vector<url::Origin>& origins) {
 #if DCHECK_IS_ON()
   base::AutoReset<bool> reset_in_on_sinks_updated(&in_on_sinks_updated_, true);
 #endif
diff --git a/chrome/browser/media/router/media_sinks_observer.h b/chrome/browser/media/router/media_sinks_observer.h
index 5a87f27..91741ab 100644
--- a/chrome/browser/media/router/media_sinks_observer.h
+++ b/chrome/browser/media/router/media_sinks_observer.h
@@ -10,7 +10,7 @@
 #include "base/macros.h"
 #include "chrome/browser/media/router/media_sink.h"
 #include "chrome/browser/media/router/media_source.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace media_router {
 
@@ -28,7 +28,7 @@
   // with |source|.
   MediaSinksObserver(MediaRouter* router,
                      const MediaSource& source,
-                     const GURL& origin);
+                     const url::Origin& origin);
   virtual ~MediaSinksObserver();
 
   // Registers with MediaRouter to start observing. Must be called before the
@@ -42,7 +42,7 @@
   // will be invoked with |sinks|. Otherwise, it will be invoked with an empty
   // list.
   void OnSinksUpdated(const std::vector<MediaSink>& sinks,
-                      const std::vector<GURL>& origins);
+                      const std::vector<url::Origin>& origins);
 
   const MediaSource& source() const { return source_; }
 
@@ -55,7 +55,7 @@
 
  private:
   const MediaSource source_;
-  const GURL origin_;
+  const url::Origin origin_;
   MediaRouter* const router_;
   bool initialized_;
 
diff --git a/chrome/browser/media/router/media_sinks_observer_unittest.cc b/chrome/browser/media/router/media_sinks_observer_unittest.cc
index c5a340c..cbcc83a 100644
--- a/chrome/browser/media/router/media_sinks_observer_unittest.cc
+++ b/chrome/browser/media/router/media_sinks_observer_unittest.cc
@@ -14,9 +14,8 @@
   MockMediaRouter router;
   MediaSource source(
       MediaSourceForPresentationUrl(GURL("https://presentation.com")));
-  GURL origin("https://origin.com");
-  std::vector<GURL> origin_list;
-  origin_list.push_back(origin);
+  url::Origin origin{GURL("https://origin.com")};
+  std::vector<url::Origin> origin_list({origin});
   std::vector<MediaSink> sink_list;
   sink_list.push_back(MediaSink("sinkId", "Sink", MediaSink::IconType::CAST));
   MockMediaSinksObserver observer(&router, source, origin);
@@ -25,9 +24,9 @@
   observer.OnSinksUpdated(sink_list, origin_list);
 
   EXPECT_CALL(observer, OnSinksReceived(SequenceEquals(sink_list)));
-  observer.OnSinksUpdated(sink_list, std::vector<GURL>());
+  observer.OnSinksUpdated(sink_list, std::vector<url::Origin>());
 
-  GURL origin2("https://differentOrigin.com");
+  url::Origin origin2{GURL("https://differentOrigin.com")};
   origin_list.clear();
   origin_list.push_back(origin2);
   EXPECT_CALL(observer, OnSinksReceived(testing::IsEmpty()));
diff --git a/chrome/browser/media/router/mock_media_router.h b/chrome/browser/media/router/mock_media_router.h
index f2d6ae4f..5fe951b 100644
--- a/chrome/browser/media/router/mock_media_router.h
+++ b/chrome/browser/media/router/mock_media_router.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/media/router/media_sink.h"
 #include "chrome/browser/media/router/media_source.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "url/origin.h"
 
 namespace media_router {
 
@@ -28,7 +29,7 @@
   MOCK_METHOD7(CreateRoute,
                void(const MediaSource::Id& source,
                     const MediaSink::Id& sink_id,
-                    const GURL& origin,
+                    const url::Origin& origin,
                     content::WebContents* web_contents,
                     const std::vector<MediaRouteResponseCallback>& callbacks,
                     base::TimeDelta timeout,
@@ -36,7 +37,7 @@
   MOCK_METHOD7(JoinRoute,
                void(const MediaSource::Id& source,
                     const std::string& presentation_id,
-                    const GURL& origin,
+                    const url::Origin& origin,
                     content::WebContents* web_contents,
                     const std::vector<MediaRouteResponseCallback>& callbacks,
                     base::TimeDelta timeout,
@@ -44,7 +45,7 @@
   MOCK_METHOD7(ConnectRouteByRouteId,
                void(const MediaSource::Id& source,
                     const MediaRoute::Id& route_id,
-                    const GURL& origin,
+                    const url::Origin& origin,
                     content::WebContents* web_contents,
                     const std::vector<MediaRouteResponseCallback>& callbacks,
                     base::TimeDelta timeout,
diff --git a/chrome/browser/media/router/mojo/media_router.mojom b/chrome/browser/media/router/mojo/media_router.mojom
index e26170d..37fc064 100644
--- a/chrome/browser/media/router/mojo/media_router.mojom
+++ b/chrome/browser/media/router/mojo/media_router.mojom
@@ -5,6 +5,7 @@
 module media_router.mojom;
 
 import "mojo/common/time.mojom";
+import "url/mojo/origin.mojom";
 
 // Represents an output sink to which media can be routed.
 struct MediaSink {
@@ -170,7 +171,7 @@
   CreateRoute(string media_source,
               string sink_id,
               string original_presentation_id,
-              string origin,
+              url.mojom.Origin origin,
               int32 tab_id,
               mojo.common.mojom.TimeDelta timeout,
               bool incognito) =>
@@ -198,7 +199,7 @@
   // occurred.
   JoinRoute(string media_source,
             string presentation_id,
-            string origin,
+            url.mojom.Origin origin,
             int32 tab_id,
             mojo.common.mojom.TimeDelta timeout,
             bool incognito) =>
@@ -232,7 +233,7 @@
   ConnectRouteByRouteId(string media_source,
                         string route_id,
                         string presentation_id,
-                        string origin,
+                        url.mojom.Origin origin,
                         int32 tab_id,
                         mojo.common.mojom.TimeDelta timeout,
                         bool incognito) =>
@@ -363,7 +364,7 @@
   // compatible with |media_source|. The result is only valid for |origins|. If
   // |origins| is empty, the result is valid for any origin.
   OnSinksReceived(string media_source, array<MediaSink> sinks,
-      array<string> origins);
+      array<url.mojom.Origin> origins);
 
   // Called when issues are reported for media routes.
   OnIssue(Issue issue);
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
index 91c840d..fc356db6 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -176,7 +176,7 @@
 void MediaRouterMojoImpl::OnSinksReceived(
     const std::string& media_source,
     std::vector<mojom::MediaSinkPtr> sinks,
-    const std::vector<std::string>& origins) {
+    const std::vector<url::Origin>& origins) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DVLOG_WITH_INSTANCE(1) << "OnSinksReceived";
   auto it = sinks_queries_.find(media_source);
@@ -185,18 +185,6 @@
     return;
   }
 
-  std::vector<GURL> origin_list;
-  origin_list.reserve(origins.size());
-  for (size_t i = 0; i < origins.size(); ++i) {
-    GURL origin(origins[i]);
-    if (!origin.is_valid()) {
-      LOG(WARNING) << "Received invalid origin: " << origin
-                   << ". Dropping result.";
-      return;
-    }
-    origin_list.push_back(origin);
-  }
-
   std::vector<MediaSink> sink_list;
   sink_list.reserve(sinks.size());
   for (size_t i = 0; i < sinks.size(); ++i)
@@ -204,7 +192,7 @@
 
   auto* sinks_query = it->second.get();
   sinks_query->has_cached_result = true;
-  sinks_query->origins.swap(origin_list);
+  sinks_query->origins = origins;
   sinks_query->cached_sink_list.swap(sink_list);
 
   if (!sinks_query->observers.might_have_observers()) {
@@ -279,55 +267,36 @@
 void MediaRouterMojoImpl::CreateRoute(
     const MediaSource::Id& source_id,
     const MediaSink::Id& sink_id,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
     bool incognito) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (!origin.is_valid()) {
-    DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin;
-    std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
-        "Invalid origin", RouteRequestResult::INVALID_ORIGIN);
-    MediaRouterMojoMetrics::RecordCreateRouteResultCode(result->result_code());
-    RunRouteRequestCallbacks(std::move(result), callbacks);
-    return;
-  }
-
   SetWakeReason(MediaRouteProviderWakeReason::CREATE_ROUTE);
   int tab_id = SessionTabHelper::IdForTab(web_contents);
   RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoCreateRoute,
-                        base::Unretained(this), source_id, sink_id,
-                        origin.is_empty() ? "" : origin.spec(), tab_id,
-                        callbacks, timeout, incognito));
+                        base::Unretained(this), source_id, sink_id, origin,
+                        tab_id, callbacks, timeout, incognito));
 }
 
 void MediaRouterMojoImpl::JoinRoute(
     const MediaSource::Id& source_id,
     const std::string& presentation_id,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
     bool incognito) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  std::unique_ptr<RouteRequestResult> error_result;
-  if (!origin.is_valid()) {
-    DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin;
-    error_result = RouteRequestResult::FromError(
-        "Invalid origin", RouteRequestResult::INVALID_ORIGIN);
-  } else if (!HasJoinableRoute()) {
+  if (!HasJoinableRoute()) {
     DVLOG_WITH_INSTANCE(1) << "No joinable routes";
-    error_result = RouteRequestResult::FromError(
+    std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
         "Route not found", RouteRequestResult::ROUTE_NOT_FOUND);
-  }
-
-  if (error_result) {
-    MediaRouterMojoMetrics::RecordJoinRouteResultCode(
-        error_result->result_code());
-    RunRouteRequestCallbacks(std::move(error_result), callbacks);
+    MediaRouterMojoMetrics::RecordJoinRouteResultCode(result->result_code());
+    RunRouteRequestCallbacks(std::move(result), callbacks);
     return;
   }
 
@@ -335,35 +304,24 @@
   int tab_id = SessionTabHelper::IdForTab(web_contents);
   RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoJoinRoute,
                         base::Unretained(this), source_id, presentation_id,
-                        origin.is_empty() ? "" : origin.spec(), tab_id,
-                        callbacks, timeout, incognito));
+                        origin, tab_id, callbacks, timeout, incognito));
 }
 
 void MediaRouterMojoImpl::ConnectRouteByRouteId(
     const MediaSource::Id& source_id,
     const MediaRoute::Id& route_id,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
     bool incognito) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (!origin.is_valid()) {
-    DVLOG_WITH_INSTANCE(1) << "Invalid origin: " << origin;
-    std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
-        "Invalid origin", RouteRequestResult::INVALID_ORIGIN);
-    MediaRouterMojoMetrics::RecordJoinRouteResultCode(result->result_code());
-    RunRouteRequestCallbacks(std::move(result), callbacks);
-    return;
-  }
-
   SetWakeReason(MediaRouteProviderWakeReason::CONNECT_ROUTE_BY_ROUTE_ID);
   int tab_id = SessionTabHelper::IdForTab(web_contents);
   RunOrDefer(base::Bind(&MediaRouterMojoImpl::DoConnectRouteByRouteId,
-                        base::Unretained(this), source_id, route_id,
-                        origin.is_empty() ? "" : origin.spec(), tab_id,
-                        callbacks, timeout, incognito));
+                        base::Unretained(this), source_id, route_id, origin,
+                        tab_id, callbacks, timeout, incognito));
 }
 
 void MediaRouterMojoImpl::TerminateRoute(const MediaRoute::Id& route_id) {
@@ -459,7 +417,8 @@
   // |observer| can be immediately notified with an empty list.
   sinks_query->observers.AddObserver(observer);
   if (availability_ == mojom::MediaRouter::SinkAvailability::UNAVAILABLE) {
-    observer->OnSinksUpdated(std::vector<MediaSink>(), std::vector<GURL>());
+    observer->OnSinksUpdated(std::vector<MediaSink>(),
+                             std::vector<url::Origin>());
   } else {
     // Need to call MRPM to start observing sinks if the query is new.
     if (new_query) {
@@ -602,7 +561,7 @@
 void MediaRouterMojoImpl::DoCreateRoute(
     const MediaSource::Id& source_id,
     const MediaSink::Id& sink_id,
-    const std::string& origin,
+    const url::Origin& origin,
     int tab_id,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
@@ -610,7 +569,6 @@
   std::string presentation_id = MediaRouterBase::CreatePresentationId();
   DVLOG_WITH_INSTANCE(1) << "DoCreateRoute " << source_id << "=>" << sink_id
                          << ", presentation ID: " << presentation_id;
-
   media_route_provider_->CreateRoute(
       source_id, sink_id, presentation_id, origin, tab_id, timeout, incognito,
       base::Bind(&MediaRouterMojoImpl::RouteResponseReceived,
@@ -621,7 +579,7 @@
 void MediaRouterMojoImpl::DoJoinRoute(
     const MediaSource::Id& source_id,
     const std::string& presentation_id,
-    const std::string& origin,
+    const url::Origin& origin,
     int tab_id,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
@@ -639,7 +597,7 @@
 void MediaRouterMojoImpl::DoConnectRouteByRouteId(
     const MediaSource::Id& source_id,
     const MediaRoute::Id& route_id,
-    const std::string& origin,
+    const url::Origin& origin,
     int tab_id,
     const std::vector<MediaRouteResponseCallback>& callbacks,
     base::TimeDelta timeout,
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.h b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
index 4ee4caf..0b38e048 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.h
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
@@ -69,14 +69,14 @@
   // enqueued for later use if the extension is temporarily suspended.
   void CreateRoute(const MediaSource::Id& source_id,
                    const MediaSink::Id& sink_id,
-                   const GURL& origin,
+                   const url::Origin& origin,
                    content::WebContents* web_contents,
                    const std::vector<MediaRouteResponseCallback>& callbacks,
                    base::TimeDelta timeout,
                    bool incognito) override;
   void JoinRoute(const MediaSource::Id& source_id,
                  const std::string& presentation_id,
-                 const GURL& origin,
+                 const url::Origin& origin,
                  content::WebContents* web_contents,
                  const std::vector<MediaRouteResponseCallback>& callbacks,
                  base::TimeDelta timeout,
@@ -84,7 +84,7 @@
   void ConnectRouteByRouteId(
       const MediaSource::Id& source,
       const MediaRoute::Id& route_id,
-      const GURL& origin,
+      const url::Origin& origin,
       content::WebContents* web_contents,
       const std::vector<MediaRouteResponseCallback>& callbacks,
       base::TimeDelta timeout,
@@ -175,7 +175,7 @@
     // Cached list of sinks for the query, if |has_cached_result| is true.
     // Empty otherwise.
     std::vector<MediaSink> cached_sink_list;
-    std::vector<GURL> origins;
+    std::vector<url::Origin> origins;
     base::ObserverList<MediaSinksObserver> observers;
 
    private:
@@ -234,14 +234,14 @@
   // These calls invoke methods in the component extension via Mojo.
   void DoCreateRoute(const MediaSource::Id& source_id,
                      const MediaSink::Id& sink_id,
-                     const std::string& origin,
+                     const url::Origin& origin,
                      int tab_id,
                      const std::vector<MediaRouteResponseCallback>& callbacks,
                      base::TimeDelta timeout,
                      bool incognito);
   void DoJoinRoute(const MediaSource::Id& source_id,
                    const std::string& presentation_id,
-                   const std::string& origin,
+                   const url::Origin& origin,
                    int tab_id,
                    const std::vector<MediaRouteResponseCallback>& callbacks,
                    base::TimeDelta timeout,
@@ -249,7 +249,7 @@
   void DoConnectRouteByRouteId(
       const MediaSource::Id& source_id,
       const MediaRoute::Id& route_id,
-      const std::string& origin,
+      const url::Origin& origin,
       int tab_id,
       const std::vector<MediaRouteResponseCallback>& callbacks,
       base::TimeDelta timeout,
@@ -286,7 +286,7 @@
   void OnIssue(const IssueInfo& issue) override;
   void OnSinksReceived(const std::string& media_source,
                        std::vector<mojom::MediaSinkPtr> sinks,
-                       const std::vector<std::string>& origins) override;
+                       const std::vector<url::Origin>& origins) override;
   void OnRoutesUpdated(
       std::vector<mojom::MediaRoutePtr> routes,
       const std::string& media_source,
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
index e360552..70df1a6 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
@@ -195,10 +195,11 @@
   // a limitation with GMock::Invoke that prevents it from using move-only types
   // in runnable parameter lists.
   EXPECT_CALL(mock_media_route_provider_,
-              CreateRoute(kSource, kSinkId, _, kOrigin, kInvalidTabId, _, _, _))
+              CreateRoute(kSource, kSinkId, _, url::Origin(GURL(kOrigin)),
+                          kInvalidTabId, _, _, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& sink,
-             const std::string& presentation_id, const std::string& origin,
+             const std::string& presentation_id, const url::Origin& origin,
              int tab_id, base::TimeDelta timeout, bool incognito,
              const mojom::MediaRouteProvider::CreateRouteCallback& cb) {
             cb.Run(CreateMojoRoute(), std::string(),
@@ -213,9 +214,10 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->CreateRoute(
-      kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks,
-      base::TimeDelta::FromMilliseconds(kTimeoutMillis), false);
+  router()->CreateRoute(kSource, kSinkId, url::Origin(GURL(kOrigin)), nullptr,
+                        route_response_callbacks,
+                        base::TimeDelta::FromMilliseconds(kTimeoutMillis),
+                        false);
   run_loop.Run();
   ExpectResultBucketCount("CreateRoute", RouteRequestResult::ResultCode::OK, 1);
 }
@@ -230,10 +232,11 @@
   // a limitation with GMock::Invoke that prevents it from using move-only types
   // in runnable parameter lists.
   EXPECT_CALL(mock_media_route_provider_,
-              CreateRoute(kSource, kSinkId, _, kOrigin, kInvalidTabId, _, _, _))
+              CreateRoute(kSource, kSinkId, _, url::Origin(GURL(kOrigin)),
+                          kInvalidTabId, _, _, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& sink,
-             const std::string& presentation_id, const std::string& origin,
+             const std::string& presentation_id, const url::Origin& origin,
              int tab_id, base::TimeDelta timeout, bool incognito,
              const mojom::MediaRouteProvider::CreateRouteCallback& cb) {
             mojom::MediaRoutePtr route = CreateMojoRoute();
@@ -252,9 +255,10 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->CreateRoute(
-      kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks,
-      base::TimeDelta::FromMilliseconds(kTimeoutMillis), true);
+  router()->CreateRoute(kSource, kSinkId, url::Origin(GURL(kOrigin)), nullptr,
+                        route_response_callbacks,
+                        base::TimeDelta::FromMilliseconds(kTimeoutMillis),
+                        true);
   run_loop.Run();
   ExpectResultBucketCount("CreateRoute", RouteRequestResult::ResultCode::OK, 1);
 }
@@ -262,11 +266,12 @@
 TEST_F(MediaRouterMojoImplTest, CreateRouteFails) {
   EXPECT_CALL(
       mock_media_route_provider_,
-      CreateRoute(kSource, kSinkId, _, kOrigin, kInvalidTabId,
+      CreateRoute(kSource, kSinkId, _, url::Origin(GURL(kOrigin)),
+                  kInvalidTabId,
                   base::TimeDelta::FromMilliseconds(kTimeoutMillis), _, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& sink,
-             const std::string& presentation_id, const std::string& origin,
+             const std::string& presentation_id, const url::Origin& origin,
              int tab_id, base::TimeDelta timeout, bool incognito,
              const mojom::MediaRouteProvider::CreateRouteCallback& cb) {
             cb.Run(mojom::MediaRoutePtr(), std::string(kError),
@@ -281,22 +286,24 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->CreateRoute(
-      kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks,
-      base::TimeDelta::FromMilliseconds(kTimeoutMillis), false);
+  router()->CreateRoute(kSource, kSinkId, url::Origin(GURL(kOrigin)), nullptr,
+                        route_response_callbacks,
+                        base::TimeDelta::FromMilliseconds(kTimeoutMillis),
+                        false);
   run_loop.Run();
   ExpectResultBucketCount("CreateRoute",
                           RouteRequestResult::ResultCode::TIMED_OUT, 1);
 }
 
 TEST_F(MediaRouterMojoImplTest, CreateRouteIncognitoMismatchFails) {
-  EXPECT_CALL(mock_media_route_provider_,
-              CreateRoute(kSource, kSinkId, _, kOrigin, kInvalidTabId,
-                          base::TimeDelta::FromMilliseconds(kTimeoutMillis),
-                          true, _))
+  EXPECT_CALL(
+      mock_media_route_provider_,
+      CreateRoute(kSource, kSinkId, _, url::Origin(GURL(kOrigin)),
+                  kInvalidTabId,
+                  base::TimeDelta::FromMilliseconds(kTimeoutMillis), true, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& sink,
-             const std::string& presentation_id, const std::string& origin,
+             const std::string& presentation_id, const url::Origin& origin,
              int tab_id, base::TimeDelta timeout, bool incognito,
              const mojom::MediaRouteProvider::CreateRouteCallback& cb) {
             cb.Run(CreateMojoRoute(), std::string(),
@@ -312,9 +319,10 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->CreateRoute(
-      kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks,
-      base::TimeDelta::FromMilliseconds(kTimeoutMillis), true);
+  router()->CreateRoute(kSource, kSinkId, url::Origin(GURL(kOrigin)), nullptr,
+                        route_response_callbacks,
+                        base::TimeDelta::FromMilliseconds(kTimeoutMillis),
+                        true);
   run_loop.Run();
   ExpectResultBucketCount(
       "CreateRoute", RouteRequestResult::ResultCode::INCOGNITO_MISMATCH, 1);
@@ -324,13 +332,14 @@
   mojom::MediaRoutePtr route = CreateMojoRoute();
   route->is_incognito = true;
 
-  EXPECT_CALL(mock_media_route_provider_,
-              CreateRoute(kSource, kSinkId, _, kOrigin, kInvalidTabId,
-                          base::TimeDelta::FromMilliseconds(kTimeoutMillis),
-                          true, _))
+  EXPECT_CALL(
+      mock_media_route_provider_,
+      CreateRoute(kSource, kSinkId, _, url::Origin(GURL(kOrigin)),
+                  kInvalidTabId,
+                  base::TimeDelta::FromMilliseconds(kTimeoutMillis), true, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& sink,
-             const std::string& presentation_id, const std::string& origin,
+             const std::string& presentation_id, const url::Origin& origin,
              int tab_id, base::TimeDelta timeout, bool incognito,
              const mojom::MediaRouteProvider::CreateRouteCallback& cb) {
             mojom::MediaRoutePtr route = CreateMojoRoute();
@@ -339,7 +348,7 @@
                    mojom::RouteRequestResultCode::OK);
           }));
   base::RunLoop run_loop;
-  router()->CreateRoute(kSource, kSinkId, GURL(kOrigin), nullptr,
+  router()->CreateRoute(kSource, kSinkId, url::Origin(GURL(kOrigin)), nullptr,
                         std::vector<MediaRouteResponseCallback>(),
                         base::TimeDelta::FromMilliseconds(kTimeoutMillis),
                         true);
@@ -383,11 +392,12 @@
   // in runnable parameter lists.
   EXPECT_CALL(
       mock_media_route_provider_,
-      JoinRoute(kSource, kPresentationId, kOrigin, kInvalidTabId,
+      JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                kInvalidTabId,
                 base::TimeDelta::FromMilliseconds(kTimeoutMillis), _, _))
       .WillOnce(Invoke([&route](
           const std::string& source, const std::string& presentation_id,
-          const std::string& origin, int tab_id, base::TimeDelta timeout,
+          const url::Origin& origin, int tab_id, base::TimeDelta timeout,
           bool incognito,
           const mojom::MediaRouteProvider::JoinRouteCallback& cb) {
         cb.Run(std::move(route), std::string(),
@@ -402,8 +412,8 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr,
-                      route_response_callbacks,
+  router()->JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                      nullptr, route_response_callbacks,
                       base::TimeDelta::FromMilliseconds(kTimeoutMillis), false);
   run_loop.Run();
   ExpectResultBucketCount("JoinRoute", RouteRequestResult::ResultCode::OK, 1);
@@ -418,8 +428,8 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr,
-                      route_response_callbacks,
+  router()->JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                      nullptr, route_response_callbacks,
                       base::TimeDelta::FromMilliseconds(kTimeoutMillis), false);
   run_loop.Run();
   ExpectResultBucketCount("JoinRoute",
@@ -437,11 +447,12 @@
 
   EXPECT_CALL(
       mock_media_route_provider_,
-      JoinRoute(kSource, kPresentationId, kOrigin, kInvalidTabId,
+      JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                kInvalidTabId,
                 base::TimeDelta::FromMilliseconds(kTimeoutMillis), _, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& presentation_id,
-             const std::string& origin, int tab_id, base::TimeDelta timeout,
+             const url::Origin& origin, int tab_id, base::TimeDelta timeout,
              bool incognito,
              const mojom::MediaRouteProvider::JoinRouteCallback& cb) {
             cb.Run(mojom::MediaRoutePtr(), std::string(kError),
@@ -456,8 +467,8 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr,
-                      route_response_callbacks,
+  router()->JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                      nullptr, route_response_callbacks,
                       base::TimeDelta::FromMilliseconds(kTimeoutMillis), false);
   run_loop.Run();
   ExpectResultBucketCount("JoinRoute",
@@ -480,11 +491,12 @@
   // in runnable parameter lists.
   EXPECT_CALL(
       mock_media_route_provider_,
-      JoinRoute(kSource, kPresentationId, kOrigin, kInvalidTabId,
+      JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                kInvalidTabId,
                 base::TimeDelta::FromMilliseconds(kTimeoutMillis), true, _))
       .WillOnce(Invoke([&route](
           const std::string& source, const std::string& presentation_id,
-          const std::string& origin, int tab_id, base::TimeDelta timeout,
+          const url::Origin& origin, int tab_id, base::TimeDelta timeout,
           bool incognito,
           const mojom::MediaRouteProvider::JoinRouteCallback& cb) {
         cb.Run(std::move(route), std::string(),
@@ -500,8 +512,8 @@
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
-  router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr,
-                      route_response_callbacks,
+  router()->JoinRoute(kSource, kPresentationId, url::Origin(GURL(kOrigin)),
+                      nullptr, route_response_callbacks,
                       base::TimeDelta::FromMilliseconds(kTimeoutMillis), true);
   run_loop.Run();
   ExpectResultBucketCount(
@@ -520,12 +532,12 @@
   // in runnable parameter lists.
   EXPECT_CALL(
       mock_media_route_provider_,
-      ConnectRouteByRouteId(kSource, kRouteId, _, kOrigin, kInvalidTabId,
-                            base::TimeDelta::FromMilliseconds(kTimeoutMillis),
-                            false, _))
+      ConnectRouteByRouteId(
+          kSource, kRouteId, _, url::Origin(GURL(kOrigin)), kInvalidTabId,
+          base::TimeDelta::FromMilliseconds(kTimeoutMillis), false, _))
       .WillOnce(Invoke([&route](
           const std::string& source, const std::string& route_id,
-          const std::string& presentation_id, const std::string& origin,
+          const std::string& presentation_id, const url::Origin& origin,
           int tab_id, base::TimeDelta timeout, bool incognito,
           const mojom::MediaRouteProvider::JoinRouteCallback& cb) {
         cb.Run(std::move(route), std::string(),
@@ -541,7 +553,8 @@
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
   router()->ConnectRouteByRouteId(
-      kSource, kRouteId, GURL(kOrigin), nullptr, route_response_callbacks,
+      kSource, kRouteId, url::Origin(GURL(kOrigin)), nullptr,
+      route_response_callbacks,
       base::TimeDelta::FromMilliseconds(kTimeoutMillis), false);
   run_loop.Run();
   ExpectResultBucketCount("JoinRoute", RouteRequestResult::ResultCode::OK, 1);
@@ -550,12 +563,12 @@
 TEST_F(MediaRouterMojoImplTest, ConnectRouteByRouteIdFails) {
   EXPECT_CALL(
       mock_media_route_provider_,
-      ConnectRouteByRouteId(kSource, kRouteId, _, kOrigin, kInvalidTabId,
-                            base::TimeDelta::FromMilliseconds(kTimeoutMillis),
-                            true, _))
+      ConnectRouteByRouteId(
+          kSource, kRouteId, _, url::Origin(GURL(kOrigin)), kInvalidTabId,
+          base::TimeDelta::FromMilliseconds(kTimeoutMillis), true, _))
       .WillOnce(Invoke(
           [](const std::string& source, const std::string& route_id,
-             const std::string& presentation_id, const std::string& origin,
+             const std::string& presentation_id, const url::Origin& origin,
              int tab_id, base::TimeDelta timeout, bool incognito,
              const mojom::MediaRouteProvider::JoinRouteCallback& cb) {
             cb.Run(mojom::MediaRoutePtr(), std::string(kError),
@@ -571,7 +584,8 @@
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
   router()->ConnectRouteByRouteId(
-      kSource, kRouteId, GURL(kOrigin), nullptr, route_response_callbacks,
+      kSource, kRouteId, url::Origin(GURL(kOrigin)), nullptr,
+      route_response_callbacks,
       base::TimeDelta::FromMilliseconds(kTimeoutMillis), true);
   run_loop.Run();
   ExpectResultBucketCount("JoinRoute",
@@ -586,12 +600,12 @@
   // in runnable parameter lists.
   EXPECT_CALL(
       mock_media_route_provider_,
-      ConnectRouteByRouteId(kSource, kRouteId, _, kOrigin, kInvalidTabId,
-                            base::TimeDelta::FromMilliseconds(kTimeoutMillis),
-                            true, _))
+      ConnectRouteByRouteId(
+          kSource, kRouteId, _, url::Origin(GURL(kOrigin)), kInvalidTabId,
+          base::TimeDelta::FromMilliseconds(kTimeoutMillis), true, _))
       .WillOnce(Invoke([&route](
           const std::string& source, const std::string& route_id,
-          const std::string& presentation_id, const std::string& origin,
+          const std::string& presentation_id, const url::Origin& origin,
           int tab_id, base::TimeDelta timeout, bool incognito,
           const mojom::MediaRouteProvider::JoinRouteCallback& cb) {
         cb.Run(std::move(route), std::string(),
@@ -608,7 +622,8 @@
   route_response_callbacks.push_back(base::Bind(
       &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler)));
   router()->ConnectRouteByRouteId(
-      kSource, kRouteId, GURL(kOrigin), nullptr, route_response_callbacks,
+      kSource, kRouteId, url::Origin(GURL(kOrigin)), nullptr,
+      route_response_callbacks,
       base::TimeDelta::FromMilliseconds(kTimeoutMillis), true);
   run_loop.Run();
   ExpectResultBucketCount(
@@ -696,7 +711,6 @@
   router()->OnSinkAvailabilityUpdated(
       mojom::MediaRouter::SinkAvailability::AVAILABLE);
   MediaSource media_source(kSource);
-  GURL origin("https://google.com");
 
   // These should only be called once even if there is more than one observer
   // for a given source.
@@ -704,13 +718,16 @@
   EXPECT_CALL(mock_media_route_provider_, StartObservingMediaSinks(kSource2));
 
   std::unique_ptr<MockMediaSinksObserver> sinks_observer(
-      new MockMediaSinksObserver(router(), media_source, origin));
+      new MockMediaSinksObserver(router(), media_source,
+                                 url::Origin(GURL(kOrigin))));
   EXPECT_TRUE(sinks_observer->Init());
   std::unique_ptr<MockMediaSinksObserver> extra_sinks_observer(
-      new MockMediaSinksObserver(router(), media_source, origin));
+      new MockMediaSinksObserver(router(), media_source,
+                                 url::Origin(GURL(kOrigin))));
   EXPECT_TRUE(extra_sinks_observer->Init());
   std::unique_ptr<MockMediaSinksObserver> unrelated_sinks_observer(
-      new MockMediaSinksObserver(router(), MediaSource(kSource2), origin));
+      new MockMediaSinksObserver(router(), MediaSource(kSource2),
+                                 url::Origin(GURL(kOrigin))));
   EXPECT_TRUE(unrelated_sinks_observer->Init());
   ProcessEventLoop();
 
@@ -739,14 +756,15 @@
       .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
   media_router_proxy_->OnSinksReceived(
       media_source.id(), std::move(mojo_sinks),
-      std::vector<std::string>(1, origin.spec()));
+      std::vector<url::Origin>(1, url::Origin(GURL(kOrigin))));
   run_loop.Run();
 
   // Since the MediaRouterMojoImpl has already received results for
   // |media_source|, return cached results to observers that are subsequently
   // registered.
   std::unique_ptr<MockMediaSinksObserver> cached_sinks_observer(
-      new MockMediaSinksObserver(router(), media_source, origin));
+      new MockMediaSinksObserver(router(), media_source,
+                                 url::Origin(GURL(kOrigin))));
   EXPECT_CALL(*cached_sinks_observer,
               OnSinksReceived(SequenceEquals(expected_sinks)));
   EXPECT_TRUE(cached_sinks_observer->Init());
@@ -754,7 +772,7 @@
   // Different origin from cached result. Empty list will be returned.
   std::unique_ptr<MockMediaSinksObserver> cached_sinks_observer2(
       new MockMediaSinksObserver(router(), media_source,
-                                 GURL("https://youtube.com")));
+                                 url::Origin(GURL("https://youtube.com"))));
   EXPECT_CALL(*cached_sinks_observer2, OnSinksReceived(IsEmpty()));
   EXPECT_TRUE(cached_sinks_observer2->Init());
 
@@ -772,19 +790,20 @@
 
 TEST_F(MediaRouterMojoImplTest,
        RegisterMediaSinksObserverWithAvailabilityChange) {
-  GURL origin("https://google.com");
 
   // When availability is UNAVAILABLE, no calls should be made to MRPM.
   router()->OnSinkAvailabilityUpdated(
       mojom::MediaRouter::SinkAvailability::UNAVAILABLE);
   MediaSource media_source(kSource);
   std::unique_ptr<MockMediaSinksObserver> sinks_observer(
-      new MockMediaSinksObserver(router(), media_source, origin));
+      new MockMediaSinksObserver(router(), media_source,
+                                 url::Origin(GURL(kOrigin))));
   EXPECT_CALL(*sinks_observer, OnSinksReceived(IsEmpty()));
   EXPECT_TRUE(sinks_observer->Init());
   MediaSource media_source2(kSource2);
   std::unique_ptr<MockMediaSinksObserver> sinks_observer2(
-      new MockMediaSinksObserver(router(), media_source2, origin));
+      new MockMediaSinksObserver(router(), media_source2,
+                                 url::Origin(GURL(kOrigin))));
   EXPECT_CALL(*sinks_observer2, OnSinksReceived(IsEmpty()));
   EXPECT_TRUE(sinks_observer2->Init());
   EXPECT_CALL(mock_media_route_provider_, StartObservingMediaSinks(kSource))
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_test.h b/chrome/browser/media/router/mojo/media_router_mojo_test.h
index 72609c8..b978c6d 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_test.h
+++ b/chrome/browser/media/router/mojo/media_router_mojo_test.h
@@ -34,7 +34,7 @@
                void(const std::string& source_urn,
                     const std::string& sink_id,
                     const std::string& presentation_id,
-                    const std::string& origin,
+                    const url::Origin& origin,
                     int tab_id,
                     base::TimeDelta timeout,
                     bool incognito,
@@ -42,7 +42,7 @@
   MOCK_METHOD7(JoinRoute,
                void(const std::string& source_urn,
                     const std::string& presentation_id,
-                    const std::string& origin,
+                    const url::Origin& origin,
                     int tab_id,
                     base::TimeDelta timeout,
                     bool incognito,
@@ -51,7 +51,7 @@
                void(const std::string& source_urn,
                     const std::string& route_id,
                     const std::string& presentation_id,
-                    const std::string& origin,
+                    const url::Origin& origin,
                     int tab_id,
                     base::TimeDelta timeout,
                     bool incognito,
diff --git a/chrome/browser/media/router/presentation_media_sinks_observer.cc b/chrome/browser/media/router/presentation_media_sinks_observer.cc
index 5bc1429..5a8d6281 100644
--- a/chrome/browser/media/router/presentation_media_sinks_observer.cc
+++ b/chrome/browser/media/router/presentation_media_sinks_observer.cc
@@ -14,7 +14,7 @@
     MediaRouter* router,
     content::PresentationScreenAvailabilityListener* listener,
     const MediaSource& source,
-    const GURL& origin)
+    const url::Origin& origin)
     : MediaSinksObserver(router, source, origin),
       listener_(listener),
       previous_availablity_(UNKNOWN) {
diff --git a/chrome/browser/media/router/presentation_media_sinks_observer.h b/chrome/browser/media/router/presentation_media_sinks_observer.h
index ce13d9b..b8960027 100644
--- a/chrome/browser/media/router/presentation_media_sinks_observer.h
+++ b/chrome/browser/media/router/presentation_media_sinks_observer.h
@@ -35,7 +35,7 @@
       MediaRouter* router,
       content::PresentationScreenAvailabilityListener* listener,
       const MediaSource& source,
-      const GURL& origin);
+      const url::Origin& origin);
   ~PresentationMediaSinksObserver() override;
 
   // MediaSinksObserver implementation.
diff --git a/chrome/browser/media/router/presentation_media_sinks_observer_unittest.cc b/chrome/browser/media/router/presentation_media_sinks_observer_unittest.cc
index 96166a8..7e9cc6b 100644
--- a/chrome/browser/media/router/presentation_media_sinks_observer_unittest.cc
+++ b/chrome/browser/media/router/presentation_media_sinks_observer_unittest.cc
@@ -20,6 +20,10 @@
 
 namespace media_router {
 
+namespace {
+constexpr char kOrigin[] = "https://google.com";
+}  // namespace
+
 class PresentationMediaSinksObserverTest : public ::testing::Test {
  public:
   PresentationMediaSinksObserverTest()
@@ -31,7 +35,7 @@
     observer_.reset(new PresentationMediaSinksObserver(
         &router_, &listener_, MediaSourceForPresentationUrl(
                                   GURL("http://example.com/presentation.html")),
-        GURL("https://google.com")));
+        url::Origin(GURL(kOrigin))));
     EXPECT_TRUE(observer_->Init());
   }
 
diff --git a/chrome/browser/media/router/presentation_request.cc b/chrome/browser/media/router/presentation_request.cc
index 89f7b4fb..3952d0a 100644
--- a/chrome/browser/media/router/presentation_request.cc
+++ b/chrome/browser/media/router/presentation_request.cc
@@ -11,10 +11,10 @@
 PresentationRequest::PresentationRequest(
     const RenderFrameHostId& render_frame_host_id,
     const std::vector<GURL>& presentation_urls,
-    const GURL& frame_url)
+    const url::Origin& frame_origin)
     : render_frame_host_id_(render_frame_host_id),
       presentation_urls_(presentation_urls),
-      frame_url_(frame_url) {
+      frame_origin_(frame_origin) {
   DCHECK(!presentation_urls_.empty());
 }
 
@@ -26,7 +26,8 @@
 bool PresentationRequest::Equals(const PresentationRequest& other) const {
   return render_frame_host_id_ == other.render_frame_host_id_ &&
          presentation_urls_ == other.presentation_urls_ &&
-         frame_url_ == other.frame_url_;
+         ((frame_origin_.unique() && other.frame_origin_.unique()) ||
+          (frame_origin_ == other.frame_origin_));
 }
 
 std::vector<MediaSource> PresentationRequest::GetMediaSources() const {
diff --git a/chrome/browser/media/router/presentation_request.h b/chrome/browser/media/router/presentation_request.h
index 1879ec1..e305673 100644
--- a/chrome/browser/media/router/presentation_request.h
+++ b/chrome/browser/media/router/presentation_request.h
@@ -10,7 +10,7 @@
 
 #include "chrome/browser/media/router/media_source.h"
 #include "chrome/browser/media/router/render_frame_host_id.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace media_router {
 
@@ -20,7 +20,7 @@
  public:
   PresentationRequest(const RenderFrameHostId& render_frame_host_id,
                       const std::vector<GURL>& presentation_urls,
-                      const GURL& frame_url);
+                      const url::Origin& frame_origin);
   PresentationRequest(const PresentationRequest& other);
   ~PresentationRequest();
 
@@ -35,7 +35,7 @@
   const std::vector<GURL>& presentation_urls() const {
     return presentation_urls_;
   }
-  const GURL& frame_url() const { return frame_url_; }
+  const url::Origin& frame_origin() const { return frame_origin_; }
 
  private:
   // ID of RenderFrameHost that initiated the request.
@@ -44,10 +44,8 @@
   // URLs of presentation.
   const std::vector<GURL> presentation_urls_;
 
-  // URL of frame from which the request was initiated.
-  // TODO(crbug.com/632623): Convert this to url::Origin as only the origin or
-  // hostname is used.
-  const GURL frame_url_;
+  // Origin of frame from which the request was initiated.
+  const url::Origin frame_origin_;
 };
 
 }  // namespace media_router
diff --git a/chrome/browser/media/router/presentation_request_unittest.cc b/chrome/browser/media/router/presentation_request_unittest.cc
index 165b5d0..8144d73 100644
--- a/chrome/browser/media/router/presentation_request_unittest.cc
+++ b/chrome/browser/media/router/presentation_request_unittest.cc
@@ -8,17 +8,17 @@
 namespace media_router {
 
 TEST(PresentationRequestTest, Equals) {
-  GURL frame_url("http://www.site.com/");
+  url::Origin frame_origin(GURL("http://www.site.com/"));
   std::vector<GURL> presentation_urls = {
       GURL("http://www.example.com/presentation.html"),
       GURL("http://www.example.net/alternate.html")};
 
   PresentationRequest request1(RenderFrameHostId(1, 2), presentation_urls,
-                               frame_url);
+                               frame_origin);
 
   // Frame IDs are different.
   PresentationRequest request2(RenderFrameHostId(3, 4), presentation_urls,
-                               frame_url);
+                               frame_origin);
   EXPECT_FALSE(request1.Equals(request2));
 
   // Presentation URLs are different.
@@ -26,19 +26,19 @@
       RenderFrameHostId(1, 2),
       {GURL("http://www.example.net/presentation.html"),
        GURL("http://www.example.com/presentation.html")},
-      frame_url);
+      frame_origin);
   EXPECT_FALSE(request1.Equals(request3));
 
   // Frame URLs are different.
   PresentationRequest request4(RenderFrameHostId(1, 2), presentation_urls,
-                               GURL("http://www.site.net/"));
+                               url::Origin(GURL("http://www.site.net/")));
   EXPECT_FALSE(request1.Equals(request4));
 
   PresentationRequest request5(
       RenderFrameHostId(1, 2),
       {GURL("http://www.example.com/presentation.html"),
        GURL("http://www.example.net/alternate.html")},
-      GURL("http://www.site.com/"));
+      url::Origin(GURL("http://www.site.com/")));
   EXPECT_TRUE(request1.Equals(request5));
 }
 
diff --git a/chrome/browser/media/router/presentation_service_delegate_impl.cc b/chrome/browser/media/router/presentation_service_delegate_impl.cc
index 59e441a..511052a 100644
--- a/chrome/browser/media/router/presentation_service_delegate_impl.cc
+++ b/chrome/browser/media/router/presentation_service_delegate_impl.cc
@@ -60,14 +60,12 @@
 
 // Gets the last committed URL for the render frame specified by
 // |render_frame_host_id|.
-GURL GetLastCommittedURLForFrame(RenderFrameHostId render_frame_host_id) {
+url::Origin GetLastCommittedURLForFrame(
+    RenderFrameHostId render_frame_host_id) {
   RenderFrameHost* render_frame_host = RenderFrameHost::FromID(
       render_frame_host_id.first, render_frame_host_id.second);
-  if (!render_frame_host)
-    return GURL();
-
-  // TODO(crbug.com/632623): Use url::Origin in place of GURL for origins
-  return render_frame_host->GetLastCommittedOrigin().GetURL();
+  DCHECK(render_frame_host);
+  return render_frame_host->GetLastCommittedOrigin();
 }
 
 // Observes messages originating from the MediaSink connected to a MediaRoute
@@ -233,7 +231,7 @@
 
   sinks_observer.reset(new PresentationMediaSinksObserver(
       router_, listener, source,
-      GetLastCommittedURLForFrame(render_frame_host_id_).GetOrigin()));
+      GetLastCommittedURLForFrame(render_frame_host_id_)));
 
   if (!sinks_observer->Init()) {
     url_to_sinks_observer_.erase(source.id());
@@ -554,9 +552,10 @@
     ClearDefaultPresentationRequest();
   } else {
     DCHECK(!callback.is_null());
-    GURL frame_url(GetLastCommittedURLForFrame(render_frame_host_id));
+    const auto& frame_origin =
+        GetLastCommittedURLForFrame(render_frame_host_id);
     PresentationRequest request(render_frame_host_id, default_presentation_urls,
-                                frame_url);
+                                frame_origin);
     default_presentation_started_callback_ = callback;
     SetDefaultPresentationRequest(request);
   }
@@ -807,8 +806,8 @@
     return;
   }
 
-  const url::Origin& origin = url::Origin(GetLastCommittedURLForFrame(
-      RenderFrameHostId(render_process_id, render_frame_id)));
+  const url::Origin& origin = GetLastCommittedURLForFrame(
+      RenderFrameHostId(render_process_id, render_frame_id));
 
 #if !defined(OS_ANDROID)
   if (IsAutoJoinPresentationId(presentation_id) &&
@@ -829,7 +828,7 @@
                  weak_factory_.GetWeakPtr(), render_process_id, render_frame_id,
                  presentation_url, presentation_id, success_cb, error_cb));
   router_->JoinRoute(MediaSourceForPresentationUrl(presentation_url).id(),
-                     presentation_id, origin.GetURL(), web_contents_,
+                     presentation_id, origin, web_contents_,
                      route_response_callbacks, base::TimeDelta(), incognito);
 }
 
diff --git a/chrome/browser/media/router/presentation_service_delegate_impl_unittest.cc b/chrome/browser/media/router/presentation_service_delegate_impl_unittest.cc
index d4543441..b8f6170 100644
--- a/chrome/browser/media/router/presentation_service_delegate_impl_unittest.cc
+++ b/chrome/browser/media/router/presentation_service_delegate_impl_unittest.cc
@@ -151,8 +151,9 @@
     EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
 
     // Should not trigger callback since request doesn't match.
-    PresentationRequest different_request(
-        RenderFrameHostId(100, 200), {presentation_url2_}, GURL(kFrameUrl));
+    PresentationRequest different_request(RenderFrameHostId(100, 200),
+                                          {presentation_url2_},
+                                          url::Origin(GURL(kFrameUrl)));
     MediaRoute* media_route = new MediaRoute("differentRouteId", source2_,
                                              "mediaSinkId", "", true, "", true);
     media_route->set_incognito(incognito);
@@ -321,7 +322,7 @@
   EXPECT_EQ(presentation_url1_, request1.presentation_urls()[0]);
   EXPECT_EQ(RenderFrameHostId(main_frame_process_id_, main_frame_routing_id_),
             request1.render_frame_host_id());
-  EXPECT_EQ(frame_url, request1.frame_url());
+  EXPECT_EQ(url::Origin(frame_url), request1.frame_origin());
 
   // Set to a new default presentation URL
   std::vector<GURL> new_urls = {presentation_url2_};
@@ -333,7 +334,7 @@
   EXPECT_EQ(presentation_url2_, request2.presentation_urls()[0]);
   EXPECT_EQ(RenderFrameHostId(main_frame_process_id_, main_frame_routing_id_),
             request2.render_frame_host_id());
-  EXPECT_EQ(frame_url, request2.frame_url());
+  EXPECT_EQ(url::Origin(frame_url), request2.frame_origin());
 
   // Remove default presentation URL.
   delegate_impl_->SetDefaultPresentationUrls(main_frame_process_id_,
@@ -367,7 +368,7 @@
   std::vector<GURL> request1_urls = {presentation_url1_};
   PresentationRequest observed_request1(
       RenderFrameHostId(main_frame_process_id_, main_frame_routing_id_),
-      request1_urls, frame_url);
+      request1_urls, url::Origin(frame_url));
   EXPECT_CALL(observer, OnDefaultPresentationChanged(Equals(observed_request1)))
       .Times(1);
   delegate_impl_->SetDefaultPresentationUrls(
@@ -383,7 +384,7 @@
   std::vector<GURL> request2_urls = {presentation_url2_};
   PresentationRequest observed_request2(
       RenderFrameHostId(main_frame_process_id_, main_frame_routing_id_),
-      request2_urls, frame_url);
+      request2_urls, url::Origin(frame_url));
   EXPECT_CALL(observer, OnDefaultPresentationChanged(Equals(observed_request2)))
       .Times(1);
   delegate_impl_->SetDefaultPresentationUrls(
diff --git a/chrome/browser/media/router/test_helper.cc b/chrome/browser/media/router/test_helper.cc
index 8d38dd65..5d98680 100644
--- a/chrome/browser/media/router/test_helper.cc
+++ b/chrome/browser/media/router/test_helper.cc
@@ -14,7 +14,7 @@
 
 MockMediaSinksObserver::MockMediaSinksObserver(MediaRouter* router,
                                                const MediaSource& source,
-                                               const GURL& origin)
+                                               const url::Origin& origin)
     : MediaSinksObserver(router, source, origin) {}
 MockMediaSinksObserver::~MockMediaSinksObserver() {
 }
diff --git a/chrome/browser/media/router/test_helper.h b/chrome/browser/media/router/test_helper.h
index 84c1001..80e2ee4 100644
--- a/chrome/browser/media/router/test_helper.h
+++ b/chrome/browser/media/router/test_helper.h
@@ -62,7 +62,7 @@
  public:
   MockMediaSinksObserver(MediaRouter* router,
                          const MediaSource& source,
-                         const GURL& origin);
+                         const url::Origin& origin);
   ~MockMediaSinksObserver() override;
 
   MOCK_METHOD1(OnSinksReceived, void(const std::vector<MediaSink>& sinks));
diff --git a/chrome/browser/resources/bluetooth_internals/bluetooth_internals.css b/chrome/browser/resources/bluetooth_internals/bluetooth_internals.css
index b19499df..c0f225a 100644
--- a/chrome/browser/resources/bluetooth_internals/bluetooth_internals.css
+++ b/chrome/browser/resources/bluetooth_internals/bluetooth_internals.css
@@ -4,12 +4,16 @@
  */
 
 :root {
-  --expandable-list-item-border: 1px solid gray;
+  --dark-primary-color: rgb(25, 118, 210);
+  --darker-primary-color: rgb(13, 71, 161);
+  --divider-border: 1px solid #bdbdbd;
   --fade-duration: 225ms;
   --header-height: 48px;
   --md-timing-function: cubic-bezier(.4, 0, .6, 1);
-  --sidebar-width: 155px;
+  --primary-color: rgb(33, 150, 243);
+  --section-padding: 1em;
   --sidebar-neg-width: calc(var(--sidebar-width) * -1);
+  --sidebar-width: 155px;
 }
 
 html,
@@ -49,10 +53,8 @@
 
 .expandable-list-item .brief-content {
   align-items: center;
-  background-color: whitesmoke;
-  border-left: var(--expandable-list-item-border);
-  border-right: var(--expandable-list-item-border);
-  border-top: var(--expandable-list-item-border);
+  border-bottom: var(--divider-border);
+  color: white;
   cursor: pointer;
   display: flex;
   font-weight: 600;
@@ -60,12 +62,16 @@
   padding: 8px;
 }
 
-.expandable-list-item.expanded > .brief-content {
-  border-bottom: var(--expandable-list-item-border);
+.service-list-item > .brief-content {
+  background-color: var(--primary-color);
 }
 
-.expandable-list-item:nth-last-child(2) > .brief-content {
-  border-bottom: var(--expandable-list-item-border);
+.characteristic-list-item > .brief-content {
+  background-color: var(--dark-primary-color);
+}
+
+.descriptor-list-item > .brief-content {
+  background-color: var(--darker-primary-color);
 }
 
 .expandable-list-item > .expanded-content {
@@ -77,8 +83,13 @@
   height: auto;
 }
 
-.expandable-list-item .info-container {
-  padding: 0 0 8px;
+.expandable-list-item .info-container > h4,
+.expandable-list-item .info-container > div {
+  margin: var(--section-padding);
+}
+
+.empty-message {
+  padding-left: calc(2 * var(--section-padding));
 }
 
 /* Page container */
@@ -94,11 +105,15 @@
 
 /* Page content */
 #page-container > section {
-  padding: 24px 16px;
+  padding: var(--section-padding);
+}
+
+#page-container .services {
+  margin: 0 calc(var(--section-padding) * -1);
 }
 
 #page-container .header-extras {
-  -webkit-margin-end: 16px;
+  -webkit-margin-end: var(--section-padding);
   -webkit-margin-start: var(--sidebar-width);
   align-items: flex-end;
   display: flex;
@@ -193,7 +208,7 @@
 }
 
 .sidebar-content button {
-  -webkit-padding-start: 16px;
+  -webkit-padding-start: var(--section-padding);
   background-color: transparent;
   border: 0;
   color: #999;
@@ -259,7 +274,7 @@
 
 table th,
 table td {
-  border: 1px solid #d9d9d9;
+  border: var(--divider-border);
   padding: 7px;
 }
 
@@ -269,10 +284,15 @@
 }
 
 table .removed {
-  background-color: #bdbdbd;
+  background-color: #e0e0e0;
 }
 
 @media screen and (max-width: 600px) {
+  table {
+    border-collapse: separate;
+    border-spacing: 0 var(--section-padding);
+  }
+
   table thead {
     display: none;
   }
@@ -287,6 +307,15 @@
     float: left;
     font-weight: bold;
   }
+
+  table th,
+  table td {
+    border-bottom: 0;
+  }
+
+  table td:last-child {
+    border-bottom: var(--divider-border);
+  }
 }
 
 /* Snackbar */
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html b/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html
index befa6ed..b22bead 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html
@@ -92,7 +92,7 @@
                 </span>
               </a>
             </div>
-            <div class="username-column selectable"
+            <div class="username-column selectable text-elide"
                 id="username">[[item.loginPair.username]]</div>
             <div class="password-column">
               <!-- Password type and disabled in order to match mock. -->
diff --git a/chrome/browser/ui/ash/cast_config_client_media_router.cc b/chrome/browser/ui/ash/cast_config_client_media_router.cc
index 333ca46..26e7847 100644
--- a/chrome/browser/ui/ash/cast_config_client_media_router.cc
+++ b/chrome/browser/ui/ash/cast_config_client_media_router.cc
@@ -95,7 +95,7 @@
     : MediaRoutesObserver(GetMediaRouter()),
       MediaSinksObserver(GetMediaRouter(),
                          media_router::MediaSourceForDesktop(),
-                         GURL(chrome::kChromeUIMediaRouterURL)),
+                         url::Origin(GURL(chrome::kChromeUIMediaRouterURL))),
       cast_config_client_(cast_config_client) {}
 
 CastDeviceCache::~CastDeviceCache() {}
@@ -226,7 +226,7 @@
   // TODO(imcheng): Pass in tab casting timeout.
   GetMediaRouter()->CreateRoute(
       media_router::MediaSourceForDesktop().id(), sink->id,
-      GURL("http://cros-cast-origin/"), nullptr,
+      url::Origin(GURL("http://cros-cast-origin/")), nullptr,
       std::vector<media_router::MediaRouteResponseCallback>(),
       base::TimeDelta(), false);
 }
diff --git a/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc b/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc
index 0184f44..0534c0c 100644
--- a/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc
+++ b/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc
@@ -119,23 +119,25 @@
 
   // The tray should be hidden when there are no sinks.
   EXPECT_FALSE(test_api.IsTrayVisible());
-  media_sinks_observer()->OnSinksUpdated(zero_sinks, std::vector<GURL>());
+  media_sinks_observer()->OnSinksUpdated(zero_sinks,
+                                         std::vector<url::Origin>());
   // Flush mojo messages from the chrome object to the ash object.
   content::RunAllPendingInMessageLoop();
   EXPECT_FALSE(test_api.IsTrayVisible());
   EXPECT_FALSE(test_api.IsTraySelectViewVisible());
 
   // The tray should be visible with any more than zero sinks.
-  media_sinks_observer()->OnSinksUpdated(one_sink, std::vector<GURL>());
+  media_sinks_observer()->OnSinksUpdated(one_sink, std::vector<url::Origin>());
   content::RunAllPendingInMessageLoop();
   EXPECT_TRUE(test_api.IsTrayVisible());
-  media_sinks_observer()->OnSinksUpdated(two_sinks, std::vector<GURL>());
+  media_sinks_observer()->OnSinksUpdated(two_sinks, std::vector<url::Origin>());
   content::RunAllPendingInMessageLoop();
   EXPECT_TRUE(test_api.IsTrayVisible());
   EXPECT_TRUE(test_api.IsTraySelectViewVisible());
 
   // And if all of the sinks go away, it should be hidden again.
-  media_sinks_observer()->OnSinksUpdated(zero_sinks, std::vector<GURL>());
+  media_sinks_observer()->OnSinksUpdated(zero_sinks,
+                                         std::vector<url::Origin>());
   content::RunAllPendingInMessageLoop();
   EXPECT_FALSE(test_api.IsTrayVisible());
   EXPECT_FALSE(test_api.IsTraySelectViewVisible());
@@ -154,7 +156,7 @@
   std::vector<media_router::MediaSink> sinks;
   sinks.push_back(MakeSink("remote_sink", "name"));
   sinks.push_back(MakeSink("local_sink", "name"));
-  media_sinks_observer()->OnSinksUpdated(sinks, std::vector<GURL>());
+  media_sinks_observer()->OnSinksUpdated(sinks, std::vector<url::Origin>());
   content::RunAllPendingInMessageLoop();
 
   // Create route combinations. More details below.
diff --git a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc
index 04862fe..487576f 100644
--- a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc
@@ -6,6 +6,7 @@
 
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
+#include "ash/wm/window_properties.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/ui/ash/ash_init.h"
@@ -24,13 +25,78 @@
 #include "chrome/browser/ui/views/frame/immersive_handler_factory_mus.h"
 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
 #include "chrome/browser/ui/views/select_file_dialog_extension_factory.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/mus/property_converter.h"
+#include "ui/aura/window_property.h"
 #include "ui/keyboard/content/keyboard.h"
 #include "ui/keyboard/keyboard_controller.h"
+#include "ui/views/mus/mus_client.h"
+#include "ui/views/mus/mus_property_mirror.h"
+
+// Relays aura content window properties to its root window (the mash frame).
+// Ash relies on various window properties for frame titles, shelf items, etc.
+// These properties are read from the client's root, not child content windows.
+class MusPropertyMirrorAsh : public views::MusPropertyMirror {
+ public:
+  MusPropertyMirrorAsh() {}
+  ~MusPropertyMirrorAsh() override {}
+
+  // MusPropertyMirror:
+  void MirrorPropertyFromWidgetWindowToRootWindow(aura::Window* window,
+                                                  aura::Window* root_window,
+                                                  const void* key) override {
+    if (key == ash::kShelfIDKey) {
+      int32_t value = window->GetProperty(ash::kShelfIDKey);
+      root_window->SetProperty(ash::kShelfIDKey, value);
+    } else if (key == ash::kShelfItemTypeKey) {
+      int32_t value = window->GetProperty(ash::kShelfItemTypeKey);
+      root_window->SetProperty(ash::kShelfItemTypeKey, value);
+    } else if (key == aura::client::kAppIconKey) {
+      gfx::ImageSkia* value = window->GetProperty(aura::client::kAppIconKey);
+      root_window->SetProperty(aura::client::kAppIconKey, value);
+    } else if (key == aura::client::kAppIdKey) {
+      std::string* value = window->GetProperty(aura::client::kAppIdKey);
+      root_window->SetProperty(aura::client::kAppIdKey, value);
+    } else if (key == aura::client::kDrawAttentionKey) {
+      bool value = window->GetProperty(aura::client::kDrawAttentionKey);
+      root_window->SetProperty(aura::client::kDrawAttentionKey, value);
+    } else if (key == aura::client::kTitleKey) {
+      base::string16* value = window->GetProperty(aura::client::kTitleKey);
+      root_window->SetProperty(aura::client::kTitleKey, value);
+    } else if (key == aura::client::kWindowIconKey) {
+      gfx::ImageSkia* value = window->GetProperty(aura::client::kWindowIconKey);
+      root_window->SetProperty(aura::client::kWindowIconKey, value);
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MusPropertyMirrorAsh);
+};
 
 ChromeBrowserMainExtraPartsAsh::ChromeBrowserMainExtraPartsAsh() {}
 
 ChromeBrowserMainExtraPartsAsh::~ChromeBrowserMainExtraPartsAsh() {}
 
+void ChromeBrowserMainExtraPartsAsh::ServiceManagerConnectionStarted(
+    content::ServiceManagerConnection* connection) {
+  if (chrome::IsRunningInMash()) {
+    // Register ash-specific window properties with Chrome's property converter.
+    // This propagates ash properties set on chrome windows to ash, via mojo.
+    DCHECK(views::MusClient::Exists());
+    views::MusClient* mus_client = views::MusClient::Get();
+    aura::WindowTreeClientDelegate* delegate = mus_client;
+    aura::PropertyConverter* converter = delegate->GetPropertyConverter();
+    converter->RegisterProperty(
+        ash::kPanelAttachedKey,
+        ui::mojom::WindowManager::kPanelAttached_Property);
+    converter->RegisterProperty(
+        ash::kShelfItemTypeKey,
+        ui::mojom::WindowManager::kShelfItemType_Property);
+
+    mus_client->SetMusPropertyMirror(base::MakeUnique<MusPropertyMirrorAsh>());
+  }
+}
+
 void ChromeBrowserMainExtraPartsAsh::PreProfileInit() {
   if (chrome::ShouldOpenAshOnStartup())
     chrome::OpenAsh(gfx::kNullAcceleratedWidget);
diff --git a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h
index a25ef3f..f75181d 100644
--- a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h
+++ b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h
@@ -30,6 +30,8 @@
   ~ChromeBrowserMainExtraPartsAsh() override;
 
   // Overridden from ChromeBrowserMainExtraParts:
+  void ServiceManagerConnectionStarted(
+      content::ServiceManagerConnection* connection) override;
   void PreProfileInit() override;
   void PostProfileInit() override;
   void PostMainMessageLoopRun() override;
diff --git a/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc
index c428fa09..7ddf1fd7 100644
--- a/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/chromeos/customization/customization_document.h"
 #include "chrome/browser/chromeos/idle_detector.h"
 #include "chrome/browser/chromeos/login/screens/core_oobe_actor.h"
-#include "chrome/browser/chromeos/login/screens/network_model.h"
+#include "chrome/browser/chromeos/login/screens/network_screen.h"
 #include "chrome/browser/chromeos/login/ui/input_events_blocker.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
@@ -55,16 +55,13 @@
 // NetworkScreenHandler, public: -----------------------------------------------
 
 NetworkScreenHandler::NetworkScreenHandler(CoreOobeActor* core_oobe_actor)
-    : BaseScreenHandler(kJsScreenPath),
-      core_oobe_actor_(core_oobe_actor),
-      model_(nullptr),
-      show_on_init_(false) {
+    : BaseScreenHandler(kJsScreenPath), core_oobe_actor_(core_oobe_actor) {
   DCHECK(core_oobe_actor_);
 }
 
 NetworkScreenHandler::~NetworkScreenHandler() {
-  if (model_)
-    model_->OnViewDestroyed(this);
+  if (screen_)
+    screen_->OnViewDestroyed(this);
 }
 
 // NetworkScreenHandler, NetworkScreenActor implementation: --------------------
@@ -106,13 +103,13 @@
 void NetworkScreenHandler::Hide() {
 }
 
-void NetworkScreenHandler::Bind(NetworkModel& model) {
-  model_ = &model;
-  BaseScreenHandler::SetBaseScreen(model_);
+void NetworkScreenHandler::Bind(NetworkScreen* screen) {
+  screen_ = screen;
+  BaseScreenHandler::SetBaseScreen(screen_);
 }
 
 void NetworkScreenHandler::Unbind() {
-  model_ = nullptr;
+  screen_ = nullptr;
   BaseScreenHandler::SetBaseScreen(nullptr);
 }
 
@@ -202,12 +199,12 @@
           .id();
 
   std::unique_ptr<base::ListValue> language_list;
-  if (model_) {
-    if (model_->GetLanguageList() &&
-        model_->GetLanguageListLocale() == application_locale) {
-      language_list.reset(model_->GetLanguageList()->DeepCopy());
+  if (screen_) {
+    if (screen_->language_list() &&
+        screen_->language_list_locale() == application_locale) {
+      language_list.reset(screen_->language_list()->DeepCopy());
     } else {
-      model_->UpdateLanguageList();
+      screen_->UpdateLanguageList();
     }
   }
 
@@ -259,7 +256,7 @@
   }
 
   // Reload localized strings if they are already resolved.
-  if (model_ && model_->GetLanguageList())
+  if (screen_ && screen_->language_list())
     ReloadLocalizedContent();
 }
 
diff --git a/chrome/browser/ui/webui/chromeos/login/network_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/network_screen_handler.h
index ab2b3d2..2c735ad 100644
--- a/chrome/browser/ui/webui/chromeos/login/network_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/network_screen_handler.h
@@ -32,7 +32,7 @@
   // NetworkView implementation:
   void Show() override;
   void Hide() override;
-  void Bind(NetworkModel& model) override;
+  void Bind(NetworkScreen* screen) override;
   void Unbind() override;
   void ShowError(const base::string16& message) override;
   void ClearErrors() override;
@@ -47,15 +47,14 @@
   void GetAdditionalParameters(base::DictionaryValue* dict) override;
   void Initialize() override;
 
- private:
   // Returns available timezones. Caller gets the ownership.
   static base::ListValue* GetTimezoneList();
 
-  CoreOobeActor* core_oobe_actor_;
-  NetworkModel* model_;
+  CoreOobeActor* core_oobe_actor_ = nullptr;
+  NetworkScreen* screen_ = nullptr;
 
   // Keeps whether screen should be shown right after initialization.
-  bool show_on_init_;
+  bool show_on_init_ = false;
 
   // Position of the network control.
   gfx::Point network_control_pos_;
diff --git a/chrome/browser/ui/webui/media_router/media_router_dialog_controller_impl_unittest.cc b/chrome/browser/ui/webui/media_router/media_router_dialog_controller_impl_unittest.cc
index 4ed40ad..0414c9c 100644
--- a/chrome/browser/ui/webui/media_router/media_router_dialog_controller_impl_unittest.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_dialog_controller_impl_unittest.cc
@@ -258,7 +258,7 @@
           new CreatePresentationConnectionRequest(
               RenderFrameHostId(1, 2),
               {GURL("http://test.com"), GURL("http://test2.com")},
-              GURL("http://example.com"),
+              url::Origin(GURL("http://example.com")),
               base::Bind(&MediaRouterDialogControllerImplTest::
                              PresentationSuccessCallback,
                          base::Unretained(this)),
diff --git a/chrome/browser/ui/webui/media_router/media_router_ui.cc b/chrome/browser/ui/webui/media_router/media_router_ui.cc
index 1c99d01..8e8e9740 100644
--- a/chrome/browser/ui/webui/media_router/media_router_ui.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_ui.cc
@@ -277,7 +277,7 @@
   query_result_manager_->AddObserver(this);
 
   // Use a placeholder URL as origin for mirroring.
-  GURL origin(chrome::kChromeUIMediaRouterURL);
+  url::Origin origin{GURL(chrome::kChromeUIMediaRouterURL)};
 
   // Desktop mirror mode is always available.
   query_result_manager_->SetSourcesForCastMode(
@@ -318,8 +318,7 @@
   std::vector<MediaSource> sources = presentation_request.GetMediaSources();
   presentation_request_.reset(new PresentationRequest(presentation_request));
   query_result_manager_->SetSourcesForCastMode(
-      MediaCastMode::DEFAULT, sources,
-      presentation_request_->frame_url().GetOrigin());
+      MediaCastMode::DEFAULT, sources, presentation_request_->frame_origin());
   // Register for MediaRoute updates.  NOTE(mfoltz): If there are multiple
   // sources that can be connected to via the dialog, this will break.  We will
   // need to observe multiple sources (keyed by sinks) in that case.  As this is
@@ -391,7 +390,7 @@
 bool MediaRouterUI::CreateRoute(const MediaSink::Id& sink_id,
                                 MediaCastMode cast_mode) {
   MediaSource::Id source_id;
-  GURL origin;
+  url::Origin origin;
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   base::TimeDelta timeout;
   bool incognito;
@@ -409,7 +408,7 @@
     const MediaSink::Id& sink_id,
     MediaCastMode cast_mode,
     MediaSource::Id* source_id,
-    GURL* origin,
+    url::Origin* origin,
     std::vector<MediaRouteResponseCallback>* route_response_callbacks,
     base::TimeDelta* timeout,
     bool* incognito) {
@@ -439,8 +438,9 @@
   }
 
   current_route_request_id_ = ++route_request_counter_;
-  *origin = for_default_source ? presentation_request_->frame_url().GetOrigin()
-                               : GURL(chrome::kChromeUIMediaRouterURL);
+  *origin = for_default_source
+                ? presentation_request_->frame_origin()
+                : url::Origin(GURL(chrome::kChromeUIMediaRouterURL));
   DVLOG(1) << "DoCreateRoute: origin: " << *origin;
 
   // There are 3 cases. In cases (1) and (3) the MediaRouterUI will need to be
@@ -486,7 +486,7 @@
 bool MediaRouterUI::ConnectRoute(const MediaSink::Id& sink_id,
                                  const MediaRoute::Id& route_id) {
   MediaSource::Id source_id;
-  GURL origin;
+  url::Origin origin;
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   base::TimeDelta timeout;
   bool incognito;
@@ -652,7 +652,7 @@
   handler_->ReturnSearchResult(found_sink_id);
 
   MediaSource::Id source_id;
-  GURL origin;
+  url::Origin origin;
   std::vector<MediaRouteResponseCallback> route_response_callbacks;
   base::TimeDelta timeout;
   bool incognito;
@@ -707,7 +707,8 @@
 }
 
 GURL MediaRouterUI::GetFrameURL() const {
-  return presentation_request_ ? presentation_request_->frame_url() : GURL();
+  return presentation_request_ ? presentation_request_->frame_origin().GetURL()
+                               : GURL();
 }
 
 std::string MediaRouterUI::GetPresentationRequestSourceName() const {
diff --git a/chrome/browser/ui/webui/media_router/media_router_ui.h b/chrome/browser/ui/webui/media_router/media_router_ui.h
index 6e4f2b0..c2d1dca 100644
--- a/chrome/browser/ui/webui/media_router/media_router_ui.h
+++ b/chrome/browser/ui/webui/media_router/media_router_ui.h
@@ -270,7 +270,7 @@
       const MediaSink::Id& sink_id,
       MediaCastMode cast_mode,
       MediaSource::Id* source_id,
-      GURL* origin,
+      url::Origin* origin,
       std::vector<MediaRouteResponseCallback>* route_response_callbacks,
       base::TimeDelta* timeout,
       bool* incognito);
diff --git a/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc b/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc
index b2cc643..c62cad7 100644
--- a/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc
@@ -114,7 +114,7 @@
   MediaSink CreateSinkCompatibleWithAllSources() {
     MediaSink sink("sinkId", "sinkName", MediaSink::GENERIC);
     for (auto* observer : media_sinks_observers_)
-      observer->OnSinksUpdated({sink}, std::vector<GURL>());
+      observer->OnSinksUpdated({sink}, std::vector<url::Origin>());
     return sink;
   }
 
@@ -170,7 +170,7 @@
   CreateMediaRouterUI(profile());
   PresentationRequest presentation_request(
       RenderFrameHostId(0, 0), {GURL("https://presentationurl.com")},
-      GURL("https://frameurl.fakeurl"));
+      url::Origin(GURL("https://frameurl.fakeurl")));
   media_router_ui_->OnDefaultPresentationChanged(presentation_request);
   std::vector<MediaRouteResponseCallback> callbacks;
   EXPECT_CALL(
@@ -208,9 +208,9 @@
 TEST_F(MediaRouterUITest, RouteRequestFromIncognito) {
   CreateMediaRouterUI(profile()->GetOffTheRecordProfile());
 
-  PresentationRequest presentation_request(RenderFrameHostId(0, 0),
-                                           {GURL("https://foo.url.com/")},
-                                           GURL("https://frameUrl"));
+  PresentationRequest presentation_request(
+      RenderFrameHostId(0, 0), {GURL("https://foo.url.com/")},
+      url::Origin(GURL("https://frameUrl")));
   media_router_ui_->OnDefaultPresentationChanged(presentation_request);
 
   EXPECT_CALL(
@@ -477,7 +477,7 @@
   create_session_request_.reset(new CreatePresentationConnectionRequest(
       RenderFrameHostId(0, 0), {GURL("http://google.com/presentation"),
                                 GURL("http://google.com/presentation2")},
-      GURL("http://google.com"),
+      url::Origin(GURL("http://google.com")),
       base::Bind(&PresentationRequestCallbacks::Success,
                  base::Unretained(&request_callbacks)),
       base::Bind(&PresentationRequestCallbacks::Error,
@@ -495,7 +495,8 @@
   PresentationRequestCallbacks request_callbacks(expected_error);
   GURL presentation_url("http://google.com/presentation");
   create_session_request_.reset(new CreatePresentationConnectionRequest(
-      RenderFrameHostId(0, 0), {presentation_url}, GURL("http://google.com"),
+      RenderFrameHostId(0, 0), {presentation_url},
+      url::Origin(GURL("http://google.com")),
       base::Bind(&PresentationRequestCallbacks::Success,
                  base::Unretained(&request_callbacks)),
       base::Bind(&PresentationRequestCallbacks::Error,
@@ -506,7 +507,7 @@
   // presentation url to cause a NotFoundError.
   std::vector<MediaSink> sinks;
   sinks.emplace_back("sink id", "sink name", MediaSink::GENERIC);
-  std::vector<GURL> origins;
+  std::vector<url::Origin> origins;
   for (auto* observer : media_sinks_observers_) {
     if (observer->source().id() != presentation_url.spec()) {
       observer->OnSinksUpdated(sinks, origins);
@@ -525,7 +526,8 @@
   PresentationRequestCallbacks request_callbacks(expected_error);
   GURL presentation_url("http://google.com/presentation");
   create_session_request_.reset(new CreatePresentationConnectionRequest(
-      RenderFrameHostId(0, 0), {presentation_url}, GURL("http://google.com"),
+      RenderFrameHostId(0, 0), {presentation_url},
+      url::Origin(GURL("http://google.com")),
       base::Bind(&PresentationRequestCallbacks::Success,
                  base::Unretained(&request_callbacks)),
       base::Bind(&PresentationRequestCallbacks::Error,
@@ -536,7 +538,7 @@
   // a NotFoundError.
   std::vector<MediaSink> sinks;
   sinks.emplace_back("sink id", "sink name", MediaSink::GENERIC);
-  std::vector<GURL> origins;
+  std::vector<url::Origin> origins;
   MediaSource::Id presentation_source_id =
       MediaSourceForPresentationUrl(presentation_url).id();
   for (auto* observer : media_sinks_observers_) {
diff --git a/chrome/browser/ui/webui/media_router/query_result_manager.cc b/chrome/browser/ui/webui/media_router/query_result_manager.cc
index 79fbb604..2bd508a 100644
--- a/chrome/browser/ui/webui/media_router/query_result_manager.cc
+++ b/chrome/browser/ui/webui/media_router/query_result_manager.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/media/router/media_router.h"
 #include "chrome/browser/media/router/media_sinks_observer.h"
 #include "content/public/browser/browser_thread.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace media_router {
 
@@ -23,7 +23,7 @@
  public:
   MediaSourceMediaSinksObserver(MediaCastMode cast_mode,
                                 const MediaSource& source,
-                                const GURL& origin,
+                                const url::Origin& origin,
                                 MediaRouter* router,
                                 QueryResultManager* result_manager)
       : MediaSinksObserver(router, source, origin),
@@ -83,7 +83,7 @@
 void QueryResultManager::SetSourcesForCastMode(
     MediaCastMode cast_mode,
     const std::vector<MediaSource>& sources,
-    const GURL& origin) {
+    const url::Origin& origin) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (sources.empty()) {
     LOG(WARNING) << "SetSourcesForCastMode called with empty sources for "
@@ -157,7 +157,7 @@
 void QueryResultManager::AddObserversForCastMode(
     MediaCastMode cast_mode,
     const std::vector<MediaSource>& sources,
-    const GURL& origin) {
+    const url::Origin& origin) {
   for (const MediaSource& source : sources) {
     if (!base::ContainsKey(sinks_observers_, source)) {
       std::unique_ptr<MediaSourceMediaSinksObserver> observer(
diff --git a/chrome/browser/ui/webui/media_router/query_result_manager.h b/chrome/browser/ui/webui/media_router/query_result_manager.h
index 553c743..da4681f 100644
--- a/chrome/browser/ui/webui/media_router/query_result_manager.h
+++ b/chrome/browser/ui/webui/media_router/query_result_manager.h
@@ -21,7 +21,9 @@
 #include "chrome/browser/ui/webui/media_router/media_cast_mode.h"
 #include "chrome/browser/ui/webui/media_router/media_sink_with_cast_modes.h"
 
-class GURL;
+namespace url {
+class Origin;
+}  // namespace url
 
 namespace media_router {
 
@@ -36,7 +38,7 @@
 //
 // Typical use:
 //
-//   GURL origin("https://origin.com");
+//   url::Origin origin{GURL("https://origin.com")};
 //   QueryResultManager::Observer* observer = ...;
 //   QueryResultManager result_manager(router);
 //   result_manager.AddObserver(observer);
@@ -89,7 +91,7 @@
   // with another cast mode, no new queries are begun.
   void SetSourcesForCastMode(MediaCastMode cast_mode,
                              const std::vector<MediaSource>& sources,
-                             const GURL& origin);
+                             const url::Origin& origin);
 
   // Stops notifying observers for |cast_mode|, and removes it from the set of
   // supported cast modes.
@@ -127,7 +129,7 @@
   // doesn't already have an associated observer.
   void AddObserversForCastMode(MediaCastMode cast_mode,
                                const std::vector<MediaSource>& sources,
-                               const GURL& origin);
+                               const url::Origin& origin);
 
   // Modifies the set of sinks compatible with |cast_mode| and |source|
   // to |new_sinks|.
diff --git a/chrome/browser/ui/webui/media_router/query_result_manager_unittest.cc b/chrome/browser/ui/webui/media_router/query_result_manager_unittest.cc
index 8e01955..8a9bbe98 100644
--- a/chrome/browser/ui/webui/media_router/query_result_manager_unittest.cc
+++ b/chrome/browser/ui/webui/media_router/query_result_manager_unittest.cc
@@ -47,7 +47,7 @@
         .WillOnce(Return(true));
     EXPECT_CALL(mock_observer_, OnResultsUpdated(_)).Times(1);
     query_result_manager_.SetSourcesForCastMode(cast_mode, {source},
-                                                GURL(kOrigin));
+                                                url::Origin(GURL(kOrigin)));
   }
 
   bool IsDefaultSourceForSink(const MediaSource* source,
@@ -108,7 +108,6 @@
 }
 
 TEST_F(QueryResultManagerTest, StartStopSinksQuery) {
-  GURL origin(kOrigin);
   CastModeSet cast_modes = query_result_manager_.GetSupportedCastModes();
   EXPECT_TRUE(cast_modes.empty());
   std::vector<MediaSource> actual_sources =
@@ -119,7 +118,7 @@
   EXPECT_CALL(mock_router_, RegisterMediaSinksObserver(_))
       .WillOnce(Return(true));
   query_result_manager_.SetSourcesForCastMode(MediaCastMode::DEFAULT, {source},
-                                              origin);
+                                              url::Origin(GURL(kOrigin)));
 
   cast_modes = query_result_manager_.GetSupportedCastModes();
   EXPECT_EQ(1u, cast_modes.size());
@@ -135,8 +134,8 @@
   EXPECT_CALL(mock_router_, UnregisterMediaSinksObserver(_)).Times(1);
   EXPECT_CALL(mock_router_, RegisterMediaSinksObserver(_))
       .WillOnce(Return(true));
-  query_result_manager_.SetSourcesForCastMode(MediaCastMode::DEFAULT,
-                                              {another_source}, origin);
+  query_result_manager_.SetSourcesForCastMode(
+      MediaCastMode::DEFAULT, {another_source}, url::Origin(GURL(kOrigin)));
 
   cast_modes = query_result_manager_.GetSupportedCastModes();
   EXPECT_EQ(1u, cast_modes.size());
@@ -167,7 +166,6 @@
   MediaSource default_source2 =
       MediaSourceForPresentationUrl(GURL("http://baz.com"));
   MediaSource tab_source = MediaSourceForTab(123);
-  GURL origin(kOrigin);
 
   query_result_manager_.AddObserver(&mock_observer_);
   DiscoverSinks(MediaCastMode::DEFAULT, default_source1);
@@ -197,7 +195,7 @@
   EXPECT_CALL(mock_observer_,
               OnResultsUpdated(VectorEquals(expected_sinks))).Times(1);
   sinks_observer_it->second->OnSinksUpdated(sinks_query_result,
-                                            std::vector<GURL>());
+                                            std::vector<url::Origin>());
 
   // Action: TAB_MIRROR -> [2, 3, 4]
   // Expected result:
@@ -226,7 +224,7 @@
   EXPECT_CALL(mock_observer_,
               OnResultsUpdated(VectorEquals(expected_sinks))).Times(1);
   sinks_observer_it->second->OnSinksUpdated(sinks_query_result,
-                                            {GURL(kOrigin)});
+                                            {url::Origin(GURL(kOrigin))});
 
   // Action: Update default presentation URL
   // Expected result:
@@ -246,8 +244,8 @@
       .WillOnce(Return(true));
   EXPECT_CALL(mock_observer_,
               OnResultsUpdated(VectorEquals(expected_sinks))).Times(1);
-  query_result_manager_.SetSourcesForCastMode(MediaCastMode::DEFAULT,
-                                              {default_source2}, origin);
+  query_result_manager_.SetSourcesForCastMode(
+      MediaCastMode::DEFAULT, {default_source2}, url::Origin(GURL(kOrigin)));
 
   // Action: DEFAULT -> [1], origins don't match
   // Expected result: [2 -> {TAB_MIRROR}, 3 -> {TAB_MIRROR}, 4 -> {TAB_MIRROR}]
@@ -260,7 +258,7 @@
   EXPECT_CALL(mock_observer_, OnResultsUpdated(VectorEquals(expected_sinks)))
       .Times(1);
   sinks_observer_it->second->OnSinksUpdated(
-      sinks_query_result, {GURL("https://differentOrigin.com")});
+      sinks_query_result, {url::Origin(GURL("https://differentOrigin.com"))});
 
   // Action: Remove TAB_MIRROR observer
   // Expected result:
@@ -291,17 +289,16 @@
   const std::vector<MediaSource> default_sources = {source_a, source_b,
                                                     source_c};
   const std::vector<MediaSource> tab_sources = {source_tab};
-  const GURL origin(kOrigin);
   const auto& sinks_observers = query_result_manager_.sinks_observers_;
 
   // There should be one MediaSinksObserver per source.
   EXPECT_CALL(mock_router_, RegisterMediaSinksObserver(_))
       .Times(4)
       .WillRepeatedly(Return(true));
-  query_result_manager_.SetSourcesForCastMode(MediaCastMode::DEFAULT,
-                                              default_sources, origin);
-  query_result_manager_.SetSourcesForCastMode(MediaCastMode::TAB_MIRROR,
-                                              tab_sources, origin);
+  query_result_manager_.SetSourcesForCastMode(
+      MediaCastMode::DEFAULT, default_sources, url::Origin(GURL(kOrigin)));
+  query_result_manager_.SetSourcesForCastMode(
+      MediaCastMode::TAB_MIRROR, tab_sources, url::Origin(GURL(kOrigin)));
 
   // Scenario (results in this order):
   // Action: URL_B -> [2, 4]
@@ -315,7 +312,7 @@
   ASSERT_TRUE(sinks_observer_it->second.get());
 
   auto& source_b_observer = sinks_observer_it->second;
-  source_b_observer->OnSinksUpdated({sink2, sink4}, std::vector<GURL>());
+  source_b_observer->OnSinksUpdated({sink2, sink4}, std::vector<url::Origin>());
   EXPECT_TRUE(IsDefaultSourceForSink(nullptr, sink1));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_b, sink2));
   EXPECT_TRUE(IsDefaultSourceForSink(nullptr, sink3));
@@ -332,7 +329,8 @@
   ASSERT_TRUE(sinks_observer_it->second.get());
 
   auto& source_c_observer = sinks_observer_it->second;
-  source_c_observer->OnSinksUpdated({sink1, sink2, sink3}, std::vector<GURL>());
+  source_c_observer->OnSinksUpdated({sink1, sink2, sink3},
+                                    std::vector<url::Origin>());
   EXPECT_TRUE(IsDefaultSourceForSink(&source_c, sink1));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_b, sink2));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_c, sink3));
@@ -349,7 +347,8 @@
   ASSERT_TRUE(sinks_observer_it->second.get());
 
   auto& source_a_observer = sinks_observer_it->second;
-  source_a_observer->OnSinksUpdated({sink2, sink3, sink4}, std::vector<GURL>());
+  source_a_observer->OnSinksUpdated({sink2, sink3, sink4},
+                                    std::vector<url::Origin>());
   EXPECT_TRUE(IsDefaultSourceForSink(&source_c, sink1));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_a, sink2));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_a, sink3));
@@ -366,7 +365,8 @@
   ASSERT_TRUE(sinks_observer_it->second.get());
 
   auto& source_tab_observer = sinks_observer_it->second;
-  source_tab_observer->OnSinksUpdated({sink1, sink2}, std::vector<GURL>());
+  source_tab_observer->OnSinksUpdated({sink1, sink2},
+                                      std::vector<url::Origin>());
   EXPECT_TRUE(IsDefaultSourceForSink(&source_c, sink1));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_a, sink2));
   EXPECT_TRUE(IsDefaultSourceForSink(&source_a, sink3));
@@ -385,17 +385,16 @@
 TEST_F(QueryResultManagerTest, AddInvalidSource) {
   const MediaSource source(
       MediaSourceForPresentationUrl(GURL("http://url.com")));
-  const GURL origin(kOrigin);
 
   EXPECT_CALL(mock_router_, RegisterMediaSinksObserver(_))
       .Times(1)
       .WillRepeatedly(Return(true));
   query_result_manager_.SetSourcesForCastMode(MediaCastMode::DEFAULT, {source},
-                                              origin);
+                                              url::Origin(GURL(kOrigin)));
   // |source| has already been registered with the default cast mode, so it
   // shouldn't get registered with tab mirroring.
-  query_result_manager_.SetSourcesForCastMode(MediaCastMode::TAB_MIRROR,
-                                              {source}, origin);
+  query_result_manager_.SetSourcesForCastMode(
+      MediaCastMode::TAB_MIRROR, {source}, url::Origin(GURL(kOrigin)));
 
   const auto& cast_mode_sources = query_result_manager_.cast_mode_sources_;
   const auto& default_sources = cast_mode_sources.at(MediaCastMode::DEFAULT);
diff --git a/chrome/browser/ui/webui/net_export_ui.cc b/chrome/browser/ui/webui/net_export_ui.cc
index 07b368c..1329b06e 100644
--- a/chrome/browser/ui/webui/net_export_ui.cc
+++ b/chrome/browser/ui/webui/net_export_ui.cc
@@ -31,6 +31,9 @@
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "content/public/browser/web_ui_message_handler.h"
+#include "net/log/net_log_capture_mode.h"
+#include "net/log/net_log_util.h"
+#include "net/url_request/url_request_context_getter.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 
 #if defined(OS_ANDROID)
@@ -59,8 +62,7 @@
 
 // This class receives javascript messages from the renderer.
 // Note that the WebUI infrastructure runs on the UI thread, therefore all of
-// this class's public methods are expected to run on the UI thread. All static
-// functions except SendEmail run on FILE_USER_BLOCKING thread.
+// this class's public methods are expected to run on the UI thread.
 class NetExportMessageHandler
     : public WebUIMessageHandler,
       public base::SupportsWeakPtr<NetExportMessageHandler>,
@@ -85,22 +87,14 @@
   void FileSelectionCanceled(void* params) override;
 
  private:
-  // Calls NetLogFileWriter's ProcessCommand with DO_START and DO_STOP commands.
-  static void ProcessNetLogCommand(
-      base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-      net_log::NetLogFileWriter* net_log_file_writer,
-      net_log::NetLogFileWriter::Command command);
+  // If |log_path| is empty, then the NetLogFileWriter will use its default
+  // log path.
+  void StartNetLogThenNotifyUI(const base::FilePath& log_path,
+                               net::NetLogCaptureMode capture_mode);
 
-  // Returns the path to the file which has NetLog data.
-  static base::FilePath GetNetLogFileName(
-      net_log::NetLogFileWriter* net_log_file_writer);
+  void StopNetLogThenNotifyUI();
 
-  // Send state/file information from NetLogFileWriter.
-  static void SendExportNetLogInfo(
-      base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-      net_log::NetLogFileWriter* net_log_file_writer);
-
-  // Send NetLog data via email. This runs on UI thread.
+  // Send NetLog data via email.
   static void SendEmail(const base::FilePath& file_to_send);
 
   // chrome://net-export can be used on both mobile and desktop platforms.
@@ -115,30 +109,23 @@
   // UI.
   static bool UsingMobileUI();
 
-  // Sets the correct start command and sends this to ProcessNetLogCommand.
-  void StartNetLog();
-
-  // Call NetExportView.onExportNetLogInfoChanged JavsScript function in the
-  // renderer, passing in |arg|. Takes ownership of |arg|.
-  void OnExportNetLogInfoChanged(base::Value* arg);
+  // Calls NetExportView.onExportNetLogInfoChanged JavaScript function in the
+  // renderer, passing in |file_writer_state|.
+  void NotifyUIWithNetLogFileWriterState(
+      std::unique_ptr<base::DictionaryValue> file_writer_state);
 
   // Opens the SelectFileDialog UI with the default path to save a
   // NetLog file.
   void ShowSelectFileDialog(const base::FilePath& default_path);
 
   // Cache of g_browser_process->net_log()->net_log_file_writer(). This
-  // is owned by ChromeNetLog which is owned by BrowserProcessImpl. There are
-  // four instances in this class where a pointer to net_log_file_writer_ is
-  // posted to the FILE_USER_BLOCKING thread. Base::Unretained is used here
-  // because BrowserProcessImpl is destroyed on the UI thread after joining the
-  // FILE_USER_BLOCKING thread making it impossible for there to be an invalid
-  // pointer to this object when going back to the UI thread. Furthermore this
-  // pointer is never dereferenced prematurely on the UI thread. Thus the
-  // lifetime of this object is assured and can be safely used with
-  // base::Unretained.
+  // is owned by ChromeNetLog which is owned by BrowserProcessImpl.
   net_log::NetLogFileWriter* net_log_file_writer_;
 
-  std::string log_mode_;
+  // The capture mode the user chose in the UI when logging started is cached
+  // here and is read after a file path is chosen in the save dialog.
+  // Its value is only valid while the save dialog is open on the desktop UI.
+  net::NetLogCaptureMode capture_mode_;
 
   scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
 
@@ -149,7 +136,11 @@
 
 NetExportMessageHandler::NetExportMessageHandler()
     : net_log_file_writer_(g_browser_process->net_log()->net_log_file_writer()),
-      weak_ptr_factory_(this) {}
+      weak_ptr_factory_(this) {
+  net_log_file_writer_->SetTaskRunners(
+      BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE_USER_BLOCKING),
+      BrowserThread::GetTaskRunnerForThread(BrowserThread::IO));
+}
 
 NetExportMessageHandler::~NetExportMessageHandler() {
   // There may be a pending file dialog, it needs to be told that the user
@@ -157,11 +148,9 @@
   if (select_file_dialog_.get())
     select_file_dialog_->ListenerDestroyed();
 
-  // Cancel any in-progress requests to collect net_log into a file.
-  BrowserThread::PostTask(BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
-                          base::Bind(&net_log::NetLogFileWriter::ProcessCommand,
-                                     base::Unretained(net_log_file_writer_),
-                                     net_log::NetLogFileWriter::DO_STOP));
+  net_log_file_writer_->StopNetLog(
+      nullptr, nullptr,
+      base::Bind([](std::unique_ptr<base::DictionaryValue>) {}));
 }
 
 void NetExportMessageHandler::RegisterMessages() {
@@ -187,19 +176,23 @@
 
 void NetExportMessageHandler::OnGetExportNetLogInfo(
     const base::ListValue* list) {
-  BrowserThread::PostTask(
-      BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
-      base::Bind(&NetExportMessageHandler::SendExportNetLogInfo,
-                 weak_ptr_factory_.GetWeakPtr(), net_log_file_writer_));
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  net_log_file_writer_->GetState(
+      base::Bind(&NetExportMessageHandler::NotifyUIWithNetLogFileWriterState,
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
 void NetExportMessageHandler::OnStartNetLog(const base::ListValue* list) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  bool result = list->GetString(0, &log_mode_);
+  std::string capture_mode_string;
+  bool result = list->GetString(0, &capture_mode_string);
   DCHECK(result);
 
+  capture_mode_ =
+      net_log::NetLogFileWriter::CaptureModeFromString(capture_mode_string);
+
   if (UsingMobileUI()) {
-    StartNetLog();
+    StartNetLogThenNotifyUI(base::FilePath(), capture_mode_);
   } else {
     base::FilePath initial_dir = last_save_dir.Pointer()->empty() ?
         DownloadPrefs::FromBrowserContext(
@@ -212,76 +205,40 @@
 }
 
 void NetExportMessageHandler::OnStopNetLog(const base::ListValue* list) {
-  ProcessNetLogCommand(weak_ptr_factory_.GetWeakPtr(), net_log_file_writer_,
-                       net_log::NetLogFileWriter::DO_STOP);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  StopNetLogThenNotifyUI();
 }
 
 void NetExportMessageHandler::OnSendNetLog(const base::ListValue* list) {
-  content::BrowserThread::PostTaskAndReplyWithResult(
-      content::BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
-      base::Bind(&NetExportMessageHandler::GetNetLogFileName,
-                 base::Unretained(net_log_file_writer_)),
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  net_log_file_writer_->GetFilePathToCompletedLog(
       base::Bind(&NetExportMessageHandler::SendEmail));
 }
 
-void NetExportMessageHandler::StartNetLog() {
-  net_log::NetLogFileWriter::Command command;
-  if (log_mode_ == "LOG_BYTES") {
-    command = net_log::NetLogFileWriter::DO_START_LOG_BYTES;
-  } else if (log_mode_ == "NORMAL") {
-    command = net_log::NetLogFileWriter::DO_START;
-  } else {
-    DCHECK_EQ("STRIP_PRIVATE_DATA", log_mode_);
-    command = net_log::NetLogFileWriter::DO_START_STRIP_PRIVATE_DATA;
-  }
+void NetExportMessageHandler::StartNetLogThenNotifyUI(
+    const base::FilePath& log_path,
+    net::NetLogCaptureMode capture_mode) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  ProcessNetLogCommand(weak_ptr_factory_.GetWeakPtr(), net_log_file_writer_,
-                       command);
+  net_log_file_writer_->StartNetLog(
+      log_path, capture_mode,
+      base::Bind(&NetExportMessageHandler::NotifyUIWithNetLogFileWriterState,
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
-// static
-void NetExportMessageHandler::ProcessNetLogCommand(
-    base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-    net_log::NetLogFileWriter* net_log_file_writer,
-    net_log::NetLogFileWriter::Command command) {
-  if (!BrowserThread::CurrentlyOn(BrowserThread::FILE_USER_BLOCKING)) {
-    BrowserThread::PostTask(
-        BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
-        base::Bind(&NetExportMessageHandler::ProcessNetLogCommand,
-                   net_export_message_handler, net_log_file_writer, command));
-    return;
-  }
+void NetExportMessageHandler::StopNetLogThenNotifyUI() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  DCHECK_CURRENTLY_ON(BrowserThread::FILE_USER_BLOCKING);
-  net_log_file_writer->ProcessCommand(command);
-  SendExportNetLogInfo(net_export_message_handler, net_log_file_writer);
-}
+  std::unique_ptr<base::DictionaryValue> ui_thread_polled_data;
 
-// static
-base::FilePath NetExportMessageHandler::GetNetLogFileName(
-    net_log::NetLogFileWriter* net_log_file_writer) {
-  DCHECK_CURRENTLY_ON(BrowserThread::FILE_USER_BLOCKING);
-  base::FilePath net_export_file_path;
-  net_log_file_writer->GetFilePath(&net_export_file_path);
-  return net_export_file_path;
-}
+  // TODO(crbug.com/438656): fill |ui_thread_polled_data| with browser-specific
+  // polled data.
 
-// static
-void NetExportMessageHandler::SendExportNetLogInfo(
-    base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-    net_log::NetLogFileWriter* net_log_file_writer) {
-  DCHECK_CURRENTLY_ON(BrowserThread::FILE_USER_BLOCKING);
-  base::DictionaryValue* dict = net_log_file_writer->GetState();
-  dict->SetBoolean("useMobileUI", UsingMobileUI());
-  base::Value* value = dict;
-  if (!BrowserThread::PostTask(
-      BrowserThread::UI, FROM_HERE,
-      base::Bind(&NetExportMessageHandler::OnExportNetLogInfoChanged,
-                 net_export_message_handler,
-                 value))) {
-    // Failed posting the task, avoid leaking.
-    delete value;
-  }
+  net_log_file_writer_->StopNetLog(
+      std::move(ui_thread_polled_data),
+      Profile::FromWebUI(web_ui())->GetRequestContext(),
+      base::Bind(&NetExportMessageHandler::NotifyUIWithNetLogFileWriterState,
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
 // static
@@ -313,11 +270,12 @@
 #endif
 }
 
-void NetExportMessageHandler::OnExportNetLogInfoChanged(base::Value* arg) {
-  std::unique_ptr<base::Value> value(arg);
+void NetExportMessageHandler::NotifyUIWithNetLogFileWriterState(
+    std::unique_ptr<base::DictionaryValue> file_writer_state) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  file_writer_state->SetBoolean("useMobileUI", UsingMobileUI());
   web_ui()->CallJavascriptFunctionUnsafe(net_log::kOnExportNetLogInfoChanged,
-                                         *arg);
+                                         *file_writer_state);
 }
 
 void NetExportMessageHandler::ShowSelectFileDialog(
@@ -345,18 +303,9 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(select_file_dialog_);
   select_file_dialog_ = nullptr;
-
   *last_save_dir.Pointer() = path.DirName();
-  BrowserThread::PostTaskAndReply(
-      BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
-      base::Bind(&net_log::NetLogFileWriter::SetUpNetExportLogPath,
-                 base::Unretained(net_log_file_writer_), path),
-      // NetExportMessageHandler is tied to the lifetime of the tab
-      // so it cannot be assured that it will be valid when this
-      // StartNetLog is called. Instead of using base::Unretained a
-      // weak pointer is used to adjust for this.
-      base::Bind(&NetExportMessageHandler::StartNetLog,
-                 weak_ptr_factory_.GetWeakPtr()));
+
+  StartNetLogThenNotifyUI(path, capture_mode_);
 }
 
 void NetExportMessageHandler::FileSelectionCanceled(void* params) {
diff --git a/chrome/test/media_router/media_router_e2e_browsertest.cc b/chrome/test/media_router/media_router_e2e_browsertest.cc
index b9fdf39..a3b7fe6 100644
--- a/chrome/test/media_router/media_router_e2e_browsertest.cc
+++ b/chrome/test/media_router/media_router_e2e_browsertest.cc
@@ -20,8 +20,7 @@
 #include "content/public/test/test_utils.h"
 #include "media/base/test_data_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
+#include "url/origin.h"
 
 // Use the following command to run e2e browser tests:
 // ./out/Debug/browser_tests --user-data-dir=<empty user data dir>
@@ -42,7 +41,7 @@
 const char kVideo[] = "video";
 const char kBearVP9Video[] = "bear-vp9.webm";
 const char kPlayer[] = "player.html";
-const char kOriginUrl[] = "http://origin/";
+const char kOrigin[] = "http://origin/";
 }  // namespace
 
 
@@ -77,7 +76,7 @@
 
 void MediaRouterE2EBrowserTest::CreateMediaRoute(
     const MediaSource& source,
-    const GURL& origin,
+    const url::Origin& origin,
     content::WebContents* web_contents) {
   DCHECK(media_router_);
   observer_.reset(new TestMediaSinksObserver(media_router_, source, origin));
@@ -146,8 +145,8 @@
   int tab_id = SessionTabHelper::IdForTab(web_contents);
 
   // Wait for 30 seconds to make sure the route is stable.
-  CreateMediaRoute(
-      MediaSourceForTab(tab_id), GURL(kOriginUrl), web_contents);
+  CreateMediaRoute(MediaSourceForTab(tab_id), url::Origin(GURL(kOrigin)),
+                   web_contents);
   Wait(base::TimeDelta::FromSeconds(30));
 
   // Wait for 10 seconds to make sure route has been stopped.
@@ -158,7 +157,7 @@
 IN_PROC_BROWSER_TEST_F(MediaRouterE2EBrowserTest, MANUAL_CastApp) {
   // Wait for 30 seconds to make sure the route is stable.
   CreateMediaRoute(MediaSourceForPresentationUrl(GURL(kCastAppPresentationUrl)),
-                   GURL(kOriginUrl), nullptr);
+                   url::Origin(GURL(kOrigin)), nullptr);
   Wait(base::TimeDelta::FromSeconds(30));
 
   // Wait for 10 seconds to make sure route has been stopped.
diff --git a/chrome/test/media_router/media_router_e2e_browsertest.h b/chrome/test/media_router/media_router_e2e_browsertest.h
index c01681b..d051a1d 100644
--- a/chrome/test/media_router/media_router_e2e_browsertest.h
+++ b/chrome/test/media_router/media_router_e2e_browsertest.h
@@ -44,7 +44,7 @@
   // requesting JoinRoute() must have the same origin as the page that
   // requested CreateRoute()).
   void CreateMediaRoute(const MediaSource& source,
-                        const GURL& origin,
+                        const url::Origin& origin,
                         content::WebContents* web_contents);
 
   // Stops the established media route and unregisters |observer_|.
diff --git a/chrome/test/media_router/test_media_sinks_observer.cc b/chrome/test/media_router/test_media_sinks_observer.cc
index dd76964..8114a63 100644
--- a/chrome/test/media_router/test_media_sinks_observer.cc
+++ b/chrome/test/media_router/test_media_sinks_observer.cc
@@ -12,7 +12,7 @@
 
 TestMediaSinksObserver::TestMediaSinksObserver(MediaRouter* router,
                                                const MediaSource& source,
-                                               const GURL& origin)
+                                               const url::Origin& origin)
     : MediaSinksObserver(router, source, origin) {}
 
 TestMediaSinksObserver::~TestMediaSinksObserver() {
diff --git a/chrome/test/media_router/test_media_sinks_observer.h b/chrome/test/media_router/test_media_sinks_observer.h
index 7068f2f0..f0da58a3 100644
--- a/chrome/test/media_router/test_media_sinks_observer.h
+++ b/chrome/test/media_router/test_media_sinks_observer.h
@@ -21,7 +21,7 @@
  public:
   TestMediaSinksObserver(MediaRouter* router,
                          const MediaSource& source,
-                         const GURL& origin);
+                         const url::Origin& origin);
   ~TestMediaSinksObserver() override;
 
   // MediaSinksObserver implementation.
diff --git a/chrome/utility/chrome_content_utility_client.h b/chrome/utility/chrome_content_utility_client.h
index 4479489..d00db735 100644
--- a/chrome/utility/chrome_content_utility_client.h
+++ b/chrome/utility/chrome_content_utility_client.h
@@ -43,7 +43,6 @@
 
  private:
   // IPC message handlers.
-  void OnUnpackWebResource(const std::string& resource_data);
 #if defined(OS_CHROMEOS)
   void OnCreateZipFile(const base::FilePath& src_dir,
                        const std::vector<base::FilePath>& src_relative_paths,
diff --git a/components/cronet/android/cronet_url_request_context_adapter.cc b/components/cronet/android/cronet_url_request_context_adapter.cc
index 6dfce48b..24d31843 100644
--- a/components/cronet/android/cronet_url_request_context_adapter.cc
+++ b/components/cronet/android/cronet_url_request_context_adapter.cc
@@ -56,6 +56,7 @@
 #include "net/http/http_auth_handler_factory.h"
 #include "net/http/http_server_properties_manager.h"
 #include "net/log/file_net_log_observer.h"
+#include "net/log/net_log_util.h"
 #include "net/log/write_to_file_net_log_observer.h"
 #include "net/nqe/external_estimate_provider.h"
 #include "net/nqe/network_qualities_prefs_manager.h"
@@ -1036,7 +1037,7 @@
 void CronetURLRequestContextAdapter::StopBoundedFileNetLogOnNetworkThread() {
   DCHECK(GetNetworkTaskRunner()->BelongsToCurrentThread());
   bounded_file_observer_->StopObserving(
-      context_.get(),
+      net::GetNetInfo(context_.get(), net::NET_INFO_ALL_SOURCES),
       base::Bind(&CronetURLRequestContextAdapter::StopNetLogCompleted,
                  base::Unretained(this)));
   bounded_file_observer_.reset();
diff --git a/components/net_log/BUILD.gn b/components/net_log/BUILD.gn
index ec0c7f8..fd7d1295 100644
--- a/components/net_log/BUILD.gn
+++ b/components/net_log/BUILD.gn
@@ -28,7 +28,9 @@
   deps = [
     ":net_log",
     "//base",
+    "//base/test:test_support",
     "//net",
+    "//net:test_support",
     "//testing/gtest",
   ]
 }
diff --git a/components/net_log/net_log_file_writer.cc b/components/net_log/net_log_file_writer.cc
index db51ca7..344edf0 100644
--- a/components/net_log/net_log_file_writer.cc
+++ b/components/net_log/net_log_file_writer.cc
@@ -6,20 +6,28 @@
 
 #include <utility>
 
+#include "base/bind.h"
+#include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "components/net_log/chrome_net_log.h"
-#include "net/log/write_to_file_net_log_observer.h"
+#include "net/log/file_net_log_observer.h"
+#include "net/log/net_log_util.h"
+#include "net/url_request/url_request_context_getter.h"
 
 namespace net_log {
 
-// Path of logs if relative to default temporary directory of
+// Path of logs relative to default temporary directory given by
 // base::GetTempDir(). Must be kept in sync with
-// chrome/android/java/res/xml/file_paths.xml. Only used if
-// not saving log file to a custom path.
+// chrome/android/java/res/xml/file_paths.xml. Only used if not saving log file
+// to a custom path.
 base::FilePath::CharType kLogRelativePath[] =
     FILE_PATH_LITERAL("net-export/chrome-net-export-log.json");
 
@@ -28,95 +36,32 @@
 base::FilePath::CharType kOldLogRelativePath[] =
     FILE_PATH_LITERAL("chrome-net-export-log.json");
 
-NetLogFileWriter::~NetLogFileWriter() {
-  if (write_to_file_observer_)
-    write_to_file_observer_->StopObserving(nullptr);
+// Adds net info from net::GetNetInfo() to |polled_data|. Must run on the
+// |net_task_runner_| of NetLogFileWriter.
+std::unique_ptr<base::DictionaryValue> AddNetInfo(
+    scoped_refptr<net::URLRequestContextGetter> context_getter,
+    std::unique_ptr<base::DictionaryValue> polled_data) {
+  DCHECK(context_getter);
+  std::unique_ptr<base::DictionaryValue> net_info = net::GetNetInfo(
+      context_getter->GetURLRequestContext(), net::NET_INFO_ALL_SOURCES);
+  if (polled_data)
+    net_info->MergeDictionary(polled_data.get());
+  return net_info;
 }
 
-void NetLogFileWriter::ProcessCommand(Command command) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  if (!EnsureInit())
-    return;
-
-  switch (command) {
-    case DO_START_LOG_BYTES:
-      StartNetLog(LOG_TYPE_LOG_BYTES);
-      break;
-    case DO_START:
-      StartNetLog(LOG_TYPE_NORMAL);
-      break;
-    case DO_START_STRIP_PRIVATE_DATA:
-      StartNetLog(LOG_TYPE_STRIP_PRIVATE_DATA);
-      break;
-    case DO_STOP:
-      StopNetLog();
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
-}
-
-bool NetLogFileWriter::GetFilePath(base::FilePath* path) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  if (log_type_ == LOG_TYPE_NONE || state_ == STATE_LOGGING)
-    return false;
-
-  if (!NetExportLogExists())
-    return false;
-
-  DCHECK(!log_path_.empty());
+// If running on a POSIX OS, this will attempt to set all the permission flags
+// of the file at |path| to 1. Will return |path| on success and the empty path
+// on failure.
+base::FilePath GetPathWithAllPermissions(const base::FilePath& path) {
+  if (!base::PathExists(path))
+    return base::FilePath();
 #if defined(OS_POSIX)
-  // Users, group and others can read, write and traverse.
-  int mode = base::FILE_PERMISSION_MASK;
-  base::SetPosixFilePermissions(log_path_, mode);
-#endif  // defined(OS_POSIX)
-
-  *path = log_path_;
-  return true;
-}
-
-base::DictionaryValue* NetLogFileWriter::GetState() {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  base::DictionaryValue* dict = new base::DictionaryValue;
-
-  EnsureInit();
-
-#ifndef NDEBUG
-  dict->SetString("file", log_path_.LossyDisplayName());
-#endif  // NDEBUG
-
-  switch (state_) {
-    case STATE_NOT_LOGGING:
-      dict->SetString("state", "NOT_LOGGING");
-      break;
-    case STATE_LOGGING:
-      dict->SetString("state", "LOGGING");
-      break;
-    case STATE_UNINITIALIZED:
-      dict->SetString("state", "UNINITIALIZED");
-      break;
-  }
-
-  switch (log_type_) {
-    case LOG_TYPE_NONE:
-      dict->SetString("logType", "NONE");
-      break;
-    case LOG_TYPE_UNKNOWN:
-      dict->SetString("logType", "UNKNOWN");
-      break;
-    case LOG_TYPE_LOG_BYTES:
-      dict->SetString("logType", "LOG_BYTES");
-      break;
-    case LOG_TYPE_NORMAL:
-      dict->SetString("logType", "NORMAL");
-      break;
-    case LOG_TYPE_STRIP_PRIVATE_DATA:
-      dict->SetString("logType", "STRIP_PRIVATE_DATA");
-      break;
-  }
-
-  return dict;
+  return base::SetPosixFilePermissions(path, base::FILE_PERMISSION_MASK)
+             ? path
+             : base::FilePath();
+#else
+  return path;
+#endif
 }
 
 NetLogFileWriter::NetLogFileWriter(
@@ -124,123 +69,316 @@
     const base::CommandLine::StringType& command_line_string,
     const std::string& channel_string)
     : state_(STATE_UNINITIALIZED),
-      log_type_(LOG_TYPE_NONE),
+      log_exists_(false),
+      log_capture_mode_known_(false),
+      log_capture_mode_(net::NetLogCaptureMode::Default()),
       chrome_net_log_(chrome_net_log),
       command_line_string_(command_line_string),
-      channel_string_(channel_string) {
-  // NetLogFileWriter can be created on one thread and used on another.
-  thread_checker_.DetachFromThread();
+      channel_string_(channel_string),
+      default_log_base_directory_getter_(base::Bind(&base::GetTempDir)),
+      weak_ptr_factory_(this) {}
+
+NetLogFileWriter::~NetLogFileWriter() {
+  if (write_to_file_observer_)
+    write_to_file_observer_->StopObserving(nullptr, base::Bind([] {}));
 }
 
-bool NetLogFileWriter::GetNetExportLogBaseDirectory(
-    base::FilePath* path) const {
+void NetLogFileWriter::StartNetLog(const base::FilePath& log_path,
+                                   net::NetLogCaptureMode capture_mode,
+                                   const StateCallback& state_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  return base::GetTempDir(path);
+
+  EnsureInitThenRun(
+      base::Bind(&NetLogFileWriter::StartNetLogAfterInitialized,
+                 weak_ptr_factory_.GetWeakPtr(), log_path, capture_mode),
+      state_callback);
 }
 
-net::NetLogCaptureMode NetLogFileWriter::GetCaptureModeForLogType(
-    LogType log_type) {
-  switch (log_type) {
-    case LOG_TYPE_LOG_BYTES:
-      return net::NetLogCaptureMode::IncludeSocketBytes();
-    case LOG_TYPE_NORMAL:
-      return net::NetLogCaptureMode::IncludeCookiesAndCredentials();
-    case LOG_TYPE_STRIP_PRIVATE_DATA:
-      return net::NetLogCaptureMode::Default();
-    case LOG_TYPE_NONE:
-    case LOG_TYPE_UNKNOWN:
-      NOTREACHED();
+void NetLogFileWriter::StopNetLog(
+    std::unique_ptr<base::DictionaryValue> polled_data,
+    scoped_refptr<net::URLRequestContextGetter> context_getter,
+    const StateCallback& state_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(net_task_runner_);
+  if (state_ == STATE_LOGGING) {
+    // Stopping the log requires first grabbing the net info on the net thread.
+    // Before posting that task to the net thread, change the state to
+    // STATE_STOP_LOGGING so that if the NetLogFileWriter receives a command
+    // while the net info is being retrieved on the net thread, the state can be
+    // checked and the command can be ignored. It's the responsibility of the
+    // commands (StartNetLog(), StopNetLog(), GetState()) to check the state
+    // before performing their actions.
+    state_ = STATE_STOPPING_LOG;
+
+    // StopLogging() will always execute its state callback asynchronously,
+    // which means |state_callback| will always be executed asynchronously
+    // relative to the StopNetLog() call regardless of how StopLogging() is
+    // called here.
+
+    if (context_getter) {
+      base::PostTaskAndReplyWithResult(
+          net_task_runner_.get(), FROM_HERE,
+          base::Bind(&AddNetInfo, context_getter, base::Passed(&polled_data)),
+          base::Bind(&NetLogFileWriter::StopNetLogAfterAddNetInfo,
+                     weak_ptr_factory_.GetWeakPtr(), state_callback));
+    } else {
+      StopNetLogAfterAddNetInfo(state_callback, std::move(polled_data));
+    }
+  } else {
+    // No-op; just run |state_callback| asynchronously.
+    RunStateCallbackAsync(state_callback);
   }
-  return net::NetLogCaptureMode::Default();
 }
 
-bool NetLogFileWriter::EnsureInit() {
+void NetLogFileWriter::GetState(const StateCallback& state_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (state_ != STATE_UNINITIALIZED)
-    return true;
-
-  if (log_path_.empty() && !SetUpDefaultNetExportLogPath())
-    return false;
-
-  state_ = STATE_NOT_LOGGING;
-  if (NetExportLogExists())
-    log_type_ = LOG_TYPE_UNKNOWN;
-  else
-    log_type_ = LOG_TYPE_NONE;
-
-  return true;
+  EnsureInitThenRun(base::Bind([] {}), state_callback);
 }
 
-void NetLogFileWriter::StartNetLog(LogType log_type) {
+void NetLogFileWriter::GetFilePathToCompletedLog(
+    const FilePathCallback& path_callback) const {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (state_ == STATE_LOGGING)
+  if (!log_exists_ || state_ == STATE_LOGGING) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(path_callback, base::FilePath()));
     return;
+  }
 
-  DCHECK_NE(STATE_UNINITIALIZED, state_);
+  DCHECK(file_task_runner_);
   DCHECK(!log_path_.empty());
 
-  // Try to make sure we can create the file.
-  // TODO(rtenneti): Find a better for doing the following. Surface some error
-  // to the user if we couldn't create the file.
-  base::ScopedFILE file(base::OpenFile(log_path_, "w"));
-  if (!file)
-    return;
-
-  log_type_ = log_type;
-  state_ = STATE_LOGGING;
-
-  std::unique_ptr<base::Value> constants(
-      ChromeNetLog::GetConstants(command_line_string_, channel_string_));
-  write_to_file_observer_.reset(new net::WriteToFileNetLogObserver());
-  write_to_file_observer_->set_capture_mode(GetCaptureModeForLogType(log_type));
-  write_to_file_observer_->StartObserving(chrome_net_log_, std::move(file),
-                                          constants.get(), nullptr);
+  base::PostTaskAndReplyWithResult(
+      file_task_runner_.get(), FROM_HERE,
+      base::Bind(&GetPathWithAllPermissions, log_path_), path_callback);
 }
 
-void NetLogFileWriter::StopNetLog() {
+void NetLogFileWriter::SetTaskRunners(
+    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> net_task_runner) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (state_ != STATE_LOGGING)
-    return;
+  if (file_task_runner_)
+    DCHECK_EQ(file_task_runner, file_task_runner_);
+  file_task_runner_ = file_task_runner;
 
-  write_to_file_observer_->StopObserving(nullptr);
-  write_to_file_observer_.reset();
-  state_ = STATE_NOT_LOGGING;
+  if (net_task_runner_)
+    DCHECK_EQ(net_task_runner, net_task_runner_);
+  net_task_runner_ = net_task_runner;
 }
 
-void NetLogFileWriter::SetUpNetExportLogPath(
-    const base::FilePath& custom_path) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  // The directory should always exist because the custom path
-  // is taken from a file selector dialog window.
-  DCHECK(base::PathExists(custom_path.DirName()));
-
-  log_path_ = custom_path;
+std::string NetLogFileWriter::CaptureModeToString(
+    net::NetLogCaptureMode capture_mode) {
+  if (capture_mode == net::NetLogCaptureMode::Default()) {
+    return "STRIP_PRIVATE_DATA";
+  } else if (capture_mode ==
+             net::NetLogCaptureMode::IncludeCookiesAndCredentials()) {
+    return "NORMAL";
+  } else if (capture_mode == net::NetLogCaptureMode::IncludeSocketBytes()) {
+    return "LOG_BYTES";
+  } else {
+    NOTREACHED();
+    return "STRIP_PRIVATE_DATA";
+  }
 }
 
-bool NetLogFileWriter::SetUpDefaultNetExportLogPath() {
+net::NetLogCaptureMode NetLogFileWriter::CaptureModeFromString(
+    const std::string& capture_mode_string) {
+  if (capture_mode_string == "STRIP_PRIVATE_DATA") {
+    return net::NetLogCaptureMode::Default();
+  } else if (capture_mode_string == "NORMAL") {
+    return net::NetLogCaptureMode::IncludeCookiesAndCredentials();
+  } else if (capture_mode_string == "LOG_BYTES") {
+    return net::NetLogCaptureMode::IncludeSocketBytes();
+  } else {
+    NOTREACHED();
+    return net::NetLogCaptureMode::Default();
+  }
+}
+
+void NetLogFileWriter::SetDefaultLogBaseDirectoryGetterForTest(
+    const DirectoryGetter& getter) {
+  default_log_base_directory_getter_ = getter;
+}
+
+void NetLogFileWriter::EnsureInitThenRun(
+    const base::Closure& after_successful_init_callback,
+    const StateCallback& state_callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  base::FilePath temp_dir;
-  if (!GetNetExportLogBaseDirectory(&temp_dir))
-    return false;
+
+  if (state_ == STATE_UNINITIALIZED) {
+    state_ = STATE_INITIALIZING;
+    // Run initialization tasks on the file thread, then the main thread. Once
+    // finished, run |after_successful_init_callback| and |state_callback| on
+    // the main thread.
+    base::PostTaskAndReplyWithResult(
+        file_task_runner_.get(), FROM_HERE,
+        base::Bind(&NetLogFileWriter::SetUpDefaultLogPath,
+                   default_log_base_directory_getter_),
+        base::Bind(&NetLogFileWriter::SetStateAfterSetUpDefaultLogPathThenRun,
+                   weak_ptr_factory_.GetWeakPtr(),
+                   after_successful_init_callback, state_callback));
+  } else if (state_ == STATE_INITIALIZING) {
+    // If NetLogFileWriter is already in the process of initializing due to a
+    // previous call to EnsureInitThenRun(), commands received by
+    // NetLogFileWriter should be ignored, so only |state_callback| will be
+    // executed.
+    // Wait for the in-progress initialization to finish before calling
+    // |state_callback|. To do this, post a dummy task to the file thread
+    // (which is guaranteed to run after the tasks belonging to the
+    // in-progress initialization have finished), and have that dummy task
+    // post |state_callback| as a reply on the main thread.
+    file_task_runner_->PostTaskAndReply(
+        FROM_HERE, base::Bind([] {}),
+        base::Bind(&NetLogFileWriter::RunStateCallback,
+                   weak_ptr_factory_.GetWeakPtr(), state_callback));
+
+  } else {
+    // NetLogFileWriter is already fully initialized. Run
+    // |after_successful_init_callback| synchronously and |state_callback|
+    // asynchronously.
+    after_successful_init_callback.Run();
+    RunStateCallbackAsync(state_callback);
+  }
+}
+
+NetLogFileWriter::DefaultLogPathResults NetLogFileWriter::SetUpDefaultLogPath(
+    const DirectoryGetter& default_log_base_directory_getter) {
+  DefaultLogPathResults results;
+  results.default_log_path_success = false;
+  results.log_exists = false;
+
+  base::FilePath default_base_dir;
+  if (!default_log_base_directory_getter.Run(&default_base_dir))
+    return results;
 
   // Delete log file at old location, if present.
-  DeleteFile(temp_dir.Append(kOldLogRelativePath), false);
+  base::DeleteFile(default_base_dir.Append(kOldLogRelativePath), false);
 
-  base::FilePath log_path = temp_dir.Append(kLogRelativePath);
+  results.default_log_path = default_base_dir.Append(kLogRelativePath);
+  if (!base::CreateDirectoryAndGetError(results.default_log_path.DirName(),
+                                        nullptr))
+    return results;
 
-  if (!base::CreateDirectoryAndGetError(log_path.DirName(), nullptr)) {
-    return false;
-  }
-
-  log_path_ = log_path;
-  return true;
+  results.log_exists = base::PathExists(results.default_log_path);
+  results.default_log_path_success = true;
+  return results;
 }
 
-bool NetLogFileWriter::NetExportLogExists() const {
+void NetLogFileWriter::SetStateAfterSetUpDefaultLogPathThenRun(
+    const base::Closure& after_successful_init_callback,
+    const StateCallback& state_callback,
+    const DefaultLogPathResults& set_up_default_log_path_results) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(!log_path_.empty());
-  return base::PathExists(log_path_);
+  DCHECK_EQ(state_, STATE_INITIALIZING);
+
+  if (set_up_default_log_path_results.default_log_path_success) {
+    state_ = STATE_NOT_LOGGING;
+    log_path_ = set_up_default_log_path_results.default_log_path;
+    log_exists_ = set_up_default_log_path_results.log_exists;
+    DCHECK(!log_capture_mode_known_);
+
+    after_successful_init_callback.Run();
+  } else {
+    state_ = STATE_UNINITIALIZED;
+  }
+
+  RunStateCallback(state_callback);
+}
+
+void NetLogFileWriter::RunStateCallback(
+    const StateCallback& state_callback) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  state_callback.Run(GetState());
+}
+
+void NetLogFileWriter::RunStateCallbackAsync(
+    const StateCallback& state_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::Bind(&NetLogFileWriter::RunStateCallback,
+                            weak_ptr_factory_.GetWeakPtr(), state_callback));
+}
+
+void NetLogFileWriter::StartNetLogAfterInitialized(
+    const base::FilePath& log_path,
+    net::NetLogCaptureMode capture_mode) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_INITIALIZING);
+  DCHECK(file_task_runner_);
+
+  if (state_ == STATE_NOT_LOGGING) {
+    if (!log_path.empty())
+      log_path_ = log_path;
+
+    DCHECK(!log_path_.empty());
+
+    state_ = STATE_LOGGING;
+    log_exists_ = true;
+    log_capture_mode_known_ = true;
+    log_capture_mode_ = capture_mode;
+
+    std::unique_ptr<base::Value> constants(
+        ChromeNetLog::GetConstants(command_line_string_, channel_string_));
+    write_to_file_observer_ =
+        base::MakeUnique<net::FileNetLogObserver>(file_task_runner_);
+    write_to_file_observer_->StartObservingUnbounded(
+        chrome_net_log_, capture_mode, log_path_, std::move(constants),
+        nullptr);
+  }
+}
+
+void NetLogFileWriter::StopNetLogAfterAddNetInfo(
+    const StateCallback& state_callback,
+    std::unique_ptr<base::DictionaryValue> polled_data) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_EQ(state_, STATE_STOPPING_LOG);
+
+  write_to_file_observer_->StopObserving(
+      std::move(polled_data),
+      base::Bind(&NetLogFileWriter::ResetObserverThenSetStateNotLogging,
+                 weak_ptr_factory_.GetWeakPtr(), state_callback));
+}
+
+void NetLogFileWriter::ResetObserverThenSetStateNotLogging(
+    const StateCallback& state_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  write_to_file_observer_.reset();
+  state_ = STATE_NOT_LOGGING;
+
+  RunStateCallback(state_callback);
+}
+
+std::unique_ptr<base::DictionaryValue> NetLogFileWriter::GetState() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto dict = base::MakeUnique<base::DictionaryValue>();
+
+#ifndef NDEBUG
+  dict->SetString("file", log_path_.LossyDisplayName());
+#endif  // NDEBUG
+
+  switch (state_) {
+    case STATE_UNINITIALIZED:
+      dict->SetString("state", "UNINITIALIZED");
+      break;
+    case STATE_INITIALIZING:
+      dict->SetString("state", "INITIALIZING");
+      break;
+    case STATE_NOT_LOGGING:
+      dict->SetString("state", "NOT_LOGGING");
+      break;
+    case STATE_LOGGING:
+      dict->SetString("state", "LOGGING");
+      break;
+    case STATE_STOPPING_LOG:
+      dict->SetString("state", "STOPPING_LOG");
+      break;
+  }
+
+  dict->SetBoolean("logExists", log_exists_);
+  dict->SetBoolean("logCaptureModeKnown", log_capture_mode_known_);
+  dict->SetString("captureMode", CaptureModeToString(log_capture_mode_));
+
+  return dict;
 }
 
 }  // namespace net_log
diff --git a/components/net_log/net_log_file_writer.h b/components/net_log/net_log_file_writer.h
index 17033e62..a8f7ac2 100644
--- a/components/net_log/net_log_file_writer.h
+++ b/components/net_log/net_log_file_writer.h
@@ -8,66 +8,135 @@
 #include <memory>
 #include <string>
 
+#include "base/callback.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
+#include "net/log/net_log_capture_mode.h"
 
 namespace base {
 class DictionaryValue;
+class SingleThreadTaskRunner;
+class Value;
 }
 
 namespace net {
-class NetLogCaptureMode;
-class WriteToFileNetLogObserver;
+class FileNetLogObserver;
+class URLRequestContextGetter;
 }
 
 namespace net_log {
 
 class ChromeNetLog;
 
-// NetLogFileWriter logs all the NetLog entries into a specified file.
+// NetLogFileWriter is used exclusively as a support class for net-export.
+// It's a singleton that acts as the interface to all NetExportMessageHandlers
+// which can tell it to start or stop logging in response to user actions from
+// net-export UIs. Because it's a singleton, the logging state can be shared
+// between multiple instances of the net-export UI. Internally, it manages an
+// instance of net::FileNetLogObserver and handles the attaching/detaching of it
+// to the ChromeNetLog. This class is used by the iOS and non-iOS
+// implementations of net-export.
 //
-// NetLogFileWriter maintains the current logging state (state_) and log file
-// type (log_type_) of the logging into a chrome-net-export-log.json file.
+// NetLogFileWriter maintains the current logging state (using the members
+// (|state_|, |log_exists_|, |log_capture_mode_known_|, |log_capture_mode_|).
+// Its three main commands are StartNetLog(), StopNetLog() and GetState(). These
+// are the only functions that may cause NetLogFileWriter to change state.
+// Also, NetLogFileWriter is lazily initialized. A portion of the initialization
+// needs to run on the |file_task_runner_|.
 //
-// The following are the possible states
-// a) Only Start is allowed (STATE_NOT_LOGGING, LOG_TYPE_NONE).
-// b) Only Stop is allowed (STATE_LOGGING).
-// c) Either Send or Start is allowed (STATE_NOT_LOGGING, anything but
-//    LOG_TYPE_NONE).
-//
-// This is created/destroyed on the main thread, but all other function calls
-// occur on a background thread.
-//
-// This relies on the UI thread outlasting all other threads for thread safety.
+// This class is created and destroyed on the UI thread, and all public entry
+// points are to be called on the UI thread. Internally, the class may run some
+// code on the |file_task_runner_| and |net_task_runner_|.
 class NetLogFileWriter {
  public:
-  // This enum lists the UI button commands it could receive.
-  enum Command {
-    DO_START_LOG_BYTES,  // Call StartNetLog logging all bytes received.
-    DO_START,            // Call StartNetLog.
-    DO_START_STRIP_PRIVATE_DATA,  // Call StartNetLog stripping private data.
-    DO_STOP,                      // Call StopNetLog.
-  };
+  // The three main commands StartNetLog(), StopNetLog(), and GetState() and the
+  // getter GetFilePathToCompletedLog() all accept a callback param which is
+  // used to notify the caller of the results of that function. For all these
+  // commands, the callback will always be executed even if the command ends up
+  // being a no-op or if some failure occurs.
 
-  virtual ~NetLogFileWriter();
+  using StateCallback =
+      base::Callback<void(std::unique_ptr<base::DictionaryValue>)>;
 
-  // Accepts the button command and executes it.
-  void ProcessCommand(Command command);
+  using FilePathCallback = base::Callback<void(const base::FilePath&)>;
 
-  // Returns true and the path to the file. If there is no file to
-  // send, then it returns false. It also returns false when actively logging to
-  // the file.
-  bool GetFilePath(base::FilePath* path);
+  using DirectoryGetter = base::Callback<bool(base::FilePath*)>;
 
-  // Creates a Value summary of the state of the NetLogFileWriter. The caller is
-  // responsible for deleting the returned value.
-  base::DictionaryValue* GetState();
+  ~NetLogFileWriter();
 
-  // Updates |log_path_| to the |custom_path|.
-  void SetUpNetExportLogPath(const base::FilePath& custom_path);
+  // Starts collecting NetLog data into the file at |log_path|. If |log_path| is
+  // empty, the default log path is used. It is a no-op if NetLogFileWriter is
+  // already collecting data into a file, and |capture_mode| is ignored.
+  // TODO(mmenke): That's rather weird behavior, think about improving it.
+  //
+  // If NetLogFileWriter is not initialized, StartNetLog() will trigger
+  // initialization.
+  //
+  // |state_callback| will be executed at the end of StartNetLog()
+  // asynchronously. If StartNetLog() is called while initialization is already
+  // in progress, |state_callback| will be called after the ongoing
+  // initialization finishes.
+  void StartNetLog(const base::FilePath& log_path,
+                   net::NetLogCaptureMode capture_mode,
+                   const StateCallback& state_callback);
+
+  // Stops collecting NetLog data into the file. It is a no-op if
+  // NetLogFileWriter is currently not logging.
+  //
+  // |polled_data| is a JSON dictionary that will be appended to the end of the
+  // log; it's for adding additional info to the log that aren't events.
+  // If |context_getter| is not null, then  StopNetLog() will automatically
+  // append net info (from net::GetNetInfo() retrieved using |context_getter|)
+  // to |polled_data|.
+  //
+  // |state_callback| will be executed at the end of StopNetLog()
+  // asynchronously, explicitly after the log file is complete.
+  void StopNetLog(std::unique_ptr<base::DictionaryValue> polled_data,
+                  scoped_refptr<net::URLRequestContextGetter> context_getter,
+                  const StateCallback& state_callback);
+
+  // Creates a Value summary of the state of the NetLogFileWriter and calls
+  // |state_callback| asynchronously with that Value as the param.
+  //
+  // If NetLogFileWriter is not initialized, GetState() will trigger
+  // initialization, and |state_callback| will be called after initialization
+  // finishes.
+  void GetState(const StateCallback& state_callback);
+
+  // Gets the log filepath. |path_callback| will be used to notify the caller
+  // when the filepath is retrieved. |path_callback| will be executed with an
+  // empty filepath if any of the following occurs:
+  // (1) The NetLogFileWriter is not initialized.
+  // (2) The log file does not exist.
+  // (3) The NetLogFileWriter is currently logging.
+  // (4) The log file's permissions could not be set to all.
+  //
+  // |path_callback| will be executed at the end of GetFilePathToCompletedLog()
+  // asynchronously.
+  void GetFilePathToCompletedLog(const FilePathCallback& path_callback) const;
+
+  // Sets the task runners used by NetLogFileWriter for doing file I/O and
+  // network I/O respectively. This must be called prior to using the
+  // NetLogFileWriter. The task runners must not be changed once set. However,
+  // calling this function again with the same parameters is OK.
+  void SetTaskRunners(
+      scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner> net_task_runner);
+
+  // Converts to/from the string representation of a capture mode used by
+  // net_export.js.
+  static std::string CaptureModeToString(net::NetLogCaptureMode capture_mode);
+  static net::NetLogCaptureMode CaptureModeFromString(
+      const std::string& capture_mode_string);
+
+  // Overrides the getter used to retrieve the default log base directory during
+  // initialization. Should only be used by unit tests.
+  void SetDefaultLogBaseDirectoryGetterForTest(const DirectoryGetter& getter);
 
  protected:
   // Constructs a NetLogFileWriter. Only one instance is created in browser
@@ -76,91 +145,109 @@
                    const base::CommandLine::StringType& command_line_string,
                    const std::string& channel_string);
 
-  // Returns path name to base::GetTempDir() directory. Returns false if
-  // base::GetTempDir() fails.
-  virtual bool GetNetExportLogBaseDirectory(base::FilePath* path) const;
-
  private:
   friend class ChromeNetLog;
   friend class NetLogFileWriterTest;
 
-  // Allow tests to access our innards for testing purposes.
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, EnsureInitFailure);
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, EnsureInitAllowStart);
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, EnsureInitAllowStartOrSend);
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, ProcessCommandDoStartAndStop);
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, DoStartClearsFile);
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, CheckAddEvent);
-  FRIEND_TEST_ALL_PREFIXES(NetLogFileWriterTest, CheckAddEventWithCustomPath);
-
-  // This enum lists the possible state NetLogFileWriter could be in. It is used
-  // to enable/disable "Start", "Stop" and "Send" (email) UI actions.
+  // The possible logging states of NetLogFileWriter.
   enum State {
     STATE_UNINITIALIZED,
+    // Currently in the process of initializing.
+    STATE_INITIALIZING,
     // Not currently logging to file.
     STATE_NOT_LOGGING,
     // Currently logging to file.
     STATE_LOGGING,
+    // Currently in the process of stopping the log.
+    STATE_STOPPING_LOG,
   };
 
-  // The type of the current log file on disk.
-  enum LogType {
-    // There is no current log file.
-    LOG_TYPE_NONE,
-    // The file predates this session. May or may not have private data.
-    // TODO(davidben): This state is kind of silly.
-    LOG_TYPE_UNKNOWN,
-    // The log includes raw bytes.
-    LOG_TYPE_LOG_BYTES,
-    // The file includes all data.
-    LOG_TYPE_NORMAL,
-    // The file has credentials and cookies stripped.
-    LOG_TYPE_STRIP_PRIVATE_DATA,
+  // Struct used to store the results of SetUpDefaultLogPath() which will be
+  // passed to InitStateThenCallback().
+  struct DefaultLogPathResults {
+    bool default_log_path_success;
+    base::FilePath default_log_path;
+    bool log_exists;
   };
 
-  // Returns the NetLog::CaptureMode corresponding to a LogType.
-  static net::NetLogCaptureMode GetCaptureModeForLogType(LogType log_type);
+  // Helper function used by StartNetLog() and GetState(). If NetLogFileWriter
+  // is uninitialized, this function will attempt initialization and then
+  // run its callbacks.
+  //
+  // |after_successful_init_callback| is only called after a successful
+  // initialization has completed (triggered by this call or a previous call).
+  // This callback is used to do actual work outside of initialization.
+  //
+  // |state_callback| is called at the end asynchronously no matter what. It's
+  // used to notify the caller of the state of NetLogFileWriter after
+  // everything's done. If EnsureInitThenRun() is called while a previously-
+  // triggered initialization is still ongoing, |state_callback| will wait
+  // until after the ongoing intialization finishes before running.
+  void EnsureInitThenRun(const base::Closure& after_successful_init_callback,
+                         const StateCallback& state_callback);
 
-  // Initializes the |state_| to STATE_NOT_LOGGING and |log_type_| to
-  // LOG_TYPE_NONE (if there is no file from earlier run) or
-  // LOG_TYPE_UNKNOWN (if there is a file from earlier run). Returns
-  // false if initialization of |log_path_| fails.
-  bool EnsureInit();
+  // Contains file-related initialization tasks. Will run on the file task
+  // runner.
+  static DefaultLogPathResults SetUpDefaultLogPath(
+      const DirectoryGetter& default_log_base_directory_getter);
 
-  // Start collecting NetLog data into chrome-net-export-log.json file in
-  // a directory, using the specified capture mode. It is a no-op if we are
-  // already collecting data into a file, and |capture_mode| is ignored.
-  // ignored.
-  // TODO(mmenke):  That's rather weird behavior, think about improving it.
-  void StartNetLog(LogType log_type);
+  // Will initialize NetLogFileWriter's state variables using the result of
+  // SetUpDefaultLogPath().
+  //
+  // |after_successful_init_callback| is only executed if
+  // |set_up_default_log_path_results| indicates SetUpDefaultLogPath()
+  // succeeded.
+  //
+  // |state_callback| is executed at the end synchronously in all cases.
+  void SetStateAfterSetUpDefaultLogPathThenRun(
+      const base::Closure& after_successful_init_callback,
+      const StateCallback& state_callback,
+      const DefaultLogPathResults& set_up_default_log_path_results);
 
-  // Stop collecting NetLog data into the file. It is a no-op if we
-  // are not collecting data into a file.
-  void StopNetLog();
+  // Gets the state, then runs |state_callback| synchronously.
+  void RunStateCallback(const StateCallback& state_callback) const;
 
-  // Updates |log_path_| to be the base::FilePath to use for log files, which
-  // will be inside the base::GetTempDir() directory. Returns false if
-  // base::GetTempDir() fails, or unable to create a subdirectory for logging
-  // within that directory.
-  bool SetUpDefaultNetExportLogPath();
+  // Asychronously calls RunStateCallback().
+  void RunStateCallbackAsync(const StateCallback& state_callback);
 
-  // Returns true if a file exists at |log_path_|.
-  bool NetExportLogExists() const;
+  // Called internally by StartNetLog(). Does the actual work needed by
+  // StartNetLog() outside of initialization and running the state callback.
+  void StartNetLogAfterInitialized(const base::FilePath& log_path,
+                                   net::NetLogCaptureMode capture_mode);
+
+  // Called internally by StopNetLog(). Does the actual work needed by
+  // StopNetLog() outside of retrieving the net info.
+  void StopNetLogAfterAddNetInfo(
+      const StateCallback& state_callback,
+      std::unique_ptr<base::DictionaryValue> polled_data);
+
+  // Contains tasks to be done after |write_to_file_observer_| has completely
+  // stopped writing.
+  void ResetObserverThenSetStateNotLogging(const StateCallback& state_callback);
+
+  std::unique_ptr<base::DictionaryValue> GetState() const;
+
+  // All members are accessed solely from the main thread (the thread that
+  // |thread_checker_| is bound to).
 
   base::ThreadChecker thread_checker_;
 
-  // Helper function for unit tests.
-  State state() const { return state_; }
-  LogType log_type() const { return log_type_; }
+  // Task runners for file-specific and net-specific tasks that must run on a
+  // file or net thread.
+  scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+  scoped_refptr<base::SingleThreadTaskRunner> net_task_runner_;
 
-  State state_;       // Current state of NetLogFileWriter.
-  LogType log_type_;  // Type of current log file on disk.
+  State state_;  // Current logging state of NetLogFileWriter.
+
+  bool log_exists_;  // Whether or not the log exists on disk.
+  bool log_capture_mode_known_;
+  net::NetLogCaptureMode log_capture_mode_;
 
   base::FilePath log_path_;  // base::FilePath to the NetLog file.
 
   // |write_to_file_observer_| watches the NetLog event stream, and
   // sends all entries to the file created in StartNetLog().
-  std::unique_ptr<net::WriteToFileNetLogObserver> write_to_file_observer_;
+  std::unique_ptr<net::FileNetLogObserver> write_to_file_observer_;
 
   // The |chrome_net_log_| is owned by the browser process, cached here to avoid
   // using global (g_browser_process).
@@ -169,6 +256,12 @@
   const base::CommandLine::StringType command_line_string_;
   const std::string channel_string_;
 
+  // Used by unit tests to override the default log base directory retrieved
+  // during initialization. This getter is initialized to base::GetTempDir().
+  DirectoryGetter default_log_base_directory_getter_;
+
+  base::WeakPtrFactory<NetLogFileWriter> weak_ptr_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(NetLogFileWriter);
 };
 
diff --git a/components/net_log/net_log_file_writer_unittest.cc b/components/net_log/net_log_file_writer_unittest.cc
index 47b8083..16c0365 100644
--- a/components/net_log/net_log_file_writer_unittest.cc
+++ b/components/net_log/net_log_file_writer_unittest.cc
@@ -15,432 +15,594 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/json/json_reader.h"
 #include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "components/net_log/chrome_net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_network_session.h"
 #include "net/log/net_log_capture_mode.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/write_to_file_net_log_observer.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
 const char kChannelString[] = "SomeChannel";
 
+// Keep this in sync with kLogRelativePath defined in net_log_file_writer.cc.
+base::FilePath::CharType kLogRelativePath[] =
+    FILE_PATH_LITERAL("net-export/chrome-net-export-log.json");
+
+const char kCaptureModeDefaultString[] = "STRIP_PRIVATE_DATA";
+const char kCaptureModeIncludeCookiesAndCredentialsString[] = "NORMAL";
+const char kCaptureModeIncludeSocketBytesString[] = "LOG_BYTES";
+
+const char kStateUninitializedString[] = "UNINITIALIZED";
+const char kStateNotLoggingString[] = "NOT_LOGGING";
+const char kStateLoggingString[] = "LOGGING";
+
 }  // namespace
 
 namespace net_log {
 
-class TestNetLogFileWriter : public NetLogFileWriter {
+// Sets |path| to |path_to_return| and always returns true. This function is
+// used to override NetLogFileWriter's usual getter for the default log base
+// directory.
+bool SetPathToGivenAndReturnTrue(const base::FilePath& path_to_return,
+                                 base::FilePath* path) {
+  *path = path_to_return;
+  return true;
+}
+
+// Checks the fields of the state returned by NetLogFileWriter's state callback.
+// |expected_log_capture_mode_string| is only checked if
+// |expected_log_capture_mode_known| is true.
+void VerifyState(const base::DictionaryValue& state,
+                 const std::string& expected_state_string,
+                 bool expected_log_exists,
+                 bool expected_log_capture_mode_known,
+                 const std::string& expected_log_capture_mode_string) {
+  std::string state_string;
+  ASSERT_TRUE(state.GetString("state", &state_string));
+  EXPECT_EQ(state_string, expected_state_string);
+
+  bool log_exists;
+  ASSERT_TRUE(state.GetBoolean("logExists", &log_exists));
+  EXPECT_EQ(log_exists, expected_log_exists);
+
+  bool log_capture_mode_known;
+  ASSERT_TRUE(state.GetBoolean("logCaptureModeKnown", &log_capture_mode_known));
+  EXPECT_EQ(log_capture_mode_known, expected_log_capture_mode_known);
+
+  if (expected_log_capture_mode_known) {
+    std::string log_capture_mode_string;
+    ASSERT_TRUE(state.GetString("captureMode", &log_capture_mode_string));
+    EXPECT_EQ(log_capture_mode_string, expected_log_capture_mode_string);
+  }
+}
+
+::testing::AssertionResult ReadCompleteLogFile(
+    const base::FilePath& log_path,
+    std::unique_ptr<base::DictionaryValue>* root) {
+  DCHECK(!log_path.empty());
+
+  if (!base::PathExists(log_path)) {
+    return ::testing::AssertionFailure() << log_path.value()
+                                         << " does not exist.";
+  }
+  // Parse log file contents into a dictionary
+  std::string log_string;
+  if (!base::ReadFileToString(log_path, &log_string)) {
+    return ::testing::AssertionFailure() << log_path.value()
+                                         << " could not be read.";
+  }
+  *root = base::DictionaryValue::From(base::JSONReader::Read(log_string));
+  if (!*root) {
+    return ::testing::AssertionFailure()
+           << "Contents of " << log_path.value()
+           << " do not form valid JSON dictionary.";
+  }
+  // Make sure the "constants" section exists
+  base::DictionaryValue* constants;
+  if (!(*root)->GetDictionary("constants", &constants)) {
+    root->reset();
+    return ::testing::AssertionFailure() << log_path.value()
+                                         << " does not contain constants.";
+  }
+  // Make sure the "events" section exists
+  base::ListValue* events;
+  if (!(*root)->GetList("events", &events)) {
+    root->reset();
+    return ::testing::AssertionFailure() << log_path.value()
+                                         << " does not contain events list.";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+void SetUpTestContextGetterWithQuicTimeoutInfo(
+    net::NetLog* net_log,
+    int quic_idle_connection_timeout_seconds,
+    scoped_refptr<net::TestURLRequestContextGetter>* context_getter) {
+  std::unique_ptr<net::TestURLRequestContext> context =
+      base::MakeUnique<net::TestURLRequestContext>(true);
+  context->set_net_log(net_log);
+
+  std::unique_ptr<net::HttpNetworkSession::Params> params(
+      new net::HttpNetworkSession::Params);
+  params->quic_idle_connection_timeout_seconds =
+      quic_idle_connection_timeout_seconds;
+
+  context->set_http_network_session_params(std::move(params));
+  context->Init();
+
+  *context_getter = new net::TestURLRequestContextGetter(
+      base::ThreadTaskRunnerHandle::Get(), std::move(context));
+}
+
+// A class that wraps around TestClosure. Provides the ability to wait on a
+// state callback and retrieve the result.
+class TestStateCallback {
  public:
-  explicit TestNetLogFileWriter(ChromeNetLog* chrome_net_log)
-      : NetLogFileWriter(
-            chrome_net_log,
-            base::CommandLine::ForCurrentProcess()->GetCommandLineString(),
-            kChannelString),
-        lie_about_net_export_log_directory_(false) {
-    EXPECT_TRUE(net_log_temp_dir_.CreateUniqueTempDir());
+  TestStateCallback()
+      : callback_(base::Bind(&TestStateCallback::SetResultThenNotify,
+                             base::Unretained(this))) {}
+
+  const base::Callback<void(std::unique_ptr<base::DictionaryValue>)>& callback()
+      const {
+    return callback_;
   }
 
-  ~TestNetLogFileWriter() override { EXPECT_TRUE(net_log_temp_dir_.Delete()); }
-
-  // NetLogFileWriter implementation:
-  bool GetNetExportLogBaseDirectory(base::FilePath* path) const override {
-    if (lie_about_net_export_log_directory_)
-      return false;
-    *path = net_log_temp_dir_.GetPath();
-    return true;
-  }
-
-  void set_lie_about_net_export_log_directory(
-      bool lie_about_net_export_log_directory) {
-    lie_about_net_export_log_directory_ = lie_about_net_export_log_directory;
+  std::unique_ptr<base::DictionaryValue> WaitForResult() {
+    test_closure_.WaitForResult();
+    return std::move(result_);
   }
 
  private:
-  bool lie_about_net_export_log_directory_;
+  void SetResultThenNotify(std::unique_ptr<base::DictionaryValue> result) {
+    result_ = std::move(result);
+    test_closure_.closure().Run();
+  }
 
-  base::ScopedTempDir net_log_temp_dir_;
+  net::TestClosure test_closure_;
+  std::unique_ptr<base::DictionaryValue> result_;
+  base::Callback<void(std::unique_ptr<base::DictionaryValue>)> callback_;
+};
+
+// A class that wraps around TestClosure. Provides the ability to wait on a
+// file path callback and retrieve the result.
+class TestFilePathCallback {
+ public:
+  TestFilePathCallback()
+      : callback_(base::Bind(&TestFilePathCallback::SetResultThenNotify,
+                             base::Unretained(this))) {}
+
+  const base::Callback<void(const base::FilePath&)>& callback() const {
+    return callback_;
+  }
+
+  const base::FilePath& WaitForResult() {
+    test_closure_.WaitForResult();
+    return result_;
+  }
+
+ private:
+  void SetResultThenNotify(const base::FilePath& result) {
+    result_ = result;
+    test_closure_.closure().Run();
+  }
+
+  net::TestClosure test_closure_;
+  base::FilePath result_;
+  base::Callback<void(const base::FilePath&)> callback_;
 };
 
 class NetLogFileWriterTest : public ::testing::Test {
  public:
   NetLogFileWriterTest()
-      : net_log_(new ChromeNetLog(
-            base::FilePath(),
-            net::NetLogCaptureMode::Default(),
+      : net_log_(base::FilePath(),
+                 net::NetLogCaptureMode::Default(),
+                 base::CommandLine::ForCurrentProcess()->GetCommandLineString(),
+                 kChannelString),
+        net_log_file_writer_(
+            &net_log_,
             base::CommandLine::ForCurrentProcess()->GetCommandLineString(),
-            kChannelString)),
-        net_log_file_writer_(new TestNetLogFileWriter(net_log_.get())) {}
+            kChannelString),
+        file_thread_("NetLogFileWriter file thread"),
+        net_thread_("NetLogFileWriter net thread") {}
 
-  std::string GetStateString() const {
-    std::unique_ptr<base::DictionaryValue> dict(
-        net_log_file_writer_->GetState());
-    std::string state;
-    EXPECT_TRUE(dict->GetString("state", &state));
-    return state;
+  // ::testing::Test implementation
+  void SetUp() override {
+    ASSERT_TRUE(log_temp_dir_.CreateUniqueTempDir());
+
+    // Override |net_log_file_writer_|'s default-log-base-directory-getter to
+    // a getter that returns the temp dir created for the test.
+    net_log_file_writer_.SetDefaultLogBaseDirectoryGetterForTest(
+        base::Bind(&SetPathToGivenAndReturnTrue, log_temp_dir_.GetPath()));
+
+    default_log_path_ = log_temp_dir_.GetPath().Append(kLogRelativePath);
+
+    ASSERT_TRUE(file_thread_.Start());
+    ASSERT_TRUE(net_thread_.Start());
+
+    net_log_file_writer_.SetTaskRunners(file_thread_.task_runner(),
+                                        net_thread_.task_runner());
+  }
+  void TearDown() override { ASSERT_TRUE(log_temp_dir_.Delete()); }
+
+  std::unique_ptr<base::DictionaryValue> FileWriterGetState() {
+    TestStateCallback test_callback;
+    net_log_file_writer_.GetState(test_callback.callback());
+    return test_callback.WaitForResult();
   }
 
-  std::string GetLogTypeString() const {
-    std::unique_ptr<base::DictionaryValue> dict(
-        net_log_file_writer_->GetState());
-    std::string log_type;
-    EXPECT_TRUE(dict->GetString("logType", &log_type));
-    return log_type;
+  base::FilePath FileWriterGetFilePathToCompletedLog() {
+    TestFilePathCallback test_callback;
+    net_log_file_writer_.GetFilePathToCompletedLog(test_callback.callback());
+    return test_callback.WaitForResult();
   }
 
-  // Make sure the export file has been created and is non-empty, as net
-  // constants will always be written to it on creation.
-  void VerifyNetExportLogExists() {
-    net_export_log_ = net_log_file_writer_->log_path_;
-    ASSERT_TRUE(base::PathExists(net_export_log_));
+  // If |custom_log_path| is empty path, |net_log_file_writer_| will use its
+  // default log path.
+  void StartThenVerifyState(const base::FilePath& custom_log_path,
+                            net::NetLogCaptureMode capture_mode,
+                            const std::string& expected_capture_mode_string) {
+    TestStateCallback test_callback;
+    net_log_file_writer_.StartNetLog(custom_log_path, capture_mode,
+                                     test_callback.callback());
+    std::unique_ptr<base::DictionaryValue> state =
+        test_callback.WaitForResult();
+    VerifyState(*state, kStateLoggingString, true, true,
+                expected_capture_mode_string);
 
-    int64_t file_size;
-    // base::GetFileSize returns proper file size on open handles.
-    ASSERT_TRUE(base::GetFileSize(net_export_log_, &file_size));
-    EXPECT_GT(file_size, 0);
+    // Make sure NetLogFileWriter::GetFilePath() returns empty path when
+    // logging.
+    EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty());
   }
 
-  // Make sure the export file has been created and a valid JSON file.  This
-  // should always be the case once logging has been stopped.
-  void VerifyNetExportLogComplete() {
-    VerifyNetExportLogExists();
+  // If |custom_log_path| is empty path, it's assumed the log file with be at
+  // |default_path_|.
+  void StopThenVerifyStateAndFile(
+      const base::FilePath& custom_log_path,
+      std::unique_ptr<base::DictionaryValue> polled_data,
+      scoped_refptr<net::URLRequestContextGetter> context_getter,
+      const std::string& expected_capture_mode_string) {
+    TestStateCallback test_callback;
+    net_log_file_writer_.StopNetLog(std::move(polled_data), context_getter,
+                                    test_callback.callback());
+    std::unique_ptr<base::DictionaryValue> state =
+        test_callback.WaitForResult();
+    VerifyState(*state, kStateNotLoggingString, true, true,
+                expected_capture_mode_string);
 
-    std::string log;
-    ASSERT_TRUE(ReadFileToString(net_export_log_, &log));
-    base::JSONReader reader;
-    std::unique_ptr<base::Value> json = base::JSONReader::Read(log);
-    EXPECT_TRUE(json);
+    const base::FilePath& log_path =
+        custom_log_path.empty() ? default_log_path_ : custom_log_path;
+    EXPECT_EQ(FileWriterGetFilePathToCompletedLog(), log_path);
+
+    std::unique_ptr<base::DictionaryValue> root;
+    ASSERT_TRUE(ReadCompleteLogFile(log_path, &root));
   }
 
-  // Verify state and GetFilePath return correct values if EnsureInit() fails.
-  void VerifyFilePathAndStateAfterEnsureInitFailure() {
-    EXPECT_EQ("UNINITIALIZED", GetStateString());
-    EXPECT_EQ(NetLogFileWriter::STATE_UNINITIALIZED,
-              net_log_file_writer_->state());
+ protected:
+  ChromeNetLog net_log_;
 
-    base::FilePath net_export_file_path;
-    EXPECT_FALSE(net_log_file_writer_->GetFilePath(&net_export_file_path));
-  }
-
-  // When we lie in NetExportLogExists, make sure state and GetFilePath return
-  // correct values.
-  void VerifyFilePathAndStateAfterEnsureInit() {
-    EXPECT_EQ("NOT_LOGGING", GetStateString());
-    EXPECT_EQ(NetLogFileWriter::STATE_NOT_LOGGING,
-              net_log_file_writer_->state());
-    EXPECT_EQ("NONE", GetLogTypeString());
-    EXPECT_EQ(NetLogFileWriter::LOG_TYPE_NONE,
-              net_log_file_writer_->log_type());
-
-    base::FilePath net_export_file_path;
-    EXPECT_FALSE(net_log_file_writer_->GetFilePath(&net_export_file_path));
-    EXPECT_FALSE(net_log_file_writer_->NetExportLogExists());
-  }
-
-  // The following methods make sure the export file has been successfully
-  // initialized by a DO_START command of the given type.
-
-  void VerifyFileAndStateAfterDoStart() {
-    VerifyFileAndStateAfterStart(
-        NetLogFileWriter::LOG_TYPE_NORMAL, "NORMAL",
-        net::NetLogCaptureMode::IncludeCookiesAndCredentials());
-  }
-
-  void VerifyFileAndStateAfterDoStartStripPrivateData() {
-    VerifyFileAndStateAfterStart(NetLogFileWriter::LOG_TYPE_STRIP_PRIVATE_DATA,
-                                 "STRIP_PRIVATE_DATA",
-                                 net::NetLogCaptureMode::Default());
-  }
-
-  void VerifyFileAndStateAfterDoStartLogBytes() {
-    VerifyFileAndStateAfterStart(NetLogFileWriter::LOG_TYPE_LOG_BYTES,
-                                 "LOG_BYTES",
-                                 net::NetLogCaptureMode::IncludeSocketBytes());
-  }
-
-  // Make sure the export file has been successfully initialized after DO_STOP
-  // command following a DO_START command of the given type.
-
-  void VerifyFileAndStateAfterDoStop() {
-    VerifyFileAndStateAfterDoStop(NetLogFileWriter::LOG_TYPE_NORMAL, "NORMAL");
-  }
-
-  void VerifyFileAndStateAfterDoStopWithStripPrivateData() {
-    VerifyFileAndStateAfterDoStop(NetLogFileWriter::LOG_TYPE_STRIP_PRIVATE_DATA,
-                                  "STRIP_PRIVATE_DATA");
-  }
-
-  void VerifyFileAndStateAfterDoStopWithLogBytes() {
-    VerifyFileAndStateAfterDoStop(NetLogFileWriter::LOG_TYPE_LOG_BYTES,
-                                  "LOG_BYTES");
-  }
-
-  std::unique_ptr<ChromeNetLog> net_log_;
   // |net_log_file_writer_| is initialized after |net_log_| so that it can stop
   // obvserving on destruction.
-  std::unique_ptr<TestNetLogFileWriter> net_log_file_writer_;
-  base::FilePath net_export_log_;
+  NetLogFileWriter net_log_file_writer_;
+
+  base::ScopedTempDir log_temp_dir_;
+
+  // The default log path that |net_log_file_writer_| will use is cached here.
+  base::FilePath default_log_path_;
+
+  base::Thread file_thread_;
+  base::Thread net_thread_;
 
  private:
-  // Checks state after one of the DO_START* commands.
-  void VerifyFileAndStateAfterStart(
-      NetLogFileWriter::LogType expected_log_type,
-      const std::string& expected_log_type_string,
-      net::NetLogCaptureMode expected_capture_mode) {
-    EXPECT_EQ(NetLogFileWriter::STATE_LOGGING, net_log_file_writer_->state());
-    EXPECT_EQ("LOGGING", GetStateString());
-    EXPECT_EQ(expected_log_type, net_log_file_writer_->log_type());
-    EXPECT_EQ(expected_log_type_string, GetLogTypeString());
-    EXPECT_EQ(expected_capture_mode,
-              net_log_file_writer_->write_to_file_observer_->capture_mode());
-
-    // Check GetFilePath returns false when still writing to the file.
-    base::FilePath net_export_file_path;
-    EXPECT_FALSE(net_log_file_writer_->GetFilePath(&net_export_file_path));
-
-    VerifyNetExportLogExists();
-  }
-
-  void VerifyFileAndStateAfterDoStop(
-      NetLogFileWriter::LogType expected_log_type,
-      const std::string& expected_log_type_string) {
-    EXPECT_EQ(NetLogFileWriter::STATE_NOT_LOGGING,
-              net_log_file_writer_->state());
-    EXPECT_EQ("NOT_LOGGING", GetStateString());
-    EXPECT_EQ(expected_log_type, net_log_file_writer_->log_type());
-    EXPECT_EQ(expected_log_type_string, GetLogTypeString());
-
-    base::FilePath net_export_file_path;
-    EXPECT_TRUE(net_log_file_writer_->GetFilePath(&net_export_file_path));
-    EXPECT_EQ(net_export_log_, net_export_file_path);
-
-    VerifyNetExportLogComplete();
-  }
-
+  // Allows tasks to be posted to the main thread.
   base::MessageLoop message_loop_;
 };
 
-TEST_F(NetLogFileWriterTest, EnsureInitFailure) {
-  net_log_file_writer_->set_lie_about_net_export_log_directory(true);
+TEST_F(NetLogFileWriterTest, InitFail) {
+  // Override net_log_file_writer_'s default log base directory getter to always
+  // fail.
+  net_log_file_writer_.SetDefaultLogBaseDirectoryGetterForTest(
+      base::Bind([](base::FilePath* path) -> bool { return false; }));
 
-  EXPECT_FALSE(net_log_file_writer_->EnsureInit());
-  VerifyFilePathAndStateAfterEnsureInitFailure();
+  // GetState() will cause |net_log_file_writer_| to initialize. In this case,
+  // initialization will fail since |net_log_file_writer_| will fail to
+  // retrieve the default log base directory.
+  VerifyState(*FileWriterGetState(), kStateUninitializedString, false, false,
+              "");
 
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFilePathAndStateAfterEnsureInitFailure();
+  // NetLogFileWriter::GetFilePath() should return empty path if uninitialized.
+  EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty());
 }
 
-TEST_F(NetLogFileWriterTest, EnsureInitAllowStart) {
-  EXPECT_TRUE(net_log_file_writer_->EnsureInit());
-  VerifyFilePathAndStateAfterEnsureInit();
+TEST_F(NetLogFileWriterTest, InitWithoutExistingLog) {
+  // GetState() will cause |net_log_file_writer_| to initialize.
+  VerifyState(*FileWriterGetState(), kStateNotLoggingString, false, false, "");
 
-  // Calling EnsureInit() second time should be a no-op.
-  EXPECT_TRUE(net_log_file_writer_->EnsureInit());
-  VerifyFilePathAndStateAfterEnsureInit();
-
-  // GetFilePath() should failed when there's no file.
-  base::FilePath net_export_file_path;
-  EXPECT_FALSE(net_log_file_writer_->GetFilePath(&net_export_file_path));
+  // NetLogFileWriter::GetFilePathToCompletedLog() should return empty path when
+  // no log file exists.
+  EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty());
 }
 
-TEST_F(NetLogFileWriterTest, EnsureInitAllowStartOrSend) {
-  net_log_file_writer_->SetUpDefaultNetExportLogPath();
-  net_export_log_ = net_log_file_writer_->log_path_;
+TEST_F(NetLogFileWriterTest, InitWithExistingLog) {
+  // Create and close an empty log file to simulate existence of a previous log
+  // file.
+  ASSERT_TRUE(
+      base::CreateDirectoryAndGetError(default_log_path_.DirName(), nullptr));
+  base::ScopedFILE empty_file(base::OpenFile(default_log_path_, "w"));
+  ASSERT_TRUE(empty_file.get());
+  empty_file.reset();
 
-  // Create and close an empty log file, to simulate an old log file already
-  // existing.
-  base::ScopedFILE created_file(base::OpenFile(net_export_log_, "w"));
-  ASSERT_TRUE(created_file.get());
-  created_file.reset();
+  // GetState() will cause |net_log_file_writer_| to initialize.
+  VerifyState(*FileWriterGetState(), kStateNotLoggingString, true, false, "");
 
-  EXPECT_TRUE(net_log_file_writer_->EnsureInit());
-
-  EXPECT_EQ("NOT_LOGGING", GetStateString());
-  EXPECT_EQ(NetLogFileWriter::STATE_NOT_LOGGING, net_log_file_writer_->state());
-  EXPECT_EQ("UNKNOWN", GetLogTypeString());
-  EXPECT_EQ(NetLogFileWriter::LOG_TYPE_UNKNOWN,
-            net_log_file_writer_->log_type());
-  EXPECT_EQ(net_export_log_, net_log_file_writer_->log_path_);
-  EXPECT_TRUE(base::PathExists(net_export_log_));
-
-  base::FilePath net_export_file_path;
-  EXPECT_TRUE(net_log_file_writer_->GetFilePath(&net_export_file_path));
-  EXPECT_TRUE(base::PathExists(net_export_file_path));
-  EXPECT_EQ(net_export_log_, net_export_file_path);
+  EXPECT_EQ(FileWriterGetFilePathToCompletedLog(), default_log_path_);
 }
 
-TEST_F(NetLogFileWriterTest, ProcessCommandDoStartAndStop) {
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+TEST_F(NetLogFileWriterTest, StartAndStopWithAllCaptureModes) {
+  const net::NetLogCaptureMode capture_modes[3] = {
+      net::NetLogCaptureMode::Default(),
+      net::NetLogCaptureMode::IncludeCookiesAndCredentials(),
+      net::NetLogCaptureMode::IncludeSocketBytes()};
 
-  // Calling a second time should be a no-op.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+  const std::string capture_mode_strings[3] = {
+      kCaptureModeDefaultString, kCaptureModeIncludeCookiesAndCredentialsString,
+      kCaptureModeIncludeSocketBytesString};
 
-  // starting with other log levels should also be no-ops.
-  net_log_file_writer_->ProcessCommand(
-      NetLogFileWriter::DO_START_STRIP_PRIVATE_DATA);
-  VerifyFileAndStateAfterDoStart();
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START_LOG_BYTES);
-  VerifyFileAndStateAfterDoStart();
+  // For each capture mode, start and stop |net_log_file_writer_| in that mode.
+  for (int i = 0; i < 3; ++i) {
+    StartThenVerifyState(base::FilePath(), capture_modes[i],
+                         capture_mode_strings[i]);
 
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
+    // Starting a second time should be a no-op.
+    StartThenVerifyState(base::FilePath(), capture_modes[i],
+                         capture_mode_strings[i]);
 
-  // Calling DO_STOP second time should be a no-op.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
+    // Starting with other capture modes should also be no-ops. This should also
+    // not affect the capture mode reported by |net_log_file_writer_|.
+    StartThenVerifyState(base::FilePath(), capture_modes[(i + 1) % 3],
+                         capture_mode_strings[i]);
+
+    StartThenVerifyState(base::FilePath(), capture_modes[(i + 2) % 3],
+                         capture_mode_strings[i]);
+
+    StopThenVerifyStateAndFile(base::FilePath(), nullptr, nullptr,
+                               capture_mode_strings[i]);
+
+    // Stopping a second time should be a no-op.
+    StopThenVerifyStateAndFile(base::FilePath(), nullptr, nullptr,
+                               capture_mode_strings[i]);
+  }
 }
 
-TEST_F(NetLogFileWriterTest,
-       ProcessCommandDoStartAndStopWithPrivateDataStripping) {
-  net_log_file_writer_->ProcessCommand(
-      NetLogFileWriter::DO_START_STRIP_PRIVATE_DATA);
-  VerifyFileAndStateAfterDoStartStripPrivateData();
+// Verify the file sizes after two consecutive starts/stops are the same (even
+// if some junk data is added in between).
+TEST_F(NetLogFileWriterTest, StartClearsFile) {
+  StartThenVerifyState(base::FilePath(), net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
 
-  // Calling a second time should be a no-op.
-  net_log_file_writer_->ProcessCommand(
-      NetLogFileWriter::DO_START_STRIP_PRIVATE_DATA);
-  VerifyFileAndStateAfterDoStartStripPrivateData();
-
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStopWithStripPrivateData();
-
-  // Calling DO_STOP second time should be a no-op.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStopWithStripPrivateData();
-}
-
-TEST_F(NetLogFileWriterTest, ProcessCommandDoStartAndStopWithByteLogging) {
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START_LOG_BYTES);
-  VerifyFileAndStateAfterDoStartLogBytes();
-
-  // Calling a second time should be a no-op.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START_LOG_BYTES);
-  VerifyFileAndStateAfterDoStartLogBytes();
-
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStopWithLogBytes();
-
-  // Calling DO_STOP second time should be a no-op.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStopWithLogBytes();
-}
-
-TEST_F(NetLogFileWriterTest, DoStartClearsFile) {
-  // Verify file sizes after two consecutive starts/stops are the same (even if
-  // we add some junk data in between).
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
-
-  int64_t start_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &start_file_size));
-
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
+  StopThenVerifyStateAndFile(base::FilePath(), nullptr, nullptr,
+                             kCaptureModeDefaultString);
 
   int64_t stop_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &stop_file_size));
-  EXPECT_GE(stop_file_size, start_file_size);
+  EXPECT_TRUE(base::GetFileSize(default_log_path_, &stop_file_size));
 
   // Add some junk at the end of the file.
   std::string junk_data("Hello");
-  EXPECT_TRUE(
-      base::AppendToFile(net_export_log_, junk_data.c_str(), junk_data.size()));
+  EXPECT_TRUE(base::AppendToFile(default_log_path_, junk_data.c_str(),
+                                 junk_data.size()));
 
   int64_t junk_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &junk_file_size));
+  EXPECT_TRUE(base::GetFileSize(default_log_path_, &junk_file_size));
   EXPECT_GT(junk_file_size, stop_file_size);
 
-  // Execute DO_START/DO_STOP commands and make sure the file is back to the
-  // size before addition of junk data.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+  // Start and stop again and make sure the file is back to the size it was
+  // before adding the junk data.
+  StartThenVerifyState(base::FilePath(), net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
 
-  int64_t new_start_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &new_start_file_size));
-  EXPECT_EQ(new_start_file_size, start_file_size);
-
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
+  StopThenVerifyStateAndFile(base::FilePath(), nullptr, nullptr,
+                             kCaptureModeDefaultString);
 
   int64_t new_stop_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &new_stop_file_size));
+  EXPECT_TRUE(base::GetFileSize(default_log_path_, &new_stop_file_size));
+
   EXPECT_EQ(new_stop_file_size, stop_file_size);
 }
 
-TEST_F(NetLogFileWriterTest, CheckAddEvent) {
-  // Add an event to |net_log_| and then test to make sure that, after we stop
-  // logging, the file is larger than the file created without that event.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+// Adds an event to the log file, then checks that the file is larger than
+// the file created without that event.
+TEST_F(NetLogFileWriterTest, AddEvent) {
+  StartThenVerifyState(base::FilePath(), net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
+
+  StopThenVerifyStateAndFile(base::FilePath(), nullptr, nullptr,
+                             kCaptureModeDefaultString);
 
   // Get file size without the event.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
-
   int64_t stop_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &stop_file_size));
+  EXPECT_TRUE(base::GetFileSize(default_log_path_, &stop_file_size));
 
-  // Perform DO_START and add an Event and then DO_STOP and then compare
-  // file sizes.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+  StartThenVerifyState(base::FilePath(), net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
 
-  // Log an event.
-  net_log_->AddGlobalEntry(net::NetLogEventType::CANCELLED);
+  net_log_.AddGlobalEntry(net::NetLogEventType::CANCELLED);
 
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
+  StopThenVerifyStateAndFile(base::FilePath(), nullptr, nullptr,
+                             kCaptureModeDefaultString);
 
   int64_t new_stop_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &new_stop_file_size));
+  EXPECT_TRUE(base::GetFileSize(default_log_path_, &new_stop_file_size));
+
   EXPECT_GE(new_stop_file_size, stop_file_size);
 }
 
-TEST_F(NetLogFileWriterTest, CheckAddEventWithCustomPath) {
-  // Using a custom path to make sure logging can still occur when
-  // the path has changed.
-  base::FilePath path;
-  net_log_file_writer_->GetNetExportLogBaseDirectory(&path);
-
-  base::FilePath::CharType kCustomPath[] =
+// Using a custom path to make sure logging can still occur when
+// the path has changed.
+TEST_F(NetLogFileWriterTest, AddEventCustomPath) {
+  base::FilePath::CharType kCustomRelativePath[] =
       FILE_PATH_LITERAL("custom/custom/chrome-net-export-log.json");
-  base::FilePath custom_path = path.Append(kCustomPath);
+  base::FilePath custom_log_path =
+      log_temp_dir_.GetPath().Append(kCustomRelativePath);
+  EXPECT_TRUE(
+      base::CreateDirectoryAndGetError(custom_log_path.DirName(), nullptr));
 
-  EXPECT_TRUE(base::CreateDirectoryAndGetError(custom_path.DirName(), nullptr));
+  StartThenVerifyState(custom_log_path, net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
 
-  net_log_file_writer_->SetUpNetExportLogPath(custom_path);
-  net_export_log_ = net_log_file_writer_->log_path_;
-  EXPECT_EQ(custom_path, net_export_log_);
-
-  // Add an event to |net_log_| and then test to make sure that, after we stop
-  // logging, the file is larger than the file created without that event.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+  StopThenVerifyStateAndFile(custom_log_path, nullptr, nullptr,
+                             kCaptureModeDefaultString);
 
   // Get file size without the event.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
-
   int64_t stop_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &stop_file_size));
+  EXPECT_TRUE(base::GetFileSize(custom_log_path, &stop_file_size));
 
-  // Perform DO_START and add an Event and then DO_STOP and then compare
-  // file sizes.
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_START);
-  VerifyFileAndStateAfterDoStart();
+  StartThenVerifyState(custom_log_path, net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
 
-  // Log an event.
-  net_log_->AddGlobalEntry(net::NetLogEventType::CANCELLED);
+  net_log_.AddGlobalEntry(net::NetLogEventType::CANCELLED);
 
-  net_log_file_writer_->ProcessCommand(NetLogFileWriter::DO_STOP);
-  VerifyFileAndStateAfterDoStop();
+  StopThenVerifyStateAndFile(custom_log_path, nullptr, nullptr,
+                             kCaptureModeDefaultString);
 
   int64_t new_stop_file_size;
-  EXPECT_TRUE(base::GetFileSize(net_export_log_, &new_stop_file_size));
+  EXPECT_TRUE(base::GetFileSize(custom_log_path, &new_stop_file_size));
   EXPECT_GE(new_stop_file_size, stop_file_size);
 }
 
+TEST_F(NetLogFileWriterTest, StopWithPolledDataAndContextGetter) {
+  // Create dummy polled data
+  const char kDummyPolledDataPath[] = "dummy_path";
+  const char kDummyPolledDataString[] = "dummy_info";
+  std::unique_ptr<base::DictionaryValue> dummy_polled_data =
+      base::MakeUnique<base::DictionaryValue>();
+  dummy_polled_data->SetString(kDummyPolledDataPath, kDummyPolledDataString);
+
+  // Create test context getter on |net_thread_| and wait for it to finish.
+  scoped_refptr<net::TestURLRequestContextGetter> context_getter;
+  const int kDummyQuicParam = 75;
+
+  net::TestClosure init_done;
+  net_thread_.task_runner()->PostTaskAndReply(
+      FROM_HERE, base::Bind(&SetUpTestContextGetterWithQuicTimeoutInfo,
+                            &net_log_, kDummyQuicParam, &context_getter),
+      init_done.closure());
+  init_done.WaitForResult();
+
+  StartThenVerifyState(base::FilePath(), net::NetLogCaptureMode::Default(),
+                       kCaptureModeDefaultString);
+
+  StopThenVerifyStateAndFile(base::FilePath(), std::move(dummy_polled_data),
+                             context_getter, kCaptureModeDefaultString);
+
+  // Read polledData from log file.
+  std::unique_ptr<base::DictionaryValue> root;
+  ASSERT_TRUE(ReadCompleteLogFile(default_log_path_, &root));
+  base::DictionaryValue* polled_data;
+  ASSERT_TRUE(root->GetDictionary("polledData", &polled_data));
+
+  // Check that it contains the field from the polled data that was passed in.
+  std::string dummy_string;
+  ASSERT_TRUE(polled_data->GetString(kDummyPolledDataPath, &dummy_string));
+  EXPECT_EQ(dummy_string, kDummyPolledDataString);
+
+  // Check that it also contains the field from the URLRequestContext that was
+  // passed in.
+  base::DictionaryValue* quic_info;
+  ASSERT_TRUE(polled_data->GetDictionary("quicInfo", &quic_info));
+  base::Value* timeout_value = nullptr;
+  int timeout;
+  ASSERT_TRUE(
+      quic_info->Get("idle_connection_timeout_seconds", &timeout_value));
+  ASSERT_TRUE(timeout_value->GetAsInteger(&timeout));
+  EXPECT_EQ(timeout, kDummyQuicParam);
+}
+
+TEST_F(NetLogFileWriterTest, ReceiveStartWhileInitializing) {
+  // Trigger initialization of |net_log_file_writer_|.
+  TestStateCallback init_callback;
+  net_log_file_writer_.GetState(init_callback.callback());
+
+  // Before running the main message loop, tell |net_log_file_writer_| to start
+  // logging. Not running the main message loop prevents the initialization
+  // process from completing, so this ensures that StartNetLog() is received
+  // before |net_log_file_writer_| finishes initialization, which means this
+  // should be a no-op.
+  TestStateCallback start_during_init_callback;
+  net_log_file_writer_.StartNetLog(base::FilePath(),
+                                   net::NetLogCaptureMode::Default(),
+                                   start_during_init_callback.callback());
+
+  // Now run the main message loop.
+  std::unique_ptr<base::DictionaryValue> init_callback_state =
+      init_callback.WaitForResult();
+
+  // The state returned by the GetState() call should be the state after
+  // initialization, which should indicate not-logging.
+  VerifyState(*init_callback_state, kStateNotLoggingString, false, false, "");
+
+  // The state returned by the ignored StartNetLog() call should also be the
+  // state after the ongoing initialization finishes, so it should be identical
+  // to the state returned by GetState().
+  std::unique_ptr<base::DictionaryValue> start_during_init_callback_state =
+      start_during_init_callback.WaitForResult();
+  VerifyState(*start_during_init_callback_state, kStateNotLoggingString, false,
+              false, "");
+
+  // Run an additional GetState() just to make sure |net_log_file_writer_| is
+  // not logging.
+  VerifyState(*FileWriterGetState(), kStateNotLoggingString, false, false, "");
+}
+
+TEST_F(NetLogFileWriterTest, ReceiveStartWhileStoppingLog) {
+  // Call StartNetLog() on |net_log_file_writer_| and wait for it to run its
+  // state callback.
+  StartThenVerifyState(base::FilePath(),
+                       net::NetLogCaptureMode::IncludeSocketBytes(),
+                       kCaptureModeIncludeSocketBytesString);
+
+  // Tell |net_log_file_writer_| to stop logging.
+  TestStateCallback stop_callback;
+  net_log_file_writer_.StopNetLog(nullptr, nullptr, stop_callback.callback());
+
+  // Before running the main message loop, tell |net_log_file_writer_| to start
+  // logging. Not running the main message loop prevents the stopping process
+  // from completing, so this ensures StartNetLog() is received before
+  // |net_log_file_writer_| finishes stopping, which means this should be a
+  // no-op.
+  TestStateCallback start_during_stop_callback;
+  net_log_file_writer_.StartNetLog(base::FilePath(),
+                                   net::NetLogCaptureMode::Default(),
+                                   start_during_stop_callback.callback());
+
+  // Now run the main message loop. Since StartNetLog() will be a no-op, it will
+  // simply run the state callback. There are no guarantees for when this state
+  // callback will execute if StartNetLog() is called during the stopping
+  // process, so the state it returns could either be stopping-log or
+  // not-logging. For simplicity, the returned state will not be verified.
+  start_during_stop_callback.WaitForResult();
+
+  // The state returned by the StopNetLog() call should be the state after
+  // stopping, which should indicate not-logging. Also, the capture mode should
+  // be the same as the one passed to the first StartNetLog() call, not the
+  // second (ignored) one.
+  std::unique_ptr<base::DictionaryValue> stop_callback_state =
+      stop_callback.WaitForResult();
+  VerifyState(*stop_callback_state, kStateNotLoggingString, true, true,
+              kCaptureModeIncludeSocketBytesString);
+
+  // Run an additional GetState() just to make sure |net_log_file_writer_| is
+  // not logging.
+  VerifyState(*FileWriterGetState(), kStateNotLoggingString, true, true,
+              kCaptureModeIncludeSocketBytesString);
+}
+
 }  // namespace net_log
diff --git a/components/net_log/resources/net_export.js b/components/net_log/resources/net_export.js
index 442379d..e1d4f4a9 100644
--- a/components/net_log/resources/net_export.js
+++ b/components/net_log/resources/net_export.js
@@ -106,19 +106,22 @@
         $('export-view-start-data').disabled = false;
 
         // If there's an existing log, allow sending it.
-        if (exportNetLogInfo.logType != 'NONE') {
+        if (!!exportNetLogInfo.logExists) {
           $('export-view-deletes-log-text').hidden = false;
           $('export-view-send-data').disabled = false;
-          if (exportNetLogInfo.logType == 'UNKNOWN') {
+          if (!exportNetLogInfo.logCaptureModeKnown) {
             $('export-view-send-old-log-text').hidden = false;
-          } else if (exportNetLogInfo.logType == 'NORMAL') {
+          } else if (exportNetLogInfo.captureMode == 'NORMAL') {
             $('export-view-private-data-text').hidden = false;
           }
         }
       } else if (exportNetLogInfo.state == 'LOGGING') {
         // Only possible to stop logging. Radio buttons reflects current state.
-        document.querySelector('input[name="log-mode"][value="' +
-                               exportNetLogInfo.logType + '"]').checked = true;
+        document
+            .querySelector(
+                'input[name="log-mode"][value="' +
+                exportNetLogInfo.captureMode + '"]')
+            .checked = true;
         $('export-view-stop-data').disabled = false;
       } else if (exportNetLogInfo.state == 'UNINITIALIZED') {
         $('export-view-file-path-text').textContent =
diff --git a/components/network_session_configurator/network_session_configurator.cc b/components/network_session_configurator/network_session_configurator.cc
index a92aebd..e1fdf9d9 100644
--- a/components/network_session_configurator/network_session_configurator.cc
+++ b/components/network_session_configurator/network_session_configurator.cc
@@ -92,6 +92,7 @@
 }
 
 bool ShouldEnableQuic(base::StringPiece quic_trial_group,
+                      const VariationParameters& quic_trial_params,
                       bool is_quic_force_disabled,
                       bool is_quic_force_enabled) {
   if (is_quic_force_disabled)
@@ -100,7 +101,10 @@
     return true;
 
   return quic_trial_group.starts_with(kQuicFieldTrialEnabledGroupName) ||
-         quic_trial_group.starts_with(kQuicFieldTrialHttpsEnabledGroupName);
+         quic_trial_group.starts_with(kQuicFieldTrialHttpsEnabledGroupName) ||
+         base::LowerCaseEqualsASCII(
+             GetVariationParam(quic_trial_params, "enable_quic"),
+             "true");
 }
 
 bool ShouldDisableQuicWhenConnectionTimesOutWithOpenStreams(
@@ -321,7 +325,8 @@
                          const std::string& quic_user_agent_id,
                          net::HttpNetworkSession::Params* params) {
   params->enable_quic = ShouldEnableQuic(
-      quic_trial_group, is_quic_force_disabled, is_quic_force_enabled);
+      quic_trial_group, quic_trial_params, is_quic_force_disabled,
+      is_quic_force_enabled);
   params->disable_quic_on_timeout_with_open_streams =
       ShouldDisableQuicWhenConnectionTimesOutWithOpenStreams(quic_trial_params);
 
diff --git a/components/network_session_configurator/network_session_configurator_unittest.cc b/components/network_session_configurator/network_session_configurator_unittest.cc
index 09fd360..38219c7c 100644
--- a/components/network_session_configurator/network_session_configurator_unittest.cc
+++ b/components/network_session_configurator/network_session_configurator_unittest.cc
@@ -99,6 +99,17 @@
             params_.quic_supported_versions);
 }
 
+TEST_F(NetworkSessionConfiguratorTest, EnableQuicFromParams) {
+  std::map<std::string, std::string> field_trial_params;
+  field_trial_params["enable_quic"] = "true";
+  variations::AssociateVariationParams("QUIC", "UseQuic", field_trial_params);
+  base::FieldTrialList::CreateFieldTrial("QUIC", "UseQuic");
+
+  ParseFieldTrials();
+
+  EXPECT_TRUE(params_.enable_quic);
+}
+
 TEST_F(NetworkSessionConfiguratorTest, EnableQuicForDataReductionProxy) {
   base::FieldTrialList::CreateFieldTrial("QUIC", "Enabled");
   base::FieldTrialList::CreateFieldTrial("DataReductionProxyUseQuic",
diff --git a/components/search_provider_logos/OWNERS b/components/search_provider_logos/OWNERS
index 7171a72..7bd15d5 100644
--- a/components/search_provider_logos/OWNERS
+++ b/components/search_provider_logos/OWNERS
@@ -1,2 +1 @@
-justincohen@chromium.org
-ianwen@chromium.org
+justincohen@chromium.org
\ No newline at end of file
diff --git a/content/browser/dom_storage/local_storage_context_mojo_unittest.cc b/content/browser/dom_storage/local_storage_context_mojo_unittest.cc
index afd7fac..4d5c201 100644
--- a/content/browser/dom_storage/local_storage_context_mojo_unittest.cc
+++ b/content/browser/dom_storage/local_storage_context_mojo_unittest.cc
@@ -727,14 +727,7 @@
   DISALLOW_COPY_AND_ASSIGN(LocalStorageContextMojoTestWithService);
 };
 
-// Enable when http://crbug.com/677194 is fixed and ServiceTest works
-// correctly on Android.
-#if defined(OS_ANDROID)
-#define MAYBE_InMemory DISABLED_InMemory
-#else
-#define MAYBE_InMemory InMemory
-#endif
-TEST_F(LocalStorageContextMojoTestWithService, MAYBE_InMemory) {
+TEST_F(LocalStorageContextMojoTestWithService, InMemory) {
   auto context = base::MakeUnique<LocalStorageContextMojo>(
       connector(), nullptr, base::FilePath(), base::FilePath());
   auto key = StdStringToUint8Vector("key");
@@ -761,14 +754,7 @@
   EXPECT_FALSE(DoTestGet(context.get(), key, &result));
 }
 
-// Enable when http://crbug.com/677194 is fixed and ServiceTest works
-// correctly on Android.
-#if defined(OS_ANDROID)
-#define MAYBE_InMemoryInvalidPath DISABLED_InMemoryInvalidPath
-#else
-#define MAYBE_InMemoryInvalidPath InMemoryInvalidPath
-#endif
-TEST_F(LocalStorageContextMojoTestWithService, MAYBE_InMemoryInvalidPath) {
+TEST_F(LocalStorageContextMojoTestWithService, InMemoryInvalidPath) {
   auto context = base::MakeUnique<LocalStorageContextMojo>(
       connector(), nullptr, base::FilePath(),
       base::FilePath(FILE_PATH_LITERAL("../../")));
@@ -791,14 +777,7 @@
   EXPECT_TRUE(FirstEntryInDir().empty());
 }
 
-// Enable when http://crbug.com/677194 is fixed and ServiceTest works
-// correctly on Android.
-#if defined(OS_ANDROID)
-#define MAYBE_OnDisk DISABLED_OnDisk
-#else
-#define MAYBE_OnDisk OnDisk
-#endif
-TEST_F(LocalStorageContextMojoTestWithService, MAYBE_OnDisk) {
+TEST_F(LocalStorageContextMojoTestWithService, OnDisk) {
   base::FilePath test_path(FILE_PATH_LITERAL("test_path"));
   auto context = base::MakeUnique<LocalStorageContextMojo>(
       connector(), nullptr, base::FilePath(), test_path);
@@ -823,14 +802,7 @@
   EXPECT_EQ(value, result);
 }
 
-// Enable when http://crbug.com/677194 is fixed and ServiceTest works
-// correctly on Android.
-#if defined(OS_ANDROID)
-#define MAYBE_InvalidVersionOnDisk DISABLED_InvalidVersionOnDisk
-#else
-#define MAYBE_InvalidVersionOnDisk InvalidVersionOnDisk
-#endif
-TEST_F(LocalStorageContextMojoTestWithService, MAYBE_InvalidVersionOnDisk) {
+TEST_F(LocalStorageContextMojoTestWithService, InvalidVersionOnDisk) {
   base::FilePath test_path(FILE_PATH_LITERAL("test_path"));
 
   // Create context and add some data to it.
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc
index 559d5956..c4a16f2f 100644
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc
@@ -30,7 +30,9 @@
                  base::Unretained(this)));
 }
 
-OffscreenCanvasCompositorFrameSink::~OffscreenCanvasCompositorFrameSink() {}
+OffscreenCanvasCompositorFrameSink::~OffscreenCanvasCompositorFrameSink() {
+  provider_->OnCompositorFrameSinkClientDestroyed(support_.frame_sink_id());
+}
 
 void OffscreenCanvasCompositorFrameSink::SetNeedsBeginFrame(
     bool needs_begin_frame) {
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc
index abd15c63..4bc06e0e 100644
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc
@@ -7,6 +7,7 @@
 #include "base/memory/ptr_util.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h"
+#include "content/browser/renderer_host/offscreen_canvas_surface_manager.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 
 namespace content {
@@ -29,6 +30,9 @@
   compositor_frame_sinks_[frame_sink_id] =
       base::MakeUnique<OffscreenCanvasCompositorFrameSink>(
           this, frame_sink_id, std::move(request), std::move(client));
+
+  OffscreenCanvasSurfaceManager::GetInstance()->RegisterFrameSinkToParent(
+      frame_sink_id);
 }
 
 cc::SurfaceManager*
@@ -39,7 +43,15 @@
 void OffscreenCanvasCompositorFrameSinkProviderImpl::
     OnCompositorFrameSinkClientConnectionLost(
         const cc::FrameSinkId& frame_sink_id) {
+  // TODO(fsamuel, xlai): Investigate why this function is not fired when user
+  // close down the window that has OffscreenCanvas commit().
   compositor_frame_sinks_.erase(frame_sink_id);
 }
 
+void OffscreenCanvasCompositorFrameSinkProviderImpl::
+    OnCompositorFrameSinkClientDestroyed(const cc::FrameSinkId& frame_sink_id) {
+  OffscreenCanvasSurfaceManager::GetInstance()->UnregisterFrameSinkFromParent(
+      frame_sink_id);
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h
index d36c1ae..3a5d630 100644
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h
@@ -33,6 +33,8 @@
 
   void OnCompositorFrameSinkClientConnectionLost(
       const cc::FrameSinkId& frame_sink_id);
+  void OnCompositorFrameSinkClientDestroyed(
+      const cc::FrameSinkId& frame_sink_id);
 
  private:
   std::unordered_map<cc::FrameSinkId,
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc b/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc
index 6393d813..357c249 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc
@@ -19,11 +19,12 @@
 }
 
 void OffscreenCanvasSurfaceFactoryImpl::CreateOffscreenCanvasSurface(
+    const cc::FrameSinkId& parent_frame_sink_id,
     const cc::FrameSinkId& frame_sink_id,
     blink::mojom::OffscreenCanvasSurfaceClientPtr client,
     blink::mojom::OffscreenCanvasSurfaceRequest request) {
-  OffscreenCanvasSurfaceImpl::Create(frame_sink_id, std::move(client),
-                                     std::move(request));
+  OffscreenCanvasSurfaceImpl::Create(parent_frame_sink_id, frame_sink_id,
+                                     std::move(client), std::move(request));
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h b/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h
index ccd6e6a..3370b52 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h
+++ b/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h
@@ -20,6 +20,7 @@
 
   // blink::mojom::OffscreenCanvasSurfaceFactory implementation.
   void CreateOffscreenCanvasSurface(
+      const cc::FrameSinkId& parent_frame_sink_id,
       const cc::FrameSinkId& frame_sink_id,
       blink::mojom::OffscreenCanvasSurfaceClientPtr client,
       blink::mojom::OffscreenCanvasSurfaceRequest request) override;
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
index bedfbdf9..27397ba 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
@@ -17,9 +17,12 @@
 namespace content {
 
 OffscreenCanvasSurfaceImpl::OffscreenCanvasSurfaceImpl(
+    const cc::FrameSinkId& parent_frame_sink_id,
     const cc::FrameSinkId& frame_sink_id,
     blink::mojom::OffscreenCanvasSurfaceClientPtr client)
-    : client_(std::move(client)), frame_sink_id_(frame_sink_id) {
+    : client_(std::move(client)),
+      frame_sink_id_(frame_sink_id),
+      parent_frame_sink_id_(parent_frame_sink_id) {
   OffscreenCanvasSurfaceManager::GetInstance()
       ->RegisterOffscreenCanvasSurfaceInstance(frame_sink_id_, this);
 }
@@ -33,12 +36,13 @@
 
 // static
 void OffscreenCanvasSurfaceImpl::Create(
+    const cc::FrameSinkId& parent_frame_sink_id,
     const cc::FrameSinkId& frame_sink_id,
     blink::mojom::OffscreenCanvasSurfaceClientPtr client,
     blink::mojom::OffscreenCanvasSurfaceRequest request) {
   std::unique_ptr<OffscreenCanvasSurfaceImpl> impl =
-      base::MakeUnique<OffscreenCanvasSurfaceImpl>(frame_sink_id,
-                                                   std::move(client));
+      base::MakeUnique<OffscreenCanvasSurfaceImpl>(
+          parent_frame_sink_id, frame_sink_id, std::move(client));
   OffscreenCanvasSurfaceImpl* surface_service = impl.get();
   surface_service->binding_ =
       mojo::MakeStrongBinding(std::move(impl), std::move(request));
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.h b/content/browser/renderer_host/offscreen_canvas_surface_impl.h
index 15c1aedb..50733d2 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.h
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.h
@@ -17,11 +17,13 @@
     : public blink::mojom::OffscreenCanvasSurface {
  public:
   OffscreenCanvasSurfaceImpl(
+      const cc::FrameSinkId& parent_frame_sink_id,
       const cc::FrameSinkId& frame_sink_id,
       blink::mojom::OffscreenCanvasSurfaceClientPtr client);
   ~OffscreenCanvasSurfaceImpl() override;
 
-  static void Create(const cc::FrameSinkId& frame_sink_id,
+  static void Create(const cc::FrameSinkId& parent_frame_sink_id,
+                     const cc::FrameSinkId& frame_sink_id,
                      blink::mojom::OffscreenCanvasSurfaceClientPtr client,
                      blink::mojom::OffscreenCanvasSurfaceRequest request);
 
@@ -33,6 +35,11 @@
   void Satisfy(const cc::SurfaceSequence& sequence) override;
 
   const cc::FrameSinkId& frame_sink_id() const { return frame_sink_id_; }
+
+  const cc::FrameSinkId& parent_frame_sink_id() const {
+    return parent_frame_sink_id_;
+  }
+
   const cc::LocalSurfaceId& current_local_surface_id() const {
     return current_local_surface_id_;
   }
@@ -42,8 +49,9 @@
   mojo::StrongBindingPtr<blink::mojom::OffscreenCanvasSurface> binding_;
 
   // Surface-related state
-  cc::FrameSinkId frame_sink_id_;
+  const cc::FrameSinkId frame_sink_id_;
   cc::LocalSurfaceId current_local_surface_id_;
+  const cc::FrameSinkId parent_frame_sink_id_;
 
   DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasSurfaceImpl);
 };
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_manager.cc b/content/browser/renderer_host/offscreen_canvas_surface_manager.cc
index 65ff06e..1eb9373f 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_manager.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_manager.cc
@@ -27,6 +27,30 @@
   return g_manager.Pointer();
 }
 
+void OffscreenCanvasSurfaceManager::RegisterFrameSinkToParent(
+    const cc::FrameSinkId& child_frame_sink_id) {
+  auto surface_iter = registered_surface_instances_.find(child_frame_sink_id);
+  if (surface_iter == registered_surface_instances_.end())
+    return;
+  OffscreenCanvasSurfaceImpl* surfaceImpl = surface_iter->second;
+  if (surfaceImpl->parent_frame_sink_id().is_valid()) {
+    GetSurfaceManager()->RegisterFrameSinkHierarchy(
+        surfaceImpl->parent_frame_sink_id(), child_frame_sink_id);
+  }
+}
+
+void OffscreenCanvasSurfaceManager::UnregisterFrameSinkFromParent(
+    const cc::FrameSinkId& child_frame_sink_id) {
+  auto surface_iter = registered_surface_instances_.find(child_frame_sink_id);
+  if (surface_iter == registered_surface_instances_.end())
+    return;
+  OffscreenCanvasSurfaceImpl* surfaceImpl = surface_iter->second;
+  if (surfaceImpl->parent_frame_sink_id().is_valid()) {
+    GetSurfaceManager()->UnregisterFrameSinkHierarchy(
+        surfaceImpl->parent_frame_sink_id(), child_frame_sink_id);
+  }
+}
+
 void OffscreenCanvasSurfaceManager::OnSurfaceCreated(
     const cc::SurfaceInfo& surface_info) {
   auto surface_iter =
@@ -38,7 +62,7 @@
 }
 
 void OffscreenCanvasSurfaceManager::RegisterOffscreenCanvasSurfaceInstance(
-    cc::FrameSinkId frame_sink_id,
+    const cc::FrameSinkId& frame_sink_id,
     OffscreenCanvasSurfaceImpl* surface_instance) {
   DCHECK(surface_instance);
   DCHECK_EQ(registered_surface_instances_.count(frame_sink_id), 0u);
@@ -46,13 +70,13 @@
 }
 
 void OffscreenCanvasSurfaceManager::UnregisterOffscreenCanvasSurfaceInstance(
-    cc::FrameSinkId frame_sink_id) {
+    const cc::FrameSinkId& frame_sink_id) {
   DCHECK_EQ(registered_surface_instances_.count(frame_sink_id), 1u);
   registered_surface_instances_.erase(frame_sink_id);
 }
 
 OffscreenCanvasSurfaceImpl* OffscreenCanvasSurfaceManager::GetSurfaceInstance(
-    cc::FrameSinkId frame_sink_id) {
+    const cc::FrameSinkId& frame_sink_id) {
   auto search = registered_surface_instances_.find(frame_sink_id);
   if (search != registered_surface_instances_.end()) {
     return search->second;
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_manager.h b/content/browser/renderer_host/offscreen_canvas_surface_manager.h
index e53cea70..651dc8cb 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_manager.h
+++ b/content/browser/renderer_host/offscreen_canvas_surface_manager.h
@@ -20,10 +20,19 @@
 
   static OffscreenCanvasSurfaceManager* GetInstance();
 
-  void RegisterOffscreenCanvasSurfaceInstance(cc::FrameSinkId,
-                                              OffscreenCanvasSurfaceImpl*);
-  void UnregisterOffscreenCanvasSurfaceInstance(cc::FrameSinkId);
-  OffscreenCanvasSurfaceImpl* GetSurfaceInstance(cc::FrameSinkId);
+  // Registration of the frame sink with the given frame sink id to its parent
+  // frame sink (if it has one), so that parent frame is able to send signals
+  // to it on begin frame.
+  void RegisterFrameSinkToParent(const cc::FrameSinkId& frame_sink_id);
+  void UnregisterFrameSinkFromParent(const cc::FrameSinkId& frame_sink_id);
+
+  void RegisterOffscreenCanvasSurfaceInstance(
+      const cc::FrameSinkId& frame_sink_id,
+      OffscreenCanvasSurfaceImpl* offscreen_canvas_surface);
+  void UnregisterOffscreenCanvasSurfaceInstance(
+      const cc::FrameSinkId& frame_sink_id);
+  OffscreenCanvasSurfaceImpl* GetSurfaceInstance(
+      const cc::FrameSinkId& frame_sink_id);
 
  private:
   friend class OffscreenCanvasSurfaceManagerTest;
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc b/content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc
index 4825388..6208564 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc
@@ -75,8 +75,8 @@
   cc::LocalSurfaceId current_local_surface_id(
       surface_id_allocator.GenerateId());
 
-  auto surface_impl = base::WrapUnique(
-      new OffscreenCanvasSurfaceImpl(frame_sink_id, std::move(client)));
+  auto surface_impl = base::WrapUnique(new OffscreenCanvasSurfaceImpl(
+      cc::FrameSinkId(), frame_sink_id, std::move(client)));
   EXPECT_EQ(1, this->getNumSurfaceImplInstances());
   EXPECT_EQ(surface_impl.get(),
             OffscreenCanvasSurfaceManager::GetInstance()->GetSurfaceInstance(
@@ -93,16 +93,17 @@
 TEST_F(OffscreenCanvasSurfaceManagerTest,
        MultiHTMLCanvasElementTransferToOffscreen) {
   blink::mojom::OffscreenCanvasSurfaceClientPtr client_a;
+  cc::FrameSinkId dummy_parent_frame_sink_id(0, 0);
   cc::FrameSinkId frame_sink_id_a(3, 3);
   cc::SurfaceIdAllocator surface_id_allocator;
-  auto surface_impl_a = base::WrapUnique(
-      new OffscreenCanvasSurfaceImpl(frame_sink_id_a, std::move(client_a)));
+  auto surface_impl_a = base::WrapUnique(new OffscreenCanvasSurfaceImpl(
+      dummy_parent_frame_sink_id, frame_sink_id_a, std::move(client_a)));
 
   blink::mojom::OffscreenCanvasSurfaceClientPtr client_b;
   cc::FrameSinkId frame_sink_id_b(4, 4);
 
-  auto surface_impl_b = base::WrapUnique(
-      new OffscreenCanvasSurfaceImpl(frame_sink_id_b, std::move(client_b)));
+  auto surface_impl_b = base::WrapUnique(new OffscreenCanvasSurfaceImpl(
+      dummy_parent_frame_sink_id, frame_sink_id_b, std::move(client_b)));
 
   EXPECT_EQ(2, this->getNumSurfaceImplInstances());
   EXPECT_EQ(surface_impl_a.get(),
diff --git a/content/renderer/gpu/render_widget_compositor.cc b/content/renderer/gpu/render_widget_compositor.cc
index fef0b8f..df35d60 100644
--- a/content/renderer/gpu/render_widget_compositor.cc
+++ b/content/renderer/gpu/render_widget_compositor.cc
@@ -651,6 +651,10 @@
   return layer_tree_host_->SendMessageToMicroBenchmark(id, std::move(value));
 }
 
+cc::FrameSinkId RenderWidgetCompositor::getFrameSinkId() {
+  return frame_sink_id_;
+}
+
 void RenderWidgetCompositor::setRootLayer(const blink::WebLayer& layer) {
   layer_tree_host_->SetRootLayer(
       static_cast<const cc_blink::WebLayerImpl*>(&layer)->layer());
diff --git a/content/renderer/gpu/render_widget_compositor.h b/content/renderer/gpu/render_widget_compositor.h
index d64bb743b..99cfc21 100644
--- a/content/renderer/gpu/render_widget_compositor.h
+++ b/content/renderer/gpu/render_widget_compositor.h
@@ -115,6 +115,7 @@
   void SetIsForOopif(bool is_for_oopif);
 
   // WebLayerTreeView implementation.
+  cc::FrameSinkId getFrameSinkId() override;
   void setRootLayer(const blink::WebLayer& layer) override;
   void clearRootLayer() override;
   cc::AnimationHost* compositorAnimationHost() override;
diff --git a/content/renderer/media/render_media_log.cc b/content/renderer/media/render_media_log.cc
index 1b3eeb5..1169f685 100644
--- a/content/renderer/media/render_media_log.cc
+++ b/content/renderer/media/render_media_log.cc
@@ -72,6 +72,12 @@
         // kind, if any, prior to sending the event batch.
         break;
 
+      case media::MediaLogEvent::DURATION_SET:
+        // Similar to the extents changed message, this may fire many times for
+        // badly muxed media. Suppress within our rate limits here.
+        last_duration_changed_event_.swap(event);
+        break;
+
       // Hold onto the most recent PIPELINE_ERROR and MEDIA_LOG_ERROR_ENTRY for
       // use in GetLastErrorMessage().
       case media::MediaLogEvent::PIPELINE_ERROR:
@@ -153,6 +159,11 @@
       last_buffered_extents_changed_event_.reset();
     }
 
+    if (last_duration_changed_event_) {
+      queued_media_events_.push_back(*last_duration_changed_event_);
+      last_duration_changed_event_.reset();
+    }
+
     queued_media_events_.swap(events_to_send);
     last_ipc_send_time_ = tick_clock_->NowTicks();
   }
diff --git a/content/renderer/media/render_media_log.h b/content/renderer/media/render_media_log.h
index e9b655ae..ed9ce62 100644
--- a/content/renderer/media/render_media_log.h
+++ b/content/renderer/media/render_media_log.h
@@ -69,8 +69,9 @@
   // For enforcing max 1 pending send.
   bool ipc_send_pending_;
 
-  // Limits the number buffered extents changed events we send over IPC to one.
+  // Limits the number of events we send over IPC to one.
   std::unique_ptr<media::MediaLogEvent> last_buffered_extents_changed_event_;
+  std::unique_ptr<media::MediaLogEvent> last_duration_changed_event_;
 
   // Holds a copy of the most recent MEDIA_ERROR_LOG_ENTRY, if any.
   std::unique_ptr<media::MediaLogEvent> last_media_error_log_entry_;
diff --git a/content/renderer/media/render_media_log_unittest.cc b/content/renderer/media/render_media_log_unittest.cc
index c383dcd8..60d3139 100644
--- a/content/renderer/media/render_media_log_unittest.cc
+++ b/content/renderer/media/render_media_log_unittest.cc
@@ -123,4 +123,27 @@
   EXPECT_EQ(media::MediaLogEvent::BUFFERED_EXTENTS_CHANGED, events[2].type);
 }
 
+TEST_F(RenderMediaLogTest, DurationChanged) {
+  AddEvent(media::MediaLogEvent::LOAD);
+  AddEvent(media::MediaLogEvent::SEEK);
+
+  // This event is handled separately and should always appear last regardless
+  // of how many times we see it.
+  AddEvent(media::MediaLogEvent::DURATION_SET);
+  AddEvent(media::MediaLogEvent::DURATION_SET);
+  AddEvent(media::MediaLogEvent::DURATION_SET);
+
+  EXPECT_EQ(0, message_count());
+  Advance(base::TimeDelta::FromMilliseconds(1000));
+  EXPECT_EQ(1, message_count());
+
+  // Verify contents. There should only be a single buffered extents changed
+  // event.
+  std::vector<media::MediaLogEvent> events = GetMediaLogEvents();
+  ASSERT_EQ(3u, events.size());
+  EXPECT_EQ(media::MediaLogEvent::LOAD, events[0].type);
+  EXPECT_EQ(media::MediaLogEvent::SEEK, events[1].type);
+  EXPECT_EQ(media::MediaLogEvent::DURATION_SET, events[2].type);
+}
+
 }  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d34f3fc..7b1389e 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1414,6 +1414,7 @@
 
   deps = [
     ":content_test_mojo_bindings",
+    ":content_unittests_catalog_source",
     ":test_support",
     "//base/test:test_support",
     "//base/third_party/dynamic_annotations",
@@ -1505,7 +1506,6 @@
   ]
 
   data_deps = [
-    ":content_unittests_catalog_copy",
     "//components/filesystem:filesystem",
     "//third_party/mesa:osmesa",
   ]
@@ -1760,14 +1760,9 @@
   embedded_services = [ ":content_unittests_manifest" ]
 }
 
-copy("content_unittests_catalog_copy") {
-  sources = get_target_outputs(":content_unittests_catalog")
-  outputs = [
-    "${root_out_dir}/content_unittests_catalog.json",
-  ]
-  deps = [
-    ":content_unittests_catalog",
-  ]
+catalog_cpp_source("content_unittests_catalog_source") {
+  catalog = ":content_unittests_catalog"
+  generated_function_name = "content::CreateContentUnittestsCatalog"
 }
 
 test("content_perftests") {
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_main.html b/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_main.html
index ecdc4ff..d2fe212 100644
--- a/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_main.html
+++ b/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_main.html
@@ -33,14 +33,26 @@
 var canvas = document.getElementById("c");
 var offscreenCanvas = canvas.transferControlToOffscreen();
 var offscreen2d = offscreenCanvas.getContext("2d");
+offscreen2d.fillStyle = "red";
+offscreen2d.fillRect(0, 0, 200, 200);
 
 function drawLoop()
 {
-  if (g_animationFrameNumber < 3) {
-    offscreen2d.fillStyle = "red";
-    offscreen2d.fillRect(0, 0, 200, 200);
+  if (g_animationFrameNumber < 10) {
     g_animationFrameNumber++;
-    offscreen2d.commit().then(drawLoop);
+    // Purposely intersperse overdraw and non-overdraw commit cases to see
+    // if OffscreenCanvas commit() handles both cases well.
+    if (0 == g_animationFrameNumber % 2) {
+      // When promise is used, the next drawLoop() is called after the first
+      // frame is resolved; therefore there is no overdraw in this case.
+      offscreen2d.commit().then(drawLoop);
+    } else {
+      // When the next drawLoop() is invoked regardless the promise resolve
+      // status of the previous commit(), the frame committed in the next
+      // drawLoop() is very likely to be overdrawn.
+      offscreen2d.commit();
+      drawLoop();
+    }
   } else {
     offscreen2d.fillStyle = "red";
     offscreen2d.fillRect(0, 0, 100, 100);
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_worker.html b/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_worker.html
index 0668fff..02f3a12 100644
--- a/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_worker.html
+++ b/content/test/data/gpu/pixel_offscreenCanvas_2d_commit_worker.html
@@ -22,16 +22,28 @@
 self.onmessage = function(e) {
   var transferredCanvas = e.data;
   g_offscreen2d = transferredCanvas.getContext("2d");
+  g_offscreen2d.fillStyle = "red";
+  g_offscreen2d.fillRect(0, 0, 200, 200);
   drawLoop();
 }  
 
 function drawLoop()
 {
-  if (g_animationFrameNumber < 3) {
-    g_offscreen2d.fillStyle = "red";
-    g_offscreen2d.fillRect(0, 0, 200, 200);
+  if (g_animationFrameNumber < 10) {
     g_animationFrameNumber++;
-    g_offscreen2d.commit().then(drawLoop);
+    // Purposely intersperse overdraw and non-overdraw commit cases to see
+    // if OffscreenCanvas commit() handles both cases well.
+    if (0 == g_animationFrameNumber % 2) {
+      // When promise is used, the next drawLoop() is called after the first
+      // frame is resolved; therefore there is no overdraw in this case.
+      g_offscreen2d.commit().then(drawLoop);
+    } else {
+      // When the next drawLoop() is invoked regardless the promise resolve
+      // status of the previous commit(), the frame committed in the next
+      // drawLoop() is very likely to be overdrawn.
+      g_offscreen2d.commit();
+      drawLoop();
+    }
   } else {
     g_offscreen2d.fillStyle = "red";
     g_offscreen2d.fillRect(0, 0, 100, 100);
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_main.html b/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_main.html
index 95bf093e..a00828de 100644
--- a/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_main.html
+++ b/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_main.html
@@ -23,7 +23,9 @@
 {
   var canvas = document.getElementById("c");
   var offscreenCanvas = canvas.transferControlToOffscreen();
-  gl = offscreenCanvas.getContext("webgl");
+  gl = offscreenCanvas.getContext("webgl", {preserveDrawingBuffer: true});
+  gl.clearColor(1, 0, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
   drawLoop();
 }
 
@@ -71,11 +73,21 @@
 
 function drawLoop()
 {
-  if (g_frameNumber < 3) {
-    gl.clearColor(1, 0, 0, 1);
-    gl.clear(gl.COLOR_BUFFER_BIT);
+  if (g_frameNumber < 10) {
     g_frameNumber++;
-    gl.commit().then(drawLoop);
+    // Purposely intersperse overdraw and non-overdraw commit cases to see
+    // if OffscreenCanvas commit() handles both cases well.
+    if (0 == g_frameNumber % 2) {
+      // When promise is used, the next drawLoop() is called after the first
+      // frame is resolved; therefore there is no overdraw in this case.
+      gl.commit().then(drawLoop);
+    } else {
+      // When the next drawLoop() is invoked regardless the promise resolve
+      // status of the previous commit(), the frame committed in the next
+      // drawLoop() is very likely to be overdrawn.
+      gl.commit();
+      drawLoop();
+    }
   } else {
     drawTriangle();
     gl.commit();
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_worker.html b/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_worker.html
index fb19d6c..9c55106 100644
--- a/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_worker.html
+++ b/content/test/data/gpu/pixel_offscreenCanvas_webgl_commit_worker.html
@@ -63,11 +63,21 @@
 
 function drawLoop()
 {
-  if (g_frameNumber < 3) {
-    gl.clearColor(1, 0, 0, 1);
-    gl.clear(gl.COLOR_BUFFER_BIT);
+  if (g_frameNumber < 10) {
     g_frameNumber++;
-    gl.commit().then(drawLoop);
+    // Purposely intersperse overdraw and non-overdraw commit cases to see
+    // if OffscreenCanvas commit() handles both cases well.
+    if (0 == g_frameNumber % 2) {
+      // When promise is used, the next drawLoop() is called after the first
+      // frame is resolved; therefore there is no overdraw in this case.
+      gl.commit().then(drawLoop);
+    } else {
+      // When the next drawLoop() is invoked regardless the promise resolve
+      // status of the previous commit(), the frame committed in the next
+      // drawLoop() is very likely to be overdrawn.
+      gl.commit();
+      drawLoop();
+    }
   } else {
     drawTriangle();
     gl.commit();
@@ -82,7 +92,9 @@
 
 self.onmessage = function(e) {
   var transferredOffscreenCanvas = e.data;
-  gl = transferredOffscreenCanvas.getContext("webgl");
+  gl = transferredOffscreenCanvas.getContext("webgl", {preserveDrawingBuffer: true});
+  gl.clearColor(1, 0, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
   drawLoop();
 };
 </script>
diff --git a/content/test/run_all_unittests.cc b/content/test/run_all_unittests.cc
index 6776ced..ad71509 100644
--- a/content/test/run_all_unittests.cc
+++ b/content/test/run_all_unittests.cc
@@ -3,40 +3,19 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
-#include "base/command_line.h"
-#include "base/files/file_path.h"
 #include "base/test/launcher/unit_test_launcher.h"
-#include "base/test/test_io_thread.h"
-#include "build/build_config.h"
 #include "content/public/test/unittest_test_suite.h"
 #include "content/test/content_test_suite.h"
-#include "mojo/edk/embedder/scoped_ipc_support.h"
+#include "content/test/content_unittests_catalog_source.h"
 #include "services/catalog/catalog.h"
 
-#if !defined(OS_ANDROID)
-
-namespace {
-
-const base::FilePath::CharType kCatalogFilename[] =
-    FILE_PATH_LITERAL("content_unittests_catalog.json");
-
-}  // namespace
-
-#endif
-
 int main(int argc, char** argv) {
   content::UnitTestTestSuite test_suite(
       new content::ContentTestSuite(argc, argv));
 
-#if !defined(OS_ANDROID)
-  catalog::Catalog::LoadDefaultCatalogManifest(
-      base::FilePath(kCatalogFilename));
-#endif
+  catalog::Catalog::SetDefaultCatalogManifest(
+      content::CreateContentUnittestsCatalog());
 
-  base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
-  mojo::edk::ScopedIPCSupport ipc_support(
-      test_io_thread.task_runner(),
-      mojo::edk::ScopedIPCSupport::ShutdownPolicy::FAST);
   return base::LaunchUnitTests(
       argc, argv, base::Bind(&content::UnitTestTestSuite::Run,
                              base::Unretained(&test_suite)));
diff --git a/extensions/browser/extension_function.cc b/extensions/browser/extension_function.cc
index 5481537..f36a072f 100644
--- a/extensions/browser/extension_function.cc
+++ b/extensions/browser/extension_function.cc
@@ -16,11 +16,14 @@
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/user_metrics.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/bad_message.h"
 #include "extensions/browser/extension_function_dispatcher.h"
 #include "extensions/browser/extension_message_filter.h"
 #include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/io_thread_extension_message_filter.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/extension_api.h"
 #include "extensions/common/extension_messages.h"
@@ -76,6 +79,27 @@
   }
 }
 
+void LogBadMessage(extensions::functions::HistogramValue histogram_value) {
+  content::RecordAction(base::UserMetricsAction("BadMessageTerminate_EFD"));
+  // Track the specific function's |histogram_value|, as this may indicate a
+  // bug in that API's implementation.
+  UMA_HISTOGRAM_ENUMERATION("Extensions.BadMessageFunctionName",
+                            histogram_value,
+                            extensions::functions::ENUM_BOUNDARY);
+}
+
+template <class T>
+void ReceivedBadMessage(T* bad_message_sender,
+                        extensions::bad_message::BadMessageReason reason,
+                        extensions::functions::HistogramValue histogram_value) {
+  LogBadMessage(histogram_value);
+  // The renderer has done validation before sending extension api requests.
+  // Therefore, we should never receive a request that is invalid in a way
+  // that JSON validation in the renderer should have caught. It could be an
+  // attacker trying to exploit the browser, so we crash the renderer instead.
+  extensions::bad_message::ReceivedBadMessage(bad_message_sender, reason);
+}
+
 class ArgumentListResponseValue
     : public ExtensionFunction::ResponseValueObject {
  public:
@@ -122,7 +146,7 @@
 class BadMessageResponseValue : public ExtensionFunction::ResponseValueObject {
  public:
   explicit BadMessageResponseValue(ExtensionFunction* function) {
-    function->set_bad_message(true);
+    function->SetBadMessage();
     NOTREACHED() << function->name() << ": bad message";
   }
 
@@ -319,6 +343,10 @@
   return error_;
 }
 
+void ExtensionFunction::SetBadMessage() {
+  bad_message_ = true;
+}
+
 bool ExtensionFunction::user_gesture() const {
   return user_gesture_ || UserGestureForTests::GetInstance()->HaveGesture();
 }
@@ -508,6 +536,18 @@
   return true;
 }
 
+void UIThreadExtensionFunction::SetBadMessage() {
+  ExtensionFunction::SetBadMessage();
+
+  if (render_frame_host()) {
+    ReceivedBadMessage(render_frame_host()->GetProcess(),
+                       is_from_service_worker()
+                           ? extensions::bad_message::EFD_BAD_MESSAGE_WORKER
+                           : extensions::bad_message::EFD_BAD_MESSAGE,
+                       histogram_value());
+  }
+}
+
 bool UIThreadExtensionFunction::OnMessageReceived(const IPC::Message& message) {
   return false;
 }
@@ -577,6 +617,15 @@
   return this;
 }
 
+void IOThreadExtensionFunction::SetBadMessage() {
+  ExtensionFunction::SetBadMessage();
+  if (ipc_sender_) {
+    ReceivedBadMessage(
+        static_cast<content::BrowserMessageFilter*>(ipc_sender_.get()),
+        extensions::bad_message::EFD_BAD_MESSAGE, histogram_value());
+  }
+}
+
 void IOThreadExtensionFunction::Destruct() const {
   BrowserThread::DeleteOnIOThread::Destruct(this);
 }
diff --git a/extensions/browser/extension_function.h b/extensions/browser/extension_function.h
index 048a0fe..82503a7 100644
--- a/extensions/browser/extension_function.h
+++ b/extensions/browser/extension_function.h
@@ -53,7 +53,7 @@
 #define EXTENSION_FUNCTION_VALIDATE(test) \
   do {                                    \
     if (!(test)) {                        \
-      this->set_bad_message(true);        \
+      this->SetBadMessage();              \
       return ValidationFailure(this);     \
     }                                     \
   } while (0)
@@ -65,7 +65,7 @@
 #define EXTENSION_FUNCTION_PRERUN_VALIDATE(test) \
   do {                                           \
     if (!(test)) {                               \
-      this->set_bad_message(true);               \
+      this->SetBadMessage();                     \
       return false;                              \
     }                                            \
   } while (0)
@@ -76,7 +76,7 @@
 #define EXTENSION_FUNCTION_ERROR(error) \
   do {                                  \
     error_ = error;                     \
-    this->set_bad_message(true);        \
+    this->SetBadMessage();              \
     return ValidationFailure(this);     \
   } while (0)
 
@@ -241,7 +241,7 @@
   // Retrieves any error string from the function.
   virtual const std::string& GetError() const;
 
-  void set_bad_message(bool bad_message) { bad_message_ = bad_message; }
+  virtual void SetBadMessage();
 
   // Specifies the name of the function. A long-lived string (such as a string
   // literal) must be provided.
@@ -506,6 +506,7 @@
   UIThreadExtensionFunction* AsUIThreadExtensionFunction() override;
 
   bool PreRunValidation(std::string* error) override;
+  void SetBadMessage() final;
 
   // Called when a message was received.
   // Should return true if it processed the message.
@@ -604,6 +605,7 @@
   IOThreadExtensionFunction();
 
   IOThreadExtensionFunction* AsIOThreadExtensionFunction() override;
+  void SetBadMessage() final;
 
   void set_ipc_sender(
       base::WeakPtr<extensions::IOThreadExtensionMessageFilter> ipc_sender,
diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc
index f63456c7f..3d7dff54 100644
--- a/extensions/browser/extension_function_dispatcher.cc
+++ b/extensions/browser/extension_function_dispatcher.cc
@@ -26,12 +26,10 @@
 #include "content/public/browser/render_process_host_observer.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/service_worker_context.h"
-#include "content/public/browser/user_metrics.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/result_codes.h"
 #include "extensions/browser/api_activity_monitor.h"
-#include "extensions/browser/bad_message.h"
 #include "extensions/browser/extension_function_registry.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
@@ -80,40 +78,16 @@
 };
 base::LazyInstance<Static> g_global_io_data = LAZY_INSTANCE_INITIALIZER;
 
-void LogBadMessage(functions::HistogramValue histogram_value) {
-  content::RecordAction(base::UserMetricsAction("BadMessageTerminate_EFD"));
-  // Track the specific function's |histogram_value|, as this may indicate a
-  // bug in that API's implementation.
-  UMA_HISTOGRAM_ENUMERATION("Extensions.BadMessageFunctionName",
-                            histogram_value, functions::ENUM_BOUNDARY);
-}
-
-template <class T>
-void ReceivedBadMessage(T* bad_message_sender,
-                        bad_message::BadMessageReason reason,
-                        functions::HistogramValue histogram_value) {
-  LogBadMessage(histogram_value);
-  // The renderer has done validation before sending extension api requests.
-  // Therefore, we should never receive a request that is invalid in a way
-  // that JSON validation in the renderer should have caught. It could be an
-  // attacker trying to exploit the browser, so we crash the renderer instead.
-  bad_message::ReceivedBadMessage(bad_message_sender, reason);
-}
-
-template <class T>
 void CommonResponseCallback(IPC::Sender* ipc_sender,
                             int routing_id,
-                            T* bad_message_sender,
                             int request_id,
                             ExtensionFunction::ResponseType type,
                             const base::ListValue& results,
-                            const std::string& error,
-                            functions::HistogramValue histogram_value) {
+                            const std::string& error) {
   DCHECK(ipc_sender);
 
   if (type == ExtensionFunction::BAD_MESSAGE) {
-    ReceivedBadMessage(bad_message_sender, bad_message::EFD_BAD_MESSAGE,
-                       histogram_value);
+    // The renderer will be shut down from ExtensionFunction::SetBadMessage().
     return;
   }
 
@@ -133,8 +107,8 @@
   if (!ipc_sender.get())
     return;
 
-  CommonResponseCallback(ipc_sender.get(), routing_id, ipc_sender.get(),
-                         request_id, type, results, error, histogram_value);
+  CommonResponseCallback(ipc_sender.get(), routing_id, request_id, type,
+                         results, error);
 }
 
 }  // namespace
@@ -183,9 +157,8 @@
                                     const std::string& error,
                                     functions::HistogramValue histogram_value) {
     CommonResponseCallback(render_frame_host_,
-                           render_frame_host_->GetRoutingID(),
-                           render_frame_host_->GetProcess(), request_id, type,
-                           results, error, histogram_value);
+                           render_frame_host_->GetRoutingID(), request_id, type,
+                           results, error);
   }
 
   base::WeakPtr<ExtensionFunctionDispatcher> dispatcher_;
@@ -248,8 +221,7 @@
     content::RenderProcessHost* sender =
         content::RenderProcessHost::FromID(render_process_id_);
     if (type == ExtensionFunction::BAD_MESSAGE) {
-      ReceivedBadMessage(sender, bad_message::EFD_BAD_MESSAGE_WORKER,
-                         histogram_value);
+      // The renderer will be shut down from ExtensionFunction::SetBadMessage().
       return;
     }
     DCHECK(sender);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 6c2986a..09ac49c 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -839,7 +839,7 @@
 
   // Get the service side ID for the bound draw framebuffer.
   // If it's back buffer, 0 is returned.
-  GLuint GetBoundDrawFramebufferServiceId();
+  GLuint GetBoundDrawFramebufferServiceId() const;
 
   // Get the format/type of the currently bound frame buffer (either FBO or
   // regular back buffer).
@@ -4457,7 +4457,7 @@
   return 0;
 }
 
-GLuint GLES2DecoderImpl::GetBoundDrawFramebufferServiceId() {
+GLuint GLES2DecoderImpl::GetBoundDrawFramebufferServiceId() const {
   Framebuffer* framebuffer = GetBoundDrawFramebuffer();
   if (framebuffer) {
     return framebuffer->service_id();
@@ -5613,14 +5613,16 @@
 }
 
 void GLES2DecoderImpl::OnFboChanged() const {
-  if (workarounds().restore_scissor_on_fbo_change)
-    state_.fbo_binding_for_scissor_workaround_dirty = true;
+  state_.fbo_binding_for_scissor_workaround_dirty = true;
 }
 
 // Called after the FBO is checked for completeness.
 void GLES2DecoderImpl::OnUseFramebuffer() const {
-  if (state_.fbo_binding_for_scissor_workaround_dirty) {
-    state_.fbo_binding_for_scissor_workaround_dirty = false;
+  if (!state_.fbo_binding_for_scissor_workaround_dirty)
+    return;
+  state_.fbo_binding_for_scissor_workaround_dirty = false;
+
+  if (workarounds().restore_scissor_on_fbo_change) {
     // The driver forgets the correct scissor when modifying the FBO binding.
     glScissor(state_.scissor_x,
               state_.scissor_y,
@@ -5631,6 +5633,26 @@
     // it's unclear how this bug works.
     glFlush();
   }
+
+  if (workarounds().force_update_scissor_state_when_binding_fbo0 &&
+      GetBoundDrawFramebufferServiceId() == 0) {
+    // The theory is that FBO0 keeps some internal (in HW regs maybe?) scissor
+    // test state, but the driver forgets to update it with GL_SCISSOR_TEST
+    // when FBO0 gets bound. (So it stuck with whatever state we last switched
+    // from it.)
+    // If the internal scissor test state was enabled, it does update its
+    // internal scissor rect with GL_SCISSOR_BOX though.
+    if (state_.enable_flags.cached_scissor_test) {
+      // The driver early outs if the new state matches previous state so some
+      // shake up is needed.
+      glDisable(GL_SCISSOR_TEST);
+      glEnable(GL_SCISSOR_TEST);
+    } else {
+      // Ditto.
+      glEnable(GL_SCISSOR_TEST);
+      glDisable(GL_SCISSOR_TEST);
+    }
+  }
 }
 
 void GLES2DecoderImpl::DoBindFramebuffer(GLenum target, GLuint client_id) {
diff --git a/gpu/config/gpu_driver_bug_list_json.cc b/gpu/config/gpu_driver_bug_list_json.cc
index 399beb8..dc2c7a8 100644
--- a/gpu/config/gpu_driver_bug_list_json.cc
+++ b/gpu/config/gpu_driver_bug_list_json.cc
@@ -19,7 +19,7 @@
 {
   "name": "gpu driver bug list",
   // Please update the version number whenever you change this file.
-  "version": "9.28",
+  "version": "9.29",
   "entries": [
     {
       "id": 1,
@@ -2323,6 +2323,16 @@
       "features": [
         "use_virtualized_gl_contexts"
       ]
+    },
+    {
+      "id": 214,
+      "description": "Certain versions of Qualcomm driver don't setup scissor state correctly when FBO0 is bound.",
+      "cr_bugs": [670607],
+      "gl_vendor": "Qualcomm.*",
+      "machine_model_name": ["Nexus 7"],
+      "features": [
+        "force_update_scissor_state_when_binding_fbo0"
+      ]
     }
   ]
   // Please update the version number at beginning of this file whenever you
diff --git a/gpu/config/gpu_driver_bug_workaround_type.h b/gpu/config/gpu_driver_bug_workaround_type.h
index 8836483..fecb82c 100644
--- a/gpu/config/gpu_driver_bug_workaround_type.h
+++ b/gpu/config/gpu_driver_bug_workaround_type.h
@@ -105,6 +105,8 @@
          force_discrete_gpu)                                 \
   GPU_OP(FORCE_INTEGRATED_GPU,                               \
          force_integrated_gpu)                               \
+  GPU_OP(FORCE_UPDATE_SCISSOR_STATE_WHEN_BINDING_FBO0,       \
+         force_update_scissor_state_when_binding_fbo0)       \
   GPU_OP(GET_FRAG_DATA_INFO_BUG,                             \
          get_frag_data_info_bug)                             \
   GPU_OP(GL_CLEAR_BROKEN,                                    \
diff --git a/ios/chrome/browser/ui/webui/net_export/BUILD.gn b/ios/chrome/browser/ui/webui/net_export/BUILD.gn
index 8ef0e09..ab4037a4 100644
--- a/ios/chrome/browser/ui/webui/net_export/BUILD.gn
+++ b/ios/chrome/browser/ui/webui/net_export/BUILD.gn
@@ -16,5 +16,6 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/ui",
     "//ios/web",
+    "//net:net",
   ]
 }
diff --git a/ios/chrome/browser/ui/webui/net_export/net_export_ui.cc b/ios/chrome/browser/ui/webui/net_export/net_export_ui.cc
index 7e2d465..087b0fa 100644
--- a/ios/chrome/browser/ui/webui/net_export/net_export_ui.cc
+++ b/ios/chrome/browser/ui/webui/net_export/net_export_ui.cc
@@ -27,6 +27,8 @@
 #include "ios/web/public/web_ui_ios_data_source.h"
 #include "ios/web/public/webui/web_ui_ios.h"
 #include "ios/web/public/webui/web_ui_ios_message_handler.h"
+#include "net/log/net_log_capture_mode.h"
+#include "net/url_request/url_request_context_getter.h"
 
 namespace {
 
@@ -61,38 +63,21 @@
   void OnSendNetLog(const base::ListValue* list);
 
  private:
-  // Calls NetLogFileWriter's ProcessCommand with DO_START and DO_STOP commands.
-  static void ProcessNetLogCommand(
-      base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-      net_log::NetLogFileWriter* net_log_file_writer,
-      net_log::NetLogFileWriter::Command command);
+  // If |log_path| is empty, then the NetLogFileWriter will use its default
+  // log path.
+  void StartNetLogThenNotifyUI(const base::FilePath& log_path,
+                               net::NetLogCaptureMode capture_mode);
 
-  // Returns the path to the file which has NetLog data.
-  static base::FilePath GetNetLogFileName(
-      net_log::NetLogFileWriter* net_log_file_writer);
+  void StopNetLogThenNotifyUI();
 
-  // Send state/file information from NetLogFileWriter.
-  static void SendExportNetLogInfo(
-      base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-      net_log::NetLogFileWriter* net_log_file_writer);
-
-  // Send NetLog data via email. This runs on UI thread.
+  // Send NetLog data via email.
   static void SendEmail(const base::FilePath& file_to_send);
 
-  // Call NetExportView.onExportNetLogInfoChanged JavsScript function in the
-  // renderer, passing in |arg|. Takes ownership of |arg|.
-  void OnExportNetLogInfoChanged(base::Value* arg);
+  void NotifyUIWithNetLogFileWriterState(
+      std::unique_ptr<base::DictionaryValue> file_writer_state);
 
   // Cache of GetApplicationContex()->GetNetLog()->net_log_file_writer(). This
-  // is owned by ChromeNetLog which is owned by BrowserProcessImpl. There are
-  // four instances in this class where a pointer to net_log_file_writer_ is
-  // posted to the FILE_USER_BLOCKING thread. Base::Unretained is used here
-  // because BrowserProcessImpl is destroyed on the UI thread after joining the
-  // FILE_USER_BLOCKING thread making it impossible for there to be an invalid
-  // pointer this object when going back to the UI thread. Furthermore this
-  // pointer is never dereferenced prematurely on the UI thread. Thus the
-  // lifetime of this object is assured and can be safely used with
-  // base::Unretained.
+  // is owned by ChromeNetLog which is owned by BrowserProcessImpl.
   net_log::NetLogFileWriter* net_log_file_writer_;
 
   base::WeakPtrFactory<NetExportMessageHandler> weak_ptr_factory_;
@@ -103,15 +88,17 @@
 NetExportMessageHandler::NetExportMessageHandler()
     : net_log_file_writer_(
           GetApplicationContext()->GetNetLog()->net_log_file_writer()),
-      weak_ptr_factory_(this) {}
+      weak_ptr_factory_(this) {
+  net_log_file_writer_->SetTaskRunners(
+      web::WebThread::GetTaskRunnerForThread(
+          web::WebThread::FILE_USER_BLOCKING),
+      web::WebThread::GetTaskRunnerForThread(web::WebThread::IO));
+}
 
 NetExportMessageHandler::~NetExportMessageHandler() {
-  // Cancel any in-progress requests to collect net_log into temporary file.
-  web::WebThread::PostTask(
-      web::WebThread::FILE_USER_BLOCKING, FROM_HERE,
-      base::Bind(&net_log::NetLogFileWriter::ProcessCommand,
-                 base::Unretained(net_log_file_writer_),
-                 net_log::NetLogFileWriter::DO_STOP));
+  net_log_file_writer_->StopNetLog(
+      nullptr, nullptr,
+      base::Bind([](std::unique_ptr<base::DictionaryValue>) {}));
 }
 
 void NetExportMessageHandler::RegisterMessages() {
@@ -137,84 +124,58 @@
 
 void NetExportMessageHandler::OnGetExportNetLogInfo(
     const base::ListValue* list) {
-  web::WebThread::PostTask(
-      web::WebThread::FILE_USER_BLOCKING, FROM_HERE,
-      base::Bind(&NetExportMessageHandler::SendExportNetLogInfo,
-                 weak_ptr_factory_.GetWeakPtr(), net_log_file_writer_));
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
+  net_log_file_writer_->GetState(
+      base::Bind(&NetExportMessageHandler::NotifyUIWithNetLogFileWriterState,
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
 void NetExportMessageHandler::OnStartNetLog(const base::ListValue* list) {
-  std::string log_mode;
-  bool result = list->GetString(0, &log_mode);
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
+  std::string capture_mode_string;
+  bool result = list->GetString(0, &capture_mode_string);
   DCHECK(result);
 
-  net_log::NetLogFileWriter::Command command;
-  if (log_mode == "LOG_BYTES") {
-    command = net_log::NetLogFileWriter::DO_START_LOG_BYTES;
-  } else if (log_mode == "NORMAL") {
-    command = net_log::NetLogFileWriter::DO_START;
-  } else {
-    DCHECK_EQ("STRIP_PRIVATE_DATA", log_mode);
-    command = net_log::NetLogFileWriter::DO_START_STRIP_PRIVATE_DATA;
-  }
-
-  ProcessNetLogCommand(weak_ptr_factory_.GetWeakPtr(), net_log_file_writer_,
-                       command);
+  net::NetLogCaptureMode capture_mode =
+      net_log::NetLogFileWriter::CaptureModeFromString(capture_mode_string);
+  StartNetLogThenNotifyUI(base::FilePath(), capture_mode);
 }
 
 void NetExportMessageHandler::OnStopNetLog(const base::ListValue* list) {
-  ProcessNetLogCommand(weak_ptr_factory_.GetWeakPtr(), net_log_file_writer_,
-                       net_log::NetLogFileWriter::DO_STOP);
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
+  StopNetLogThenNotifyUI();
 }
 
 void NetExportMessageHandler::OnSendNetLog(const base::ListValue* list) {
-  web::WebThread::PostTaskAndReplyWithResult(
-      web::WebThread::FILE_USER_BLOCKING, FROM_HERE,
-      base::Bind(&NetExportMessageHandler::GetNetLogFileName,
-                 base::Unretained(net_log_file_writer_)),
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
+  net_log_file_writer_->GetFilePathToCompletedLog(
       base::Bind(&NetExportMessageHandler::SendEmail));
 }
 
-// static
-void NetExportMessageHandler::ProcessNetLogCommand(
-    base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-    net_log::NetLogFileWriter* net_log_file_writer,
-    net_log::NetLogFileWriter::Command command) {
-  if (!web::WebThread::CurrentlyOn(web::WebThread::FILE_USER_BLOCKING)) {
-    web::WebThread::PostTask(
-        web::WebThread::FILE_USER_BLOCKING, FROM_HERE,
-        base::Bind(&NetExportMessageHandler::ProcessNetLogCommand,
-                   net_export_message_handler, net_log_file_writer, command));
-    return;
-  }
+void NetExportMessageHandler::StartNetLogThenNotifyUI(
+    const base::FilePath& log_path,
+    net::NetLogCaptureMode capture_mode) {
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
 
-  DCHECK_CURRENTLY_ON(web::WebThread::FILE_USER_BLOCKING);
-  net_log_file_writer->ProcessCommand(command);
-  SendExportNetLogInfo(net_export_message_handler, net_log_file_writer);
+  net_log_file_writer_->StartNetLog(
+      log_path, capture_mode,
+      base::Bind(&NetExportMessageHandler::NotifyUIWithNetLogFileWriterState,
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
-// static
-base::FilePath NetExportMessageHandler::GetNetLogFileName(
-    net_log::NetLogFileWriter* net_log_file_writer) {
-  DCHECK_CURRENTLY_ON(web::WebThread::FILE_USER_BLOCKING);
-  base::FilePath net_export_file_path;
-  net_log_file_writer->GetFilePath(&net_export_file_path);
-  return net_export_file_path;
-}
+void NetExportMessageHandler::StopNetLogThenNotifyUI() {
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
 
-// static
-void NetExportMessageHandler::SendExportNetLogInfo(
-    base::WeakPtr<NetExportMessageHandler> net_export_message_handler,
-    net_log::NetLogFileWriter* net_log_file_writer) {
-  DCHECK_CURRENTLY_ON(web::WebThread::FILE_USER_BLOCKING);
-  base::Value* value = net_log_file_writer->GetState();
-  if (!web::WebThread::PostTask(
-          web::WebThread::UI, FROM_HERE,
-          base::Bind(&NetExportMessageHandler::OnExportNetLogInfoChanged,
-                     net_export_message_handler, value))) {
-    // Failed posting the task, avoid leaking.
-    delete value;
-  }
+  std::unique_ptr<base::DictionaryValue> ui_thread_polled_data;
+
+  // TODO(crbug.com/438656): fill |ui_thread_polled_data| with browser-specific
+  // polled data.
+
+  net_log_file_writer_->StopNetLog(
+      std::move(ui_thread_polled_data),
+      GetApplicationContext()->GetSystemURLRequestContext(),
+      base::Bind(&NetExportMessageHandler::NotifyUIWithNetLogFileWriterState,
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
 // static
@@ -235,10 +196,11 @@
                    IDS_IOS_NET_EXPORT_NO_EMAIL_ACCOUNTS_ALERT_MESSAGE);
 }
 
-void NetExportMessageHandler::OnExportNetLogInfoChanged(base::Value* arg) {
-  std::unique_ptr<base::Value> value(arg);
+void NetExportMessageHandler::NotifyUIWithNetLogFileWriterState(
+    std::unique_ptr<base::DictionaryValue> file_writer_state) {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  web_ui()->CallJavascriptFunction(net_log::kOnExportNetLogInfoChanged, *arg);
+  web_ui()->CallJavascriptFunction(net_log::kOnExportNetLogInfoChanged,
+                                   *file_writer_state);
 }
 
 }  // namespace
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index dd80dfc..249f97a 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -566,6 +566,10 @@
   last_packet_timestamp_ = buffer->timestamp();
   last_packet_duration_ = buffer->duration();
 
+  const base::TimeDelta new_duration = last_packet_timestamp_;
+  if (new_duration > duration_ || duration_ == kNoTimestamp)
+    duration_ = new_duration;
+
   buffer_queue_.Push(buffer);
   SatisfyPendingRead();
 }
@@ -753,10 +757,6 @@
   liveness_ = liveness;
 }
 
-base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const {
-  return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts);
-}
-
 Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const {
   return buffered_ranges_;
 }
@@ -1445,6 +1445,7 @@
   // Good to go: set the duration and bitrate and notify we're done
   // initializing.
   host_->SetDuration(max_duration);
+  duration_ = max_duration;
   duration_known_ = (max_duration != kInfiniteDuration);
 
   int64_t filesize_in_bytes = 0;
@@ -1706,25 +1707,23 @@
   if (result < 0 || IsMaxMemoryUsageReached()) {
     DVLOG(1) << __func__ << " result=" << result
              << " IsMaxMemoryUsageReached=" << IsMaxMemoryUsageReached();
-    // Update the duration based on the highest elapsed time across all streams
-    // if it was previously unknown.
-    if (!duration_known_) {
-      base::TimeDelta max_duration;
+    // Update the duration based on the highest elapsed time across all streams.
+    base::TimeDelta max_duration;
+    for (const auto& stream : streams_) {
+      if (!stream)
+        continue;
 
-      for (const auto& stream : streams_) {
-        if (!stream)
-          continue;
-
-        base::TimeDelta duration = stream->GetElapsedTime();
-        if (duration != kNoTimestamp && duration > max_duration)
-          max_duration = duration;
-      }
-
-      if (max_duration > base::TimeDelta()) {
-        host_->SetDuration(max_duration);
-        duration_known_ = true;
-      }
+      base::TimeDelta duration = stream->duration();
+      if (duration != kNoTimestamp && duration > max_duration)
+        max_duration = duration;
     }
+
+    if (duration_ == kInfiniteDuration || max_duration > duration_) {
+      host_->SetDuration(max_duration);
+      duration_known_ = true;
+      duration_ = max_duration;
+    }
+
     // If we have reached the end of stream, tell the downstream filters about
     // the event.
     StreamHasEnded();
@@ -1753,6 +1752,15 @@
     FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index].get();
     if (demuxer_stream->enabled())
       demuxer_stream->EnqueuePacket(std::move(packet));
+
+    // If duration estimate was incorrect, update it and tell higher layers.
+    if (duration_known_) {
+      const base::TimeDelta duration = demuxer_stream->duration();
+      if (duration != kNoTimestamp && duration > duration_) {
+        duration_ = duration;
+        host_->SetDuration(duration_);
+      }
+    }
   }
 
   // Keep reading until we've reached capacity.
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index be632cd..d12ec891 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -123,10 +123,6 @@
   // Returns the range of buffered data in this stream.
   Ranges<base::TimeDelta> GetBufferedRanges() const;
 
-  // Returns elapsed time based on the already queued packets.
-  // Used to determine stream duration when it's not known ahead of time.
-  base::TimeDelta GetElapsedTime() const;
-
   // Returns true if this stream has capacity for additional data.
   bool HasAvailableCapacity();
 
@@ -354,6 +350,7 @@
   // Set if we know duration of the audio stream. Used when processing end of
   // stream -- at this moment we definitely know duration.
   bool duration_known_;
+  base::TimeDelta duration_;
 
   // FFmpegURLProtocol implementation and corresponding glue bits.
   std::unique_ptr<BlockingUrlProtocol> url_protocol_;
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index 6699a93b..c96883f 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -111,7 +111,7 @@
                                  media::PipelineStatus expected_pipeline_status,
                                  base::Time timeline_offset) {
     if (expected_pipeline_status == PIPELINE_OK)
-      EXPECT_CALL(host_, SetDuration(_));
+      EXPECT_CALL(host_, SetDuration(_)).Times(AnyNumber());
     WaitableMessageLoopEvent event;
     demuxer_->Initialize(&host_, event.GetPipelineStatusCB(), enable_text);
     demuxer_->timeline_offset_ = timeline_offset;
@@ -223,8 +223,10 @@
   }
 
   // Accessor to demuxer internals.
-  void set_duration_known(bool duration_known) {
+  void SetDurationKnown(bool duration_known) {
     demuxer_->duration_known_ = duration_known;
+    if (!duration_known)
+      demuxer_->duration_ = kInfiniteDuration;
   }
 
   bool IsStreamStopped(DemuxerStream::Type type) {
@@ -815,8 +817,8 @@
   // Verify that end of stream buffers are created.
   CreateDemuxer("bear-320x240.webm");
   InitializeDemuxer();
-  set_duration_known(false);
-  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2767)));
+  SetDurationKnown(false);
+  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2744)));
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::AUDIO));
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::VIDEO));
 }
@@ -825,8 +827,8 @@
   // Verify that end of stream buffers are created.
   CreateDemuxer("bear-320x240-video-only.webm");
   InitializeDemuxer();
-  set_duration_known(false);
-  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2736)));
+  SetDurationKnown(false);
+  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2703)));
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::VIDEO));
 }
 
@@ -834,8 +836,8 @@
   // Verify that end of stream buffers are created.
   CreateDemuxer("bear-320x240-audio-only.webm");
   InitializeDemuxer();
-  set_duration_known(false);
-  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2767)));
+  SetDurationKnown(false);
+  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2744)));
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::AUDIO));
 }
 
@@ -844,8 +846,8 @@
   // if there are streams in the file that we don't support.
   CreateDemuxer("vorbis_audio_wmv_video.mkv");
   InitializeDemuxer();
-  set_duration_known(false);
-  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(1014)));
+  SetDurationKnown(false);
+  EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(991)));
   ReadUntilEndOfStream(demuxer_->GetStream(DemuxerStream::AUDIO));
 }
 
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index 0cb1ad3..8d10310 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -834,7 +834,8 @@
 TEST_P(BasicPlaybackTest, PlayToEnd) {
   PlaybackTestData data = GetParam();
 
-  ASSERT_EQ(PIPELINE_OK, Start(data.filename, kClockless));
+  ASSERT_EQ(PIPELINE_OK,
+            Start(data.filename, kClockless | kUnreliableDuration));
   EXPECT_EQ(data.start_time_ms, demuxer_->GetStartTime().InMilliseconds());
   EXPECT_EQ(data.duration_ms, pipeline_->GetMediaDuration().InMilliseconds());
 
@@ -875,7 +876,7 @@
 };
 
 // TODO(chcunningham): Migrate other basic playback tests to TEST_P.
-INSTANTIATE_TEST_CASE_P(PropritaryCodecs,
+INSTANTIATE_TEST_CASE_P(ProprietaryCodecs,
                         BasicPlaybackTest,
                         testing::ValuesIn(kADTSTests));
 
@@ -887,7 +888,7 @@
 };
 
 // TODO(chcunningham): Migrate other basic MSE playback tests to TEST_P.
-INSTANTIATE_TEST_CASE_P(PropritaryCodecs,
+INSTANTIATE_TEST_CASE_P(ProprietaryCodecs,
                         BasicMSEPlaybackTest,
                         testing::ValuesIn(kMediaSourceADTSTests));
 
@@ -2546,7 +2547,7 @@
 // Ensures audio-only playback with missing or negative timestamps works.  Tests
 // the common live-streaming case for chained ogg.  See http://crbug.com/396864.
 TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOgg) {
-  ASSERT_EQ(PIPELINE_OK, Start("double-sfx.ogg"));
+  ASSERT_EQ(PIPELINE_OK, Start("double-sfx.ogg", kUnreliableDuration));
   Play();
   ASSERT_TRUE(WaitUntilOnEnded());
   ASSERT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
@@ -2555,7 +2556,7 @@
 // Ensures audio-video playback with missing or negative timestamps fails softly
 // instead of crashing.  See http://crbug.com/396864.
 TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOggVideo) {
-  ASSERT_EQ(PIPELINE_OK, Start("double-bear.ogv"));
+  ASSERT_EQ(PIPELINE_OK, Start("double-bear.ogv", kUnreliableDuration));
   Play();
   EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError());
   ASSERT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
diff --git a/media/test/pipeline_integration_test_base.cc b/media/test/pipeline_integration_test_base.cc
index f40f590..6115e472 100644
--- a/media/test/pipeline_integration_test_base.cc
+++ b/media/test/pipeline_integration_test_base.cc
@@ -144,13 +144,22 @@
       .Times(AnyNumber());
   EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING))
       .Times(AnyNumber());
-  // Permit at most two calls to OnDurationChange.  CheckDuration will make sure
-  // that no more than one of them is a finite duration.  This allows the
-  // pipeline to call back at the end of the media with the known duration.
-  EXPECT_CALL(*this, OnDurationChange())
-      .Times(AtMost(2))
-      .WillRepeatedly(
-          Invoke(this, &PipelineIntegrationTestBase::CheckDuration));
+  // If the test is expected to have reliable duration information, permit at
+  // most two calls to OnDurationChange.  CheckDuration will make sure that no
+  // more than one of them is a finite duration.  This allows the pipeline to
+  // call back at the end of the media with the known duration.
+  //
+  // In the event of unreliable duration information, just set the expectation
+  // that it's called at least once. Such streams may repeatedly update their
+  // duration as new packets are demuxed.
+  if (test_type & kUnreliableDuration) {
+    EXPECT_CALL(*this, OnDurationChange()).Times(AtLeast(1));
+  } else {
+    EXPECT_CALL(*this, OnDurationChange())
+        .Times(AtMost(2))
+        .WillRepeatedly(
+            Invoke(this, &PipelineIntegrationTestBase::CheckDuration));
+  }
   EXPECT_CALL(*this, OnVideoNaturalSizeChange(_)).Times(AtMost(1));
   EXPECT_CALL(*this, OnVideoOpacityChange(_)).WillRepeatedly(Return());
   CreateDemuxer(std::move(data_source));
diff --git a/media/test/pipeline_integration_test_base.h b/media/test/pipeline_integration_test_base.h
index 6e17ad01..064fd17f 100644
--- a/media/test/pipeline_integration_test_base.h
+++ b/media/test/pipeline_integration_test_base.h
@@ -68,7 +68,8 @@
     kNormal = 0,
     kHashed = 1,
     kClockless = 2,
-    kExpectDemuxerFailure = 4
+    kExpectDemuxerFailure = 4,
+    kUnreliableDuration = 8,
   };
 
   // Starts the pipeline with a file specified by |filename|, optionally with a
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 88a9037e..8dfc8bf 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -110,6 +110,8 @@
     "base/hash_value.h",
     "base/host_port_pair.cc",
     "base/host_port_pair.h",
+    "base/interval.h",
+    "base/interval_set.h",
     "base/io_buffer.cc",
     "base/io_buffer.h",
     "base/ip_address.cc",
@@ -1205,8 +1207,6 @@
       "quic/core/frames/quic_stream_frame.h",
       "quic/core/frames/quic_window_update_frame.cc",
       "quic/core/frames/quic_window_update_frame.h",
-      "quic/core/interval.h",
-      "quic/core/interval_set.h",
       "quic/core/quic_ack_listener_interface.cc",
       "quic/core/quic_ack_listener_interface.h",
       "quic/core/quic_alarm.cc",
@@ -1322,6 +1322,7 @@
       "quic/platform/api/quic_bug_tracker.h",
       "quic/platform/api/quic_clock.cc",
       "quic/platform/api/quic_clock.h",
+      "quic/platform/api/quic_containers.h",
       "quic/platform/api/quic_export.h",
       "quic/platform/api/quic_ip_address.cc",
       "quic/platform/api/quic_ip_address.h",
@@ -1344,6 +1345,7 @@
       "quic/platform/impl/quic_bug_tracker_impl.h",
       "quic/platform/impl/quic_chromium_clock.cc",
       "quic/platform/impl/quic_chromium_clock.h",
+      "quic/platform/impl/quic_containers_impl.h",
       "quic/platform/impl/quic_export_impl.h",
       "quic/platform/impl/quic_ip_address_impl.cc",
       "quic/platform/impl/quic_ip_address_impl.h",
@@ -4062,6 +4064,8 @@
     "base/host_mapping_rules_unittest.cc",
     "base/host_port_pair_unittest.cc",
     "base/int128_unittest.cc",
+    "base/interval_set_test.cc",
+    "base/interval_test.cc",
     "base/ip_address_unittest.cc",
     "base/ip_endpoint_unittest.cc",
     "base/ip_pattern_unittest.cc",
@@ -4434,8 +4438,6 @@
     "quic/core/crypto/quic_random_test.cc",
     "quic/core/crypto/strike_register_test.cc",
     "quic/core/frames/quic_frames_test.cc",
-    "quic/core/interval_set_test.cc",
-    "quic/core/interval_test.cc",
     "quic/core/quic_alarm_test.cc",
     "quic/core/quic_arena_scoped_ptr_test.cc",
     "quic/core/quic_bandwidth_test.cc",
diff --git a/net/quic/core/interval.h b/net/base/interval.h
similarity index 98%
rename from net/quic/core/interval.h
rename to net/base/interval.h
index eaae9c9..24f3853 100644
--- a/net/quic/core/interval.h
+++ b/net/base/interval.h
@@ -57,8 +57,8 @@
 //   EXPECT_TRUE(r1.Empty());  // Now r1 is empty.
 //   EXPECT_FALSE(r1.Contains(r1.min()));  // e.g. doesn't contain its own min.
 
-#ifndef NET_QUIC_CORE_INTERVAL_H_
-#define NET_QUIC_CORE_INTERVAL_H_
+#ifndef NET_BASE_INTERVAL_H_
+#define NET_BASE_INTERVAL_H_
 
 #include <stddef.h>
 
@@ -299,4 +299,4 @@
 
 }  // namespace net
 
-#endif  // NET_QUIC_CORE_INTERVAL_H_
+#endif  // NET_BASE_INTERVAL_H_
diff --git a/net/quic/core/interval_set.h b/net/base/interval_set.h
similarity index 99%
rename from net/quic/core/interval_set.h
rename to net/base/interval_set.h
index ac0695c..60c6880b 100644
--- a/net/quic/core/interval_set.h
+++ b/net/base/interval_set.h
@@ -49,8 +49,8 @@
 //   EXPECT_EQ(1, intervals.Size());
 //   EXPECT_TRUE(intervals.Contains(Interval<int>(20, 40)));
 
-#ifndef NET_QUIC_CORE_INTERVAL_SET_H_
-#define NET_QUIC_CORE_INTERVAL_SET_H_
+#ifndef NET_BASE_INTERVAL_SET_H_
+#define NET_BASE_INTERVAL_SET_H_
 
 #include <stddef.h>
 
@@ -62,7 +62,7 @@
 #include <vector>
 
 #include "base/logging.h"
-#include "net/quic/core/interval.h"
+#include "net/base/interval.h"
 
 namespace net {
 
@@ -855,4 +855,4 @@
 
 }  // namespace net
 
-#endif  // NET_QUIC_CORE_INTERVAL_SET_H_
+#endif  // NET_BASE_INTERVAL_SET_H_
diff --git a/net/quic/core/interval_set_test.cc b/net/base/interval_set_test.cc
similarity index 99%
rename from net/quic/core/interval_set_test.cc
rename to net/base/interval_set_test.cc
index 9fcdddb..ecf2d19 100644
--- a/net/quic/core/interval_set_test.cc
+++ b/net/base/interval_set_test.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/quic/core/interval_set.h"
+#include "net/base/interval_set.h"
 
 #include <stdarg.h>
 
diff --git a/net/quic/core/interval_test.cc b/net/base/interval_test.cc
similarity index 99%
rename from net/quic/core/interval_test.cc
rename to net/base/interval_test.cc
index da8643aa..5d440c59 100644
--- a/net/quic/core/interval_test.cc
+++ b/net/base/interval_test.cc
@@ -9,7 +9,7 @@
 // Author: Will Neveitt (wneveitt@google.com)
 // ----------------------------------------------------------------------
 
-#include "net/quic/core/interval.h"
+#include "net/base/interval.h"
 
 #include "base/logging.h"
 #include "net/test/gtest_util.h"
diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc
index 59b15e03..7da5adaf 100644
--- a/net/http/http_stream_parser.cc
+++ b/net/http/http_stream_parser.cc
@@ -968,13 +968,19 @@
 
     // If the port is not the default for the scheme, assume it's not a real
     // HTTP/0.9 response, and fail the request.
-    // TODO(crbug.com/624462):  Further restrict the cases in which we allow
-    // HTTP/0.9.
-    std::string scheme(request_->url.scheme());
+    base::StringPiece scheme = request_->url.scheme_piece();
     if (!http_09_on_non_default_ports_enabled_ &&
-        url::DefaultPortForScheme(scheme.c_str(), scheme.length()) !=
+        url::DefaultPortForScheme(scheme.data(), scheme.length()) !=
             request_->url.EffectiveIntPort()) {
-      return ERR_INVALID_HTTP_RESPONSE;
+      // Allow Shoutcast responses over HTTP, as it's somewhat common and relies
+      // on HTTP/0.9 on weird ports to work.
+      // See
+      // https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/qS63pYso4P0
+      if (read_buf_->offset() < 3 || scheme != "http" ||
+          !base::LowerCaseEqualsASCII(
+              base::StringPiece(read_buf_->StartOfBuffer(), 3), "icy")) {
+        return ERR_INVALID_HTTP_RESPONSE;
+      }
     }
 
     headers = new HttpResponseHeaders(std::string("HTTP/0.9 200 OK"));
diff --git a/net/http/http_stream_parser_unittest.cc b/net/http/http_stream_parser_unittest.cc
index 9fa3bc0d..e9d9cde 100644
--- a/net/http/http_stream_parser_unittest.cc
+++ b/net/http/http_stream_parser_unittest.cc
@@ -1213,6 +1213,12 @@
     reads_.push_back(MockRead(SYNCHRONOUS, sequence_number_++, data.data()));
   }
 
+  // Simple overload - the above method requires using std::strings that outlive
+  // the function call.  This version works with inlined C-style strings.
+  void AddRead(const char* data) {
+    reads_.push_back(MockRead(SYNCHRONOUS, sequence_number_++, data));
+  }
+
   void SetupParserAndSendRequest() {
     reads_.push_back(MockRead(SYNCHRONOUS, 0, sequence_number_++));  // EOF
 
@@ -1281,50 +1287,55 @@
     const char* url;
     bool http_09_on_non_default_ports_enabled;
 
-    // Expected result when trying to read headers.
-    Error expected_header_error;
+    // Expected result when trying to read headers and response is an HTTP/0.9
+    // non-Shoutcast response.
+    Error expected_09_header_error;
+
+    // Expected result when trying to read headers for a shoutcast response.
+    Error expected_shoutcast_header_error;
   };
 
   const TestCase kTestCases[] = {
       // Default ports should work for HTTP/0.9, regardless of whether the port
       // is explicitly specified or not.
-      {"http://foo.com/", false, OK},
-      {"http://foo.com:80/", false, OK},
-      {"https://foo.com/", false, OK},
-      {"https://foo.com:443/", false, OK},
+      {"http://foo.com/", false, OK, OK},
+      {"http://foo.com:80/", false, OK, OK},
+      {"https://foo.com/", false, OK, OK},
+      {"https://foo.com:443/", false, OK, OK},
 
       // Non-standard ports should not support HTTP/0.9, by default.
-      {"http://foo.com:8080/", false, ERR_INVALID_HTTP_RESPONSE},
-      {"https://foo.com:8080/", false, ERR_INVALID_HTTP_RESPONSE},
-      {"http://foo.com:443/", false, ERR_INVALID_HTTP_RESPONSE},
-      {"https://foo.com:80/", false, ERR_INVALID_HTTP_RESPONSE},
+      {"http://foo.com:8080/", false, ERR_INVALID_HTTP_RESPONSE, OK},
+      {"https://foo.com:8080/", false, ERR_INVALID_HTTP_RESPONSE,
+       ERR_INVALID_HTTP_RESPONSE},
+      {"http://foo.com:443/", false, ERR_INVALID_HTTP_RESPONSE, OK},
+      {"https://foo.com:80/", false, ERR_INVALID_HTTP_RESPONSE,
+       ERR_INVALID_HTTP_RESPONSE},
 
       // Allowing non-default ports should not break the default ones.
-      {"http://foo.com/", true, OK},
-      {"http://foo.com:80/", true, OK},
-      {"https://foo.com/", true, OK},
-      {"https://foo.com:443/", true, OK},
+      {"http://foo.com/", true, OK, OK},
+      {"http://foo.com:80/", true, OK, OK},
+      {"https://foo.com/", true, OK, OK},
+      {"https://foo.com:443/", true, OK, OK},
 
       // Check that non-default ports works.
-      {"http://foo.com:8080/", true, OK},
-      {"https://foo.com:8080/", true, OK},
-      {"http://foo.com:443/", true, OK},
-      {"https://foo.com:80/", true, OK},
+      {"http://foo.com:8080/", true, OK, OK},
+      {"https://foo.com:8080/", true, OK, OK},
+      {"http://foo.com:443/", true, OK, OK},
+      {"https://foo.com:80/", true, OK, OK},
   };
 
-  std::string response = "hello\r\nworld\r\n";
-  int response_size = response.size();
+  const std::string kResponse = "hello\r\nworld\r\n";
 
   for (const auto& test_case : kTestCases) {
     SimpleGetRunner get_runner;
     get_runner.set_url(GURL(test_case.url));
     get_runner.set_http_09_on_non_default_ports_enabled(
         test_case.http_09_on_non_default_ports_enabled);
-    get_runner.AddRead(response);
+    get_runner.AddRead(kResponse);
     get_runner.SetupParserAndSendRequest();
 
-    get_runner.ReadHeadersExpectingError(test_case.expected_header_error);
-    if (test_case.expected_header_error != OK)
+    get_runner.ReadHeadersExpectingError(test_case.expected_09_header_error);
+    if (test_case.expected_09_header_error != OK)
       continue;
 
     ASSERT_TRUE(get_runner.response_info()->headers);
@@ -1332,12 +1343,74 @@
               get_runner.response_info()->headers->GetStatusLine());
 
     EXPECT_EQ(0, get_runner.parser()->received_bytes());
-    int read_lengths[] = {response_size, 0};
-    get_runner.ReadBody(response_size, read_lengths);
-    EXPECT_EQ(response_size, get_runner.parser()->received_bytes());
+    int read_lengths[] = {kResponse.size(), 0};
+    get_runner.ReadBody(kResponse.size(), read_lengths);
+    EXPECT_EQ(kResponse.size(),
+              static_cast<size_t>(get_runner.parser()->received_bytes()));
     EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP0_9,
               get_runner.response_info()->connection_info);
   }
+
+  const std::string kShoutcastResponse = "ICY 200 blah\r\n\r\n";
+  for (const auto& test_case : kTestCases) {
+    SimpleGetRunner get_runner;
+    get_runner.set_url(GURL(test_case.url));
+    get_runner.set_http_09_on_non_default_ports_enabled(
+        test_case.http_09_on_non_default_ports_enabled);
+    get_runner.AddRead(kShoutcastResponse);
+    get_runner.SetupParserAndSendRequest();
+
+    get_runner.ReadHeadersExpectingError(
+        test_case.expected_shoutcast_header_error);
+    if (test_case.expected_shoutcast_header_error != OK)
+      continue;
+
+    ASSERT_TRUE(get_runner.response_info()->headers);
+    EXPECT_EQ("HTTP/0.9 200 OK",
+              get_runner.response_info()->headers->GetStatusLine());
+
+    EXPECT_EQ(0, get_runner.parser()->received_bytes());
+    int read_lengths[] = {kShoutcastResponse.size(), 0};
+    get_runner.ReadBody(kShoutcastResponse.size(), read_lengths);
+    EXPECT_EQ(kShoutcastResponse.size(),
+              static_cast<size_t>(get_runner.parser()->received_bytes()));
+    EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP0_9,
+              get_runner.response_info()->connection_info);
+  }
+}
+
+// Make sure that Shoutcast is recognized when receiving one byte at a time.
+TEST(HttpStreamParser, ShoutcastSingleByteReads) {
+  SimpleGetRunner get_runner;
+  get_runner.set_url(GURL("http://foo.com:8080/"));
+  get_runner.set_http_09_on_non_default_ports_enabled(false);
+  get_runner.AddRead("i");
+  get_runner.AddRead("c");
+  get_runner.AddRead("Y");
+  // Needed because HttpStreamParser::Read returns ERR_CONNECTION_CLOSED on
+  // small response headers, which HttpNetworkTransaction replaces with net::OK.
+  // TODO(mmenke): Can we just change that behavior?
+  get_runner.AddRead(" Extra stuff");
+  get_runner.SetupParserAndSendRequest();
+
+  get_runner.ReadHeadersExpectingError(OK);
+  EXPECT_EQ("HTTP/0.9 200 OK",
+            get_runner.response_info()->headers->GetStatusLine());
+}
+
+// Make sure that Shoutcast is recognized when receiving any string starting
+// with "ICY", regardless of capitalization, and without a space following it
+// (The latter behavior is just to match HTTP detection).
+TEST(HttpStreamParser, ShoutcastWeirdHeader) {
+  SimpleGetRunner get_runner;
+  get_runner.set_url(GURL("http://foo.com:8080/"));
+  get_runner.set_http_09_on_non_default_ports_enabled(false);
+  get_runner.AddRead("iCyCreamSundae");
+  get_runner.SetupParserAndSendRequest();
+
+  get_runner.ReadHeadersExpectingError(OK);
+  EXPECT_EQ("HTTP/0.9 200 OK",
+            get_runner.response_info()->headers->GetStatusLine());
 }
 
 // Make sure that HTTP/0.9 isn't allowed in the truncated header case on a weird
diff --git a/net/http2/decoder/decode_buffer.cc b/net/http2/decoder/decode_buffer.cc
index 15235bae..d7afcd5 100644
--- a/net/http2/decoder/decode_buffer.cc
+++ b/net/http2/decoder/decode_buffer.cc
@@ -6,85 +6,50 @@
 
 namespace net {
 
-bool DecodeBuffer::SlowDecodeUnsignedInt(uint32_t field_size,
-                                         uint32_t field_offset,
-                                         uint32_t* decode_offset,
-                                         uint32_t* value) {
-  DCHECK_LT(0u, field_size);
-  DCHECK_LE(field_size, 4u);
-  DCHECK(decode_offset != nullptr);
-  DCHECK_LE(field_offset, *decode_offset);
-  const uint32_t next_field_offset = field_offset + field_size;
-  if (*decode_offset == field_offset) {
-    // Starting to decode field. It is possible we will reach this point
-    // twice, once when we've just exhausted the input, and once when
-    // resuming decoding with a new input buffer.
-    // Clear the field; we do NOT assume that the caller has done so
-    // previously.
-    *value = 0;
-  } else if (*decode_offset >= next_field_offset) {
-    // We already decoded this field.
-    return true;
-  }
-  do {
-    if (Empty()) {
-      return false;  // Not done decoding.
-    }
-    *value = *value << 8 | DecodeUInt8();
-    (*decode_offset)++;
-  } while (*decode_offset < next_field_offset);
-  return true;
+#ifndef NDEBUG
+// These are part of validating during tests that there is at most one
+// DecodeBufferSubset instance at a time for any DecodeBuffer instance.
+void DecodeBuffer::set_subset_of_base(DecodeBuffer* base,
+                                      const DecodeBufferSubset* subset) {
+  DCHECK_EQ(this, subset);
+  base->set_subset(subset);
 }
+void DecodeBuffer::clear_subset_of_base(DecodeBuffer* base,
+                                        const DecodeBufferSubset* subset) {
+  DCHECK_EQ(this, subset);
+  base->clear_subset(subset);
+}
+void DecodeBuffer::set_subset(const DecodeBufferSubset* subset) {
+  DCHECK(subset != nullptr);
+  DCHECK_EQ(subset_, nullptr) << "There is already a subset";
+  subset_ = subset;
+}
+void DecodeBuffer::clear_subset(const DecodeBufferSubset* subset) {
+  DCHECK(subset != nullptr);
+  DCHECK_EQ(subset_, subset);
+  subset_ = nullptr;
+}
+void DecodeBufferSubset::DebugSetup() {
+  start_base_offset_ = base_buffer_->Offset();
+  max_base_offset_ = start_base_offset_ + FullSize();
+  DCHECK_LE(max_base_offset_, base_buffer_->FullSize());
 
-bool DecodeBuffer::SlowDecodeUInt8(uint32_t field_offset,
-                                   uint32_t* decode_offset,
-                                   uint8_t* value) {
-  uint32_t tmp = *value;
-  const bool done = SlowDecodeUnsignedInt(1 /* field_size */, field_offset,
-                                          decode_offset, &tmp);
-  *value = tmp & 0xff;
-  DCHECK_EQ(tmp, *value);
-  return done;
+  // Ensure that there is only one DecodeBufferSubset at a time for a base.
+  set_subset_of_base(base_buffer_, this);
 }
+void DecodeBufferSubset::DebugTearDown() {
+  // Ensure that the base hasn't been modified.
+  DCHECK_EQ(start_base_offset_, base_buffer_->Offset())
+      << "The base buffer was modified";
 
-bool DecodeBuffer::SlowDecodeUInt16(uint32_t field_offset,
-                                    uint32_t* decode_offset,
-                                    uint16_t* value) {
-  uint32_t tmp = *value;
-  const bool done = SlowDecodeUnsignedInt(2 /* field_size */, field_offset,
-                                          decode_offset, &tmp);
-  *value = tmp & 0xffff;
-  DCHECK_EQ(tmp, *value);
-  return done;
-}
+  // Ensure that we haven't gone beyond the maximum allowed offset.
+  size_t offset = Offset();
+  DCHECK_LE(offset, FullSize());
+  DCHECK_LE(start_base_offset_ + offset, max_base_offset_);
+  DCHECK_LE(max_base_offset_, base_buffer_->FullSize());
 
-bool DecodeBuffer::SlowDecodeUInt24(uint32_t field_offset,
-                                    uint32_t* decode_offset,
-                                    uint32_t* value) {
-  uint32_t tmp = *value;
-  const bool done = SlowDecodeUnsignedInt(3 /* field_size */, field_offset,
-                                          decode_offset, &tmp);
-  *value = tmp & 0xffffff;
-  DCHECK_EQ(tmp, *value);
-  return done;
+  clear_subset_of_base(base_buffer_, this);
 }
-
-bool DecodeBuffer::SlowDecodeUInt31(uint32_t field_offset,
-                                    uint32_t* decode_offset,
-                                    uint32_t* value) {
-  uint32_t tmp = *value;
-  const bool done = SlowDecodeUnsignedInt(4 /* field_size */, field_offset,
-                                          decode_offset, &tmp);
-  *value = tmp & 0x7fffffff;
-  DCHECK_EQ(tmp & 0x7fffffff, *value);
-  return done;
-}
-
-bool DecodeBuffer::SlowDecodeUInt32(uint32_t field_offset,
-                                    uint32_t* decode_offset,
-                                    uint32_t* value) {
-  return SlowDecodeUnsignedInt(4 /* field_size */, field_offset, decode_offset,
-                               value);
-}
+#endif
 
 }  // namespace net
diff --git a/net/http2/decoder/decode_buffer.h b/net/http2/decoder/decode_buffer.h
index 828e4bf5..06aa128a 100644
--- a/net/http2/decoder/decode_buffer.h
+++ b/net/http2/decoder/decode_buffer.h
@@ -5,14 +5,12 @@
 #ifndef NET_HTTP2_DECODER_DECODE_BUFFER_H_
 #define NET_HTTP2_DECODER_DECODE_BUFFER_H_
 
-// DecodeBuffer provides primitives for decoding various integer types found
-// in HTTP/2 frames.
-// DecodeBuffer wraps a byte array from which we can read and decode serialized
-// HTTP/2 frames, or parts thereof. DecodeBuffer is intended only for stack
-// allocation, where the caller is typically going to use the DecodeBuffer
+// DecodeBuffer provides primitives for decoding various integer types found in
+// HTTP/2 frames. It wraps a byte array from which we can read and decode
+// serialized HTTP/2 frames, or parts thereof. DecodeBuffer is intended only for
+// stack allocation, where the caller is typically going to use the DecodeBuffer
 // instance as part of decoding the entire buffer before returning to its own
-// caller. Only the concrete Slow* methods are defined in the cc file,
-// all other methods are defined in this header file to enable inlining.
+// caller.
 
 #include <stddef.h>
 #include <stdint.h>
@@ -116,105 +114,24 @@
     return b1 << 24 | b2 << 16 | b3 << 8 | b4;
   }
 
-  // SlowDecode* routines are used for decoding a multi-field structure when
-  // there may not be enough bytes in the buffer to decode the entirety of the
-  // structure.
-
-  // Read as much of an unsigned int field of an encoded structure as possible,
-  // keeping track via decode_offset of our position in the encoded structure.
-  // Returns true if the field has been fully decoded.
-  // |field_size| is the number of bytes of the encoding of the field (usually
-  // a compile time fixed value).
-  // |field_offset| is the offset of the first byte of the encoding of the field
-  // within the encoding of that structure (usually a compile time fixed value).
-  // |*decode_offset| is the offset of the byte to be decoded next.
-  // |*value| is the storage for the decoded value, and is used for storing
-  // partially decoded values; if some, but not all, bytes of the encoding are
-  // available then this method will return having stored the decoded bytes into
-  // *value.
-  bool SlowDecodeUnsignedInt(uint32_t field_size,
-                             uint32_t field_offset,
-                             uint32_t* decode_offset,
-                             uint32_t* value);
-
-  // Like SlowDecodeUnsignedInt, but specifically for 8-bit unsigned integers.
-  // Obviously a byte can't be split (on our byte addressable machines), but
-  // a larger structure containing such a field might be.
-  bool SlowDecodeUInt8(uint32_t field_offset,
-                       uint32_t* decode_offset,
-                       uint8_t* value);
-
-  // Like SlowDecodeUnsignedInt, but specifically for 16-bit unsigned integers.
-  bool SlowDecodeUInt16(uint32_t field_offset,
-                        uint32_t* decode_offset,
-                        uint16_t* value);
-
-  // Like SlowDecodeUnsignedInt, but specifically for 24-bit unsigned integers.
-  bool SlowDecodeUInt24(uint32_t field_offset,
-                        uint32_t* decode_offset,
-                        uint32_t* value);
-
-  // Like SlowDecodeUnsignedInt, but specifically for 31-bit unsigned integers.
-  // (same definition as for DecodeUInt31).
-  bool SlowDecodeUInt31(uint32_t field_offset,
-                        uint32_t* decode_offset,
-                        uint32_t* value);
-
-  // Like SlowDecodeUnsignedInt, but specifically for 31-bit unsigned integers.
-  bool SlowDecodeUInt32(uint32_t field_offset,
-                        uint32_t* decode_offset,
-                        uint32_t* value);
-
-  // Decodes an enum value, where the size (in bytes) of the encoding must be
-  // stated explicitly. It is assumed that under the covers enums are really
-  // just integers, and that we can static_cast them to and from uint32.
-  template <typename E>
-  bool SlowDecodeEnum(uint32_t field_size,
-                      uint32_t field_offset,
-                      uint32_t* decode_offset,
-                      E* value) {
-    uint32_t tmp = static_cast<uint32_t>(*value);
-    const bool done =
-        SlowDecodeUnsignedInt(field_size, field_offset, decode_offset, &tmp);
-    *value = static_cast<E>(tmp);
-    DCHECK_EQ(tmp, static_cast<uint32_t>(*value));
-    return done;
-  }
-
-  // We assume the decode buffers will typically be modest in size (i.e. a few
-  // K).
-  // Let's make sure during testing that we don't go very high, with 32MB
-  // selected rather arbitrarily.
+  // We assume the decode buffers will typically be modest in size (i.e. often a
+  // few KB, perhaps as high as 100KB). Let's make sure during testing that we
+  // don't go very high, with 32MB selected rather arbitrarily.
   static constexpr size_t MaxDecodeBufferLength() { return 1 << 25; }
 
  protected:
 #ifndef NDEBUG
   // These are part of validating during tests that there is at most one
   // DecodeBufferSubset instance at a time for any DecodeBuffer instance.
-  void set_subset_of_base(DecodeBuffer* base,
-                          const DecodeBufferSubset* subset) {
-    DCHECK_EQ(this, subset);
-    base->set_subset(subset);
-  }
+  void set_subset_of_base(DecodeBuffer* base, const DecodeBufferSubset* subset);
   void clear_subset_of_base(DecodeBuffer* base,
-                            const DecodeBufferSubset* subset) {
-    DCHECK_EQ(this, subset);
-    base->clear_subset(subset);
-  }
+                            const DecodeBufferSubset* subset);
 #endif
 
  private:
 #ifndef NDEBUG
-  void set_subset(const DecodeBufferSubset* subset) {
-    DCHECK(subset != nullptr);
-    DCHECK_EQ(subset_, nullptr) << "There is already a subset";
-    subset_ = subset;
-  }
-  void clear_subset(const DecodeBufferSubset* subset) {
-    DCHECK(subset != nullptr);
-    DCHECK_EQ(subset_, subset);
-    subset_ = nullptr;
-  }
+  void set_subset(const DecodeBufferSubset* subset);
+  void clear_subset(const DecodeBufferSubset* subset);
 #endif
 
   // Prevent heap allocation of DecodeBuffer.
@@ -243,43 +160,33 @@
 // DecodeBuffer, though they can be nested (i.e. a DecodeBufferSubset's
 // base may itself be a DecodeBufferSubset). This avoids the AdvanceCursor
 // being called erroneously.
-class DecodeBufferSubset : public DecodeBuffer {
+class NET_EXPORT_PRIVATE DecodeBufferSubset : public DecodeBuffer {
  public:
   DecodeBufferSubset(DecodeBuffer* base, size_t subset_len)
       : DecodeBuffer(base->cursor(), base->MinLengthRemaining(subset_len)),
-#ifndef NDEBUG
-        start_base_offset_(base->Offset()),
-        max_base_offset_(start_base_offset_ + FullSize()),
-#endif
         base_buffer_(base) {
 #ifndef NDEBUG
-    DCHECK_LE(max_base_offset_, base->FullSize());
-    set_subset_of_base(base_buffer_, this);
+    DebugSetup();
 #endif
   }
 
   ~DecodeBufferSubset() {
     size_t offset = Offset();
 #ifndef NDEBUG
-    clear_subset_of_base(base_buffer_, this);
-    DCHECK_LE(Offset(), FullSize());
-    DCHECK_EQ(start_base_offset_, base_buffer_->Offset())
-        << "The base buffer was modified";
-    DCHECK_LE(offset, FullSize());
-    DCHECK_LE(start_base_offset_ + offset, base_buffer_->FullSize());
+    DebugTearDown();
 #endif
     base_buffer_->AdvanceCursor(offset);
-#ifndef NDEBUG
-    DCHECK_GE(max_base_offset_, base_buffer_->Offset());
-#endif
   }
 
  private:
-#ifndef NDEBUG
-  const size_t start_base_offset_;  // Used for DCHECKs.
-  const size_t max_base_offset_;    // Used for DCHECKs.
-#endif
   DecodeBuffer* const base_buffer_;
+#ifndef NDEBUG
+  size_t start_base_offset_;  // Used for DCHECKs.
+  size_t max_base_offset_;    // Used for DCHECKs.
+
+  void DebugSetup();
+  void DebugTearDown();
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(DecodeBufferSubset);
 };
diff --git a/net/http2/decoder/decode_buffer_test.cc b/net/http2/decoder/decode_buffer_test.cc
index a7279656..4494122 100644
--- a/net/http2/decoder/decode_buffer_test.cc
+++ b/net/http2/decoder/decode_buffer_test.cc
@@ -47,127 +47,11 @@
   TestEnum8 f8;
 };
 
-const size_t kF1Offset = 0;
-const size_t kF2Offset = 1;
-const size_t kF3Offset = 3;
-const size_t kF4Offset = 6;
-const size_t kF5Offset = 10;
-const size_t kF6Offset = 14;
-const size_t kF7Offset = 18;
-const size_t kF8Offset = 19;
-
 class DecodeBufferTest : public ::testing::Test {
  public:
   DecodeBufferTest() {}
 
  protected:
-  // Double checks the call fn(f).
-  template <typename T>
-  bool SlowDecodeField(DecodeBuffer* b,
-                       size_t field_size,
-                       size_t field_offset,
-                       std::function<bool(DecodeBuffer*)> fn,
-                       T* f) {
-    VLOG(2) << "Remaining: " << b->Remaining();
-    VLOG(2) << "field_size: " << field_size;
-    VLOG(2) << "field_offset: " << field_offset;
-    VLOG(2) << "decode_offset_: " << decode_offset_;
-    EXPECT_GE(decode_offset_, field_offset);
-    bool had_data = b->HasData();
-    VLOG(2) << "had_data: " << had_data;
-    uint32_t old = static_cast<uint32_t>(*f);
-    VLOG(2) << "old: " << old;
-    size_t old_decode_offset = decode_offset_;
-    bool done = fn(b);
-    VLOG(2) << "done: " << done;
-    if (old_decode_offset == decode_offset_) {
-      // Didn't do any decoding (may have no input, or may have already
-      // decoded this field).
-      if (done) {
-        EXPECT_LE(field_offset + field_size, decode_offset_);
-        // Shouldn't have modified already decoded field.
-        EXPECT_EQ(old, static_cast<uint32_t>(*f));
-      } else {
-        EXPECT_TRUE(!had_data);
-      }
-    } else {
-      // Did some decoding.
-      EXPECT_TRUE(had_data);
-      EXPECT_LT(old_decode_offset, decode_offset_);
-      if (done) {
-        EXPECT_EQ(field_offset + field_size, decode_offset_);
-      } else {
-        EXPECT_GT(field_offset + field_size, decode_offset_);
-      }
-    }
-    VLOG(2) << "---------------------------------------";
-    return done;
-  }
-
-
-  void SlowDecodeTestStruct(StringPiece input, TestStruct* p) {
-    VLOG(2) << "############################################################";
-    EXPECT_LE(10u, input.size());
-    decode_offset_ = 0;
-    auto decode_f1 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeUInt8(kF1Offset, &decode_offset_, &p->f1);
-    };
-    auto decode_f2 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeUInt16(kF2Offset, &decode_offset_, &p->f2);
-    };
-    auto decode_f3 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeUInt24(kF3Offset, &decode_offset_, &p->f3);
-    };
-    auto decode_f4 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeUInt32(kF4Offset, &decode_offset_, &p->f4);
-    };
-    auto decode_f5 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeUInt31(kF5Offset, &decode_offset_, &p->f5);
-    };
-    auto decode_f6 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeEnum(4, kF6Offset, &decode_offset_, &p->f6);
-    };
-    auto decode_f7 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeEnum(1, kF7Offset, &decode_offset_, &p->f7);
-    };
-    auto decode_f8 = [this, p](DecodeBuffer* db) {
-      return db->SlowDecodeEnum(1, kF8Offset, &decode_offset_, &p->f8);
-    };
-    while (input.size() > 0) {
-      size_t size = input.size();
-      // Sometimes check that zero length input is OK.
-      auto r = random_.Next();
-      if (r % 100 == 0) {
-        size = 0;
-      } else if (size > 1) {
-        auto r = random_.Next();
-        size = (r % size) + 1;
-      }
-      VLOG(2) << "================= input size " << size;
-      DecodeBuffer b(input.data(), size);
-      size_t old_decode_offset = decode_offset_;
-      if (SlowDecodeField(&b, 1, kF1Offset, decode_f1, &p->f1) &&
-          SlowDecodeField(&b, 2, kF2Offset, decode_f2, &p->f2) &&
-          SlowDecodeField(&b, 3, kF3Offset, decode_f3, &p->f3) &&
-          SlowDecodeField(&b, 4, kF4Offset, decode_f4, &p->f4) &&
-          SlowDecodeField(&b, 4, kF5Offset, decode_f5, &p->f5) &&
-          SlowDecodeField(&b, 4, kF6Offset, decode_f6, &p->f6) &&
-          SlowDecodeField(&b, 1, kF7Offset, decode_f7, &p->f7) &&
-          SlowDecodeField(&b, 1, kF8Offset, decode_f8, &p->f8)) {
-        EXPECT_TRUE(b.Empty());
-        EXPECT_EQ(size, input.size());
-        EXPECT_EQ(input.size(), b.Offset());  // All input consumed.
-        return;
-      }
-      EXPECT_EQ(old_decode_offset + size, decode_offset_);
-      EXPECT_TRUE(b.Empty());
-      EXPECT_EQ(size, b.Offset());    // All input consumed.
-      EXPECT_LT(size, input.size());  // More remains.
-      input = StringPiece(input.data() + size, input.size() - size);
-    }
-    ADD_FAILURE() << "Ran out of input! decode_offset_ = " << decode_offset_;
-  }
-
   Http2Random random_;
   uint32_t decode_offset_;
 };
@@ -179,60 +63,6 @@
   EXPECT_EQ(0x1223u, b1.DecodeUInt16());
   EXPECT_EQ(0x344556u, b1.DecodeUInt24());
   EXPECT_EQ(0x6778899Au, b1.DecodeUInt32());
-
-  DecodeBuffer b2(data, strlen(data));
-  uint8_t b;
-  decode_offset_ = 0;
-  EXPECT_TRUE(b2.SlowDecodeUInt8(0, &decode_offset_, &b));
-  EXPECT_EQ(1, b);
-  uint16_t s;
-  decode_offset_ = 0;
-  EXPECT_TRUE(b2.SlowDecodeUInt16(0, &decode_offset_, &s));
-  EXPECT_EQ(0x1223, s);
-  uint32_t i;
-  decode_offset_ = 0;
-  EXPECT_TRUE(b2.SlowDecodeUInt24(0, &decode_offset_, &i));
-  //  EXPECT_EQ(0x344556, b1.DecodeUInt24());
-  //  EXPECT_EQ(0x6778899a, b1.DecodeUInt32());
-}
-
-// Decode the structure many times, where we'll pass different partitions
-// into DecodeSlowly.
-TEST_F(DecodeBufferTest, SlowDecodeTestStruct) {
-  // clang-format off
-  const char data[] = {
-    0x12u,                       // f1
-    0x23u, 0x34u,                // f2
-    0x45u, 0x56u, 0x67u,         // f3
-    0x78u, 0x89u, 0x9au, 0xabu,  // f4
-    0xfeu, 0xedu, 0xdcu, 0xcbu,  // f5 (high-bit will be cleared.)
-    0x00u, 0x0fu, 0x42u, 0x40u,  // f6 (kValue1M)
-    0x63u,                       // f7 (kValue99)
-    0x81u,                       // f8 (kMaskLo | kMaskHi)
-  };
-  // clang-format on
-  StringPiece input(data, sizeof data);
-  for (int i = 0; i < 200; ++i) {
-    TestStruct ts;
-    // Init the struct to random garbage.
-    ts.f1 = random_.Rand8();
-    ts.f2 = random_.Rand16();
-    ts.f3 = random_.Rand32();
-    ts.f4 = random_.Rand32();
-    ts.f5 = 0x80000000 | random_.Rand32();  // Ensure high-bit is set.
-    ts.f6 = static_cast<TestEnumClass32>(random_.Rand32());
-    ts.f7 = static_cast<TestEnumClass8>(random_.Rand8());
-    ts.f8 = static_cast<TestEnum8>(random_.Rand8());
-    SlowDecodeTestStruct(input, &ts);
-    ASSERT_EQ(0x12u, ts.f1);
-    ASSERT_EQ(0x2334u, ts.f2);
-    ASSERT_EQ(0x455667u, ts.f3);
-    ASSERT_EQ(0x78899AABu, ts.f4);
-    ASSERT_EQ(0x7EEDDCCBu, ts.f5);
-    ASSERT_EQ(TestEnumClass32::kValue1M, ts.f6);
-    ASSERT_EQ(TestEnumClass8::kValue99, ts.f7);
-    ASSERT_EQ(kMaskLo | kMaskHi, ts.f8);
-  }
 }
 
 // Make sure that DecodeBuffer is not copying input, just pointing into
diff --git a/net/http2/decoder/decode_http2_structures.cc b/net/http2/decoder/decode_http2_structures.cc
index cd419e3..e03be23 100644
--- a/net/http2/decoder/decode_http2_structures.cc
+++ b/net/http2/decoder/decode_http2_structures.cc
@@ -24,34 +24,6 @@
   out->stream_id = b->DecodeUInt31();
 }
 
-bool MaybeDecode(Http2FrameHeader* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2FrameHeader::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2FrameHeader* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_GT(Http2FrameHeader::EncodedSize(), *offset);
-  if (b->SlowDecodeUInt24(0 /* field_offset */, offset, &out->payload_length) &&
-      b->SlowDecodeEnum(1 /* field_size */, 3 /* field_offset */, offset,
-                        &out->type) &&
-      b->SlowDecodeEnum(1 /* field_size */, 4 /* field_offset */, offset,
-                        &out->flags) &&
-      b->SlowDecodeUInt31(5 /* field_offset */, offset, &out->stream_id)) {
-    DCHECK_EQ(Http2FrameHeader::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_GT(Http2FrameHeader::EncodedSize(), *offset);
-  return false;
-}
-
 // Http2PriorityFields decoding:
 
 void DoDecode(Http2PriorityFields* out, DecodeBuffer* b) {
@@ -70,47 +42,6 @@
   out->weight = b->DecodeUInt8() + 1;
 }
 
-bool MaybeDecode(Http2PriorityFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2PriorityFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2PriorityFields* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_GT(Http2PriorityFields::EncodedSize(), *offset);
-  const uint32_t start_offset = *offset;
-  if (b->SlowDecodeUInt32(0 /* field_offset */, offset,
-                          &out->stream_dependency) &&
-      b->SlowDecodeUnsignedInt(1,  // field_size
-                               4,  // field_offset
-                               offset, &out->weight)) {
-    DCHECK_EQ(Http2PriorityFields::EncodedSize(), *offset);
-    if (start_offset < *offset) {
-      // First time here. Extract is_exclusive from stream_dependency.
-      const uint32_t stream_id_only = out->stream_dependency & StreamIdMask();
-      if (out->stream_dependency != stream_id_only) {
-        out->stream_dependency = stream_id_only;
-        out->is_exclusive = true;
-      } else {
-        out->is_exclusive = false;
-      }
-      // Need to add one to the weight field because the encoding is 0-255, but
-      // interpreted as 1-256.
-      ++(out->weight);
-    }
-    return true;
-  }
-  DCHECK_GT(Http2PriorityFields::EncodedSize(), *offset);
-  return false;
-}
-
 // Http2RstStreamFields decoding:
 
 void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b) {
@@ -120,31 +51,6 @@
   out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32());
 }
 
-bool MaybeDecode(Http2RstStreamFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2RstStreamFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2RstStreamFields* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_GT(Http2RstStreamFields::EncodedSize(), *offset);
-
-  if (b->SlowDecodeEnum(4 /* field_size */, 0 /* field_offset */, offset,
-                        &out->error_code)) {
-    DCHECK_EQ(Http2RstStreamFields::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_GT(Http2RstStreamFields::EncodedSize(), *offset);
-  return false;
-}
-
 // Http2SettingFields decoding:
 
 void DoDecode(Http2SettingFields* out, DecodeBuffer* b) {
@@ -155,32 +61,6 @@
   out->value = b->DecodeUInt32();
 }
 
-bool MaybeDecode(Http2SettingFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2SettingFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2SettingFields* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_LT(*offset, Http2SettingFields::EncodedSize());
-
-  if (b->SlowDecodeEnum(2 /* field_size */, 0 /* field_offset */, offset,
-                        &out->parameter) &&
-      b->SlowDecodeUInt32(2 /* field_offset */, offset, &out->value)) {
-    DCHECK_EQ(Http2SettingFields::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_LT(*offset, Http2SettingFields::EncodedSize());
-  return false;
-}
-
 // Http2PushPromiseFields decoding:
 
 void DoDecode(Http2PushPromiseFields* out, DecodeBuffer* b) {
@@ -190,32 +70,6 @@
   out->promised_stream_id = b->DecodeUInt31();
 }
 
-bool MaybeDecode(Http2PushPromiseFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2PushPromiseFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2PushPromiseFields* out,
-                DecodeBuffer* b,
-                uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_LT(*offset, Http2PushPromiseFields::EncodedSize());
-  if (b->SlowDecodeUInt31(0 /* field_offset */, offset,
-                          &out->promised_stream_id)) {
-    DCHECK_EQ(Http2PushPromiseFields::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_LT(*offset, Http2PushPromiseFields::EncodedSize());
-  return false;
-}
-
 // Http2PingFields decoding:
 
 void DoDecode(Http2PingFields* out, DecodeBuffer* b) {
@@ -226,30 +80,6 @@
   b->AdvanceCursor(Http2PingFields::EncodedSize());
 }
 
-bool MaybeDecode(Http2PingFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2PingFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2PingFields* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_LT(*offset, Http2PingFields::EncodedSize());
-  while (*offset < Http2PingFields::EncodedSize()) {
-    if (b->Empty()) {
-      return false;
-    }
-    out->opaque_data[(*offset)++] = b->DecodeUInt8();
-  }
-  return true;
-}
-
 // Http2GoAwayFields decoding:
 
 void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b) {
@@ -260,31 +90,6 @@
   out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32());
 }
 
-bool MaybeDecode(Http2GoAwayFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2GoAwayFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2GoAwayFields* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_LT(*offset, Http2GoAwayFields::EncodedSize());
-  if (b->SlowDecodeUInt31(0 /* field_offset */, offset, &out->last_stream_id) &&
-      b->SlowDecodeEnum(4 /* field_size */, 4 /* field_offset */, offset,
-                        &out->error_code)) {
-    DCHECK_EQ(Http2GoAwayFields::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_LT(*offset, Http2GoAwayFields::EncodedSize());
-  return false;
-}
-
 // Http2WindowUpdateFields decoding:
 
 void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) {
@@ -294,32 +99,6 @@
   out->window_size_increment = b->DecodeUInt31();
 }
 
-bool MaybeDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2WindowUpdateFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2WindowUpdateFields* out,
-                DecodeBuffer* b,
-                uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_LT(*offset, Http2WindowUpdateFields::EncodedSize());
-  if (b->SlowDecodeUInt31(0 /* field_offset */, offset,
-                          &out->window_size_increment)) {
-    DCHECK_EQ(Http2WindowUpdateFields::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_LT(*offset, Http2WindowUpdateFields::EncodedSize());
-  return false;
-}
-
 // Http2AltSvcFields decoding:
 
 void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b) {
@@ -329,27 +108,4 @@
   out->origin_length = b->DecodeUInt16();
 }
 
-bool MaybeDecode(Http2AltSvcFields* out, DecodeBuffer* b) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  if (b->Remaining() >= Http2AltSvcFields::EncodedSize()) {
-    DoDecode(out, b);
-    return true;
-  }
-  return false;
-}
-
-bool SlowDecode(Http2AltSvcFields* out, DecodeBuffer* b, uint32_t* offset) {
-  DCHECK_NE(nullptr, out);
-  DCHECK_NE(nullptr, b);
-  DCHECK_NE(nullptr, offset);
-  DCHECK_LT(*offset, Http2AltSvcFields::EncodedSize());
-  if (b->SlowDecodeUInt16(0 /* field_offset */, offset, &out->origin_length)) {
-    DCHECK_EQ(Http2AltSvcFields::EncodedSize(), *offset);
-    return true;
-  }
-  DCHECK_LT(*offset, Http2AltSvcFields::EncodedSize());
-  return false;
-}
-
 }  // namespace net
diff --git a/net/http2/decoder/decode_http2_structures.h b/net/http2/decoder/decode_http2_structures.h
index 7cee46f..1ff4d39 100644
--- a/net/http2/decoder/decode_http2_structures.h
+++ b/net/http2/decoder/decode_http2_structures.h
@@ -7,14 +7,6 @@
 
 // Provides functions for decoding the fixed size structures in the HTTP/2 spec.
 
-// TODO(jamessynge): Consider whether the value of the SlowDecode methods is
-// worth their complexity; in particular, dropping back to buffering at most
-// 9 bytes (the largest fixed size structure) may actually be more efficient
-// than using the SlowDecode methods, or at least worth the complexity
-// reduction.
-// See http2_structure_decoder.h et al for an experiment in removing all except
-// DoDecode.
-
 #include "net/base/net_export.h"
 #include "net/http2/decoder/decode_buffer.h"
 #include "net/http2/http2_structures.h"
@@ -22,7 +14,7 @@
 namespace net {
 
 // DoDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from start
-// to end, advancing the cursor by STRUCTURE::EncodedSize(). The decoder buffer
+// to end, advancing the cursor by STRUCTURE::EncodedSize(). The decode buffer
 // must be large enough (i.e. b->Remaining() >= STRUCTURE::EncodedSize()).
 
 NET_EXPORT_PRIVATE void DoDecode(Http2FrameHeader* out, DecodeBuffer* b);
@@ -35,60 +27,6 @@
 NET_EXPORT_PRIVATE void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b);
 NET_EXPORT_PRIVATE void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b);
 
-// MaybeDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from
-// start to end if the decoder buffer is large enough, advancing the cursor
-// by STRUCTURE::EncodedSize(), then returns true.
-// If the decode buffer isn't large enough, does nothing and returns false.
-// The buffer is large enough if b->Remaining() >= STRUCTURE::EncodedSize().
-
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2FrameHeader* out, DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2PriorityFields* out, DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2RstStreamFields* out, DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2SettingFields* out, DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2PushPromiseFields* out,
-                                    DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2PingFields* out, DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2GoAwayFields* out, DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2WindowUpdateFields* out,
-                                    DecodeBuffer* b);
-NET_EXPORT_PRIVATE bool MaybeDecode(Http2AltSvcFields* out, DecodeBuffer* b);
-
-// SlowDecode(STRUCTURE* out, DecodeBuffer* b, uint32_t* offset) provides
-// incremental decoding of a structure, supporting cases where the structure
-// is split across multiple input buffers. *offset represents the offset within
-// the encoding of the structure, in the range [0, STRUCTURE::EncodedSize()].
-// Returns true when it is able to completely decode the structure, false
-// before that. Updates *offset to record the progress decoding the structure;
-// if false is returned, then b->Remaining() == 0 when SlowDecode returns.
-
-NET_EXPORT_PRIVATE bool SlowDecode(Http2FrameHeader* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2PriorityFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2RstStreamFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2SettingFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2PushPromiseFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2PingFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2GoAwayFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2WindowUpdateFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-NET_EXPORT_PRIVATE bool SlowDecode(Http2AltSvcFields* out,
-                                   DecodeBuffer* b,
-                                   uint32_t* offset);
-
 }  // namespace net
 
 #endif  // NET_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
diff --git a/net/http2/decoder/decode_http2_structures_test.cc b/net/http2/decoder/decode_http2_structures_test.cc
index 1c84cf7..71cd286 100644
--- a/net/http2/decoder/decode_http2_structures_test.cc
+++ b/net/http2/decoder/decode_http2_structures_test.cc
@@ -7,10 +7,6 @@
 // Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined
 // in net/http2/http2_structures.h).
 
-// TODO(jamessynge): Combine tests of DoDecode, MaybeDecode, SlowDecode and
-// Http2StructureDecoder test using gUnit's support for tests parameterized
-// by type.
-
 #include <stddef.h>
 #include <string>
 
@@ -21,7 +17,7 @@
 #include "net/http2/http2_constants.h"
 #include "net/http2/http2_structures_test_util.h"
 #include "net/http2/tools/http2_frame_builder.h"
-#include "net/http2/tools/random_decoder_test.h"
+#include "net/http2/tools/http2_random.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::AssertionFailure;
@@ -34,6 +30,11 @@
 namespace test {
 namespace {
 
+template <typename T, size_t N>
+StringPiece ToStringPiece(T (&data)[N]) {
+  return StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T));
+}
+
 template <class S>
 string SerializeStructure(const S& s) {
   Http2FrameBuilder fb;
@@ -43,112 +44,27 @@
 }
 
 template <class S>
-class StructureDecoderTest : public RandomDecoderTest {
+class StructureDecoderTest : public ::testing::Test {
  protected:
   typedef S Structure;
 
   StructureDecoderTest() : random_decode_count_(100) {
     CHECK_LE(random_decode_count_, 1000u * 1000u) << "That should be plenty!";
-    // IF the test adds more data after the encoded structure, stop as
-    // soon as the structure is decoded.
-    stop_decode_on_done_ = true;
-  }
-  ~StructureDecoderTest() override {}
-
-  // Reset the decoding to the start of the structure, and overwrite the
-  // current contents of |structure_|, in to which we'll decode the buffer.
-  DecodeStatus StartDecoding(DecodeBuffer* b) override {
-    decode_offset_ = 0;
-    Randomize(&structure_);
-    return ResumeDecoding(b);
-  }
-
-  DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
-    // If we're at the start...
-    if (decode_offset_ == 0) {
-      const uint32_t start_offset = b->Offset();
-      const char* const start_cursor = b->cursor();
-      // ... attempt to decode the entire structure.
-      if (MaybeDecode(&structure_, b)) {
-        ++fast_decode_count_;
-        EXPECT_EQ(S::EncodedSize(), b->Offset() - start_offset);
-
-        if (!HasFailure()) {
-          // Success. Confirm that SlowDecode produces the same result.
-          DecodeBuffer b2(start_cursor, b->Offset() - start_offset);
-          S second;
-          Randomize(&second);
-          uint32_t second_offset = 0;
-          EXPECT_TRUE(SlowDecode(&second, &b2, &second_offset));
-          EXPECT_EQ(S::EncodedSize(), second_offset);
-          EXPECT_EQ(structure_, second);
-        }
-
-        // Test can't easily tell if MaybeDecode or SlowDecode is used, so
-        // update decode_offset_ as if SlowDecode had been used to completely
-        // decode.
-        decode_offset_ = S::EncodedSize();
-        return DecodeStatus::kDecodeDone;
-      }
-    }
-
-    // We didn't have enough in the first buffer to decode everything, so we'll
-    // reach here multiple times until we've completely decoded the structure.
-    if (SlowDecode(&structure_, b, &decode_offset_)) {
-      ++slow_decode_count_;
-      EXPECT_EQ(S::EncodedSize(), decode_offset_);
-      return DecodeStatus::kDecodeDone;
-    }
-
-    // Drained the input buffer, but not yet done.
-    EXPECT_TRUE(b->Empty());
-    EXPECT_GT(S::EncodedSize(), decode_offset_);
-
-    return DecodeStatus::kDecodeInProgress;
   }
 
   // Set the fields of |*p| to random values.
-  void Randomize(S* p) { ::net::test::Randomize(p, RandomPtr()); }
+  void Randomize(S* p) { ::net::test::Randomize(p, &random_); }
 
   // Fully decodes the Structure at the start of data, and confirms it matches
   // *expected (if provided).
   void DecodeLeadingStructure(const S* expected, StringPiece data) {
     ASSERT_LE(S::EncodedSize(), data.size());
-    DecodeBuffer original(data);
-
-    // The validator is called after each of the several times that the input
-    // DecodeBuffer is decoded, each with a different segmentation of the input.
-    // Validate that structure_ matches the expected value, if provided.
-    Validator validator = [expected, this](
-        const DecodeBuffer& db, DecodeStatus status) -> AssertionResult {
-      if (expected != nullptr && *expected != structure_) {
-        return AssertionFailure()
-               << "Expected structs to be equal\nExpected: " << *expected
-               << "\n  Actual: " << structure_;
-      }
-      return AssertionSuccess();
-    };
-
-    // First validate that decoding is done and that we've advanced the cursor
-    // the expected amount.
-    validator = ValidateDoneAndOffset(S::EncodedSize(), validator);
-
-    // Decode several times, with several segmentations of the input buffer.
-    fast_decode_count_ = 0;
-    slow_decode_count_ = 0;
-    EXPECT_TRUE(DecodeAndValidateSeveralWays(
-        &original, false /*return_non_zero_on_first*/, validator));
-
-    if (!HasFailure()) {
-      EXPECT_EQ(S::EncodedSize(), decode_offset_);
-      EXPECT_EQ(S::EncodedSize(), original.Offset());
-      EXPECT_LT(0u, fast_decode_count_);
-      EXPECT_LT(0u, slow_decode_count_);
-      if (expected != nullptr) {
-        DVLOG(1) << "DecodeLeadingStructure expected: " << *expected;
-        DVLOG(1) << "DecodeLeadingStructure   actual: " << structure_;
-        EXPECT_EQ(*expected, structure_);
-      }
+    DecodeBuffer db(data);
+    Randomize(&structure_);
+    DoDecode(&structure_, &db);
+    EXPECT_EQ(db.Offset(), S::EncodedSize());
+    if (expected != nullptr) {
+      EXPECT_EQ(structure_, *expected);
     }
   }
 
@@ -179,6 +95,7 @@
     TestDecodingRandomizedStructures(random_decode_count_);
   }
 
+  Http2Random random_;
   const size_t random_decode_count_;
   uint32_t decode_offset_ = 0;
   S structure_;
diff --git a/net/log/file_net_log_observer.cc b/net/log/file_net_log_observer.cc
index e49452df8..ef2c616 100644
--- a/net/log/file_net_log_observer.cc
+++ b/net/log/file_net_log_observer.cc
@@ -117,10 +117,10 @@
   // Stop()).
   virtual void Initialize(std::unique_ptr<base::Value> constants_value) = 0;
 
-  // Closes the events array opened in Initialize() and writes |tab_info| to
-  // disk. If |tab_info| cannot be converted to proper JSON, then it
+  // Closes the events array opened in Initialize() and writes |polled_data| to
+  // disk. If |polled_data| cannot be converted to proper JSON, then it
   // is ignored.
-  virtual void Stop(std::unique_ptr<base::Value> tab_info) = 0;
+  virtual void Stop(std::unique_ptr<base::Value> polled_data) = 0;
 
   // Drains |queue_| from WriteQueue into a local file queue and writes the
   // events in the queue to disk.
@@ -129,6 +129,9 @@
   // Deletes all netlog files. It is not valid to call any method of
   // FileNetLogObserver after DeleteAllFiles().
   virtual void DeleteAllFiles() = 0;
+
+  void FlushThenStop(scoped_refptr<WriteQueue> write_queue,
+                     std::unique_ptr<base::Value> polled_data);
 };
 
 // This implementation of FileWriter is used when the observer is in bounded
@@ -147,7 +150,7 @@
 
   // FileNetLogObserver::FileWriter implementation
   void Initialize(std::unique_ptr<base::Value> constants_value) override;
-  void Stop(std::unique_ptr<base::Value> tab_info) override;
+  void Stop(std::unique_ptr<base::Value> polled_data) override;
   void Flush(scoped_refptr<WriteQueue> write_queue) override;
   void DeleteAllFiles() override;
 
@@ -196,7 +199,7 @@
 
   // FileNetLogObserver::FileWriter implementation
   void Initialize(std::unique_ptr<base::Value> constants_value) override;
-  void Stop(std::unique_ptr<base::Value> tab_info) override;
+  void Stop(std::unique_ptr<base::Value> polled_data) override;
   void Flush(scoped_refptr<WriteQueue> write_queue) override;
   void DeleteAllFiles() override;
 
@@ -297,19 +300,12 @@
   net_log->DeprecatedAddObserver(this, capture_mode);
 }
 
-void FileNetLogObserver::StopObserving(URLRequestContext* url_request_context,
+void FileNetLogObserver::StopObserving(std::unique_ptr<base::Value> polled_data,
                                        const base::Closure& callback) {
-  file_task_runner_->PostTask(
-      FROM_HERE, base::Bind(&FileNetLogObserver::FileWriter::Flush,
-                            base::Unretained(file_writer_), write_queue_));
-
   file_task_runner_->PostTaskAndReply(
-      FROM_HERE,
-      base::Bind(
-          &FileNetLogObserver::FileWriter::Stop, base::Unretained(file_writer_),
-          base::Passed(url_request_context ? GetNetInfo(url_request_context,
-                                                        NET_INFO_ALL_SOURCES)
-                                           : nullptr)),
+      FROM_HERE, base::Bind(&FileNetLogObserver::FileWriter::FlushThenStop,
+                            base::Unretained(file_writer_), write_queue_,
+                            base::Passed(&polled_data)),
       callback);
 
   net_log()->DeprecatedRemoveObserver(this);
@@ -354,6 +350,7 @@
 
   return queue_.size();
 }
+
 void FileNetLogObserver::WriteQueue::SwapQueue(EventQueue* local_queue) {
   DCHECK(local_queue->empty());
   base::AutoLock lock(lock_);
@@ -365,6 +362,13 @@
 
 FileNetLogObserver::FileWriter::~FileWriter() {}
 
+void FileNetLogObserver::FileWriter::FlushThenStop(
+    scoped_refptr<FileNetLogObserver::WriteQueue> write_queue,
+    std::unique_ptr<base::Value> polled_data) {
+  Flush(write_queue);
+  Stop(std::move(polled_data));
+}
+
 FileNetLogObserver::BoundedFileWriter::BoundedFileWriter(
     const base::FilePath& directory,
     size_t max_file_size,
@@ -401,18 +405,18 @@
 }
 
 void FileNetLogObserver::BoundedFileWriter::Stop(
-    std::unique_ptr<base::Value> tab_info) {
+    std::unique_ptr<base::Value> polled_data) {
   DCHECK(task_runner_->RunsTasksOnCurrentThread());
 
   base::ScopedFILE closing_file(
       base::OpenFile(directory_.AppendASCII("end_netlog.json"), "w"));
 
   std::string json;
-  if (tab_info)
-    base::JSONWriter::Write(*tab_info, &json);
+  if (polled_data)
+    base::JSONWriter::Write(*polled_data, &json);
 
   fprintf(closing_file.get(), "]%s}\n",
-          json.empty() ? "" : (",\"tabInfo\": " + json + "\n").c_str());
+          json.empty() ? "" : (",\n\"polledData\": " + json + "\n").c_str());
 
   // Flush all fprintfs to disk so that files can be safely accessed on
   // callback.
@@ -496,15 +500,15 @@
 }
 
 void FileNetLogObserver::UnboundedFileWriter::Stop(
-    std::unique_ptr<base::Value> tab_info) {
+    std::unique_ptr<base::Value> polled_data) {
   DCHECK(task_runner_->RunsTasksOnCurrentThread());
 
   std::string json;
-  if (tab_info)
-    base::JSONWriter::Write(*tab_info, &json);
+  if (polled_data)
+    base::JSONWriter::Write(*polled_data, &json);
 
   fprintf(file_.get(), "]%s}\n",
-          json.empty() ? "" : (",\n\"tabInfo\": " + json + "\n").c_str());
+          json.empty() ? "" : (",\n\"polledData\": " + json + "\n").c_str());
 
   // Flush all fprintfs to disk so that the file can be safely accessed on
   // callback.
diff --git a/net/log/file_net_log_observer.h b/net/log/file_net_log_observer.h
index f8a8d90..b7c8a2f 100644
--- a/net/log/file_net_log_observer.h
+++ b/net/log/file_net_log_observer.h
@@ -109,10 +109,9 @@
   // once all file writing is complete and the netlog files can be accessed
   // safely.
   //
-  // |url_request_context| is an optional argument used to add additional
-  // network stack state to the log. If the context is non-NULL,
-  // StopObserving() must be called on the context's thread.
-  void StopObserving(URLRequestContext* url_request_context,
+  // |polled_data| is an optional argument used to add additional network stack
+  // state to the log.
+  void StopObserving(std::unique_ptr<base::Value> polled_data,
                      const base::Closure& callback);
 
   // NetLog::ThreadSafeObserver
diff --git a/net/log/file_net_log_observer_unittest.cc b/net/log/file_net_log_observer_unittest.cc
index ffe6ba4..a330e48 100644
--- a/net/log/file_net_log_observer_unittest.cc
+++ b/net/log/file_net_log_observer_unittest.cc
@@ -357,22 +357,19 @@
   ASSERT_EQ(kConstantString, constants_string);
 }
 
-TEST_P(FileNetLogObserverTest, GeneratesValidJSONWithContext) {
+TEST_P(FileNetLogObserverTest, GeneratesValidJSONWithPolledData) {
   TestClosure closure;
 
   StartObserving(nullptr, nullptr);
 
-  // Create unique context.
-  TestURLRequestContext context(true);
-  context.set_net_log(&net_log_);
-  const int kDummyParam = 75;
-  std::unique_ptr<HttpNetworkSession::Params> params(
-      new HttpNetworkSession::Params);
-  params->quic_idle_connection_timeout_seconds = kDummyParam;
-  context.set_http_network_session_params(std::move(params));
-  context.Init();
+  // Create dummy polled data
+  const char kDummyPolledDataPath[] = "dummy_path";
+  const char kDummyPolledDataString[] = "dummy_info";
+  std::unique_ptr<base::DictionaryValue> dummy_polled_data =
+      base::MakeUnique<base::DictionaryValue>();
+  dummy_polled_data->SetString(kDummyPolledDataPath, kDummyPolledDataString);
 
-  logger_->StopObserving(&context, closure.closure());
+  logger_->StopObserving(std::move(dummy_polled_data), closure.closure());
 
   closure.WaitForResult();
 
@@ -386,19 +383,15 @@
   // Make sure additional information is present and validate it.
   base::DictionaryValue* dict;
   ASSERT_TRUE(root->GetAsDictionary(&dict));
-  base::DictionaryValue* tab_info;
-  base::DictionaryValue* quic_info;
-  ASSERT_TRUE(dict->GetDictionary("tabInfo", &tab_info));
-  ASSERT_TRUE(tab_info->GetDictionary("quicInfo", &quic_info));
-  base::Value* timeout_value = nullptr;
-  int timeout;
-  ASSERT_TRUE(
-      quic_info->Get("idle_connection_timeout_seconds", &timeout_value));
-  ASSERT_TRUE(timeout_value->GetAsInteger(&timeout));
-  ASSERT_EQ(timeout, kDummyParam);
+  base::DictionaryValue* polled_data;
+  std::string dummy_string;
+  ASSERT_TRUE(dict->GetDictionary("polledData", &polled_data));
+  ASSERT_TRUE(polled_data->GetString(kDummyPolledDataPath, &dummy_string));
+  ASSERT_EQ(dummy_string, kDummyPolledDataString);
 }
 
-TEST_P(FileNetLogObserverTest, GeneratesValidJSONWithContextWithActiveRequest) {
+TEST_P(FileNetLogObserverTest,
+       GeneratesValidJSONWithPolledDataWithActiveRequest) {
   TestClosure closure;
 
   // Create context, start a request.
@@ -415,7 +408,8 @@
 
   StartObserving(nullptr, &context);
 
-  logger_->StopObserving(&context, closure.closure());
+  logger_->StopObserving(net::GetNetInfo(&context, NET_INFO_ALL_SOURCES),
+                         closure.closure());
 
   closure.WaitForResult();
 
@@ -429,8 +423,8 @@
   // Make sure additional information is present, but don't validate it.
   base::DictionaryValue* dict;
   ASSERT_TRUE(root->GetAsDictionary(&dict));
-  base::DictionaryValue* tab_info;
-  ASSERT_TRUE(dict->GetDictionary("tabInfo", &tab_info));
+  base::DictionaryValue* polled_data;
+  ASSERT_TRUE(dict->GetDictionary("polledData", &polled_data));
 }
 
 // Adds events concurrently from several different threads. The exact order of
diff --git a/net/quic/core/congestion_control/bandwidth_sampler.h b/net/quic/core/congestion_control/bandwidth_sampler.h
index a9ec905..0bedd6c 100644
--- a/net/quic/core/congestion_control/bandwidth_sampler.h
+++ b/net/quic/core/congestion_control/bandwidth_sampler.h
@@ -5,10 +5,10 @@
 #ifndef NET_QUIC_CORE_CONGESTION_CONTROL_BANDWIDTH_SAMPLER_H_
 #define NET_QUIC_CORE_CONGESTION_CONTROL_BANDWIDTH_SAMPLER_H_
 
-#include "net/base/linked_hash_map.h"
 #include "net/quic/core/quic_bandwidth.h"
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_time.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/platform/api/quic_export.h"
 
 namespace net {
@@ -208,7 +208,7 @@
           is_app_limited(sampler.is_app_limited_) {}
   };
 
-  typedef linked_hash_map<QuicPacketNumber, ConnectionStateOnSentPacket>
+  typedef QuicLinkedHashMap<QuicPacketNumber, ConnectionStateOnSentPacket>
       ConnectionStateMap;
 
   // The total number of congestion controlled bytes sent during the connection.
diff --git a/net/quic/core/frames/quic_ack_frame.h b/net/quic/core/frames/quic_ack_frame.h
index 9e76aee..816a4ac 100644
--- a/net/quic/core/frames/quic_ack_frame.h
+++ b/net/quic/core/frames/quic_ack_frame.h
@@ -9,8 +9,8 @@
 #include <string>
 
 #include "base/strings/string_piece.h"
-#include "net/quic/core/interval_set.h"
 #include "net/quic/core/quic_types.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/platform/api/quic_export.h"
 
 namespace net {
@@ -20,9 +20,9 @@
 // larger new packet numbers are added, with the occasional random access.
 class QUIC_EXPORT_PRIVATE PacketNumberQueue {
  public:
-  using const_iterator = IntervalSet<QuicPacketNumber>::const_iterator;
+  using const_iterator = QuicIntervalSet<QuicPacketNumber>::const_iterator;
   using const_reverse_iterator =
-      IntervalSet<QuicPacketNumber>::const_reverse_iterator;
+      QuicIntervalSet<QuicPacketNumber>::const_reverse_iterator;
 
   PacketNumberQueue();
   PacketNumberQueue(const PacketNumberQueue& other);
@@ -92,7 +92,7 @@
       const PacketNumberQueue& q);
 
  private:
-  IntervalSet<QuicPacketNumber> packet_number_intervals_;
+  QuicIntervalSet<QuicPacketNumber> packet_number_intervals_;
 };
 
 struct QUIC_EXPORT_PRIVATE QuicAckFrame {
diff --git a/net/quic/core/quic_blocked_writer_interface.h b/net/quic/core/quic_blocked_writer_interface.h
index ea6bd47..3a85b80 100644
--- a/net/quic/core/quic_blocked_writer_interface.h
+++ b/net/quic/core/quic_blocked_writer_interface.h
@@ -24,15 +24,6 @@
   virtual void OnCanWrite() = 0;
 };
 
-// Hash pointers as if they were int's, but bring more entropy to the lower
-// bits.
-struct QuicBlockedWriterInterfacePtrHash {
-  std::size_t operator()(const net::QuicBlockedWriterInterface* ptr) const {
-    size_t k = reinterpret_cast<size_t>(ptr);
-    return k + (k >> 6);
-  }
-};
-
 }  // namespace net
 
 #endif  // NET_QUIC_CORE_QUIC_BLOCKED_WRITER_INTERFACE_H_
diff --git a/net/quic/core/quic_buffered_packet_store.h b/net/quic/core/quic_buffered_packet_store.h
index 1319a8ef..8c4e42d3 100644
--- a/net/quic/core/quic_buffered_packet_store.h
+++ b/net/quic/core/quic_buffered_packet_store.h
@@ -7,12 +7,12 @@
 
 #include <list>
 
-#include "net/base/linked_hash_map.h"
 #include "net/quic/core/quic_alarm.h"
 #include "net/quic/core/quic_alarm_factory.h"
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_time.h"
 #include "net/quic/platform/api/quic_clock.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/platform/api/quic_export.h"
 #include "net/quic/platform/api/quic_socket_address.h"
 
@@ -68,7 +68,7 @@
     QuicTime creation_time;
   };
 
-  typedef linked_hash_map<QuicConnectionId, BufferedPacketList>
+  typedef QuicLinkedHashMap<QuicConnectionId, BufferedPacketList>
       BufferedPacketMap;
 
   class QUIC_EXPORT_PRIVATE VisitorInterface {
@@ -155,7 +155,7 @@
 
   // Keeps track of connection with CHLO buffered up already and the order they
   // arrive.
-  linked_hash_map<QuicConnectionId, bool> connections_with_chlo_;
+  QuicLinkedHashMap<QuicConnectionId, bool> connections_with_chlo_;
 };
 
 }  // namespace net
diff --git a/net/quic/core/quic_one_block_arena_test.cc b/net/quic/core/quic_one_block_arena_test.cc
index 2f060252..2a81c219 100644
--- a/net/quic/core/quic_one_block_arena_test.cc
+++ b/net/quic/core/quic_one_block_arena_test.cc
@@ -6,7 +6,7 @@
 
 #include <cstdint>
 
-#include "net/quic/core/interval_set.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/test_tools/quic_test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -41,7 +41,7 @@
 TEST(QuicOneBlockArenaTest, NoOverlaps) {
   QuicOneBlockArena<1024> arena;
   std::vector<QuicArenaScopedPtr<TestObject>> objects;
-  IntervalSet<uintptr_t> used;
+  QuicIntervalSet<uintptr_t> used;
   for (size_t i = 0; i < 1024 / kMaxAlign; ++i) {
     QuicArenaScopedPtr<TestObject> ptr = arena.New<TestObject>();
     EXPECT_TRUE(ptr.is_from_arena());
diff --git a/net/quic/core/quic_sent_packet_manager.h b/net/quic/core/quic_sent_packet_manager.h
index 7ed8b90..c135b2f9 100644
--- a/net/quic/core/quic_sent_packet_manager.h
+++ b/net/quic/core/quic_sent_packet_manager.h
@@ -14,7 +14,6 @@
 #include <vector>
 
 #include "base/macros.h"
-#include "net/base/linked_hash_map.h"
 #include "net/quic/core/congestion_control/general_loss_algorithm.h"
 #include "net/quic/core/congestion_control/loss_detection_interface.h"
 #include "net/quic/core/congestion_control/pacing_sender.h"
@@ -24,6 +23,7 @@
 #include "net/quic/core/quic_pending_retransmission.h"
 #include "net/quic/core/quic_sustained_bandwidth_recorder.h"
 #include "net/quic/core/quic_unacked_packet_map.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/platform/api/quic_export.h"
 
 namespace net {
@@ -240,7 +240,7 @@
     LOSS_MODE,
   };
 
-  typedef linked_hash_map<QuicPacketNumber, TransmissionType>
+  typedef QuicLinkedHashMap<QuicPacketNumber, TransmissionType>
       PendingRetransmissionMap;
 
   // Updates the least_packet_awaited_by_peer.
diff --git a/net/quic/core/quic_session.h b/net/quic/core/quic_session.h
index 87d60f0d..77fc237 100644
--- a/net/quic/core/quic_session.h
+++ b/net/quic/core/quic_session.h
@@ -12,12 +12,10 @@
 #include <map>
 #include <memory>
 #include <string>
-#include <unordered_map>
 #include <unordered_set>
 #include <vector>
 
 #include "base/compiler_specific.h"
-#include "base/containers/small_map.h"
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
 #include "net/quic/core/quic_connection.h"
@@ -26,6 +24,7 @@
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_stream.h"
 #include "net/quic/core/quic_write_blocked_list.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/platform/api/quic_export.h"
 
 namespace net {
@@ -250,12 +249,10 @@
   bool flow_control_invariant() { return flow_control_invariant_; }
 
  protected:
-  using StaticStreamMap =
-      base::SmallMap<std::unordered_map<QuicStreamId, QuicStream*>, 2>;
+  using StaticStreamMap = QuicSmallMap<QuicStreamId, QuicStream*, 2>;
 
-  using DynamicStreamMap = base::SmallMap<
-      std::unordered_map<QuicStreamId, std::unique_ptr<QuicStream>>,
-      10>;
+  using DynamicStreamMap =
+      QuicSmallMap<QuicStreamId, std::unique_ptr<QuicStream>, 10>;
 
   using ClosedStreams = std::vector<std::unique_ptr<QuicStream>>;
 
diff --git a/net/quic/platform/api/quic_containers.h b/net/quic/platform/api/quic_containers.h
new file mode 100644
index 0000000..5c007e0f
--- /dev/null
+++ b/net/quic/platform/api/quic_containers.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_PLATFORM_API_QUIC_CONTAINERS_H_
+#define NET_QUIC_PLATFORM_API_QUIC_CONTAINERS_H_
+
+#include "net/quic/platform/impl/quic_containers_impl.h"
+
+namespace net {
+
+// A map which offers insertion-ordered iteration.
+template <typename Key, typename Value>
+using QuicLinkedHashMap = QuicLinkedHashMapImpl<Key, Value>;
+
+// Used for maps that are typically small, then it is faster than (for example)
+// hash_map which is optimized for large data sets. QuicSmallMap upgrades itself
+// automatically to a QuicSmallMapImpl-specified map when it runs out of space.
+template <typename Key, typename Value, int Size>
+using QuicSmallMap = QuicSmallMapImpl<Key, Value, Size>;
+
+// A data structure used to represent a sorted set of non-empty, non-adjacent,
+// and mutually disjoint intervals.
+template <typename T>
+using QuicIntervalSet = QuicIntervalSetImpl<T>;
+
+}  // namespace net
+
+#endif  // NET_QUIC_PLATFORM_API_QUIC_CONTAINERS_H_
diff --git a/net/quic/platform/impl/quic_containers_impl.h b/net/quic/platform/impl/quic_containers_impl.h
new file mode 100644
index 0000000..287b73b3
--- /dev/null
+++ b/net/quic/platform/impl/quic_containers_impl.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_PLATFORM_IMPL_QUIC_CONTAINERS_IMPL_H_
+#define NET_QUIC_PLATFORM_IMPL_QUIC_CONTAINERS_IMPL_H_
+
+#include <unordered_map>
+
+#include "base/containers/small_map.h"
+#include "net/base/interval_set.h"
+#include "net/base/linked_hash_map.h"
+
+namespace net {
+
+// A map which offers insertion-ordered iteration.
+template <typename Key, typename Value>
+using QuicLinkedHashMapImpl = linked_hash_map<Key, Value>;
+
+// A map which is faster than (for example) hash_map for a certain number of
+// unique key-value-pair elements, and upgrades itself to unordered_map when
+// runs out of space.
+template <typename Key, typename Value, int Size>
+using QuicSmallMapImpl = base::SmallMap<std::unordered_map<Key, Value>, Size>;
+
+// A data structure used to represent a sorted set of non-empty, non-adjacent,
+// and mutually disjoint intervals.
+template <typename T>
+using QuicIntervalSetImpl = IntervalSet<T>;
+
+}  // namespace net
+
+#endif  // NET_QUIC_PLATFORM_IMPL_QUIC_CONTAINERS_IMPL_H_
diff --git a/net/tools/quic/quic_client.h b/net/tools/quic/quic_client.h
index 7a479f6..1329ae0 100644
--- a/net/tools/quic/quic_client.h
+++ b/net/tools/quic/quic_client.h
@@ -18,6 +18,7 @@
 #include "net/quic/core/quic_client_push_promise_index.h"
 #include "net/quic/core/quic_config.h"
 #include "net/quic/core/quic_spdy_stream.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/tools/epoll_server/epoll_server.h"
 #include "net/tools/quic/quic_client_base.h"
 #include "net/tools/quic/quic_client_session.h"
@@ -90,7 +91,7 @@
 
   EpollServer* epoll_server() { return epoll_server_; }
 
-  const linked_hash_map<int, QuicSocketAddress>& fd_address_map() const {
+  const QuicLinkedHashMap<int, QuicSocketAddress>& fd_address_map() const {
     return fd_address_map_;
   }
 
@@ -105,7 +106,7 @@
 
   // Map mapping created UDP sockets to their addresses. By using linked hash
   // map, the order of socket creation can be recorded.
-  linked_hash_map<int, QuicSocketAddress> fd_address_map_;
+  QuicLinkedHashMap<int, QuicSocketAddress> fd_address_map_;
 
   // If overflow_supported_ is true, this will be the number of packets dropped
   // during the lifetime of the server.
diff --git a/net/tools/quic/quic_dispatcher.h b/net/tools/quic/quic_dispatcher.h
index 527eaa5b..266e3e4 100644
--- a/net/tools/quic/quic_dispatcher.h
+++ b/net/tools/quic/quic_dispatcher.h
@@ -13,7 +13,6 @@
 #include <vector>
 
 #include "base/macros.h"
-#include "net/base/linked_hash_map.h"
 #include "net/quic/core/crypto/quic_compressed_certs_cache.h"
 #include "net/quic/core/crypto/quic_random.h"
 #include "net/quic/core/quic_blocked_writer_interface.h"
@@ -23,6 +22,7 @@
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_session.h"
 #include "net/quic/core/quic_version_manager.h"
+#include "net/quic/platform/api/quic_containers.h"
 #include "net/quic/platform/api/quic_socket_address.h"
 
 #include "net/tools/quic/quic_process_packet_interface.h"
@@ -44,10 +44,7 @@
                        public QuicBufferedPacketStore::VisitorInterface {
  public:
   // Ideally we'd have a linked_hash_set: the  boolean is unused.
-  typedef linked_hash_map<QuicBlockedWriterInterface*,
-                          bool,
-                          QuicBlockedWriterInterfacePtrHash>
-      WriteBlockedList;
+  typedef QuicLinkedHashMap<QuicBlockedWriterInterface*, bool> WriteBlockedList;
 
   QuicDispatcher(const QuicConfig& config,
                  const QuicCryptoServerConfig* crypto_config,
diff --git a/net/tools/quic/quic_time_wait_list_manager.h b/net/tools/quic/quic_time_wait_list_manager.h
index 3c4dc02..998bb36 100644
--- a/net/tools/quic/quic_time_wait_list_manager.h
+++ b/net/tools/quic/quic_time_wait_list_manager.h
@@ -15,13 +15,13 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "net/base/linked_hash_map.h"
 #include "net/quic/core/quic_blocked_writer_interface.h"
 #include "net/quic/core/quic_connection.h"
 #include "net/quic/core/quic_framer.h"
 #include "net/quic/core/quic_packet_writer.h"
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_session.h"
+#include "net/quic/platform/api/quic_containers.h"
 
 namespace net {
 
@@ -181,8 +181,8 @@
     bool connection_rejected_statelessly;
   };
 
-  // linked_hash_map allows lookup by ConnectionId and traversal in add order.
-  typedef linked_hash_map<QuicConnectionId, ConnectionIdData> ConnectionIdMap;
+  // QuicLinkedHashMap allows lookup by ConnectionId and traversal in add order.
+  typedef QuicLinkedHashMap<QuicConnectionId, ConnectionIdData> ConnectionIdMap;
   ConnectionIdMap connection_id_map_;
 
   // Pending public reset packets that need to be sent out to the client
diff --git a/remoting/host/win/BUILD.gn b/remoting/host/win/BUILD.gn
index 780cd50..581e702 100644
--- a/remoting/host/win/BUILD.gn
+++ b/remoting/host/win/BUILD.gn
@@ -116,7 +116,6 @@
   deps = [
     "//base:i18n",
     "//components/policy/core/common",
-    "//content/public/common",
     "//crypto",
     "//device/power_save_blocker",
     "//google_apis",
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 6153cdf..abb7af6 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -14,4 +14,9 @@
     "//services/image_decoder:tests",
     "//services/test:run_all_unittests",
   ]
+
+  if (is_android) {
+    # Some tests need to initialize V8.
+    deps += [ "//v8:v8_external_startup_data_assets" ]
+  }
 }
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 1cc22fe6..bfca386 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -929,6 +929,38 @@
         "test": "sandbox_linux_unittests"
       },
       {
+        "override_isolate_target": "service_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:dec8cc6fd715753846d0aca1693dc63844ea55d6"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "hard_timeout": 120,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "service_unittests"
+      },
+      {
         "override_isolate_target": "sql_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index c2f04d0..e28a15c 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -405,6 +405,15 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "args": [
+          "--ozone-platform=x11"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -791,6 +800,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -914,7 +929,7 @@
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 10
+          "shards": 20
         },
         "test": "browser_tests"
       },
@@ -1131,6 +1146,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index bb609f85..5f6a780 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -2193,6 +2193,38 @@
         "test": "sandbox_linux_unittests"
       },
       {
+        "override_isolate_target": "service_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:dec8cc6fd715753846d0aca1693dc63844ea55d6"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "KTU84P",
+              "device_type": "hammerhead"
+            }
+          ],
+          "hard_timeout": 120,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "service_unittests"
+      },
+      {
         "override_isolate_target": "sql_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -3119,6 +3151,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -3719,6 +3757,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -4116,6 +4160,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index b0d76dbd..bab06cb 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -297,6 +297,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -655,6 +661,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -1014,6 +1026,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
@@ -1361,6 +1379,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "skia_unittests"
       },
       {
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index 957053f..c49e356 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -373,6 +373,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "setup_unittests"
       },
       {
@@ -825,6 +831,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "setup_unittests"
       },
       {
@@ -1292,6 +1304,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "setup_unittests"
       },
       {
@@ -1700,6 +1718,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "service_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "setup_unittests"
       },
       {
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index a70eec7..f46cc83 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -754,6 +754,10 @@
     "label": "//services/service_manager/tests:service_manager_unittests",
     "type": "console_test_launcher",
   },
+  "service_unittests": {
+    "label": "//services:service_unittests",
+    "type": "console_test_launcher",
+  },
   "setup_unittests": {
     "label": "//chrome/installer/setup:setup_unittests",
     "type": "console_test_launcher",
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 7584d36..dde4a27 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2000,27 +2000,6 @@
             ]
         }
     ],
-    "SafeBrowsingReportPhishingErrorLink": [
-        {
-            "platforms": [
-                "android",
-                "linux",
-                "mac",
-                "win"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled"
-                },
-                {
-                    "name": "Control"
-                },
-                {
-                    "name": "Disabled"
-                }
-            ]
-        }
-    ],
     "SafeBrowsingScoutTransitionStudy": [
         {
             "platforms": [
@@ -2056,18 +2035,16 @@
             ]
         }
     ],
-    "SafeBrowsingUpdateFrequency": [
+    "SafeBrowsingUseLocalBlacklist": [
         {
             "platforms": [
-                "linux",
-                "mac",
-                "win"
+                "android"
             ],
             "experiments": [
                 {
-                    "name": "UpdateTime15m",
+                    "name": "Use3PAPI",
                     "params": {
-                        "NextUpdateIntervalInMinutes": "15"
+                        "check_local_blacklist": "false"
                     }
                 }
             ]
diff --git a/third_party/WebKit/LayoutTests/bluetooth/generate.py b/third_party/WebKit/LayoutTests/bluetooth/generate.py
index 127d3e4..b81d14706 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/generate.py
+++ b/third_party/WebKit/LayoutTests/bluetooth/generate.py
@@ -95,8 +95,14 @@
 
         template_name = os.path.splitext(os.path.basename(template))[0]
 
-        result = re.search(r'CALLS\(\[(.*?)\]\)', template_file_data, re.MULTILINE
-            | re.DOTALL)
+        # Find function names in multiline pattern: CALLS( [ function_name,function_name2[UUID] ])
+        result = re.search(
+            r'CALLS\(' + # CALLS(
+            r'[^\[]*' +  # Any characters not [, allowing for new lines.
+            r'\[' +      # [
+            r'(.*?)' +   # group matching: function_name(), function_name2[UUID]
+            r'\]\)',     # adjacent closing characters: ])
+            template_file_data, re.MULTILINE | re.DOTALL)
 
         if result is None:
             raise Exception('Template must contain \'CALLS\' tokens')
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/device-disconnects-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/device-disconnects-before.js
index a1d51bdb..d38ca4a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/device-disconnects-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/device-disconnects-before.js
@@ -1,21 +1,24 @@
 'use strict';
 promise_test(t => {
   return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}],
-      optionalServices: [request_disconnection_service_uuid]
-    }))
-    .then(device => {
-      return device.gatt.connect()
-        .then(gattServer => get_request_disconnection(gattServer))
-        .then(requestDisconnection => requestDisconnection())
-        .then(() => assert_promise_rejects_with_message(
-          device.gatt.CALLS([
-            getPrimaryService('heart_rate')|
-            getPrimaryServices()|
-            getPrimaryServices('heart_rate')[UUID]]),
-          new DOMException(
-            'GATT Server is disconnected. Cannot retrieve services.',
-            'NetworkError')));
-    });
+      .then(() => requestDeviceWithKeyDown({
+              filters: [{services: ['heart_rate']}],
+              optionalServices: [request_disconnection_service_uuid]
+            }))
+      .then(device => {
+        return device.gatt.connect()
+            .then(gattServer => get_request_disconnection(gattServer))
+            .then(requestDisconnection => requestDisconnection())
+            .then(
+                () => assert_promise_rejects_with_message(
+                    device.gatt.CALLS(
+                        [getPrimaryService('heart_rate') |
+                         getPrimaryServices() |
+                         getPrimaryServices('heart_rate')[UUID]]),
+                    new DOMException(
+                        'GATT Server is disconnected. ' +
+                            'Cannot retrieve services. ' +
+                            '(Re)connect first with `device.gatt.connect`.',
+                        'NetworkError')));
+      });
 }, 'Device disconnects before FUNCTION_NAME. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
index 0c305be..4c6d9db 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
@@ -2,23 +2,25 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.CALLS([
-          getPrimaryService('heart_rate')|
-          getPrimaryServices()|
-          getPrimaryServices('heart_rate')[UUID]]),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      // Disconnect called to clear attributeInstanceMap and allow the
-      // object to get garbage collected.
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () =>
+              requestDeviceWithKeyDown({filters: [{services: ['heart_rate']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.CALLS(
+                [getPrimaryService('heart_rate') | getPrimaryServices() |
+                 getPrimaryServices('heart_rate')[UUID]]),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        // Disconnect called to clear attributeInstanceMap and allow the
+        // object to get garbage collected.
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a FUNCTION_NAME call that failed. ' +
    'Should not crash.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
index 3304c81..f2bab5df 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
@@ -2,21 +2,24 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['health_thermometer']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.CALLS([
-          getPrimaryService('health_thermometer')|
-          getPrimaryServices()|
-          getPrimaryServices('health_thermometer')[UUID]]),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () => requestDeviceWithKeyDown(
+              {filters: [{services: ['health_thermometer']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.CALLS(
+                [getPrimaryService('health_thermometer') |
+                 getPrimaryServices() |
+                 getPrimaryServices('health_thermometer')[UUID]]),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a FUNCTION_NAME call that succeeds. ' +
    'Should not crash.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-error.html
index 92e7cea9..d66147c8 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-error.html
@@ -18,7 +18,9 @@
            requestDisconnection();
            return assert_promise_rejects_with_message(
              device.gatt.getPrimaryService('battery_service'),
-             new DOMException('GATT Server disconnected while retrieving services.',
+             new DOMException('GATT Server is disconnected. ' +
+                                'Cannot retrieve services. ' +
+                                '(Re)connect first with `device.gatt.connect`.',
                               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-success.html
index 32d6a75..a106aa32 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-disconnects-during-success.html
@@ -17,7 +17,9 @@
           requestDisconnection();
           return assert_promise_rejects_with_message(
             device.gatt.getPrimaryService('heart_rate'),
-            new DOMException('GATT Server disconnected while retrieving services.',
+            new DOMException('GATT Server is disconnected. ' +
+                               'Cannot retrieve services. ' +
+                               '(Re)connect first with `device.gatt.connect`.',
                              'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-error.html
index e7b8fc6..30bd57d 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-error.html
@@ -15,7 +15,9 @@
       let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         gatt.getPrimaryService('battery_service'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => gatt.connect()).then(() => promise);
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-success.html
index 37113d0..6f0110e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/device-reconnects-during-success.html
@@ -14,7 +14,9 @@
       let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         gatt.getPrimaryService('heart_rate'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => gatt.connect()).then(() => promise);
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-before.html
index 8710d160..f781400c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-before.html
@@ -13,7 +13,9 @@
       gattServer.disconnect();
       return assert_promise_rejects_with_message(
         gattServer.getPrimaryService('heart_rate'),
-        new DOMException('GATT Server is disconnected. Cannot retrieve services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
     });
 }, 'disconnect() called before getPrimaryService. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-error.html
index 6615258..88d0f88 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-error.html
@@ -13,7 +13,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryService('battery_service'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-success.html
index e65a881..2691e6b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnect-called-during-success.html
@@ -12,7 +12,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryService('heart_rate'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnected-device.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnected-device.html
index 4f3eb6cd..c93318f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnected-device.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/disconnected-device.html
@@ -10,7 +10,9 @@
       filters: [{services: ['heart_rate']}]}))
     .then(device => assert_promise_rejects_with_message(
       device.gatt.getPrimaryService('heart_rate'),
-      new DOMException('GATT Server is disconnected. Cannot retrieve services.',
+      new DOMException('GATT Server is disconnected. ' +
+                         'Cannot retrieve services. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                        'NetworkError')));
 }, 'getPrimaryService() called before connecting. Reject with NetworkError.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-device-disconnects-before.html
index c4e53f5..6a3cf5b4 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-device-disconnects-before.html
@@ -7,20 +7,23 @@
 'use strict';
 promise_test(t => {
   return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}],
-      optionalServices: [request_disconnection_service_uuid]
-    }))
-    .then(device => {
-      return device.gatt.connect()
-        .then(gattServer => get_request_disconnection(gattServer))
-        .then(requestDisconnection => requestDisconnection())
-        .then(() => assert_promise_rejects_with_message(
-          device.gatt.getPrimaryService('heart_rate'),
-          new DOMException(
-            'GATT Server is disconnected. Cannot retrieve services.',
-            'NetworkError')));
-    });
+      .then(() => requestDeviceWithKeyDown({
+              filters: [{services: ['heart_rate']}],
+              optionalServices: [request_disconnection_service_uuid]
+            }))
+      .then(device => {
+        return device.gatt.connect()
+            .then(gattServer => get_request_disconnection(gattServer))
+            .then(requestDisconnection => requestDisconnection())
+            .then(
+                () => assert_promise_rejects_with_message(
+                    device.gatt.getPrimaryService('heart_rate'),
+                    new DOMException(
+                        'GATT Server is disconnected. ' +
+                            'Cannot retrieve services. ' +
+                            '(Re)connect first with `device.gatt.connect`.',
+                        'NetworkError')));
+      });
 }, 'Device disconnects before getPrimaryService. Reject with NetworkError.');
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.html
index 8805e1c..42d9af2 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.html
@@ -8,21 +8,24 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.getPrimaryService('heart_rate'),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      // Disconnect called to clear attributeInstanceMap and allow the
-      // object to get garbage collected.
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () =>
+              requestDeviceWithKeyDown({filters: [{services: ['heart_rate']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.getPrimaryService('heart_rate'),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        // Disconnect called to clear attributeInstanceMap and allow the
+        // object to get garbage collected.
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a getPrimaryService call that failed. ' +
    'Should not crash.');
 
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.html
index ca80f091..09ca3a028 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.html
@@ -8,19 +8,22 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['health_thermometer']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.getPrimaryService('health_thermometer'),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () => requestDeviceWithKeyDown(
+              {filters: [{services: ['health_thermometer']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.getPrimaryService('health_thermometer'),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a getPrimaryService call that succeeds. ' +
    'Should not crash.');
 
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-error.html
index f0aebe6..44896d5 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-error.html
@@ -13,7 +13,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryService('battery_service'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return gattServer.connect().then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-success.html
index 336040e4..5ee0904 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/reconnect-during-success.html
@@ -12,7 +12,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryService('heart_rate'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return gattServer.connect().then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-error-with-uuid.html
index 00beaa9..22aa0b16 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-error-with-uuid.html
@@ -18,7 +18,9 @@
           requestDisconnection();
           return assert_promise_rejects_with_message(
             device.gatt.getPrimaryServices('battery_service'),
-            new DOMException('GATT Server disconnected while retrieving services.',
+            new DOMException('GATT Server is disconnected. ' +
+                               'Cannot retrieve services. ' +
+                               '(Re)connect first with `device.gatt.connect`.',
                              'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success-with-uuid.html
index 02d4a7a..f4612b21 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success-with-uuid.html
@@ -17,7 +17,9 @@
           requestDisconnection();
           return assert_promise_rejects_with_message(
             device.gatt.getPrimaryServices('heart_rate'),
-            new DOMException('GATT Server disconnected while retrieving services.',
+            new DOMException('GATT Server is disconnected. ' +
+                               'Cannot retrieve services. ' +
+                               '(Re)connect first with `device.gatt.connect`.',
                              'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success.html
index eb43988f..4729e703 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-disconnects-during-success.html
@@ -17,7 +17,9 @@
           requestDisconnection();
           return assert_promise_rejects_with_message(
             device.gatt.getPrimaryServices(),
-            new DOMException('GATT Server disconnected while retrieving services.',
+            new DOMException('GATT Server is disconnected. ' +
+                               'Cannot retrieve services. ' +
+                               '(Re)connect first with `device.gatt.connect`.',
                              'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-error-with-uuid.html
index 0127897..ad7c1ec8 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-error-with-uuid.html
@@ -15,7 +15,9 @@
       let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         gatt.getPrimaryServices('battery_service'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => gatt.connect()).then(() => promise);
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success-with-uuid.html
index 4783179..b45338f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success-with-uuid.html
@@ -13,7 +13,9 @@
       let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         gatt.getPrimaryServices('heart_rate'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => gatt.connect()).then(() => promise);
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success.html
index ceb5fa4..dd57748 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/device-reconnects-during-success.html
@@ -14,7 +14,9 @@
       let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         gatt.getPrimaryServices(),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => gatt.connect()).then(() => promise);
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before-with-uuid.html
index deadc01e..1fbfb97 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before-with-uuid.html
@@ -14,7 +14,9 @@
       gattServer.disconnect();
       return assert_promise_rejects_with_message(
         gattServer.getPrimaryServices('heart_rate'),
-        new DOMException('GATT Server is disconnected. Cannot retrieve services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
     });
 }, 'disconnect() called before getPrimaryServices. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before.html
index 6b54a11..829889de 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-before.html
@@ -14,7 +14,9 @@
       gattServer.disconnect();
       return assert_promise_rejects_with_message(
         gattServer.getPrimaryServices(),
-        new DOMException('GATT Server is disconnected. Cannot retrieve services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
     });
 }, 'disconnect() called before getPrimaryServices. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-error-with-uuid.html
index 9230211..4d07675c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-error-with-uuid.html
@@ -13,7 +13,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryServices('battery_service'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success-with-uuid.html
index 4a0393e..7e915c8 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success-with-uuid.html
@@ -13,7 +13,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryServices('heart_rate'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success.html
index 56cd37cc..21995d1 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnect-called-during-success.html
@@ -13,7 +13,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryServices(),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device-with-uuid.html
index c7ce6ea..3eac0fd 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device-with-uuid.html
@@ -10,7 +10,9 @@
       filters: [{services: ['heart_rate']}]}))
     .then(device => assert_promise_rejects_with_message(
       device.gatt.getPrimaryServices('heart_rate'),
-      new DOMException('GATT Server is disconnected. Cannot retrieve services.',
+      new DOMException('GATT Server is disconnected. ' +
+                         'Cannot retrieve services. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                        'NetworkError')));
 }, 'getPrimaryServices called before connecting. Reject with NetworkError.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device.html
index ac78bc4..465eef59 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/disconnected-device.html
@@ -10,7 +10,9 @@
       filters: [{services: ['heart_rate']}]}))
     .then(device => assert_promise_rejects_with_message(
       device.gatt.getPrimaryServices(),
-      new DOMException('GATT Server is disconnected. Cannot retrieve services.',
+      new DOMException('GATT Server is disconnected. ' +
+                         'Cannot retrieve services. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                        'NetworkError')));
 }, 'getPrimaryServices() called before connecting. Reject with NetworkError.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before-with-uuid.html
index 554bf65..6cecf4e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before-with-uuid.html
@@ -7,20 +7,23 @@
 'use strict';
 promise_test(t => {
   return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}],
-      optionalServices: [request_disconnection_service_uuid]
-    }))
-    .then(device => {
-      return device.gatt.connect()
-        .then(gattServer => get_request_disconnection(gattServer))
-        .then(requestDisconnection => requestDisconnection())
-        .then(() => assert_promise_rejects_with_message(
-          device.gatt.getPrimaryServices('heart_rate'),
-          new DOMException(
-            'GATT Server is disconnected. Cannot retrieve services.',
-            'NetworkError')));
-    });
+      .then(() => requestDeviceWithKeyDown({
+              filters: [{services: ['heart_rate']}],
+              optionalServices: [request_disconnection_service_uuid]
+            }))
+      .then(device => {
+        return device.gatt.connect()
+            .then(gattServer => get_request_disconnection(gattServer))
+            .then(requestDisconnection => requestDisconnection())
+            .then(
+                () => assert_promise_rejects_with_message(
+                    device.gatt.getPrimaryServices('heart_rate'),
+                    new DOMException(
+                        'GATT Server is disconnected. ' +
+                            'Cannot retrieve services. ' +
+                            '(Re)connect first with `device.gatt.connect`.',
+                        'NetworkError')));
+      });
 }, 'Device disconnects before getPrimaryServices. Reject with NetworkError.');
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before.html
index a89a8c1..885c693 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-device-disconnects-before.html
@@ -7,20 +7,23 @@
 'use strict';
 promise_test(t => {
   return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}],
-      optionalServices: [request_disconnection_service_uuid]
-    }))
-    .then(device => {
-      return device.gatt.connect()
-        .then(gattServer => get_request_disconnection(gattServer))
-        .then(requestDisconnection => requestDisconnection())
-        .then(() => assert_promise_rejects_with_message(
-          device.gatt.getPrimaryServices(),
-          new DOMException(
-            'GATT Server is disconnected. Cannot retrieve services.',
-            'NetworkError')));
-    });
+      .then(() => requestDeviceWithKeyDown({
+              filters: [{services: ['heart_rate']}],
+              optionalServices: [request_disconnection_service_uuid]
+            }))
+      .then(device => {
+        return device.gatt.connect()
+            .then(gattServer => get_request_disconnection(gattServer))
+            .then(requestDisconnection => requestDisconnection())
+            .then(
+                () => assert_promise_rejects_with_message(
+                    device.gatt.getPrimaryServices(),
+                    new DOMException(
+                        'GATT Server is disconnected. ' +
+                            'Cannot retrieve services. ' +
+                            '(Re)connect first with `device.gatt.connect`.',
+                        'NetworkError')));
+      });
 }, 'Device disconnects before getPrimaryServices. Reject with NetworkError.');
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.html
index 77dc9bb2..e3f632b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.html
@@ -8,21 +8,24 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.getPrimaryServices('heart_rate'),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      // Disconnect called to clear attributeInstanceMap and allow the
-      // object to get garbage collected.
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () =>
+              requestDeviceWithKeyDown({filters: [{services: ['heart_rate']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.getPrimaryServices('heart_rate'),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        // Disconnect called to clear attributeInstanceMap and allow the
+        // object to get garbage collected.
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a getPrimaryServices call that failed. ' +
    'Should not crash.');
 
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.html
index c53416a..4bc900a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.html
@@ -8,21 +8,24 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.getPrimaryServices(),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      // Disconnect called to clear attributeInstanceMap and allow the
-      // object to get garbage collected.
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () =>
+              requestDeviceWithKeyDown({filters: [{services: ['heart_rate']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.getPrimaryServices(),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        // Disconnect called to clear attributeInstanceMap and allow the
+        // object to get garbage collected.
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a getPrimaryServices call that failed. ' +
    'Should not crash.');
 
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.html
index 6bcaa648..89e6565 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.html
@@ -8,19 +8,22 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['health_thermometer']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.getPrimaryServices('health_thermometer'),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () => requestDeviceWithKeyDown(
+              {filters: [{services: ['health_thermometer']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.getPrimaryServices('health_thermometer'),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a getPrimaryServices call that succeeds. ' +
    'Should not crash.');
 
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.html
index fb235165..5185205 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.html
@@ -8,19 +8,22 @@
 promise_test(() => {
   let promise;
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['health_thermometer']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      promise = assert_promise_rejects_with_message(
-        gattServer.getPrimaryServices(),
-        new DOMException(
-          'GATT Server disconnected while retrieving services.',
-          'NetworkError'));
-      gattServer.disconnect();
-    })
-    .then(runGarbageCollection)
-    .then(() => promise);
+      .then(
+          () => requestDeviceWithKeyDown(
+              {filters: [{services: ['health_thermometer']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        promise = assert_promise_rejects_with_message(
+            gattServer.getPrimaryServices(),
+            new DOMException(
+                'GATT Server is disconnected. ' +
+                    'Cannot retrieve services. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
+                'NetworkError'));
+        gattServer.disconnect();
+      })
+      .then(runGarbageCollection)
+      .then(() => promise);
 }, 'Garbage Collection ran during a getPrimaryServices call that succeeds. ' +
    'Should not crash.');
 
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-error-with-uuid.html
index 2220befb..f3c140c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-error-with-uuid.html
@@ -13,7 +13,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryServices('battery_service'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return gattServer.connect().then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success-with-uuid.html
index da81869d..d0ffca2 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success-with-uuid.html
@@ -12,7 +12,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryServices('heart_rate'),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return gattServer.connect().then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success.html
index c8ea25ad..c6da0ac 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/reconnect-during-success.html
@@ -12,7 +12,9 @@
     .then(gattServer => {
       let promise = assert_promise_rejects_with_message(
         gattServer.getPrimaryServices(),
-        new DOMException('GATT Server disconnected while retrieving services.',
+        new DOMException('GATT Server is disconnected. ' +
+                           'Cannot retrieve services. ' +
+                           '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       gattServer.disconnect();
       return gattServer.connect().then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/compositing/video/video-reflection-expected.png b/third_party/WebKit/LayoutTests/compositing/video/video-reflection-expected.png
new file mode 100644
index 0000000..3f8a497
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/video/video-reflection-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/fast/canvas/OffscreenCanvas-commit-frameless-doc.html b/third_party/WebKit/LayoutTests/fast/canvas/OffscreenCanvas-commit-frameless-doc.html
new file mode 100644
index 0000000..10542b10
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/canvas/OffscreenCanvas-commit-frameless-doc.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+// This test aims to ensure that OffscreenCanvas.commit() does not
+// crash for a placeholder canvas under frameless document.
+// Since the document is invisible, the resultant image should be
+// not visible too. But users must be able to draw to the OffscreenCanvas
+// and do canvas-operations on the frameless placeholder canvas.
+// TODO(crbug.com/683172): Modify this test after handling for 
+// frameless canvas is done.
+function createFramelessCanvas() {
+  var framelessDoc = document.implementation.createHTMLDocument("frameless");
+  var canvas = framelessDoc.createElement("canvas");
+  canvas.width = 50;
+  canvas.height = 50;
+  return canvas;
+}
+
+function transferControlAndCommit(canvas) {
+  var offscreenCanvas = canvas.transferControlToOffscreen();
+  var ctx = offscreenCanvas.getContext("2d");
+  ctx.fillStyle = "blue";
+  ctx.fillRect(0, 0, 50, 50);
+  ctx.commit();
+  return offscreenCanvas;
+}
+
+test(function() {
+  var offscreenCanvas = transferControlAndCommit(createFramelessCanvas());
+  var ctx = offscreenCanvas.getContext("2d");
+  var pixels =  ctx.getImageData(0, 0, 1, 1).data;
+  assert_array_equals(pixels, [0, 0, 255, 255]);
+}, "Verify that the getImageData() works on the OffscreenCanvas context of a frameless canvas");
+
+async_test(function(t) {
+  var canvas = createFramelessCanvas();
+  var offscreenCanvas = transferControlAndCommit(canvas);
+
+  var c = document.createElement("canvas");
+  c.width = 50;
+  c.height = 50;
+  var ctx2 = c.getContext("2d");
+  setTimeout(function() {
+    ctx2.drawImage(canvas, 0, 0);
+    var pixels = ctx2.getImageData(0, 0, 1, 1).data;
+    t.step(function() {
+      assert_array_equals(pixels, [0, 0, 255, 255]);
+    });
+    t.done();
+  }, 0);
+}, "Verify that the placeholder canvas can be used as an image source");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/appcache/appcache-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/appcache/appcache-test.js
index 0fe0642..219cfb2 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/appcache/appcache-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/appcache/appcache-test.js
@@ -162,7 +162,7 @@
     }
 
     var handler = InspectorTest.waitForFrameManifestURLAndStatus.bind(this, frameId, manifestURL, status, callback);
-    InspectorTest.addSniffer(SDK.ApplicationCacheModel.prototype, "_frameManifestUpdated", handler);
+    InspectorTest.addSniffer(Resources.ApplicationCacheModel.prototype, "_frameManifestUpdated", handler);
 }
 
 InspectorTest.startApplicationCacheStatusesRecording = function()
@@ -188,7 +188,7 @@
         }
     }
 
-    InspectorTest.addSniffer(SDK.ApplicationCacheModel.prototype, "_frameManifestUpdated", addRecord, true);
+    InspectorTest.addSniffer(Resources.ApplicationCacheModel.prototype, "_frameManifestUpdated", addRecord, true);
 }
 
 InspectorTest.ensureFrameStatusEventsReceived = function(frameId, count, callback)
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/remove-while-loading.html b/third_party/WebKit/LayoutTests/http/tests/media/remove-while-loading.html
index edae717..d990655 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/remove-while-loading.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/remove-while-loading.html
@@ -15,6 +15,6 @@
 
     var mediaFile = findMediaFile("video", "content/test");
     var mimeType = mimeTypeForFile(mediaFile);
-    video.src = "http://127.0.0.1:8000/resources/load-and-stall.cgi?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000";
+    video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000";
 });
 </script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall-before-meta-data.html b/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall-before-meta-data.html
index 0cc49a92..f1363f3 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall-before-meta-data.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall-before-meta-data.html
@@ -20,6 +20,6 @@
     var mimeType = mimeTypeForFile(mediaFile);
 
     // Load should stall very early in the loading process.
-    video.src = "http://127.0.0.1:8000/resources/load-and-stall.cgi?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=1&stallFor=4";
+    video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=1&stallFor=4";
 });
 </script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall.html b/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall.html
index 8ab9dcf..d445f59 100644
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall.html
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-play-stall.html
@@ -131,7 +131,7 @@
     // URL will load part of the file, pause for 8 seconds, then load the rest.
     // The delay of 8 seconds is chosen to reduce flakiness in waiting for the
     // stalled event, which should arrive after roughly 3 seconds of inactivity.
-    video.src = "http://127.0.0.1:8000/resources/load-and-stall.cgi?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000&stallFor=8";
+    video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000&stallFor=8";
 
 }, "Stalled download pauses playback. When download resumes playback continues. Verify events and readyStates.");
 </script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/resources/load-and-stall.cgi b/third_party/WebKit/LayoutTests/http/tests/resources/load-and-stall.cgi
deleted file mode 100755
index f0aa12d..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/resources/load-and-stall.cgi
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/perl -wT
-
-use CGI;
-use File::stat;
-use Time::HiRes;
-
-$query = new CGI;
-$name = $query->param('name');
-$stallAt = $query->param('stallAt');
-$stallFor = $query->param('stallFor');
-$mimeType = $query->param('mimeType');
-
-my $filesize = stat($name)->size;
-print "Content-type: " . $mimeType . "\n"; 
-print "Content-Length: " . $filesize . "\n\n";
-
-open FILE, $name or die;
-binmode FILE;
-$total = 0;
-my ($buf, $data, $n);
-while (($n = read FILE, $data, 1024) != 0) {
-    $total += $n;
-    if ($total > $stallAt) {
-        if (defined $stallFor) {
-            Time::HiRes::sleep($stallFor)
-        }
-        last;
-    }
-    print $data;
-}
-close(FILE);
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-allowed.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-allowed.html
index 667c66c6..f6d5b4a0 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-allowed.html
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-allowed.html
@@ -14,6 +14,6 @@
     var mediaFile = findMediaFile("video", "content/test");
     var mimeType = mimeTypeForFile(mediaFile);
 
-    video.src = "http://127.0.0.1:8000/resources/load-and-stall.cgi?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000";
+    video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000";
 });
 </script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-blocked.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-blocked.html
index 580573b..3f7047b 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-blocked.html
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-blocked.html
@@ -16,6 +16,6 @@
     var mediaFile = findMediaFile("video", "content/test");
     var mimeType = mimeTypeForFile(mediaFile);
 
-    video.src = "http://127.0.0.1:8000/resources/load-and-stall.cgi?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000";
+    video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000";
 });
 </script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-redirect-blocked-by-connect-src.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-redirect-blocked-by-connect-src.html
index 51c655b..00b5aea 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-redirect-blocked-by-connect-src.html
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/media-src-redirect-blocked-by-connect-src.html
@@ -17,7 +17,7 @@
         });
         var mediaFile = findMediaFile("video", "content/test");
         var mimeType = mimeTypeForFile(mediaFile);
-        video.src = "/security/resources/redir.php?url=" + encodeURIComponent("http://localhost:8000/resources/load-and-stall.cgi?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000");
+        video.src = "/security/resources/redir.php?url=" + encodeURIComponent("http://localhost:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000");
         video.load();
     </script>
 </body>
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/breakpoint-manager.js b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/breakpoint-manager.js
index 1abfd1619..e2940b4 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/breakpoint-manager.js
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-breakpoints/breakpoint-manager.js
@@ -40,20 +40,28 @@
 {
     capabilities = capabilities || InspectorTest._pageCapabilities;
     var MockTarget = class extends SDK.Target {
-        constructor(name, connectionFactory, callback) {
-            super(InspectorTest.testTargetManager, name, capabilities, connectionFactory, null, callback);
+        constructor(name, connectionFactory) {
+            super(InspectorTest.testTargetManager, name, capabilities, connectionFactory, null);
             this._inspectedURL = InspectorTest.mainTarget.inspectedURL();
-            this.consoleModel = new SDK.ConsoleModel(this);
-            this.networkManager = new SDK.NetworkManager(this);
-            this.runtimeModel = new SDK.RuntimeModel(this);
-            this.securityOriginManager = SDK.SecurityOriginManager.fromTarget(this);
-            this.resourceTreeModel = new SDK.ResourceTreeModel(this, this.networkManager, this.securityOriginManager);
+            this.consoleModel = this.model(SDK.ConsoleModel);
+            this.model(SDK.NetworkManager);
+            this.resourceTreeModel = this.model(SDK.ResourceTreeModel);
             this.resourceTreeModel._cachedResourcesProcessed = true;
             this.resourceTreeModel._frameAttached("42", 0);
-            this.debuggerModel = debuggerModelConstructor ? new debuggerModelConstructor(this) : new SDK.DebuggerModel(this);
-            this._modelByConstructor.set(SDK.DebuggerModel, this.debuggerModel);
-            this.domModel = new SDK.DOMModel(this);
-            this.cssModel = new SDK.CSSModel(this, this.domModel);
+            this.runtimeModel = this.model(SDK.RuntimeModel);
+            if (debuggerModelConstructor) {
+                this.debuggerModel = new debuggerModelConstructor(this);
+                this._modelByConstructor.set(SDK.DebuggerModel, this.debuggerModel);
+            } else {
+                this.debuggerModel = this.model(SDK.DebuggerModel);
+            }
+            this.model(SDK.DOMModel);
+            this.model(SDK.CSSModel);
+            this.subTargetsManager = this.model(SDK.SubTargetsManager);
+            this.cpuProfilerModel = this.model(SDK.CPUProfilerModel);
+            this.heapProfilerModel = this.model(SDK.HeapProfilerModel);
+            this.tracingManager = new SDK.TracingManager(this);
+            this.serviceWorkerManager = this.model(SDK.ServiceWorkerManager);
         }
 
         _loadedWithCapabilities()
@@ -224,7 +232,8 @@
 InspectorTest.DebuggerModelMock = class extends SDK.SDKModel {
     constructor(target)
     {
-        super(SDK.DebuggerModel, target);
+        super(target);
+        this._target = target;
         this._breakpointResolvedEventTarget = new Common.Object();
         this._scripts = {};
         this._breakpoints = {};
diff --git a/third_party/WebKit/LayoutTests/media/color-profile-video-expected.png b/third_party/WebKit/LayoutTests/media/color-profile-video-expected.png
index 3f9c98d..40b164b 100644
--- a/third_party/WebKit/LayoutTests/media/color-profile-video-expected.png
+++ b/third_party/WebKit/LayoutTests/media/color-profile-video-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/media/color-profile-video-seek-expected.png b/third_party/WebKit/LayoutTests/media/color-profile-video-seek-expected.png
deleted file mode 100644
index 05ab7e4b..0000000
--- a/third_party/WebKit/LayoutTests/media/color-profile-video-seek-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/media/color-profile-video-seek-object-fit-expected.png b/third_party/WebKit/LayoutTests/media/color-profile-video-seek-object-fit-expected.png
index 7d1b32a..a42b57c 100644
--- a/third_party/WebKit/LayoutTests/media/color-profile-video-seek-object-fit-expected.png
+++ b/third_party/WebKit/LayoutTests/media/color-profile-video-seek-object-fit-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/media/content/test.ogv b/third_party/WebKit/LayoutTests/media/content/test.ogv
index 6b328a2..0c55f6c 100644
--- a/third_party/WebKit/LayoutTests/media/content/test.ogv
+++ b/third_party/WebKit/LayoutTests/media/content/test.ogv
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-reflection-expected.png b/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-reflection-expected.png
deleted file mode 100644
index d33dd43..0000000
--- a/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-reflection-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-expected.png
new file mode 100644
index 0000000..f60f9a7f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-filter-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-filter-expected.png
index d5b7636..7ea462b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/color-profile-video-seek-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png
index 18e09ed..d465793 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-vertical-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-vertical-expected.png
index 8337751f..9272bd8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-aspect-ratio-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-aspect-ratio-expected.png
index f2bbaaa..297246d 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-aspect-ratio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-aspect-ratio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-canvas-alpha-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-canvas-alpha-expected.png
index e36c748..5d02b16 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-canvas-alpha-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-canvas-alpha-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-layer-crash-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-layer-crash-expected.png
index e09c402..b9ac197d 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-layer-crash-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-layer-crash-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-overlay-cast-dark-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-overlay-cast-dark-rendering-expected.png
index 1bf8453..1e717487 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-overlay-cast-dark-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-overlay-cast-dark-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-remove-insert-repaints-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-remove-insert-repaints-expected.png
index e10859f3..b12a3ac6 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-remove-insert-repaints-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-remove-insert-repaints-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-replaces-poster-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-replaces-poster-expected.png
index ffbf2f5e..6c444912 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-replaces-poster-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-replaces-poster-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-transformed-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-transformed-expected.png
index a8e369c..30d2dcfd 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-transformed-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-transformed-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
index b156012..5911e2b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-expected.png
index d76402c..64aca40 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png
index 484ecd8..0646082 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png
index 484ecd8..0646082 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png
index 484ecd8..0646082 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png
index 1b38c2b..511fb3f 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/media/track/track-cue-rendering-vertical-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/media/track/track-cue-rendering-vertical-expected.png
index 2d59e04..8821d6e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-reflection-expected.png b/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-reflection-expected.png
deleted file mode 100644
index d33dd43..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-reflection-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-expected.png
new file mode 100644
index 0000000..f60f9a7f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-filter-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-filter-expected.png
index d5b7636..7ea462b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/color-profile-video-seek-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png
index 4eb49cd..4d4cb940 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-vertical-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-vertical-expected.png
index 1bfe4e67..6512c5d3 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-aspect-ratio-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-aspect-ratio-expected.png
index 7a2356d..6fb71684 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-aspect-ratio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-aspect-ratio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-canvas-alpha-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-canvas-alpha-expected.png
index e36c748..5d02b16 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-canvas-alpha-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-canvas-alpha-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-layer-crash-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-layer-crash-expected.png
index 1d0f819..045af76 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-layer-crash-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-layer-crash-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-overlay-cast-dark-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-overlay-cast-dark-rendering-expected.png
index a3cb05f..a757e8c 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-overlay-cast-dark-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-overlay-cast-dark-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-remove-insert-repaints-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-remove-insert-repaints-expected.png
index e10859f3..b12a3ac6 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-remove-insert-repaints-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-remove-insert-repaints-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-replaces-poster-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-replaces-poster-expected.png
index 5c3f656..bd8df39 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-replaces-poster-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-replaces-poster-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-transformed-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-transformed-expected.png
index 67f1b35c..5142f6d9 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-transformed-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-transformed-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png
index 3875d130..a624ad74 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-expected.png
index 7ce3beab..722f96d 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/compositing/video/video-reflection-expected.png b/third_party/WebKit/LayoutTests/platform/win/compositing/video/video-reflection-expected.png
deleted file mode 100644
index d5bc17a..0000000
--- a/third_party/WebKit/LayoutTests/platform/win/compositing/video/video-reflection-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-expected.png
new file mode 100644
index 0000000..db562b2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-filter-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-filter-expected.png
index 9568c397..976186d 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/color-profile-video-seek-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-horizontal-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-horizontal-expected.png
index 38a67e5..ee4fd8fc 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-vertical-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-vertical-expected.png
index b2a9b78..b6e4369 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-aspect-ratio-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-aspect-ratio-expected.png
index b7fb48f2..f6d4533 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-aspect-ratio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-aspect-ratio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-canvas-alpha-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-canvas-alpha-expected.png
index 3deed2a..885587fb 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-canvas-alpha-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-canvas-alpha-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-overlay-cast-dark-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-overlay-cast-dark-rendering-expected.png
index 97158e8..1fe5973 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-overlay-cast-dark-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-overlay-cast-dark-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-remove-insert-repaints-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-remove-insert-repaints-expected.png
index 64b896b..133416c5 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-remove-insert-repaints-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-remove-insert-repaints-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-replaces-poster-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-replaces-poster-expected.png
index 5393b7b..b920eb24 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-replaces-poster-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-replaces-poster-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png
index 6376f66..bc8187f 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-expected.png
index 0774893..40b6630 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/media/track/track-cue-rendering-vertical-expected.png b/third_party/WebKit/LayoutTests/platform/win7/media/track/track-cue-rendering-vertical-expected.png
index 12d4060..f044bbf 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win7/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/media/video-layer-crash-expected.png b/third_party/WebKit/LayoutTests/platform/win7/media/video-layer-crash-expected.png
new file mode 100644
index 0000000..a8422bd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win7/media/video-layer-crash-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/media/video-transformed-expected.png b/third_party/WebKit/LayoutTests/platform/win7/media/video-transformed-expected.png
index e3f31f9..b6e99d4b 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/media/video-transformed-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win7/media/video-transformed-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/resources/dump-as-markup.js b/third_party/WebKit/LayoutTests/resources/dump-as-markup.js
index ee22396e..e068429 100644
--- a/third_party/WebKit/LayoutTests/resources/dump-as-markup.js
+++ b/third_party/WebKit/LayoutTests/resources/dump-as-markup.js
@@ -126,8 +126,7 @@
 
 Markup.get = function(node)
 {
-    var shadowRootList = {};
-    var markup = Markup._getShadowHostIfPossible(node, 0, shadowRootList);
+    var markup = Markup._getShadowHostIfPossible(node, 0);
     if (markup)
         return markup.substring(1);
 
@@ -136,13 +135,13 @@
 
     // Don't print any markup for the root node.
     for (var i = 0, len = node.childNodes.length; i < len; i++)
-        markup += Markup._get(node.childNodes[i], 0, shadowRootList);
+        markup += Markup._get(node.childNodes[i], 0);
     return markup.substring(1);
 }
 
 // Returns the markup for the given node. To be used for cases where a test needs
 // to get the markup but not clobber the whole page.
-Markup._get = function(node, depth, shadowRootList)
+Markup._get = function(node, depth)
 {
     var str = Markup._indent(depth);
 
@@ -217,24 +216,25 @@
 
         break;
     case Node.DOCUMENT_FRAGMENT_NODE:
-        if (shadowRootList && internals.address(node) in shadowRootList)
+        if (node.constructor.name == "ShadowRoot")
           str += "<shadow:root>";
         else
           str += "content";
     }
 
+    // TODO(esprehn): This should definitely be ==.
     if (node.namespaceURI = 'http://www.w3.org/1999/xhtml' && node.tagName == 'TEMPLATE')
-        str += Markup._get(node.content, depth + 1, shadowRootList);
+        str += Markup._get(node.content, depth + 1);
 
     for (var i = 0, len = node.childNodes.length; i < len; i++) {
         var selection = Markup._getSelectionMarker(node, i);
         if (selection)
             str += Markup._indent(depth + 1) + selection;
 
-        str += Markup._get(node.childNodes[i], depth + 1, shadowRootList);
+        str += Markup._get(node.childNodes[i], depth + 1);
     }
     
-    str += Markup._getShadowHostIfPossible(node, depth, shadowRootList);
+    str += Markup._getShadowHostIfPossible(node, depth);
     
     var selection = Markup._getSelectionMarker(node, i);
     if (selection)
@@ -243,13 +243,12 @@
     return str;
 }
 
-Markup._getShadowHostIfPossible = function (node, depth, shadowRootList)
+Markup._getShadowHostIfPossible = function (node, depth)
 {
     if (!Markup._useHTML5libOutputFormat && node.nodeType == Node.ELEMENT_NODE && window.internals) {
         var root = window.internals.shadowRoot(node);
         if (root) {
-            shadowRootList[internals.address(root)] = true;
-            return Markup._get(root, depth + 1, shadowRootList);
+            return Markup._get(root, depth + 1);
         }
     }
     return '';
diff --git a/third_party/WebKit/Source/core/dom/Element.cpp b/third_party/WebKit/Source/core/dom/Element.cpp
index 81d88538..7e63fa9 100644
--- a/third_party/WebKit/Source/core/dom/Element.cpp
+++ b/third_party/WebKit/Source/core/dom/Element.cpp
@@ -1934,7 +1934,7 @@
   }
 
   if (hasCustomStyleCallbacks())
-    didRecalcStyle(change);
+    didRecalcStyle();
 }
 
 PassRefPtr<ComputedStyle> Element::propagateInheritedProperties(
@@ -3728,7 +3728,7 @@
   DCHECK(hasCustomStyleCallbacks());
 }
 
-void Element::didRecalcStyle(StyleRecalcChange) {
+void Element::didRecalcStyle() {
   DCHECK(hasCustomStyleCallbacks());
 }
 
diff --git a/third_party/WebKit/Source/core/dom/Element.h b/third_party/WebKit/Source/core/dom/Element.h
index b1d8247..8a26e38 100644
--- a/third_party/WebKit/Source/core/dom/Element.h
+++ b/third_party/WebKit/Source/core/dom/Element.h
@@ -769,7 +769,7 @@
   void childrenChanged(const ChildrenChange&) override;
 
   virtual void willRecalcStyle(StyleRecalcChange);
-  virtual void didRecalcStyle(StyleRecalcChange);
+  virtual void didRecalcStyle();
   virtual PassRefPtr<ComputedStyle> customStyleForLayoutObject();
 
   virtual bool shouldRegisterAsNamedItem() const { return false; }
diff --git a/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.cpp b/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.cpp
index a66a9a4..e6be908 100644
--- a/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.cpp
+++ b/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.cpp
@@ -319,7 +319,7 @@
   nextLayoutObject->destroy();
 }
 
-void FirstLetterPseudoElement::didRecalcStyle(StyleRecalcChange) {
+void FirstLetterPseudoElement::didRecalcStyle() {
   if (!layoutObject())
     return;
 
diff --git a/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.h b/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.h
index d7c1b83a..487b7163 100644
--- a/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.h
+++ b/third_party/WebKit/Source/core/dom/FirstLetterPseudoElement.h
@@ -61,7 +61,7 @@
  private:
   explicit FirstLetterPseudoElement(Element*);
 
-  void didRecalcStyle(StyleRecalcChange) override;
+  void didRecalcStyle() override;
 
   void attachFirstLetterTextLayoutObjects();
   ComputedStyle* styleForFirstLetter(LayoutObject*);
diff --git a/third_party/WebKit/Source/core/dom/PseudoElement.cpp b/third_party/WebKit/Source/core/dom/PseudoElement.cpp
index d82156f..fbdcc29 100644
--- a/third_party/WebKit/Source/core/dom/PseudoElement.cpp
+++ b/third_party/WebKit/Source/core/dom/PseudoElement.cpp
@@ -147,7 +147,7 @@
   return pseudoElementLayoutObjectIsNeeded(&style);
 }
 
-void PseudoElement::didRecalcStyle(StyleRecalcChange) {
+void PseudoElement::didRecalcStyle() {
   if (!layoutObject())
     return;
 
diff --git a/third_party/WebKit/Source/core/dom/PseudoElement.h b/third_party/WebKit/Source/core/dom/PseudoElement.h
index 1585f6b..e05dacf 100644
--- a/third_party/WebKit/Source/core/dom/PseudoElement.h
+++ b/third_party/WebKit/Source/core/dom/PseudoElement.h
@@ -55,7 +55,7 @@
   PseudoElement(Element*, PseudoId);
 
  private:
-  void didRecalcStyle(StyleRecalcChange) override;
+  void didRecalcStyle() override;
 
   PseudoId m_pseudoId;
 };
diff --git a/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp b/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
index 084703a4..26c1d6f 100644
--- a/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
@@ -38,6 +38,7 @@
 #include "core/dom/ExceptionCode.h"
 #include "core/dom/TaskRunnerHelper.h"
 #include "core/fileapi/File.h"
+#include "core/frame/FrameHost.h"
 #include "core/frame/ImageBitmap.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/Settings.h"
@@ -56,6 +57,7 @@
 #include "core/layout/LayoutHTMLCanvas.h"
 #include "core/layout/api/LayoutViewItem.h"
 #include "core/layout/compositing/PaintLayerCompositor.h"
+#include "core/page/ChromeClient.h"
 #include "core/paint/PaintLayer.h"
 #include "core/paint/PaintTiming.h"
 #include "platform/Histogram.h"
@@ -1423,9 +1425,18 @@
 
 void HTMLCanvasElement::createLayer() {
   DCHECK(!m_surfaceLayerBridge);
-  m_surfaceLayerBridge = WTF::wrapUnique(new CanvasSurfaceLayerBridge(this));
-  // Creates a placeholder layer first before Surface is created.
-  m_surfaceLayerBridge->createSolidColorLayer();
+  LocalFrame* frame = document().frame();
+  WebLayerTreeView* layerTreeView = nullptr;
+  // TODO(xlai): Ensure OffscreenCanvas commit() is still functional when a
+  // frame-less HTML canvas's document is reparenting under another frame.
+  // See crbug.com/683172.
+  if (frame) {
+    layerTreeView = frame->host()->chromeClient().getWebLayerTreeView(frame);
+    m_surfaceLayerBridge =
+        WTF::wrapUnique(new CanvasSurfaceLayerBridge(this, layerTreeView));
+    // Creates a placeholder layer first before Surface is created.
+    m_surfaceLayerBridge->createSolidColorLayer();
+  }
 }
 
 void HTMLCanvasElement::OnWebLayerReplaced() {
diff --git a/third_party/WebKit/Source/core/html/HTMLFormControlElement.cpp b/third_party/WebKit/Source/core/html/HTMLFormControlElement.cpp
index b80f79c..137234d 100644
--- a/third_party/WebKit/Source/core/html/HTMLFormControlElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFormControlElement.cpp
@@ -346,7 +346,7 @@
   return fastGetAttribute(valueAttr);
 }
 
-void HTMLFormControlElement::didRecalcStyle(StyleRecalcChange) {
+void HTMLFormControlElement::didRecalcStyle() {
   if (LayoutObject* layoutObject = this->layoutObject())
     layoutObject->updateFromElement();
 }
diff --git a/third_party/WebKit/Source/core/html/HTMLFormControlElement.h b/third_party/WebKit/Source/core/html/HTMLFormControlElement.h
index 308b763..f0cb919 100644
--- a/third_party/WebKit/Source/core/html/HTMLFormControlElement.h
+++ b/third_party/WebKit/Source/core/html/HTMLFormControlElement.h
@@ -161,7 +161,7 @@
                           InputDeviceCapabilities* sourceCapabilities) override;
   void willCallDefaultEventHandler(const Event&) final;
 
-  void didRecalcStyle(StyleRecalcChange) override;
+  void didRecalcStyle() override;
 
   // This must be called any time the result of willValidate() has changed.
   void setNeedsWillValidateCheck();
diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
index afd5e73..a43e6d6 100644
--- a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
@@ -642,7 +642,7 @@
     layoutObject()->updateFromElement();
 }
 
-void HTMLMediaElement::didRecalcStyle(StyleRecalcChange) {
+void HTMLMediaElement::didRecalcStyle() {
   if (layoutObject())
     layoutObject()->updateFromElement();
 }
diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.h b/third_party/WebKit/Source/core/html/HTMLMediaElement.h
index 3428d5e..d3d8cb9 100644
--- a/third_party/WebKit/Source/core/html/HTMLMediaElement.h
+++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.h
@@ -348,7 +348,7 @@
   InsertionNotificationRequest insertedInto(ContainerNode*) final;
   void didNotifySubtreeInsertionsToDocument() override;
   void removedFrom(ContainerNode*) final;
-  void didRecalcStyle(StyleRecalcChange) final;
+  void didRecalcStyle() final;
 
   bool canStartSelection() const override { return false; }
 
diff --git a/third_party/WebKit/Source/core/html/HTMLSelectElement.cpp b/third_party/WebKit/Source/core/html/HTMLSelectElement.cpp
index 64894942..ad5881b 100644
--- a/third_party/WebKit/Source/core/html/HTMLSelectElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLSelectElement.cpp
@@ -1947,8 +1947,8 @@
     m_popup->hide();
 }
 
-void HTMLSelectElement::didRecalcStyle(StyleRecalcChange change) {
-  HTMLFormControlElementWithState::didRecalcStyle(change);
+void HTMLSelectElement::didRecalcStyle() {
+  HTMLFormControlElementWithState::didRecalcStyle();
   if (popupIsVisible())
     m_popup->updateFromElement(PopupMenu::ByStyleChange);
 }
diff --git a/third_party/WebKit/Source/core/html/HTMLSelectElement.h b/third_party/WebKit/Source/core/html/HTMLSelectElement.h
index 05ea599c..10c3c59 100644
--- a/third_party/WebKit/Source/core/html/HTMLSelectElement.h
+++ b/third_party/WebKit/Source/core/html/HTMLSelectElement.h
@@ -200,7 +200,7 @@
   bool isPresentationAttribute(const QualifiedName&) const override;
 
   LayoutObject* createLayoutObject(const ComputedStyle&) override;
-  void didRecalcStyle(StyleRecalcChange) override;
+  void didRecalcStyle() override;
   void detachLayoutTree(const AttachContext& = AttachContext()) override;
   void appendToFormData(FormData&) override;
   void didAddUserAgentShadowRoot(ShadowRoot&) override;
diff --git a/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp b/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp
index 4a47555..ea7dd1d0 100644
--- a/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp
+++ b/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp
@@ -236,6 +236,7 @@
 ScriptPromise OffscreenCanvas::commit(RefPtr<StaticBitmapImage> image,
                                       bool isWebGLSoftwareRendering,
                                       ScriptState* scriptState) {
+  getOrCreateFrameDispatcher()->setNeedsBeginFrame(true);
   if (m_commitPromiseResolver) {
     if (image) {
       m_overdrawFrame = std::move(image);
@@ -266,6 +267,11 @@
   } else if (m_commitPromiseResolver) {
     m_commitPromiseResolver->resolve();
     m_commitPromiseResolver.clear();
+    // We need to tell parent frame to stop sending signals on begin frame to
+    // avoid overhead once we resolve the promise.
+    // In the case of overdraw frame (if block), we still need to wait for one
+    // more frame time to resolve the existing promise.
+    getOrCreateFrameDispatcher()->setNeedsBeginFrame(false);
   }
 }
 
diff --git a/third_party/WebKit/Source/core/page/ChromeClient.h b/third_party/WebKit/Source/core/page/ChromeClient.h
index f4492e9b..66b4ea06 100644
--- a/third_party/WebKit/Source/core/page/ChromeClient.h
+++ b/third_party/WebKit/Source/core/page/ChromeClient.h
@@ -72,6 +72,7 @@
 class WebFrameScheduler;
 class WebImage;
 class WebLayer;
+class WebLayerTreeView;
 
 struct CompositedSelection;
 struct DateTimeChooserParameters;
@@ -343,6 +344,8 @@
 
   virtual void installSupplements(LocalFrame&) {}
 
+  virtual WebLayerTreeView* getWebLayerTreeView(LocalFrame*) { return nullptr; }
+
   DECLARE_TRACE();
 
  protected:
diff --git a/third_party/WebKit/Source/core/testing/Internals.cpp b/third_party/WebKit/Source/core/testing/Internals.cpp
index cfbfd93..96e8343 100644
--- a/third_party/WebKit/Source/core/testing/Internals.cpp
+++ b/third_party/WebKit/Source/core/testing/Internals.cpp
@@ -280,13 +280,6 @@
   return WorkerThread::workerThreadCount();
 }
 
-String Internals::address(Node* node) {
-  char buf[32];
-  sprintf(buf, "%p", node);
-
-  return String(buf);
-}
-
 GCObservation* Internals::observeGC(ScriptValue scriptValue) {
   v8::Local<v8::Value> observedValue = scriptValue.v8Value();
   DCHECK(!observedValue.IsEmpty());
diff --git a/third_party/WebKit/Source/core/testing/Internals.h b/third_party/WebKit/Source/core/testing/Internals.h
index d269bde..1d5f541 100644
--- a/third_party/WebKit/Source/core/testing/Internals.h
+++ b/third_party/WebKit/Source/core/testing/Internals.h
@@ -90,8 +90,6 @@
 
   String elementLayoutTreeAsText(Element*, ExceptionState&);
 
-  String address(Node*);
-
   GCObservation* observeGC(ScriptValue);
 
   bool isPreloaded(const String& url);
diff --git a/third_party/WebKit/Source/core/testing/Internals.idl b/third_party/WebKit/Source/core/testing/Internals.idl
index 42ca8c2..827dd408 100644
--- a/third_party/WebKit/Source/core/testing/Internals.idl
+++ b/third_party/WebKit/Source/core/testing/Internals.idl
@@ -27,8 +27,6 @@
 [
     DoNotCheckConstants,
 ] interface Internals {
-    DOMString address(Node node);
-
     GCObservation observeGC(any observed);
 
     [RaisesException] DOMString elementLayoutTreeAsText(Element element);
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn
index 2de885e..762402fa 100644
--- a/third_party/WebKit/Source/devtools/BUILD.gn
+++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -380,6 +380,7 @@
   "front_end/quick_open/filteredListWidget.css",
   "front_end/quick_open/FilteredListWidget.js",
   "front_end/quick_open/module.json",
+  "front_end/resources/ApplicationCacheModel.js",
   "front_end/resources/ApplicationCacheItemsView.js",
   "front_end/resources/appManifestView.css",
   "front_end/resources/AppManifestView.js",
@@ -413,7 +414,6 @@
   "front_end/screencast/ScreencastApp.js",
   "front_end/screencast/screencastView.css",
   "front_end/screencast/ScreencastView.js",
-  "front_end/sdk/ApplicationCacheModel.js",
   "front_end/sdk/Connections.js",
   "front_end/sdk/ConsoleModel.js",
   "front_end/sdk/ContentProviders.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo.png b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo.png
index 4c3a7e7..0e2b8cede 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_2x.png b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_2x.png
index 46f3fea0..edbb15b8 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_2x.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_2x.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw.png b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw.png
index 4835e22b..5cb533a5 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw_2x.png b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw_2x.png
index d4ccb3a..ee475cc 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw_2x.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/audits_logo_bw_2x.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes b/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes
index ea846dc..777c1b61 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes
+++ b/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes
@@ -1,11 +1,13 @@
 {
     "securityIcons.svg": "27676f7c1f1542659c7c49a8052259dc",
     "resourceGlyphs.svg": "8e1947b1fa4aac49cbc081f85f44d412",
+    "audits_logo_bw.svg": "203dcb2ba32ef0f4595ad45bb8feffab",
     "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28",
     "errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45",
     "smallIcons.svg": "e42007c7e8da94ecab276fd254f0623e",
     "settingsListRemove.svg": "ce9e7c5c5cdaef28e6ee51d9478d5485",
-    "toolbarButtonGlyphs.svg": "a2f97ba793fb31f61930f934c0837e34",
+    "toolbarButtonGlyphs.svg": "0c0a900daedf6dc35bd5b12424d8181c",
     "breakpoint.svg": "69cd92d807259c022791112809b97799",
-    "search.svg": "fc990dd3836aec510d7ca1f36c2a3142"
+    "search.svg": "fc990dd3836aec510d7ca1f36c2a3142",
+    "audits_logo.svg": "647095d7981857c22a816eef12f75b91"
 }
\ No newline at end of file
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes b/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes
index 80c095d47..777c1b61 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes
+++ b/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes
@@ -1,13 +1,13 @@
 {
     "securityIcons.svg": "27676f7c1f1542659c7c49a8052259dc",
     "resourceGlyphs.svg": "8e1947b1fa4aac49cbc081f85f44d412",
-    "search.svg": "fc990dd3836aec510d7ca1f36c2a3142",
+    "audits_logo_bw.svg": "203dcb2ba32ef0f4595ad45bb8feffab",
     "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28",
     "errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45",
     "smallIcons.svg": "e42007c7e8da94ecab276fd254f0623e",
     "settingsListRemove.svg": "ce9e7c5c5cdaef28e6ee51d9478d5485",
-    "toolbarButtonGlyphs.svg": "a2f97ba793fb31f61930f934c0837e34",
+    "toolbarButtonGlyphs.svg": "0c0a900daedf6dc35bd5b12424d8181c",
     "breakpoint.svg": "69cd92d807259c022791112809b97799",
-    "audits_logo_bw.svg": "203dcb2ba32ef0f4595ad45bb8feffab",
-    "audits_logo.svg": "0790aa567cc61be5484a30490c41147b"
+    "search.svg": "fc990dd3836aec510d7ca1f36c2a3142",
+    "audits_logo.svg": "647095d7981857c22a816eef12f75b91"
 }
\ No newline at end of file
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/toolbarButtonGlyphs.svg b/third_party/WebKit/Source/devtools/front_end/Images/src/toolbarButtonGlyphs.svg
index ff8ce6de..4834fe0c 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/src/toolbarButtonGlyphs.svg
+++ b/third_party/WebKit/Source/devtools/front_end/Images/src/toolbarButtonGlyphs.svg
@@ -21,9 +21,9 @@
            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
      showgrid="true"
      id="namedview3397"
-     inkscape:zoom="2.8284271"
-     inkscape:cx="197.76819"
-     inkscape:cy="59.322134"
+     inkscape:zoom="5.6568542"
+     inkscape:cx="272.90169"
+     inkscape:cy="91.541869"
      inkscape:window-width="1573"
      inkscape:window-height="1037"
      inkscape:window-x="81"
@@ -1146,13 +1146,11 @@
      style="fill:none"
      inkscape:connector-curvature="0"
      d="m 288,72 h 24 v 24 h -24 z"
-     id="path3763" /><g
-     id="g3844"
-     transform="matrix(-1,0,0,-1,607.00004,168)"><path
-       sodipodi:nodetypes="ssccccccccsssssss"
-       inkscape:connector-curvature="0"
-       id="path3654"
-       d="m 304.5,76 c -0.73331,0 -1.5,0.7 -1.5,1.5 l 0,1.5 1.5,0 0,-1 7,0 0.0781,12 -7.07819,0 1.3e-4,-1 -1.5,0 0,1.5 c 0,0.8 0.76669,1.5 1.5,1.5 l 7,0 c 0.7333,0 1.5,-0.7 1.5,-1.5 l 0,-13 c 0,-0.8 -0.61045,-1.5 -1.34375,-1.5 z" /><path
-       id="path3765"
-       d="m 304.58443,84.588 c 0.0247,-0.192 0.0432,-0.384 0.0432,-0.588 0,-0.204 -0.0185,-0.396 -0.0432,-0.588 l 1.30152,-0.99 c 0.1172,-0.09 0.14804,-0.252 0.074,-0.384 l -1.23366,-2.076 c -0.0741,-0.132 -0.24056,-0.18 -0.37627,-0.132 l -1.53591,0.6 c -0.32075,-0.24 -0.66617,-0.438 -1.04244,-0.588 l -0.2344,-1.59 C 301.51877,78.108 301.38923,78 301.23502,78 h -2.46732 c -0.15421,0 -0.28374,0.108 -0.30225,0.252 l -0.23439,1.59 c -0.37627,0.15 -0.7217,0.354 -1.04245,0.588 l -1.5359,-0.6 c -0.14187,-0.054 -0.30225,0 -0.37627,0.132 l -1.23366,2.076 c -0.0802,0.132 -0.0432,0.294 0.074,0.384 l 1.30151,0.99 c -0.0247,0.192 -0.0432,0.39 -0.0432,0.588 0,0.198 0.0185,0.396 0.0432,0.588 l -1.30151,0.99 c -0.11719,0.09 -0.14803,0.252 -0.074,0.384 l 1.23366,2.076 c 0.0741,0.132 0.24057,0.18 0.37627,0.132 l 1.5359,-0.6 c 0.32075,0.24 0.66618,0.438 1.04245,0.588 l 0.23439,1.59 c 0.0185,0.144 0.14804,0.252 0.30225,0.252 h 2.46732 c 0.15421,0 0.28375,-0.108 0.30225,-0.252 l 0.2344,-1.59 c 0.37627,-0.15 0.72169,-0.354 1.04244,-0.588 l 1.53591,0.6 c 0.14188,0.054 0.30225,0 0.37627,-0.132 l 1.23366,-2.076 c 0.0741,-0.132 0.0432,-0.294 -0.074,-0.384 l -1.30152,-0.99 z m -4.58305,1.512 c -1.19048,0 -2.15891,-0.942 -2.15891,-2.1 0,-1.158 0.96843,-2.1 2.15891,-2.1 1.19048,0 2.15891,0.942 2.15891,2.1 0,1.158 -0.96843,2.1 -2.15891,2.1 z"
-       inkscape:connector-curvature="0" /></g></svg>
\ No newline at end of file
+     id="path3763" /><path
+     style="fill:none"
+     inkscape:connector-curvature="0"
+     d="m 236.19418,70.013825 h 24 v 24 h -24 z"
+     id="path3343" /><path
+     inkscape:connector-curvature="0"
+     d="m 309.34846,84.686 c 0.0288,-0.224 0.0504,-0.448 0.0504,-0.686 0,-0.238 -0.0216,-0.462 -0.0504,-0.686 l 1.51844,-1.155 c 0.13673,-0.105 0.17272,-0.294 0.0863,-0.448 l -1.43928,-2.422 c -0.0863,-0.154 -0.28065,-0.21 -0.43897,-0.154 l -1.7919,0.7 c -0.37422,-0.28 -0.77722,-0.511 -1.2162,-0.686 l -0.27341,-1.855 C 305.77186,77.126 305.62075,77 305.44084,77 h -2.87854 c -0.17992,0 -0.33104,0.126 -0.35263,0.294 l -0.27346,1.855 c -0.43898,0.175 -0.84198,0.413 -1.21619,0.686 l -1.7919,-0.7 c -0.16551,-0.063 -0.35262,0 -0.43898,0.154 l -1.43926,2.422 c -0.0935,0.154 -0.0504,0.343 0.0863,0.448 l 1.51843,1.155 c -0.0287,0.224 -0.0504,0.455 -0.0504,0.686 0,0.231 0.0216,0.462 0.0504,0.686 l -1.51843,1.155 c -0.13673,0.105 -0.17271,0.294 -0.0863,0.448 l 1.43926,2.422 c 0.0863,0.154 0.28067,0.21 0.43898,0.154 l 1.7919,-0.7 c 0.37421,0.28 0.77721,0.511 1.21619,0.686 l 0.27346,1.855 c 0.0216,0.168 0.17271,0.294 0.35263,0.294 h 2.87854 c 0.17991,0 0.33103,-0.126 0.35263,-0.294 l 0.27345,-1.855 c 0.43898,-0.175 0.84198,-0.413 1.21619,-0.686 l 1.7919,0.7 c 0.16552,0.063 0.35263,0 0.43898,-0.154 l 1.43927,-2.422 c 0.0863,-0.154 0.0504,-0.343 -0.0863,-0.448 l -1.51844,-1.155 z m -5.34689,1.764 c -1.38892,0 -2.51874,-1.099 -2.51874,-2.45 0,-1.351 1.12982,-2.45 2.51874,-2.45 1.38889,0 2.51872,1.099 2.51872,2.45 0,1.351 -1.12983,2.45 -2.51872,2.45 z"
+     id="path3345" /></svg>
\ No newline at end of file
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs.png b/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs.png
index 6e16294..1eafcac 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs_2x.png b/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs_2x.png
index 4f809ef..afa1c011 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs_2x.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/toolbarButtonGlyphs_2x.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js b/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js
index a3bb2bf..e262c257 100644
--- a/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/accessibility/AccessibilityModel.js
@@ -215,7 +215,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(Accessibility.AccessibilityModel, target);
+    super(target);
     this._agent = target.accessibilityAgent();
 
     /** @type {!Map<string, !Accessibility.AccessibilityNode>} */
@@ -225,13 +225,10 @@
 
   /**
    * @param {!SDK.Target} target
-   * @return {!Accessibility.AccessibilityModel}
+   * @return {?Accessibility.AccessibilityModel}
    */
   static fromTarget(target) {
-    if (!target[Accessibility.AccessibilityModel._symbol])
-      target[Accessibility.AccessibilityModel._symbol] = new Accessibility.AccessibilityModel(target);
-
-    return target[Accessibility.AccessibilityModel._symbol];
+    return target.model(Accessibility.AccessibilityModel);
   }
 
   clear() {
@@ -314,4 +311,4 @@
   }
 };
 
-Accessibility.AccessibilityModel._symbol = Symbol('AccessibilityModel');
+SDK.SDKModel.register(Accessibility.AccessibilityModel, SDK.Target.Capability.DOM);
diff --git a/third_party/WebKit/Source/devtools/front_end/animation/AnimationModel.js b/third_party/WebKit/Source/devtools/front_end/animation/AnimationModel.js
index 7b62f5b..28b24e4c 100644
--- a/third_party/WebKit/Source/devtools/front_end/animation/AnimationModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/animation/AnimationModel.js
@@ -10,7 +10,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(Animation.AnimationModel, target);
+    super(target);
     this._agent = target.animationAgent();
     target.registerAnimationDispatcher(new Animation.AnimationDispatcher(this));
     /** @type {!Map.<string, !Animation.AnimationModel.Animation>} */
@@ -31,12 +31,7 @@
    * @return {?Animation.AnimationModel}
    */
   static fromTarget(target) {
-    if (!target.hasDOMCapability())
-      return null;
-    if (!target[Animation.AnimationModel._symbol])
-      target[Animation.AnimationModel._symbol] = new Animation.AnimationModel(target);
-
-    return target[Animation.AnimationModel._symbol];
+    return target.model(Animation.AnimationModel);
   }
 
   _reset() {
@@ -191,14 +186,14 @@
   }
 };
 
+SDK.SDKModel.register(Animation.AnimationModel, SDK.Target.Capability.DOM);
+
 /** @enum {symbol} */
 Animation.AnimationModel.Events = {
   AnimationGroupStarted: Symbol('AnimationGroupStarted'),
   ModelReset: Symbol('ModelReset')
 };
 
-Animation.AnimationModel._symbol = Symbol('AnimationModel');
-
 
 /**
  * @unrestricted
diff --git a/third_party/WebKit/Source/devtools/front_end/layers/LayerTreeModel.js b/third_party/WebKit/Source/devtools/front_end/layers/LayerTreeModel.js
index cb4cc7bd..ba1f9de 100644
--- a/third_party/WebKit/Source/devtools/front_end/layers/LayerTreeModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/layers/LayerTreeModel.js
@@ -33,7 +33,7 @@
  */
 Layers.LayerTreeModel = class extends SDK.SDKModel {
   constructor(target) {
-    super(Layers.LayerTreeModel, target);
+    super(target);
     target.registerLayerTreeDispatcher(new Layers.LayerTreeDispatcher(this));
     SDK.targetManager.addEventListener(SDK.TargetManager.Events.MainFrameNavigated, this._onMainFrameNavigated, this);
     /** @type {?SDK.LayerTreeBase} */
@@ -45,13 +45,7 @@
    * @return {?Layers.LayerTreeModel}
    */
   static fromTarget(target) {
-    if (!target.hasDOMCapability())
-      return null;
-
-    var model = target.model(Layers.LayerTreeModel);
-    if (!model)
-      model = new Layers.LayerTreeModel(target);
-    return model;
+    return target.model(Layers.LayerTreeModel);
   }
 
   disable() {
@@ -131,6 +125,8 @@
   }
 };
 
+SDK.SDKModel.register(Layers.LayerTreeModel, SDK.Target.Capability.DOM);
+
 /** @enum {symbol} */
 Layers.LayerTreeModel.Events = {
   LayerTreeChanged: Symbol('LayerTreeChanged'),
diff --git a/third_party/WebKit/Source/devtools/front_end/perf_ui/PieChart.js b/third_party/WebKit/Source/devtools/front_end/perf_ui/PieChart.js
index eedbd72..bb6fa81 100644
--- a/third_party/WebKit/Source/devtools/front_end/perf_ui/PieChart.js
+++ b/third_party/WebKit/Source/devtools/front_end/perf_ui/PieChart.js
@@ -43,9 +43,18 @@
     var root = this._shadowRoot.createChild('div', 'root');
     var svg = this._createSVGChild(root, 'svg');
     this._group = this._createSVGChild(svg, 'g');
-    var background = this._createSVGChild(this._group, 'circle');
-    background.setAttribute('r', 1.01);
-    background.setAttribute('fill', 'hsl(0, 0%, 90%)');
+    this._innerR = 0.618;
+    var strokeWidth = 1 / size;
+    var circle = this._createSVGChild(this._group, 'circle');
+    circle.setAttribute('r', 1);
+    circle.setAttribute('stroke', 'hsl(0, 0%, 80%)');
+    circle.setAttribute('fill', 'transparent');
+    circle.setAttribute('stroke-width', strokeWidth);
+    circle = this._createSVGChild(this._group, 'circle');
+    circle.setAttribute('r', this._innerR);
+    circle.setAttribute('stroke', 'hsl(0, 0%, 80%)');
+    circle.setAttribute('fill', 'transparent');
+    circle.setAttribute('stroke-width', strokeWidth);
     this._foregroundElement = root.createChild('div', 'pie-chart-foreground');
     if (showTotal)
       this._totalElement = this._foregroundElement.createChild('div', 'pie-chart-total');
@@ -97,8 +106,14 @@
     this._lastAngle += sliceAngle;
     var x2 = Math.cos(this._lastAngle);
     var y2 = Math.sin(this._lastAngle);
+    var r2 = this._innerR;
+    var x3 = x2 * r2;
+    var y3 = y2 * r2;
+    var x4 = x1 * r2;
+    var y4 = y1 * r2;
     var largeArc = sliceAngle > Math.PI ? 1 : 0;
-    path.setAttribute('d', 'M0,0 L' + x1 + ',' + y1 + ' A1,1,0,' + largeArc + ',1,' + x2 + ',' + y2 + ' Z');
+    path.setAttribute('d',
+        `M${x1},${y1} A1,1,0,${largeArc},1,${x2},${y2} L${x3},${y3} A${r2},${r2},0,${largeArc},0,${x4},${y4} Z`);
     path.setAttribute('fill', color);
     this._slices.push(path);
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/ApplicationCacheModel.js b/third_party/WebKit/Source/devtools/front_end/resources/ApplicationCacheModel.js
similarity index 85%
rename from third_party/WebKit/Source/devtools/front_end/sdk/ApplicationCacheModel.js
rename to third_party/WebKit/Source/devtools/front_end/resources/ApplicationCacheModel.js
index 17352c30..613b0d3 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/ApplicationCacheModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ApplicationCacheModel.js
@@ -29,18 +29,18 @@
 /**
  * @unrestricted
  */
-SDK.ApplicationCacheModel = class extends SDK.SDKModel {
+Resources.ApplicationCacheModel = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {!SDK.ResourceTreeModel} resourceTreeModel
    */
-  constructor(target, resourceTreeModel) {
-    super(SDK.ApplicationCacheModel, target);
+  constructor(target) {
+    super(target);
 
-    target.registerApplicationCacheDispatcher(new SDK.ApplicationCacheDispatcher(this));
+    target.registerApplicationCacheDispatcher(new Resources.ApplicationCacheDispatcher(this));
     this._agent = target.applicationCacheAgent();
     this._agent.enable();
 
+    var resourceTreeModel = SDK.ResourceTreeModel.fromTarget(target);
     resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameNavigated, this._frameNavigated, this);
     resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameDetached, this._frameDetached, this);
 
@@ -53,10 +53,10 @@
 
   /**
    * @param {!SDK.Target} target
-   * @return {?SDK.ApplicationCacheModel}
+   * @return {?Resources.ApplicationCacheModel}
    */
   static fromTarget(target) {
-    return target.model(SDK.ApplicationCacheModel);
+    return target.model(Resources.ApplicationCacheModel);
   }
 
   _frameNavigated(event) {
@@ -80,7 +80,7 @@
   reset() {
     this._statuses = {};
     this._manifestURLsByFrame = {};
-    this.dispatchEventToListeners(SDK.ApplicationCacheModel.Events.FrameManifestsReset);
+    this.dispatchEventToListeners(Resources.ApplicationCacheModel.Events.FrameManifestsReset);
   }
 
   _mainFrameNavigated() {
@@ -140,11 +140,11 @@
 
     if (!this._manifestURLsByFrame[frameId]) {
       this._manifestURLsByFrame[frameId] = manifestURL;
-      this.dispatchEventToListeners(SDK.ApplicationCacheModel.Events.FrameManifestAdded, frameId);
+      this.dispatchEventToListeners(Resources.ApplicationCacheModel.Events.FrameManifestAdded, frameId);
     }
 
     if (statusChanged)
-      this.dispatchEventToListeners(SDK.ApplicationCacheModel.Events.FrameManifestStatusUpdated, frameId);
+      this.dispatchEventToListeners(Resources.ApplicationCacheModel.Events.FrameManifestStatusUpdated, frameId);
   }
 
   /**
@@ -157,7 +157,7 @@
     delete this._manifestURLsByFrame[frameId];
     delete this._statuses[frameId];
 
-    this.dispatchEventToListeners(SDK.ApplicationCacheModel.Events.FrameManifestRemoved, frameId);
+    this.dispatchEventToListeners(Resources.ApplicationCacheModel.Events.FrameManifestRemoved, frameId);
   }
 
   /**
@@ -219,12 +219,14 @@
    */
   _networkStateUpdated(isNowOnline) {
     this._onLine = isNowOnline;
-    this.dispatchEventToListeners(SDK.ApplicationCacheModel.Events.NetworkStateChanged, isNowOnline);
+    this.dispatchEventToListeners(Resources.ApplicationCacheModel.Events.NetworkStateChanged, isNowOnline);
   }
 };
 
+SDK.SDKModel.register(Resources.ApplicationCacheModel, SDK.Target.Capability.DOM);
+
 /** @enum {symbol} */
-SDK.ApplicationCacheModel.Events = {
+Resources.ApplicationCacheModel.Events = {
   FrameManifestStatusUpdated: Symbol('FrameManifestStatusUpdated'),
   FrameManifestAdded: Symbol('FrameManifestAdded'),
   FrameManifestRemoved: Symbol('FrameManifestRemoved'),
@@ -236,7 +238,7 @@
  * @implements {Protocol.ApplicationCacheDispatcher}
  * @unrestricted
  */
-SDK.ApplicationCacheDispatcher = class {
+Resources.ApplicationCacheDispatcher = class {
   constructor(applicationCacheModel) {
     this._applicationCacheModel = applicationCacheModel;
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js b/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js
index c674334..3e25d9e 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js
@@ -143,7 +143,7 @@
     }
 
     if (set.has(Protocol.Storage.StorageType.Appcache) || hasAll) {
-      var appcacheModel = SDK.ApplicationCacheModel.fromTarget(this._target);
+      var appcacheModel = Resources.ApplicationCacheModel.fromTarget(this._target);
       if (appcacheModel)
         appcacheModel.reset();
     }
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/DOMStorageModel.js b/third_party/WebKit/Source/devtools/front_end/resources/DOMStorageModel.js
index 04588c4e..d166423 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/DOMStorageModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/DOMStorageModel.js
@@ -109,12 +109,11 @@
 Resources.DOMStorageModel = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {!SDK.SecurityOriginManager} securityOriginManager
    */
-  constructor(target, securityOriginManager) {
-    super(Resources.DOMStorageModel, target);
+  constructor(target) {
+    super(target);
 
-    this._securityOriginManager = securityOriginManager;
+    this._securityOriginManager = SDK.SecurityOriginManager.fromTarget(target);
     /** @type {!Object.<string, !Resources.DOMStorage>} */
     this._storages = {};
     this._agent = target.domstorageAgent();
@@ -125,11 +124,7 @@
    * @return {!Resources.DOMStorageModel}
    */
   static fromTarget(target) {
-    var model = target.model(Resources.DOMStorageModel);
-    if (!model)
-      model = new Resources.DOMStorageModel(target, SDK.SecurityOriginManager.fromTarget(target));
-
-    return model;
+    return /** @type {!Resources.DOMStorageModel} */ (target.model(Resources.DOMStorageModel));
   }
 
   enable() {
@@ -289,6 +284,8 @@
   }
 };
 
+SDK.SDKModel.register(Resources.DOMStorageModel, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 Resources.DOMStorageModel.Events = {
   DOMStorageAdded: Symbol('DOMStorageAdded'),
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/DatabaseModel.js b/third_party/WebKit/Source/devtools/front_end/resources/DatabaseModel.js
index 6490293..255546c 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/DatabaseModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/DatabaseModel.js
@@ -133,7 +133,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(Resources.DatabaseModel, target);
+    super(target);
 
     this._databases = [];
     this._agent = target.databaseAgent();
@@ -145,10 +145,7 @@
    * @return {!Resources.DatabaseModel}
    */
   static fromTarget(target) {
-    if (!target[Resources.DatabaseModel._symbol])
-      target[Resources.DatabaseModel._symbol] = new Resources.DatabaseModel(target);
-
-    return target[Resources.DatabaseModel._symbol];
+    return /** @type {!Resources.DatabaseModel} */ (target.model(Resources.DatabaseModel));
   }
 
   enable() {
@@ -186,6 +183,9 @@
   }
 };
 
+SDK.SDKModel.register(Resources.DatabaseModel, SDK.Target.Capability.None);
+
+
 /** @implements {Common.Emittable} */
 Resources.DatabaseModel.DatabaseAddedEvent = class {
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js
index c8bbc665..75850e2 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js
@@ -34,11 +34,10 @@
 Resources.IndexedDBModel = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {!SDK.SecurityOriginManager} securityOriginManager
    */
-  constructor(target, securityOriginManager) {
-    super(Resources.IndexedDBModel, target);
-    this._securityOriginManager = securityOriginManager;
+  constructor(target) {
+    super(target);
+    this._securityOriginManager = SDK.SecurityOriginManager.fromTarget(target);
     this._agent = target.indexedDBAgent();
 
     /** @type {!Map.<!Resources.IndexedDBModel.DatabaseId, !Resources.IndexedDBModel.Database>} */
@@ -144,10 +143,7 @@
    * @return {!Resources.IndexedDBModel}
    */
   static fromTarget(target) {
-    var model = target.model(Resources.IndexedDBModel);
-    if (!model)
-      model = new Resources.IndexedDBModel(target, SDK.SecurityOriginManager.fromTarget(target));
-    return model;
+    return /** @type {!Resources.IndexedDBModel} */ (target.model(Resources.IndexedDBModel));
   }
 
   enable() {
@@ -430,6 +426,8 @@
   }
 };
 
+SDK.SDKModel.register(Resources.IndexedDBModel, SDK.Target.Capability.None);
+
 Resources.IndexedDBModel.KeyTypes = {
   NumberType: 'number',
   StringType: 'string',
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js b/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js
index 357ba474..6474809 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js
@@ -700,24 +700,24 @@
    * @param {!SDK.ResourceTreeModel} resourceTreeModel
    */
   _populateApplicationCacheTree(resourceTreeModel) {
-    this._applicationCacheModel = new SDK.ApplicationCacheModel(this._target, resourceTreeModel);
+    this._applicationCacheModel = Resources.ApplicationCacheModel.fromTarget(this._target);
 
     this._applicationCacheViews = {};
     this._applicationCacheFrameElements = {};
     this._applicationCacheManifestElements = {};
 
     this._applicationCacheModel.addEventListener(
-        SDK.ApplicationCacheModel.Events.FrameManifestAdded, this._applicationCacheFrameManifestAdded, this);
+        Resources.ApplicationCacheModel.Events.FrameManifestAdded, this._applicationCacheFrameManifestAdded, this);
     this._applicationCacheModel.addEventListener(
-        SDK.ApplicationCacheModel.Events.FrameManifestRemoved, this._applicationCacheFrameManifestRemoved, this);
+        Resources.ApplicationCacheModel.Events.FrameManifestRemoved, this._applicationCacheFrameManifestRemoved, this);
     this._applicationCacheModel.addEventListener(
-        SDK.ApplicationCacheModel.Events.FrameManifestsReset, this._resetAppCache, this);
+        Resources.ApplicationCacheModel.Events.FrameManifestsReset, this._resetAppCache, this);
 
     this._applicationCacheModel.addEventListener(
-        SDK.ApplicationCacheModel.Events.FrameManifestStatusUpdated, this._applicationCacheFrameManifestStatusChanged,
-        this);
+        Resources.ApplicationCacheModel.Events.FrameManifestStatusUpdated,
+        this._applicationCacheFrameManifestStatusChanged, this);
     this._applicationCacheModel.addEventListener(
-        SDK.ApplicationCacheModel.Events.NetworkStateChanged, this._applicationCacheNetworkStateChanged, this);
+        Resources.ApplicationCacheModel.Events.NetworkStateChanged, this._applicationCacheNetworkStateChanged, this);
   }
 
   _applicationCacheFrameManifestAdded(event) {
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/module.json b/third_party/WebKit/Source/devtools/front_end/resources/module.json
index 4c645d7..8d45eee 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/resources/module.json
@@ -24,6 +24,7 @@
         "components"
     ],
     "scripts": [
+        "ApplicationCacheModel.js",
         "AppManifestView.js",
         "ApplicationCacheItemsView.js",
         "ClearStorageView.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/CPUProfilerModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/CPUProfilerModel.js
index 6393c51..500369d 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/CPUProfilerModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/CPUProfilerModel.js
@@ -34,7 +34,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.CPUProfilerModel, target);
+    super(target);
     this._isRecording = false;
     target.registerProfilerDispatcher(this);
     target.profilerAgent().enable();
@@ -129,6 +129,9 @@
   }
 };
 
+// TODO(dgozman): should be JS.
+SDK.SDKModel.register(SDK.CPUProfilerModel, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 SDK.CPUProfilerModel.Events = {
   ConsoleProfileStarted: Symbol('ConsoleProfileStarted'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js
index da779c0..cab9e63 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js
@@ -34,11 +34,10 @@
 SDK.CSSModel = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {!SDK.DOMModel} domModel
    */
-  constructor(target, domModel) {
-    super(SDK.CSSModel, target);
-    this._domModel = domModel;
+  constructor(target) {
+    super(target);
+    this._domModel = /** @type {!SDK.DOMModel} */ (SDK.DOMModel.fromTarget(target));
     this._agent = target.cssAgent();
     this._styleLoader = new SDK.CSSModel.ComputedStyleLoader(this);
     SDK.targetManager.addEventListener(SDK.TargetManager.Events.MainFrameNavigated, this._mainFrameNavigated, this);
@@ -1001,6 +1000,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.CSSModel, SDK.Target.Capability.DOM);
+
 /** @typedef {!{range: !Protocol.CSS.SourceRange, styleSheetId: !Protocol.CSS.StyleSheetId, wasUsed: boolean}} */
 SDK.CSSModel.RuleUsage;
 
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/ConsoleModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/ConsoleModel.js
index 9793a4b..e839a2f 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/ConsoleModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/ConsoleModel.js
@@ -34,10 +34,9 @@
 SDK.ConsoleModel = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {?Protocol.LogAgent} logAgent
    */
-  constructor(target, logAgent) {
-    super(SDK.ConsoleModel, target);
+  constructor(target) {
+    super(target);
 
     /** @type {!Array.<!SDK.ConsoleMessage>} */
     this._messages = [];
@@ -45,7 +44,8 @@
     this._messageByExceptionId = new Map();
     this._warnings = 0;
     this._errors = 0;
-    this._logAgent = logAgent;
+    /** @type {?Protocol.LogAgent} */
+    this._logAgent = target.hasLogCapability() ? target.logAgent() : null;
     if (this._logAgent) {
       target.registerLogDispatcher(new SDK.LogDispatcher(this));
       this._logAgent.enable();
@@ -225,6 +225,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.ConsoleModel, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 SDK.ConsoleModel.Events = {
   ConsoleCleared: Symbol('ConsoleCleared'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
index 9d304fc..b0394b65 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
@@ -1048,7 +1048,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.DOMModel, target);
+    super(target);
 
     this._agent = target.domAgent();
 
@@ -1908,6 +1908,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.DOMModel, SDK.Target.Capability.DOM);
+
 /** @enum {symbol} */
 SDK.DOMModel.Events = {
   AttrModified: Symbol('AttrModified'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
index 5e6d0f07..634bc730a 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
@@ -36,7 +36,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.DebuggerModel, target);
+    super(target);
 
     target.registerDebuggerDispatcher(new SDK.DebuggerDispatcher(this));
     this._agent = target.debuggerAgent();
@@ -84,9 +84,7 @@
    * @return {?SDK.DebuggerModel}
    */
   static fromTarget(target) {
-    if (!target || !target.hasJSCapability())
-      return null;
-    return target.model(SDK.DebuggerModel);
+    return target ? target.model(SDK.DebuggerModel) : null;
   }
 
   /**
@@ -837,6 +835,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.DebuggerModel, SDK.Target.Capability.JS);
+
 /** @typedef {{location: ?SDK.DebuggerModel.Location, functionName: string}} */
 SDK.DebuggerModel.FunctionDetails;
 
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/HeapProfilerModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/HeapProfilerModel.js
index 7179be7..c53f92d 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/HeapProfilerModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/HeapProfilerModel.js
@@ -6,7 +6,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.HeapProfilerModel, target);
+    super(target);
     target.registerHeapProfilerDispatcher(new SDK.HeapProfilerDispatcher(this));
     this._enabled = false;
     this._heapProfilerAgent = target.heapProfilerAgent();
@@ -70,6 +70,9 @@
   }
 };
 
+// TODO(dgozman): should be JS.
+SDK.SDKModel.register(SDK.HeapProfilerModel, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 SDK.HeapProfilerModel.Events = {
   HeapStatsUpdate: Symbol('HeapStatsUpdate'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkLog.js b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkLog.js
index d2632d7..b33346e 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkLog.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkLog.js
@@ -31,15 +31,15 @@
 /**
  * @unrestricted
  */
-SDK.NetworkLog = class extends SDK.SDKModel {
+SDK.NetworkLog = class {
   /**
    * @param {!SDK.Target} target
    * @param {!SDK.ResourceTreeModel} resourceTreeModel
    * @param {!SDK.NetworkManager} networkManager
    */
   constructor(target, resourceTreeModel, networkManager) {
-    super(SDK.NetworkLog, target);
-
+    this._target = target;
+    target[SDK.NetworkLog._logSymbol] = this;
     /** @type {!Array<!SDK.NetworkRequest>} */
     this._requests = [];
     /** @type {!Object<string, !SDK.NetworkRequest>} */
@@ -53,11 +53,18 @@
   }
 
   /**
+   * @return {!SDK.Target}
+   */
+  target() {
+    return this._target;
+  }
+
+  /**
    * @param {!SDK.Target} target
    * @return {?SDK.NetworkLog}
    */
   static fromTarget(target) {
-    return target.model(SDK.NetworkLog);
+    return target[SDK.NetworkLog._logSymbol] || null;
   }
 
   /**
@@ -326,3 +333,4 @@
 
 SDK.NetworkLog._initiatorDataSymbol = Symbol('InitiatorData');
 SDK.NetworkLog._pageLoadForRequestSymbol = Symbol('PageLoadForRequest');
+SDK.NetworkLog._logSymbol = Symbol('NetworkLog');
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
index f8f244b..e890b949 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
@@ -36,7 +36,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.NetworkManager, target);
+    super(target);
     this._dispatcher = new SDK.NetworkDispatcher(this);
     this._target = target;
     this._networkAgent = target.networkAgent();
@@ -122,6 +122,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.NetworkManager, SDK.Target.Capability.Network);
+
 /** @enum {symbol} */
 SDK.NetworkManager.Events = {
   RequestStarted: Symbol('RequestStarted'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/ResourceTreeModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/ResourceTreeModel.js
index 035b098..327dac9e 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/ResourceTreeModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/ResourceTreeModel.js
@@ -34,11 +34,11 @@
 SDK.ResourceTreeModel = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {?SDK.NetworkManager} networkManager
-   * @param {!SDK.SecurityOriginManager} securityOriginManager
    */
-  constructor(target, networkManager, securityOriginManager) {
-    super(SDK.ResourceTreeModel, target);
+  constructor(target) {
+    super(target);
+
+    var networkManager = SDK.NetworkManager.fromTarget(target);
     if (networkManager) {
       networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, this._onRequestFinished, this);
       networkManager.addEventListener(
@@ -47,7 +47,7 @@
 
     this._agent = target.pageAgent();
     this._agent.enable();
-    this._securityOriginManager = securityOriginManager;
+    this._securityOriginManager = SDK.SecurityOriginManager.fromTarget(target);
 
     this._fetchResourceTree();
 
@@ -446,6 +446,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.ResourceTreeModel, SDK.Target.Capability.DOM);
+
 /** @enum {symbol} */
 SDK.ResourceTreeModel.Events = {
   FrameAdded: Symbol('FrameAdded'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/RuntimeModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/RuntimeModel.js
index 7a09eeb..8d9464a 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/RuntimeModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/RuntimeModel.js
@@ -36,7 +36,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.RuntimeModel, target);
+    super(target);
 
     this._agent = target.runtimeAgent();
     this.target().registerRuntimeDispatcher(new SDK.RuntimeDispatcher(this));
@@ -323,6 +323,9 @@
   }
 };
 
+// TODO(dgozman): should be JS.
+SDK.SDKModel.register(SDK.RuntimeModel, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 SDK.RuntimeModel.Events = {
   ExecutionContextCreated: Symbol('ExecutionContextCreated'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/SecurityOriginManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/SecurityOriginManager.js
index 0c579c05..4b780c2 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/SecurityOriginManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/SecurityOriginManager.js
@@ -9,7 +9,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.SecurityOriginManager, target);
+    super(target);
 
     /** @type {!Set<string>} */
     this._securityOrigins = new Set();
@@ -21,10 +21,7 @@
    * @return {!SDK.SecurityOriginManager}
    */
   static fromTarget(target) {
-    var securityOriginManager = target.model(SDK.SecurityOriginManager);
-    if (!securityOriginManager)
-      securityOriginManager = new SDK.SecurityOriginManager(target);
-    return securityOriginManager;
+    return /** @type {!SDK.SecurityOriginManager} */ (target.model(SDK.SecurityOriginManager));
   }
 
   /**
@@ -68,6 +65,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.SecurityOriginManager, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 SDK.SecurityOriginManager.Events = {
   SecurityOriginAdded: Symbol('SecurityOriginAdded'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerCacheModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerCacheModel.js
index f6fe747..85217de7 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerCacheModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerCacheModel.js
@@ -8,17 +8,16 @@
   /**
    * Invariant: This model can only be constructed on a ServiceWorker target.
    * @param {!SDK.Target} target
-   * @param {!SDK.SecurityOriginManager} securityOriginManager
    */
-  constructor(target, securityOriginManager) {
-    super(SDK.ServiceWorkerCacheModel, target);
+  constructor(target) {
+    super(target);
 
     /** @type {!Map<string, !SDK.ServiceWorkerCacheModel.Cache>} */
     this._caches = new Map();
 
     this._agent = target.cacheStorageAgent();
 
-    this._securityOriginManager = securityOriginManager;
+    this._securityOriginManager = SDK.SecurityOriginManager.fromTarget(target);
 
     /** @type {boolean} */
     this._enabled = false;
@@ -29,12 +28,7 @@
    * @return {?SDK.ServiceWorkerCacheModel}
    */
   static fromTarget(target) {
-    if (!target.hasBrowserCapability())
-      return null;
-    var instance = target.model(SDK.ServiceWorkerCacheModel);
-    if (!instance)
-      instance = new SDK.ServiceWorkerCacheModel(target, SDK.SecurityOriginManager.fromTarget(target));
-    return instance;
+    return target.model(SDK.ServiceWorkerCacheModel);
   }
 
   enable() {
@@ -271,6 +265,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.ServiceWorkerCacheModel, SDK.Target.Capability.Browser);
+
 /** @enum {symbol} */
 SDK.ServiceWorkerCacheModel.Events = {
   CacheAdded: Symbol('CacheAdded'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerManager.js
index 6fe0efe..01a7eef 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/ServiceWorkerManager.js
@@ -31,12 +31,11 @@
 /**
  * @unrestricted
  */
-SDK.ServiceWorkerManager = class extends SDK.SDKObject {
+SDK.ServiceWorkerManager = class extends SDK.SDKModel {
   /**
    * @param {!SDK.Target} target
-   * @param {!SDK.SubTargetsManager} subTargetsManager
    */
-  constructor(target, subTargetsManager) {
+  constructor(target) {
     super(target);
     target.registerServiceWorkerDispatcher(new SDK.ServiceWorkerDispatcher(this));
     this._lastAnonymousTargetId = 0;
@@ -48,7 +47,8 @@
     if (this._forceUpdateSetting.get())
       this._forceUpdateSettingChanged();
     this._forceUpdateSetting.addChangeListener(this._forceUpdateSettingChanged, this);
-    new SDK.ServiceWorkerContextNamer(target, this, subTargetsManager);
+    new SDK.ServiceWorkerContextNamer(
+        target, this, /** @type {!SDK.SubTargetsManager} */ (SDK.SubTargetsManager.fromTarget(target)));
   }
 
   enable() {
@@ -244,6 +244,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.ServiceWorkerManager, SDK.Target.Capability.Target | SDK.Target.Capability.Browser);
+
 /** @enum {symbol} */
 SDK.ServiceWorkerManager.Events = {
   RegistrationUpdated: Symbol('RegistrationUpdated'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js
index d5845ad..b8501c5 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js
@@ -9,7 +9,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(SDK.SubTargetsManager, target);
+    super(target);
     target.registerTargetDispatcher(new SDK.SubTargetsDispatcher(this));
     this._lastAnonymousTargetId = 0;
     this._agent = target.targetAgent();
@@ -242,6 +242,8 @@
   }
 };
 
+SDK.SDKModel.register(SDK.SubTargetsManager, SDK.Target.Capability.Target);
+
 /** @enum {symbol} */
 SDK.SubTargetsManager.Events = {
   AvailableNodeTargetsChanged: Symbol('AvailableNodeTargetsChanged'),
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/Target.js b/third_party/WebKit/Source/devtools/front_end/sdk/Target.js
index 3d0c173..5b49b92 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/Target.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/Target.js
@@ -24,7 +24,7 @@
     this._parentTarget = parentTarget;
     this._id = SDK.Target._nextId++;
 
-    /** @type {!Map.<!Function, !SDK.SDKModel>} */
+    /** @type {!Map.<function(new:SDK.SDKModel, !SDK.Target), !SDK.SDKModel>} */
     this._modelByConstructor = new Map();
   }
 
@@ -136,11 +136,18 @@
   }
 
   /**
-   * @param {function(new: (!T<!SDK.SDKModel>), ...)} modelClass
+   * @param {function(new:T, !SDK.Target)} modelClass
    * @return {?T}
    * @template T
    */
   model(modelClass) {
+    if (!this._modelByConstructor.get(modelClass)) {
+      var capabilities = SDK.SDKModel._capabilitiesByModelClass.get(modelClass);
+      if (capabilities === undefined)
+        throw 'Model class is not registered';
+      if ((this._capabilitiesMask & capabilities) === capabilities)
+        this._modelByConstructor.set(modelClass, new modelClass(this));
+    }
     return this._modelByConstructor.get(modelClass) || null;
   }
 
@@ -184,6 +191,8 @@
   Network: 16,
   Target: 32,
 
+  None: 0,
+
   AllForTests: 63
 };
 
@@ -214,12 +223,10 @@
  */
 SDK.SDKModel = class extends SDK.SDKObject {
   /**
-   * @param {!Function} modelClass
    * @param {!SDK.Target} target
    */
-  constructor(modelClass, target) {
+  constructor(target) {
     super(target);
-    target._modelByConstructor.set(modelClass, this);
   }
 
   /**
@@ -249,3 +256,17 @@
     this.dispose();
   }
 };
+
+
+/**
+ * @param {function(new:SDK.SDKModel, !SDK.Target)} modelClass
+ * @param {number} capabilities
+ */
+SDK.SDKModel.register = function(modelClass, capabilities) {
+  if (!SDK.SDKModel._capabilitiesByModelClass)
+    SDK.SDKModel._capabilitiesByModelClass = new Map();
+  SDK.SDKModel._capabilitiesByModelClass.set(modelClass, capabilities);
+};
+
+/** @type {!Map<function(new:SDK.SDKModel, !SDK.Target), number>} */
+SDK.SDKModel._capabilitiesByModelClass;
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
index 31d26722..992546e 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
@@ -164,44 +164,30 @@
   createTarget(name, capabilitiesMask, connectionFactory, parentTarget) {
     var target = new SDK.Target(this, name, capabilitiesMask, connectionFactory, parentTarget);
 
-    var logAgent = target.hasLogCapability() ? target.logAgent() : null;
-
     /** @type {!SDK.ConsoleModel} */
-    target.consoleModel = new SDK.ConsoleModel(target, logAgent);
+    target.consoleModel = /** @type {!SDK.ConsoleModel} */ (target.model(SDK.ConsoleModel));
 
-    var networkManager = null;
-    var resourceTreeModel = null;
-    if (target.hasNetworkCapability())
-      networkManager = new SDK.NetworkManager(target);
-    if (networkManager && target.hasDOMCapability()) {
-      resourceTreeModel =
-          new SDK.ResourceTreeModel(target, networkManager, SDK.SecurityOriginManager.fromTarget(target));
+    var networkManager = target.model(SDK.NetworkManager);
+    var resourceTreeModel = target.model(SDK.ResourceTreeModel);
+    if (networkManager && resourceTreeModel)
       new SDK.NetworkLog(target, resourceTreeModel, networkManager);
-    }
 
     /** @type {!SDK.RuntimeModel} */
-    target.runtimeModel = new SDK.RuntimeModel(target);
-
-    if (target.hasJSCapability())
-      new SDK.DebuggerModel(target);
-
-    if (resourceTreeModel) {
-      var domModel = new SDK.DOMModel(target);
-      // TODO(eostroukhov) CSSModel should not depend on RTM
-      new SDK.CSSModel(target, domModel);
-    }
+    target.runtimeModel = /** @type {!SDK.RuntimeModel} */ (target.model(SDK.RuntimeModel));
+    target.model(SDK.DebuggerModel);
+    target.model(SDK.DOMModel);
+    target.model(SDK.CSSModel);
 
     /** @type {?SDK.SubTargetsManager} */
-    target.subTargetsManager = target.hasTargetCapability() ? new SDK.SubTargetsManager(target) : null;
+    target.subTargetsManager = target.model(SDK.SubTargetsManager);
     /** @type {!SDK.CPUProfilerModel} */
-    target.cpuProfilerModel = new SDK.CPUProfilerModel(target);
+    target.cpuProfilerModel = /** @type {!SDK.CPUProfilerModel} */ (target.model(SDK.CPUProfilerModel));
     /** @type {!SDK.HeapProfilerModel} */
-    target.heapProfilerModel = new SDK.HeapProfilerModel(target);
+    target.heapProfilerModel = /** @type {!SDK.HeapProfilerModel} */ (target.model(SDK.HeapProfilerModel));
 
     target.tracingManager = new SDK.TracingManager(target);
 
-    if (target.subTargetsManager && target.hasBrowserCapability())
-      target.serviceWorkerManager = new SDK.ServiceWorkerManager(target, target.subTargetsManager);
+    target.serviceWorkerManager = target.model(SDK.ServiceWorkerManager);
 
     this.addTarget(target);
     return target;
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/module.json b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
index a7799096..0e4c4b4 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
@@ -92,7 +92,6 @@
     "scripts": [
         "Target.js",
         "TargetManager.js",
-        "ApplicationCacheModel.js",
         "Connections.js",
         "ConsoleModel.js",
         "ContentProviders.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/security/SecurityModel.js b/third_party/WebKit/Source/devtools/front_end/security/SecurityModel.js
index 26bf882..157f418 100644
--- a/third_party/WebKit/Source/devtools/front_end/security/SecurityModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/security/SecurityModel.js
@@ -9,7 +9,7 @@
    * @param {!SDK.Target} target
    */
   constructor(target) {
-    super(Security.SecurityModel, target);
+    super(target);
     this._dispatcher = new Security.SecurityDispatcher(this);
     this._securityAgent = target.securityAgent();
     target.registerSecurityDispatcher(this._dispatcher);
@@ -21,10 +21,7 @@
    * @return {?Security.SecurityModel}
    */
   static fromTarget(target) {
-    var model = target.model(Security.SecurityModel);
-    if (!model)
-      model = new Security.SecurityModel(target);
-    return model;
+    return target.model(Security.SecurityModel);
   }
 
   /**
@@ -61,6 +58,8 @@
   }
 };
 
+SDK.SDKModel.register(Security.SecurityModel, SDK.Target.Capability.None);
+
 /** @enum {symbol} */
 Security.SecurityModel.Events = {
   SecurityStateChanged: Symbol('SecurityStateChanged')
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtils.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtils.js
index fa83cb8..7df5edc 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtils.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtils.js
@@ -1454,7 +1454,7 @@
       other:
           new Timeline.TimelineCategory('other', Common.UIString('Other'), false, 'hsl(0, 0%, 87%)', 'hsl(0, 0%, 79%)'),
       idle: new Timeline.TimelineCategory(
-          'idle', Common.UIString('Idle'), false, 'hsl(0, 100%, 100%)', 'hsl(0, 100%, 100%)')
+          'idle', Common.UIString('Idle'), false, 'hsl(0, 0%, 98%)', 'hsl(0, 0%, 98%)')
     };
     return Timeline.TimelineUIUtils._categories;
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
index 89b0e4a38..33cd18b 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
@@ -46,7 +46,6 @@
 };
 
 /**
- * @unrestricted
  * @implements {UI.ListDelegate}
  */
 UI.SuggestBox = class {
@@ -70,9 +69,19 @@
     this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true);
     this._userInteracted = false;
     this._captureEnter = captureEnter;
-    this._viewportWidth = '100vw';
     this._hasVerticalScroll = false;
     this._userEnteredText = '';
+
+    /** @type {?UI.SuggestBox.Overlay} */
+    this._overlay = null;
+    /** @type {?AnchorBox} */
+    this._lastAnchorBox = null;
+    this._lastItemCount = 0;
+    this._hideTimeoutId = 0;
+    /** @type {?Element} */
+    this._bodyElement = null;
+    /** @type {?string} */
+    this._onlyCompletion = null;
   }
 
   /**
@@ -157,7 +166,7 @@
   _onBoxMouseDown(event) {
     if (this._hideTimeoutId) {
       window.clearTimeout(this._hideTimeoutId);
-      delete this._hideTimeoutId;
+      this._hideTimeoutId = 0;
     }
     event.preventDefault();
   }
@@ -190,11 +199,11 @@
     this._userInteracted = false;
     this._bodyElement.removeEventListener('mousedown', this._maybeHideBound, true);
     this._element.ownerDocument.defaultView.removeEventListener('resize', this._hideBound, false);
-    delete this._bodyElement;
+    this._bodyElement = null;
     this._container.remove();
     this._overlay.dispose();
-    delete this._overlay;
-    delete this._lastAnchorBox;
+    this._overlay = null;
+    this._lastAnchorBox = null;
   }
 
   /**
@@ -335,7 +344,7 @@
    * @param {string} userEnteredText
    */
   updateSuggestions(anchorBox, completions, selectHighestPriority, canShowForSingleItem, userEnteredText) {
-    delete this._onlyCompletion;
+    this._onlyCompletion = null;
     if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) {
       this._userEnteredText = userEnteredText;
 
@@ -404,7 +413,7 @@
     if (!this._userInteracted && this._captureEnter)
       return false;
 
-    var hasSelectedItem = !!this._list.selectedItem() || this._onlyCompletion;
+    var hasSelectedItem = !!this._list.selectedItem() || !!this._onlyCompletion;
     this.acceptSuggestion();
 
     // Report the event as non-handled if there is no selected item,
@@ -423,9 +432,6 @@
  */
 UI.SuggestBox.Suggestions;
 
-/**
- * @unrestricted
- */
 UI.SuggestBox.Overlay = class {
   /**
    * // FIXME: make SuggestBox work for multiple documents.
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js b/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
index 2dc28a22..1e79d22 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
@@ -1400,7 +1400,8 @@
        * @this {Node}
        */
       function toggleCheckbox(event) {
-        if (event.target !== checkboxElement && event.target !== this) {
+        var deepTarget = event.deepElementFromPoint();
+        if (deepTarget !== checkboxElement && deepTarget !== this) {
           event.consume();
           checkboxElement.click();
         }
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTServer.cpp b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTServer.cpp
index 8697c84..384c5ced 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTServer.cpp
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTServer.cpp
@@ -21,10 +21,9 @@
 
 namespace {
 
-const char kGATTServerDisconnected[] =
-    "GATT Server disconnected while retrieving services.";
 const char kGATTServerNotConnected[] =
-    "GATT Server is disconnected. Cannot retrieve services.";
+    "GATT Server is disconnected. Cannot retrieve services. (Re)connect first "
+    "with `device.gatt.connect`.";
 
 }  // namespace
 
@@ -108,7 +107,7 @@
   // If the device is disconnected, reject.
   if (!RemoveFromActiveAlgorithms(resolver)) {
     resolver->reject(
-        DOMException::create(NetworkError, kGATTServerDisconnected));
+        DOMException::create(NetworkError, kGATTServerNotConnected));
     return;
   }
 
diff --git a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
index 67b4817..a1e03c9 100644
--- a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
+++ b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
@@ -17,6 +17,7 @@
 #include "public/platform/Platform.h"
 #include "public/platform/WebCompositorSupport.h"
 #include "public/platform/WebLayer.h"
+#include "public/platform/WebLayerTreeView.h"
 #include "public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom-blink.h"
 #include "ui/gfx/geometry/size.h"
 #include "wtf/Functional.h"
@@ -55,11 +56,13 @@
 }  // namespace
 
 CanvasSurfaceLayerBridge::CanvasSurfaceLayerBridge(
-    CanvasSurfaceLayerBridgeObserver* observer)
+    CanvasSurfaceLayerBridgeObserver* observer,
+    WebLayerTreeView* layerTreeView)
     : m_weakFactory(this),
       m_observer(observer),
       m_binding(this),
-      m_frameSinkId(Platform::current()->generateFrameSinkId()) {
+      m_frameSinkId(Platform::current()->generateFrameSinkId()),
+      m_parentFrameSinkId(layerTreeView->getFrameSinkId()) {
   m_refFactory =
       new OffscreenCanvasSurfaceReferenceFactory(m_weakFactory.GetWeakPtr());
 
@@ -67,8 +70,11 @@
   mojom::blink::OffscreenCanvasSurfaceFactoryPtr serviceFactory;
   Platform::current()->interfaceProvider()->getInterface(
       mojo::MakeRequest(&serviceFactory));
+  // TODO(xlai): Ensure OffscreenCanvas commit() is still functional when a
+  // frame-less HTML canvas's document is reparenting under another frame.
+  // See crbug.com/683172.
   serviceFactory->CreateOffscreenCanvasSurface(
-      m_frameSinkId, m_binding.CreateInterfacePtrAndBind(),
+      m_parentFrameSinkId, m_frameSinkId, m_binding.CreateInterfacePtrAndBind(),
       mojo::MakeRequest(&m_service));
 }
 
diff --git a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.h b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.h
index 89a473a..ce505128 100644
--- a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.h
+++ b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.h
@@ -22,6 +22,7 @@
 namespace blink {
 
 class WebLayer;
+class WebLayerTreeView;
 
 class PLATFORM_EXPORT CanvasSurfaceLayerBridgeObserver {
  public:
@@ -34,7 +35,8 @@
 class PLATFORM_EXPORT CanvasSurfaceLayerBridge
     : NON_EXPORTED_BASE(public mojom::blink::OffscreenCanvasSurfaceClient) {
  public:
-  explicit CanvasSurfaceLayerBridge(CanvasSurfaceLayerBridgeObserver*);
+  explicit CanvasSurfaceLayerBridge(CanvasSurfaceLayerBridgeObserver*,
+                                    WebLayerTreeView*);
   ~CanvasSurfaceLayerBridge();
   void createSolidColorLayer();
   WebLayer* getWebLayer() const { return m_webLayer.get(); }
@@ -58,8 +60,9 @@
   mojom::blink::OffscreenCanvasSurfacePtr m_service;
   mojo::Binding<mojom::blink::OffscreenCanvasSurfaceClient> m_binding;
 
-  cc::FrameSinkId m_frameSinkId;
+  const cc::FrameSinkId m_frameSinkId;
   cc::SurfaceId m_currentSurfaceId;
+  const cc::FrameSinkId m_parentFrameSinkId;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcher.h b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcher.h
index d5b7885..d3bc686 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcher.h
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcher.h
@@ -25,6 +25,7 @@
   virtual void dispatchFrame(RefPtr<StaticBitmapImage>,
                              double commitStartTime,
                              bool isWebGLSoftwareRendering) = 0;
+  virtual void setNeedsBeginFrame(bool) = 0;
   virtual void reclaimResource(unsigned resourceId) = 0;
 
   virtual void reshape(int width, int height) = 0;
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
index a88f8c2..752acbf0 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
@@ -27,12 +27,6 @@
 
 namespace blink {
 
-// This constant specifies the maximum number of pixel buffer that are allowed
-// to co-exist at a given time. The minimum number is 2 (double buffered).
-// larger numbers can help maintain a steadier frame rates, but they increase
-// latency.
-const int kMaximumOffscreenCanvasBufferCount = 3;
-
 OffscreenCanvasFrameDispatcherImpl::OffscreenCanvasFrameDispatcherImpl(
     OffscreenCanvasFrameDispatcherClient* client,
     uint32_t clientId,
@@ -45,21 +39,25 @@
       m_width(width),
       m_height(height),
       m_changeSizeForNextCommit(false),
+      m_needsBeginFrame(false),
       m_nextResourceId(1u),
       m_binding(this),
       m_placeholderCanvasId(canvasId) {
-  m_currentLocalSurfaceId = m_surfaceIdAllocator.GenerateId();
-  DCHECK(!m_sink.is_bound());
-  mojom::blink::OffscreenCanvasCompositorFrameSinkProviderPtr provider;
-  Platform::current()->interfaceProvider()->getInterface(
-      mojo::MakeRequest(&provider));
-  provider->CreateCompositorFrameSink(m_frameSinkId,
-                                      m_binding.CreateInterfacePtrAndBind(),
-                                      mojo::MakeRequest(&m_sink));
+  if (m_frameSinkId.is_valid()) {
+    // Only frameless canvas pass an invalid frame sink id; we don't create
+    // mojo channel for this special case.
+    m_currentLocalSurfaceId = m_surfaceIdAllocator.GenerateId();
+    DCHECK(!m_sink.is_bound());
+    mojom::blink::OffscreenCanvasCompositorFrameSinkProviderPtr provider;
+    Platform::current()->interfaceProvider()->getInterface(
+        mojo::MakeRequest(&provider));
+    provider->CreateCompositorFrameSink(m_frameSinkId,
+                                        m_binding.CreateInterfacePtrAndBind(),
+                                        mojo::MakeRequest(&m_sink));
+  }
 }
 
 OffscreenCanvasFrameDispatcherImpl::~OffscreenCanvasFrameDispatcherImpl() {
-  m_syntheticBeginFrameTask.cancel();
 }
 
 void OffscreenCanvasFrameDispatcherImpl::setTransferableResourceToSharedBitmap(
@@ -185,6 +183,23 @@
 
 }  // namespace
 
+void OffscreenCanvasFrameDispatcherImpl::postImageToPlaceholder(
+    RefPtr<StaticBitmapImage> image) {
+  // After this point, |image| can only be used on the main thread, until
+  // it is returned.
+  image->transfer();
+  RefPtr<WebTaskRunner> dispatcherTaskRunner =
+      Platform::current()->currentThread()->getWebTaskRunner();
+
+  Platform::current()->mainThread()->getWebTaskRunner()->postTask(
+      BLINK_FROM_HERE,
+      crossThreadBind(updatePlaceholderImage, this->createWeakPtr(),
+                      WTF::passed(std::move(dispatcherTaskRunner)),
+                      m_placeholderCanvasId, std::move(image),
+                      m_nextResourceId));
+  m_spareResourceLocks.insert(m_nextResourceId);
+}
+
 void OffscreenCanvasFrameDispatcherImpl::dispatchFrame(
     RefPtr<StaticBitmapImage> image,
     double commitStartTime,
@@ -192,6 +207,10 @@
     called on SwiftShader. */) {
   if (!image || !verifyImageSize(image->size()))
     return;
+  if (!m_frameSinkId.is_valid()) {
+    postImageToPlaceholder(std::move(image));
+    return;
+  }
   cc::CompositorFrame frame;
   // TODO(crbug.com/652931): update the device_scale_factor
   frame.metadata.device_scale_factor = 1.0f;
@@ -247,19 +266,7 @@
     }
   }
 
-  // After this point, |image| can only be used on the main thread, until
-  // it is returned.
-  image->transfer();
-  RefPtr<WebTaskRunner> dispatcherTaskRunner =
-      Platform::current()->currentThread()->getWebTaskRunner();
-
-  Platform::current()->mainThread()->getWebTaskRunner()->postTask(
-      BLINK_FROM_HERE,
-      crossThreadBind(updatePlaceholderImage, this->createWeakPtr(),
-                      WTF::passed(std::move(dispatcherTaskRunner)),
-                      m_placeholderCanvasId, std::move(image), resource.id));
-  m_spareResourceLocks.insert(m_nextResourceId);
-
+  postImageToPlaceholder(std::move(image));
   commitTypeHistogram.count(commitType);
 
   m_nextResourceId++;
@@ -371,44 +378,26 @@
     m_currentLocalSurfaceId = m_surfaceIdAllocator.GenerateId();
     m_changeSizeForNextCommit = false;
   }
+
   m_sink->SubmitCompositorFrame(m_currentLocalSurfaceId, std::move(frame));
-
-  // TODO(crbug.com/674744): Get BeginFrame to fire on its own.
-  scheduleSyntheticBeginFrame();
-}
-
-void OffscreenCanvasFrameDispatcherImpl::scheduleSyntheticBeginFrame() {
-  m_syntheticBeginFrameTask =
-      Platform::current()
-          ->currentThread()
-          ->getWebTaskRunner()
-          ->postDelayedCancellableTask(
-              BLINK_FROM_HERE,
-              WTF::bind(&OffscreenCanvasFrameDispatcherImpl::OnBeginFrame,
-                        WTF::unretained(this), cc::BeginFrameArgs()),
-              16);
 }
 
 void OffscreenCanvasFrameDispatcherImpl::DidReceiveCompositorFrameAck() {
   // TODO(fsamuel): Implement this.
 }
 
+void OffscreenCanvasFrameDispatcherImpl::setNeedsBeginFrame(
+    bool needsBeginFrame) {
+  if (m_sink && needsBeginFrame != m_needsBeginFrame) {
+    m_needsBeginFrame = needsBeginFrame;
+    m_sink->SetNeedsBeginFrame(needsBeginFrame);
+  }
+}
+
 void OffscreenCanvasFrameDispatcherImpl::OnBeginFrame(
     const cc::BeginFrameArgs& beginFrameArgs) {
-  if (!client())
-    return;
-  unsigned framesInFlight = m_cachedImages.size() + m_sharedBitmaps.size() +
-                            m_cachedTextureIds.size();
-
-  // Limit the rate of compositor commits.
-  if (framesInFlight < kMaximumOffscreenCanvasBufferCount) {
-    client()->beginFrame();
-  } else {
-    // TODO(crbug.com/674744): Get BeginFrame to fire on its own.
-    // The following call is to reschedule the frame in cases where we encounter
-    // a backlog.
-    scheduleSyntheticBeginFrame();
-  }
+  DCHECK(client());
+  client()->beginFrame();
 }
 
 void OffscreenCanvasFrameDispatcherImpl::ReclaimResources(
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h
index 8fe5f43..cb197b7 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.h
@@ -32,6 +32,7 @@
 
   // OffscreenCanvasFrameDispatcher implementation.
   ~OffscreenCanvasFrameDispatcherImpl() final;
+  void setNeedsBeginFrame(bool) final;
   void dispatchFrame(RefPtr<StaticBitmapImage>,
                      double commitStartTime,
                      bool isWebGLSoftwareRendering = false) final;
@@ -56,15 +57,13 @@
  private:
   // Surface-related
   cc::SurfaceIdAllocator m_surfaceIdAllocator;
-  void scheduleSyntheticBeginFrame();  // To be removed (crbug.com/674744)
-
-  TaskHandle m_syntheticBeginFrameTask;  // To be removed (crbug.com/674744)
   const cc::FrameSinkId m_frameSinkId;
   cc::LocalSurfaceId m_currentLocalSurfaceId;
 
   int m_width;
   int m_height;
   bool m_changeSizeForNextCommit;
+  bool m_needsBeginFrame;
 
   unsigned m_nextResourceId;
   HashMap<unsigned, RefPtr<StaticBitmapImage>> m_cachedImages;
@@ -73,6 +72,7 @@
   HashSet<unsigned> m_spareResourceLocks;
 
   bool verifyImageSize(const IntSize);
+  void postImageToPlaceholder(RefPtr<StaticBitmapImage>);
 
   cc::mojom::blink::MojoCompositorFrameSinkPtr m_sink;
   mojo::Binding<cc::mojom::blink::MojoCompositorFrameSinkClient> m_binding;
diff --git a/third_party/WebKit/Source/web/ChromeClientImpl.cpp b/third_party/WebKit/Source/web/ChromeClientImpl.cpp
index c41e111..617bc5e 100644
--- a/third_party/WebKit/Source/web/ChromeClientImpl.cpp
+++ b/third_party/WebKit/Source/web/ChromeClientImpl.cpp
@@ -51,6 +51,7 @@
 #include "core/layout/compositing/CompositedSelection.h"
 #include "core/loader/DocumentLoader.h"
 #include "core/loader/FrameLoadRequest.h"
+#include "core/page/ChromeClient.h"
 #include "core/page/Page.h"
 #include "core/page/PopupOpeningObserver.h"
 #include "modules/accessibility/AXObject.h"
@@ -928,6 +929,11 @@
   return false;
 }
 
+WebLayerTreeView* ChromeClientImpl::getWebLayerTreeView(LocalFrame* frame) {
+  WebLocalFrameImpl* webFrame = WebLocalFrameImpl::fromFrame(frame);
+  return webFrame->localRoot()->frameWidget()->getLayerTreeView();
+}
+
 void ChromeClientImpl::setEventListenerProperties(
     LocalFrame* frame,
     WebEventListenerClass eventClass,
diff --git a/third_party/WebKit/Source/web/ChromeClientImpl.h b/third_party/WebKit/Source/web/ChromeClientImpl.h
index 1daf994..4d94948b 100644
--- a/third_party/WebKit/Source/web/ChromeClientImpl.h
+++ b/third_party/WebKit/Source/web/ChromeClientImpl.h
@@ -228,6 +228,8 @@
 
   void installSupplements(LocalFrame&) override;
 
+  WebLayerTreeView* getWebLayerTreeView(LocalFrame*) override;
+
  private:
   explicit ChromeClientImpl(WebViewImpl*);
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py
index d00ba69d..7bfd8901 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py
@@ -58,8 +58,9 @@
         self.web = web or MockWeb()
 
         self._scm = scm
-        # FIXME: we should never initialize the SCM by default, since the real
-        # object doesn't either. This has caused at least one bug (see bug 89498).
+        # TODO(qyearsley): we should never initialize the SCM by default, since
+        # the real object doesn't either. This has caused at least one bug
+        # (see bug 89498).
         if initialize_scm_by_default:
             self.initialize_scm()
         self.buildbot = MockBuildBot()
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py
index aafb14e..7dac6d5c 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py
@@ -49,7 +49,7 @@
 def is_exportable(chromium_commit, local_wpt):
     """Checks whether a given patch is exportable and can be applied."""
     patch = chromium_commit.format_patch()
-    return (patch and
-            local_wpt.test_patch(patch) and
-            'NOEXPORT=true' not in chromium_commit.message() and
-            not chromium_commit.message().startswith('Import '))
+    return ('NOEXPORT=true' not in chromium_commit.message() and
+            not chromium_commit.message().startswith('Import ') and
+            patch and
+            local_wpt.test_patch(patch, chromium_commit))
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common_unittest.py
index bc8ea51..e859d256 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common_unittest.py
@@ -20,8 +20,8 @@
 
 class MockLocalWPT(object):
 
-    def test_patch(self, _):
-        return True
+    def test_patch(self, patch, chromium_commit):  # pylint: disable=unused-argument
+        return 'patch'
 
 
 class CommonTest(unittest.TestCase):
@@ -68,7 +68,8 @@
             ['git', 'rev-list', 'beefcafe..HEAD', '--reverse', '--',
              'badbeef8/third_party/WebKit/LayoutTests/external/wpt/'],
             ['git', 'diff-tree', '--name-only', '--no-commit-id', '-r', 'badbeef8', '--',
-             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt']
+             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt'],
+            ['git', 'show', '--format=%B', '--no-patch', 'badbeef8']
         ])
 
     def test_ignores_reverted_commits_with_noexport_true(self):
@@ -87,7 +88,9 @@
             ['git', 'rev-list', 'beefcafe..HEAD', '--reverse', '--',
              'badbeef8/third_party/WebKit/LayoutTests/external/wpt/'],
             ['git', 'diff-tree', '--name-only', '--no-commit-id', '-r', 'badbeef8', '--',
-             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt']])
+             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt'],
+            ['git', 'show', '--format=%B', '--no-patch', 'badbeef8']
+        ])
 
     def test_ignores_commits_that_start_with_import(self):
         host = MockHost()
@@ -105,5 +108,7 @@
             ['git', 'rev-list', 'beefcafe..HEAD', '--reverse', '--',
              'badbeef8/third_party/WebKit/LayoutTests/external/wpt/'],
             ['git', 'diff-tree', '--name-only', '--no-commit-id', '-r', 'badbeef8', '--',
-             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt']
+             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt'],
+            ['git', 'show', '--format=%B', '--no-patch', 'badbeef8'],
+            ['git', 'show', '--format=%B', '--no-patch', 'badbeef8'],
         ])
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py
index d5616de..0081f9e 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py
@@ -101,7 +101,7 @@
 
         return self.branch_name
 
-    def test_patch(self, patch):
+    def test_patch(self, patch, chromium_commit=None):
         """Returns the expected output of a patch against origin/master.
 
         Args:
@@ -120,7 +120,10 @@
             self.run(['git', 'add', '.'])
             output = self.run(['git', 'diff', 'origin/master'])
         except ScriptError:
-            _log.warning('Patch did not apply cleanly, skipping...')
+            _log.warning('Patch did not apply cleanly, skipping.')
+            if chromium_commit:
+                _log.warning('Commit details:\n%s\n%s', chromium_commit.sha,
+                             chromium_commit.subject())
             output = ''
 
         self.clean()
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
index a0dbc3b..0a809a7 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
@@ -39,6 +39,7 @@
 
     def __init__(self, host):
         self.host = host
+        self.host.initialize_scm()
         self.executive = host.executive
         self.fs = host.filesystem
         self.finder = WebKitFinder(self.fs)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
index 84c8d92..2af21d19 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
@@ -4,6 +4,7 @@
 
 import unittest
 
+from webkitpy.common.checkout.scm.git_mock import MockGit
 from webkitpy.common.host_mock import MockHost
 from webkitpy.common.system.executive_mock import MockExecutive
 from webkitpy.w3c.test_importer import TestImporter
@@ -127,3 +128,24 @@
                     '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/MANIFEST.json'
                 ]
             ])
+
+    def test_get_directory_owners(self):
+        host = MockHost()
+        host.filesystem.write_text_file(
+            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations',
+            '## Owners: someone@chromium.org\n'
+            '# external/wpt/foo [ Pass ]\n')
+        git = MockGit()
+        git.changed_files = lambda: ['third_party/WebKit/LayoutTests/external/wpt/foo/x.html']
+        host.scm = lambda: git
+        importer = TestImporter(host)
+        self.assertEqual(importer.get_directory_owners(), {'someone@chromium.org': 'external/wpt/foo'})
+
+    def test_get_directory_owners_no_changed_files(self):
+        host = MockHost()
+        host.filesystem.write_text_file(
+            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations',
+            '## Owners: someone@chromium.org\n'
+            '# external/wpt/foo [ Pass ]\n')
+        importer = TestImporter(host)
+        self.assertEqual(importer.get_directory_owners(), {})
diff --git a/third_party/WebKit/public/platform/WebLayerTreeView.h b/third_party/WebKit/public/platform/WebLayerTreeView.h
index 6b7889e7..95e02689 100644
--- a/third_party/WebKit/public/platform/WebLayerTreeView.h
+++ b/third_party/WebKit/public/platform/WebLayerTreeView.h
@@ -33,6 +33,7 @@
 #include "WebEventListenerProperties.h"
 #include "WebFloatPoint.h"
 #include "WebSize.h"
+#include "cc/surfaces/frame_sink_id.h"
 
 namespace cc {
 class AnimationHost;
@@ -163,6 +164,9 @@
   virtual void updateTouchRectsForSubframeIfNecessary() {}
   virtual void setHaveScrollEventHandlers(bool) {}
 
+  // Returns the FrameSinkId of the widget associated with this layer tree view.
+  virtual cc::FrameSinkId getFrameSinkId() { return cc::FrameSinkId(); }
+
   // Debugging / dangerous ---------------------------------------------
 
   virtual WebEventListenerProperties eventListenerProperties(
diff --git a/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom b/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom
index 2eaca70e..afd4249 100644
--- a/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom
+++ b/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom
@@ -22,7 +22,8 @@
 };
 
 interface OffscreenCanvasSurfaceFactory {
-  CreateOffscreenCanvasSurface(cc.mojom.FrameSinkId frame_sink_id,
+  CreateOffscreenCanvasSurface(cc.mojom.FrameSinkId parent_frame_sink_id,
+                               cc.mojom.FrameSinkId frame_sink_id,
                                OffscreenCanvasSurfaceClient client,
                                OffscreenCanvasSurface& service);
 };
diff --git a/third_party/gif_player/OWNERS b/third_party/gif_player/OWNERS
index fc6a6ef..aae21dcc 100644
--- a/third_party/gif_player/OWNERS
+++ b/third_party/gif_player/OWNERS
@@ -1 +1,2 @@
-ianwen@chromium.org
\ No newline at end of file
+bauerb@chromium.org
+tedchoc@chromium.org
\ No newline at end of file
diff --git a/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp b/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp
index 8ad798f..c173373 100644
--- a/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp
+++ b/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp
@@ -679,12 +679,15 @@
       "pagePopup",
       "paintWorklet",
       "path",
+      "position",
       "processingInstruction",
       "readyState",
       "relList",
+      "referrer",
       "referrerPolicy",
       "resource",
       "response",
+      "restrictedKeyMap",
       "sandboxSupport",
       "screenInfo",
       "screenOrientationController",
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index b4af785..f04d27b9e 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -96227,6 +96227,7 @@
   <int value="1367671275" label="enable-proximity-auth-proximity-detection"/>
   <int value="1371092708" label="disable-desktop-capture-picker-old-ui"/>
   <int value="1371907429" label="enable-wallet-card-import"/>
+  <int value="1372199493" label="windows10-custom-titlebar"/>
   <int value="1372680885" label="enable-mtp-write-support"/>
   <int value="1373777956" label="disable-threaded-gpu-rasterization"/>
   <int value="1378310092" label="disable-suggestions-service"/>
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index 90abb154..64d61c4 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -22,6 +22,7 @@
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/views/corewm/tooltip_aura.h"
 #include "ui/views/mus/mus_client.h"
+#include "ui/views/mus/mus_property_mirror.h"
 #include "ui/views/mus/window_manager_frame_values.h"
 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
 #include "ui/views/widget/native_widget_aura.h"
@@ -192,6 +193,7 @@
   aura::Env::GetInstance()->AddObserver(this);
   MusClient::Get()->AddObserver(this);
   native_widget_delegate_->AsWidget()->AddObserver(this);
+  desktop_native_widget_aura_->content_window()->AddObserver(this);
   // DesktopNativeWidgetAura registers the association between |content_window_|
   // and Widget, but code may also want to go from the root (window()) to the
   // Widget. This call enables that.
@@ -206,6 +208,7 @@
   // the cursor-client needs to be unset on the root-window before
   // |cursor_manager_| is destroyed.
   aura::client::SetCursorClient(window(), nullptr);
+  desktop_native_widget_aura_->content_window()->RemoveObserver(this);
   native_widget_delegate_->AsWidget()->RemoveObserver(this);
   MusClient::Get()->RemoveObserver(this);
   aura::Env::GetInstance()->RemoveObserver(this);
@@ -725,6 +728,22 @@
   is_active_ = active;
 }
 
+void DesktopWindowTreeHostMus::OnWindowPropertyChanged(aura::Window* window,
+                                                       const void* key,
+                                                       intptr_t old) {
+  DCHECK_EQ(window, desktop_native_widget_aura_->content_window());
+  DCHECK(!window->GetRootWindow() || this->window() == window->GetRootWindow());
+  if (!this->window())
+    return;
+
+  // Allow mus clients to mirror widget window properties to their root windows.
+  MusPropertyMirror* property_mirror = MusClient::Get()->mus_property_mirror();
+  if (property_mirror) {
+    property_mirror->MirrorPropertyFromWidgetWindowToRootWindow(
+        window, this->window(), key);
+  }
+}
+
 void DesktopWindowTreeHostMus::ShowImpl() {
   native_widget_delegate_->OnNativeWidgetVisibilityChanging(true);
   // Using ui::SHOW_STATE_NORMAL matches that of DesktopWindowTreeHostX11.
diff --git a/ui/views/mus/desktop_window_tree_host_mus.h b/ui/views/mus/desktop_window_tree_host_mus.h
index fa074ba..92c58e3 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.h
+++ b/ui/views/mus/desktop_window_tree_host_mus.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "ui/aura/env_observer.h"
 #include "ui/aura/mus/window_tree_host_mus.h"
+#include "ui/aura/window_observer.h"
 #include "ui/views/mus/mus_client_observer.h"
 #include "ui/views/mus/mus_export.h"
 #include "ui/views/widget/desktop_aura/desktop_window_tree_host.h"
@@ -27,6 +28,7 @@
     : public DesktopWindowTreeHost,
       public MusClientObserver,
       public WidgetObserver,
+      public aura::WindowObserver,
       public aura::WindowTreeHostMus,
       public aura::EnvObserver {
  public:
@@ -129,7 +131,12 @@
   // WidgetObserver:
   void OnWidgetActivationChanged(Widget* widget, bool active) override;
 
-  // WindowTreeHostMus:
+  // aura::WindowObserver:
+  void OnWindowPropertyChanged(aura::Window* window,
+                               const void* key,
+                               intptr_t old) override;
+
+  // aura::WindowTreeHostMus:
   void ShowImpl() override;
   void HideImpl() override;
   void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels) override;
diff --git a/ui/views/mus/mus_client.cc b/ui/views/mus/mus_client.cc
index 2e23362..743ea284 100644
--- a/ui/views/mus/mus_client.cc
+++ b/ui/views/mus/mus_client.cc
@@ -26,6 +26,7 @@
 #include "ui/views/mus/aura_init.h"
 #include "ui/views/mus/clipboard_mus.h"
 #include "ui/views/mus/desktop_window_tree_host_mus.h"
+#include "ui/views/mus/mus_property_mirror.h"
 #include "ui/views/mus/pointer_watcher_event_router.h"
 #include "ui/views/mus/screen_mus.h"
 #include "ui/views/views_delegate.h"
@@ -228,6 +229,10 @@
 void MusClient::RemoveObserver(MusClientObserver* observer) {
   observer_list_.RemoveObserver(observer);
 }
+void MusClient::SetMusPropertyMirror(
+    std::unique_ptr<MusPropertyMirror> mirror) {
+  mus_property_mirror_ = std::move(mirror);
+}
 
 void MusClient::OnEmbed(
     std::unique_ptr<aura::WindowTreeHostMus> window_tree_host) {
diff --git a/ui/views/mus/mus_client.h b/ui/views/mus/mus_client.h
index 72792a7..cccc44b0 100644
--- a/ui/views/mus/mus_client.h
+++ b/ui/views/mus/mus_client.h
@@ -45,6 +45,7 @@
 namespace views {
 
 class MusClientObserver;
+class MusPropertyMirror;
 class PointerWatcherEventRouter;
 class ScreenMus;
 
@@ -58,8 +59,7 @@
 
 // MusClient establishes a connection to mus and sets up necessary state so that
 // aura and views target mus. This class is useful for typical clients, not the
-// WindowManager. Most clients don't create this directly, rather use
-// AuraInit.
+// WindowManager. Most clients don't create this directly, rather use AuraInit.
 class VIEWS_MUS_EXPORT MusClient
     : public aura::WindowTreeClientDelegate,
       public ScreenMusDelegate,
@@ -104,6 +104,11 @@
   void AddObserver(MusClientObserver* observer);
   void RemoveObserver(MusClientObserver* observer);
 
+  void SetMusPropertyMirror(std::unique_ptr<MusPropertyMirror> mirror);
+  MusPropertyMirror* mus_property_mirror() {
+    return mus_property_mirror_.get();
+  }
+
  private:
   friend class AuraInit;
   friend class test::MusClientTestApi;
@@ -140,6 +145,7 @@
   std::unique_ptr<ScreenMus> screen_;
 
   std::unique_ptr<aura::PropertyConverter> property_converter_;
+  std::unique_ptr<MusPropertyMirror> mus_property_mirror_;
 
   std::unique_ptr<aura::WindowTreeClient> window_tree_client_;
 
diff --git a/ui/views/mus/mus_property_mirror.h b/ui/views/mus/mus_property_mirror.h
new file mode 100644
index 0000000..f7a9a6d
--- /dev/null
+++ b/ui/views/mus/mus_property_mirror.h
@@ -0,0 +1,34 @@
+// Copyright 2017 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 UI_VIEWS_MUS_MUS_PROPERTY_MIRROR_H_
+#define UI_VIEWS_MUS_MUS_PROPERTY_MIRROR_H_
+
+#include "ui/views/mus/mus_export.h"
+
+namespace aura {
+class Window;
+}
+
+namespace views {
+
+// Facilitates copying mus client window properties to their mash frame windows.
+class VIEWS_MUS_EXPORT MusPropertyMirror {
+ public:
+  virtual ~MusPropertyMirror() {}
+
+  // Called when a property with the given |key| has changed for |window|.
+  // |window| is what mus clients get when calling |widget->GetNativeWindow()|.
+  // |root_window| is the top-level window representing the widget that is owned
+  // by the window manager and that the window manager observes for changes.
+  // Various ash features rely on property values of mus clients' root windows.
+  virtual void MirrorPropertyFromWidgetWindowToRootWindow(
+      aura::Window* window,
+      aura::Window* root_window,
+      const void* key) = 0;
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_MUS_MUS_PROPERTY_MIRROR_H_