diff --git a/ash/autoclick/mus/BUILD.gn b/ash/autoclick/mus/BUILD.gn
index 558bd4d7..c69bc11 100644
--- a/ash/autoclick/mus/BUILD.gn
+++ b/ash/autoclick/mus/BUILD.gn
@@ -22,7 +22,6 @@
     "//mojo/common",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//services/ui/public/cpp",
     "//services/ui/public/interfaces",
     "//ui/aura",
@@ -45,7 +44,6 @@
     "//base",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//ui/views/mus:for_mojo_application",
   ]
 
diff --git a/ash/touch_hud/mus/BUILD.gn b/ash/touch_hud/mus/BUILD.gn
index 1915e0e..8c82e8d 100644
--- a/ash/touch_hud/mus/BUILD.gn
+++ b/ash/touch_hud/mus/BUILD.gn
@@ -21,7 +21,6 @@
     "//mojo/common",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//services/ui/public/cpp",
     "//services/ui/public/interfaces",
     "//ui/views",
@@ -43,7 +42,6 @@
     "//base",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//ui/views/mus:for_mojo_application",
   ]
 
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index fa0bd60..a957fa7 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -942,7 +942,6 @@
     "//cc/ipc",
     "//cc/ipc:interfaces",
     "//cc/paint",
-    "//cc/paint",
     "//cc/surfaces",
     "//cc/surfaces:surface_id",
     "//gpu",
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 440dc93..493eb02 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -192,14 +192,12 @@
     "//components/precache/android:precache_java",
     "//components/safe_browsing_db/android:safe_browsing_java",
     "//components/safe_json/android:safe_json_java",
-    "//components/safe_json/android:safe_json_java",
     "//components/signin/core/browser/android:java",
     "//components/spellcheck/browser/android:java",
     "//components/sync/android:sync_java",
     "//components/url_formatter/android:url_formatter_java",
     "//components/variations/android:variations_java",
     "//components/web_contents_delegate_android:web_contents_delegate_android_java",
-    "//components/web_contents_delegate_android:web_contents_delegate_android_java",
     "//components/web_restrictions:web_restrictions_java",
     "//content/public/android:content_java",
     "//device/geolocation:geolocation_java",
diff --git a/chrome/android/java/res/layout/default_search_engine_first_run_fragment.xml b/chrome/android/java/res/layout/default_search_engine_first_run_fragment.xml
index b74e07d..bc59ef6e 100644
--- a/chrome/android/java/res/layout/default_search_engine_first_run_fragment.xml
+++ b/chrome/android/java/res/layout/default_search_engine_first_run_fragment.xml
@@ -48,7 +48,7 @@
                 <View style="@style/Divider" />
 
                 <org.chromium.chrome.browser.widget.RadioButtonLayout
-                    android:id="@+id/engine_controls"
+                    android:id="@+id/default_search_engine_dialog_options"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_marginStart="12dp"
diff --git a/chrome/android/java/res/values/ids.xml b/chrome/android/java/res/values/ids.xml
index 82c7330..db7bcb5e 100644
--- a/chrome/android/java/res/values/ids.xml
+++ b/chrome/android/java/res/values/ids.xml
@@ -17,6 +17,7 @@
     <item name="tabswitcher_multiple_drawable" type="id"/>
     <item name="tabswitcher_single_drawable" type="id"/>
     <item name="custom_tab_bottom_bar_wrapper" type="id"/>
+    <item name="default_search_engine_dialog_options" type="id"/>
 
     <!-- InfoBar constants -->
     <item type="id" name="button_primary" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java
index 2ba8475..ff48198 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java
@@ -123,6 +123,6 @@
      * Callback when Browser Actions menu dialog is shown.
      */
     private void onMenuShown() {
-        beginLoadingLibrary();
+        startNativeInitialization();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
index ea4cad95..aa8996b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
@@ -33,7 +33,8 @@
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View rootView = inflater.inflate(
                 R.layout.default_search_engine_first_run_fragment, container, false);
-        mEngineLayout = (RadioButtonLayout) rootView.findViewById(R.id.engine_controls);
+        mEngineLayout = (RadioButtonLayout) rootView.findViewById(
+                R.id.default_search_engine_dialog_options);
         mButton = (Button) rootView.findViewById(R.id.button_primary);
         mButton.setEnabled(false);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
index 0a3ced3..86fb779 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
@@ -278,7 +278,7 @@
     /**
      * Call to begin loading the library, if it was delayed.
      */
-    protected void beginLoadingLibrary() {
+    protected void startNativeInitialization() {
         assert shouldDelayBrowserStartup();
         ChromeBrowserInitializer.getInstance(this).handlePreNativeStartup(this);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
index 0ee7f70..945d74d0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
@@ -11,6 +11,7 @@
 import android.widget.Button;
 
 import org.chromium.base.Callback;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.locale.LocaleManager.SearchEnginePromoType;
@@ -20,6 +21,12 @@
 
 /** A dialog that forces the user to choose a default search engine. */
 public class DefaultSearchEnginePromoDialog extends PromoDialog {
+    /** Notified about events happening to the dialog. */
+    public static interface DefaultSearchEnginePromoDialogObserver {
+        void onDialogShown(DefaultSearchEnginePromoDialog shownDialog);
+    }
+    private static DefaultSearchEnginePromoDialogObserver sObserver;
+
     /** Used to determine the promo dialog contents. */
     @SearchEnginePromoType
     private final int mDialogType;
@@ -83,6 +90,7 @@
         okButton.setEnabled(false);
 
         RadioButtonLayout radioButtons = new RadioButtonLayout(getContext());
+        radioButtons.setId(R.id.default_search_engine_dialog_options);
         addControl(radioButtons);
 
         Runnable dismissRunnable = new Runnable() {
@@ -96,6 +104,12 @@
     }
 
     @Override
+    public void show() {
+        super.show();
+        if (sObserver != null) sObserver.onDialogShown(this);
+    }
+
+    @Override
     public void onDismiss(DialogInterface dialog) {
         if (mHelper.getCurrentlySelectedKeyword() == null) {
             // This shouldn't happen, but in case it does, finish the Activity so that the user has
@@ -107,4 +121,11 @@
             mOnDismissed.onResult(mHelper.getCurrentlySelectedKeyword() != null);
         }
     }
+
+    /** See {@link #sObserver}. */
+    @VisibleForTesting
+    @Nullable
+    public static void setObserverForTests(DefaultSearchEnginePromoDialogObserver observer) {
+        sObserver = observer;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS
new file mode 100644
index 0000000..c806110
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+dfalcantara@chromium.org
+tedchoc@chromium.org
+yusufo@chromium.org
+
+# COMPONENT: UI>Browser>Mobile>SearchWidget
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/OWNERS
new file mode 100644
index 0000000..c806110
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+dfalcantara@chromium.org
+tedchoc@chromium.org
+yusufo@chromium.org
+
+# COMPONENT: UI>Browser>Mobile>SearchWidget
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
index dbb7e91f..86b6ba1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.searchwidget;
 
+import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
 import android.support.v4.app.ActivityOptionsCompat;
@@ -38,24 +39,40 @@
 public class SearchActivity extends AsyncInitializationActivity
         implements SnackbarManageable, SearchActivityLocationBarLayout.Delegate {
     /** Notified about events happening inside a SearchActivity. */
-    public interface SearchActivityObserver {
-        /** Called when {@link SearchActivity#setContentView} is done. */
-        void onSetContentView();
+    public static class SearchActivityDelegate {
+        /**
+         * Called when {@link SearchActivity#setContentView} is deciding whether to continue loading
+         * the native library immediately.
+         * @return Whether or not native initialization should proceed immediately.
+         */
+        boolean shouldDelayNativeInitialization() {
+            return false;
+        }
 
-        /** Called when {@link SearchActivity#finishNativeInitialization} is done. */
-        void onFinishNativeInitialization();
+        /**
+         * Called to launch the search engine dialog if it's needed.
+         * @param activity Activity that is launching the dialog.
+         * @param callback Called when the dialog has been dismissed.
+         * @return Whether or not the search dialog was shown.
+         */
+        boolean showSearchEngineDialogIfNeeded(Activity activity, Callback<Boolean> callback) {
+            return LocaleManager.getInstance().showSearchEnginePromoIfNeeded(activity, callback);
+        }
 
         /** Called when {@link SearchActivity#finishDeferredInitialization} is done. */
-        void onFinishDeferredInitialization();
+        void onFinishDeferredInitialization() {}
+
+        /** Returning true causes the Activity to finish itself immediately when starting up. */
+        boolean isActivityDisabledForTests() {
+            return false;
+        }
     }
 
     private static final String TAG = "searchwidget";
-
-    /** Setting this field causes the Activity to finish itself immediately for tests. */
-    private static boolean sIsDisabledForTest;
+    private static final Object DELEGATE_LOCK = new Object();
 
     /** Notified about events happening for the SearchActivity. */
-    private static SearchActivityObserver sObserver;
+    private static SearchActivityDelegate sDelegate;
 
     /** Main content view. */
     private ViewGroup mContentView;
@@ -75,7 +92,7 @@
 
     @Override
     protected boolean isStartedUpCorrectly(Intent intent) {
-        if (sIsDisabledForTest) return false;
+        if (getActivityDelegate().isActivityDisabledForTests()) return false;
         return super.isStartedUpCorrectly(intent);
     }
 
@@ -110,22 +127,18 @@
         mSearchBox.initializeControls(new WindowDelegate(getWindow()), getWindowAndroid());
 
         // Kick off everything needed for the user to type into the box.
-        // TODO(dfalcantara): We should prevent the user from doing anything while we're running the
-        //                    logic to determine if they need to see a search engine promo.  Given
-        //                    that the logic requires native to be loaded, we'll have to make some
-        //                    easy Java-only first-pass checks.
         beginQuery();
         mSearchBox.showCachedZeroSuggestResultsIfAvailable();
 
         // Kick off loading of the native library.
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                beginLoadingLibrary();
-            }
-        });
-
-        if (sObserver != null) sObserver.onSetContentView();
+        if (!getActivityDelegate().shouldDelayNativeInitialization()) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    startNativeInitialization();
+                }
+            });
+        }
     }
 
     @Override
@@ -146,28 +159,26 @@
         final Callback<Boolean> deferredCallback = new Callback<Boolean>() {
             @Override
             public void onResult(Boolean result) {
-                finishDeferredInitialization(result);
+                if (result == null || !result.booleanValue()) {
+                    Log.e(TAG, "User failed to select a default search engine.");
+                    finish();
+                    return;
+                }
+
+                finishDeferredInitialization();
             }
         };
-        if (!LocaleManager.getInstance().showSearchEnginePromoIfNeeded(this, deferredCallback)) {
+        if (!getActivityDelegate().showSearchEngineDialogIfNeeded(this, deferredCallback)) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    deferredCallback.onResult(true);
+                    finishDeferredInitialization();
                 }
             });
         }
-
-        if (sObserver != null) sObserver.onFinishNativeInitialization();
     }
 
-    private void finishDeferredInitialization(Boolean result) {
-        if (result == null || !result.booleanValue()) {
-            Log.e(TAG, "User failed to select a default search engine.");
-            finish();
-            return;
-        }
-
+    void finishDeferredInitialization() {
         mIsActivityUsable = true;
         if (mQueuedUrl != null) loadUrl(mQueuedUrl);
 
@@ -175,7 +186,7 @@
         CustomTabsConnection.getInstance(getApplication()).warmup(0);
         mSearchBox.onDeferredStartup(isVoiceSearchIntent());
 
-        if (sObserver != null) sObserver.onFinishDeferredInitialization();
+        getActivityDelegate().onFinishDeferredInitialization();
     }
 
     @Override
@@ -259,15 +270,22 @@
         overridePendingTransition(0, R.anim.activity_close_exit);
     }
 
-    /** See {@link #sIsDisabledForTest}. */
+    @Override
     @VisibleForTesting
-    static void disableForTests() {
-        sIsDisabledForTest = true;
+    public final void startNativeInitialization() {
+        super.startNativeInitialization();
     }
 
-    /** See {@link #sObserver}. */
+    private static SearchActivityDelegate getActivityDelegate() {
+        synchronized (DELEGATE_LOCK) {
+            if (sDelegate == null) sDelegate = new SearchActivityDelegate();
+        }
+        return sDelegate;
+    }
+
+    /** See {@link #sDelegate}. */
     @VisibleForTesting
-    static void setObserverForTests(SearchActivityObserver observer) {
-        sObserver = observer;
+    static void setDelegateForTests(SearchActivityDelegate delegate) {
+        sDelegate = delegate;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
index 16b7170..eeb225c7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 import android.os.Handler;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
 
@@ -68,6 +69,7 @@
     void onDeferredStartup(boolean isVoiceSearchIntent) {
         SearchWidgetProvider.updateCachedVoiceSearchAvailability(isVoiceSearchEnabled());
         if (isVoiceSearchIntent && mUrlBar.isFocused()) onUrlFocusChange(true);
+        if (!TextUtils.isEmpty(mUrlBar.getText())) onTextChangedForAutocomplete(false);
     }
 
     /** Begins a new query. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
index 2e9c804..9678cf2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
@@ -392,7 +392,7 @@
 
     /** Sets an {@link SearchWidgetProviderDelegate} to interact with. */
     @VisibleForTesting
-    static void setDelegateForTest(SearchWidgetProviderDelegate delegate) {
+    static void setActivityDelegateForTest(SearchWidgetProviderDelegate delegate) {
         assert sDelegate == null;
         sDelegate = delegate;
     }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 410ce8e..918e0ef5 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1425,6 +1425,7 @@
   "javatests/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsControllerTest.java",
   "javatests/src/org/chromium/chrome/browser/invalidation/InvalidationServiceTest.java",
   "javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperTest.java",
+  "javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java",
   "javatests/src/org/chromium/chrome/browser/media/RouterTestUtils.java",
   "javatests/src/org/chromium/chrome/browser/media/remote/CastNotificationTest.java",
   "javatests/src/org/chromium/chrome/browser/media/remote/CastPositionTransferTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
index 8884b7e6..ffbdd52e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
@@ -15,7 +15,6 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
-import android.view.ViewGroup;
 import android.widget.Button;
 
 import org.junit.After;
@@ -31,9 +30,9 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
+import org.chromium.chrome.browser.locale.DefaultSearchEngineDialogHelperUtils;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.locale.LocaleManager.SearchEnginePromoType;
-import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.MultiActivityTestRule;
@@ -52,7 +51,6 @@
 
     private FirstRunActivityTestObserver mTestObserver = new FirstRunActivityTestObserver();
     private Activity mActivity;
-    private String mSelectedEngine;
 
     @Before
     public void setUp() throws Exception {
@@ -307,35 +305,8 @@
             Assert.assertTrue("Search engine page wasn't shown.",
                     freProperties.getBoolean(FirstRunActivity.SHOW_SEARCH_ENGINE_PAGE));
             int jumpCallCount = mTestObserver.jumpToPageCallback.getCallCount();
-
-            // Click on the first search engine option available.
-            CriteriaHelper.pollUiThread(new Criteria() {
-                @Override
-                public boolean isSatisfied() {
-                    ViewGroup options = (ViewGroup) mActivity.findViewById(R.id.engine_controls);
-                    return options.getChildCount() > 0;
-                }
-            });
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    ViewGroup options = (ViewGroup) mActivity.findViewById(R.id.engine_controls);
-                    options.getChildAt(0).performClick();
-                    mSelectedEngine = (String) (options.getChildAt(0).getTag());
-                }
-            });
-
-            clickButton(mActivity, R.id.button_primary, "Failed to select default search engine");
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    Assert.assertEquals("Search engine wasn't set",
-                            TemplateUrlService.getInstance()
-                                    .getDefaultSearchEngineTemplateUrl()
-                                    .getKeyword(),
-                            mSelectedEngine);
-                }
-            });
+            DefaultSearchEngineDialogHelperUtils.clickOnFirstEngine(
+                    mActivity.findViewById(android.R.id.content));
 
             mTestObserver.jumpToPageCallback.waitForCallback(
                     "Failed to try moving to next screen", jumpCallCount);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java
new file mode 100644
index 0000000..a7d1ba7
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java
@@ -0,0 +1,78 @@
+// 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.
+
+package org.chromium.chrome.browser.locale;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Assert;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.search_engines.TemplateUrlService;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+
+/**
+ * Utilities for interacting with a {@link DefaultSearchEngineDialogHelper}.
+ */
+public class DefaultSearchEngineDialogHelperUtils {
+    private static final int OPTION_LAYOUT_ID = R.id.default_search_engine_dialog_options;
+    private static final int OK_BUTTON_ID = R.id.button_primary;
+
+    private static String sSelectedEngine;
+
+    /** Clicks on the first search engine option available. */
+    public static void clickOnFirstEngine(final View rootView) {
+        // Wait for the options to appear.
+        CriteriaHelper.pollUiThread(new Criteria() {
+            @Override
+            public boolean isSatisfied() {
+                ViewGroup options = (ViewGroup) rootView.findViewById(OPTION_LAYOUT_ID);
+                return options.getChildCount() > 0;
+            }
+        });
+
+        // Click on the first search engine option available.
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                ViewGroup options = (ViewGroup) rootView.findViewById(OPTION_LAYOUT_ID);
+                options.getChildAt(0).performClick();
+                sSelectedEngine = (String) (options.getChildAt(0).getTag());
+            }
+        });
+
+        // Wait for the OK button to be clicakble.
+        CriteriaHelper.pollUiThread(new Criteria() {
+            @Override
+            public boolean isSatisfied() {
+                View view = rootView.findViewById(OK_BUTTON_ID);
+                return view != null && view.isEnabled();
+            }
+        });
+
+        // Click on the OK button.
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                View view = rootView.findViewById(OK_BUTTON_ID);
+                view.performClick();
+            }
+        });
+
+        // Confirm the engine was set appropriately.
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                Assert.assertEquals("Search engine wasn't set",
+                        TemplateUrlService.getInstance()
+                                .getDefaultSearchEngineTemplateUrl()
+                                .getKeyword(),
+                        sSelectedEngine);
+            }
+        });
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS
new file mode 100644
index 0000000..64295110
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/OWNERS
@@ -0,0 +1 @@
+file://chrome/android/java/src/org/chromium/chrome/browser/locale/OWNERS
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS
new file mode 100644
index 0000000..d0dbd1eb
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/OWNERS
@@ -0,0 +1 @@
+file://chrome/android/java/src/org/chromium/chrome/browser/searchwidget/OWNERS
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index 853573cd..21eb45e4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -21,6 +21,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.SuppressFBWarnings;
 import org.chromium.base.test.util.CallbackHelper;
@@ -28,8 +30,12 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.locale.DefaultSearchEngineDialogHelperUtils;
+import org.chromium.chrome.browser.locale.DefaultSearchEnginePromoDialog;
+import org.chromium.chrome.browser.locale.DefaultSearchEnginePromoDialog.DefaultSearchEnginePromoDialogObserver;
+import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.omnibox.UrlBar;
-import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityObserver;
+import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityDelegate;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.MultiActivityTestRule;
@@ -38,64 +44,97 @@
 import org.chromium.content.browser.test.util.CriteriaHelper;
 import org.chromium.content.browser.test.util.KeyUtils;
 
+import java.util.concurrent.Callable;
+
 /**
  * Tests the {@link SearchActivity}.
  *
  * TODO(dfalcantara): Add tests for:
- *                    + Checking that user can type in the box before native has loaded and still
- *                      have suggestions appear.
- *
  *                    + Performing a search query.
- *                        + Performing a search query while the SearchActivity is alive and the
- *                          default search engine is changed outside the SearchActivity.
  *
- *                    + Handling the promo dialog.
+ *                    + Performing a search query while the SearchActivity is alive and the
+ *                      default search engine is changed outside the SearchActivity.
+ *
+ *                    + Add microphone tests somehow (vague query + confident query).
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class SearchActivityTest {
-    private static class TestObserver implements SearchActivityObserver {
-        public final CallbackHelper onSetContentViewCallback = new CallbackHelper();
-        public final CallbackHelper onFinishNativeInitializationCallback = new CallbackHelper();
+    private static class TestDelegate
+            extends SearchActivityDelegate implements DefaultSearchEnginePromoDialogObserver {
+        public final CallbackHelper shouldDelayNativeInitializationCallback = new CallbackHelper();
+        public final CallbackHelper showSearchEngineDialogIfNeededCallback = new CallbackHelper();
         public final CallbackHelper onFinishDeferredInitializationCallback = new CallbackHelper();
+        public final CallbackHelper onPromoDialogShownCallback = new CallbackHelper();
+
+        public boolean shouldDelayLoadingNative;
+        public boolean shouldDelayDeferredInitialization;
+        public boolean shouldShowRealSearchDialog;
+
+        public DefaultSearchEnginePromoDialog shownPromoDialog;
 
         @Override
-        public void onSetContentView() {
-            onSetContentViewCallback.notifyCalled();
+        boolean shouldDelayNativeInitialization() {
+            shouldDelayNativeInitializationCallback.notifyCalled();
+            return shouldDelayLoadingNative;
         }
 
         @Override
-        public void onFinishNativeInitialization() {
-            onFinishNativeInitializationCallback.notifyCalled();
+        boolean showSearchEngineDialogIfNeeded(Activity activity, Callback<Boolean> callback) {
+            showSearchEngineDialogIfNeededCallback.notifyCalled();
+
+            if (shouldShowRealSearchDialog) {
+                LocaleManager.setInstanceForTest(new LocaleManager() {
+                    @Override
+                    public int getSearchEnginePromoShowType() {
+                        return SEARCH_ENGINE_PROMO_SHOW_EXISTING;
+                    }
+                });
+                return super.showSearchEngineDialogIfNeeded(activity, callback);
+            }
+
+            return shouldDelayDeferredInitialization;
         }
 
         @Override
         public void onFinishDeferredInitialization() {
             onFinishDeferredInitializationCallback.notifyCalled();
         }
+
+        @Override
+        public void onDialogShown(DefaultSearchEnginePromoDialog dialog) {
+            shownPromoDialog = dialog;
+            onPromoDialogShownCallback.notifyCalled();
+        }
     }
 
     @Rule
     @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     public MultiActivityTestRule mTestRule = new MultiActivityTestRule();
 
-    private TestObserver mTestObserver;
+    private TestDelegate mTestDelegate;
 
     @Before
     public void setUp() {
-        mTestObserver = new TestObserver();
-        SearchActivity.setObserverForTests(mTestObserver);
+        mTestDelegate = new TestDelegate();
+        SearchActivity.setDelegateForTests(mTestDelegate);
+        DefaultSearchEnginePromoDialog.setObserverForTests(mTestDelegate);
     }
 
     @After
     public void tearDown() {
-        SearchActivity.setObserverForTests(null);
+        SearchActivity.setDelegateForTests(null);
     }
 
     @Test
     @SmallTest
     public void testOmniboxSuggestionContainerAppears() throws Exception {
-        Activity searchActivity = fullyStartSearchActivity();
+        SearchActivity searchActivity = startSearchActivity();
+
+        // Wait for the Activity to fully load.
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
 
         // Type in anything.  It should force the suggestions to appear.
         setUrlBarText(searchActivity, "anything.");
@@ -108,10 +147,15 @@
     @Test
     @SmallTest
     public void testStartsBrowserAfterUrlSubmitted() throws Exception {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        Activity searchActivity = fullyStartSearchActivity();
+        SearchActivity searchActivity = startSearchActivity();
+
+        // Wait for the Activity to fully load.
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
 
         // Monitor for ChromeTabbedActivity.
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         ActivityMonitor browserMonitor =
                 new ActivityMonitor(ChromeTabbedActivity.class.getName(), null, false);
         instrumentation.addMonitor(browserMonitor);
@@ -120,8 +164,249 @@
         setUrlBarText(searchActivity, "about:blank");
         final UrlBar urlBar = (UrlBar) searchActivity.findViewById(R.id.url_bar);
         KeyUtils.singleKeyEventView(instrumentation, urlBar, KeyEvent.KEYCODE_ENTER);
+        waitForChromeTabbedActivityToStart(browserMonitor);
+    }
 
-        // Wait for ChromeTabbedActivity to start.
+    @Test
+    @SmallTest
+    public void testTypeBeforeNativeIsLoaded() throws Exception {
+        // Wait for the activity to load, but don't let it load the native library.
+        mTestDelegate.shouldDelayLoadingNative = true;
+        final SearchActivity searchActivity = startSearchActivity();
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        Assert.assertEquals(0, mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
+        Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+
+        // Set some text in the search box (but don't hit enter).
+        setUrlBarText(searchActivity, "about:blank");
+
+        // Start loading native, then let the activity finish initialization.
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                searchActivity.startNativeInitialization();
+            }
+        });
+
+        Assert.assertEquals(
+                1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
+
+        // Omnibox suggestions should appear now.
+        final SearchActivityLocationBarLayout locationBar =
+                (SearchActivityLocationBarLayout) searchActivity.findViewById(
+                        R.id.search_location_bar);
+        OmniboxTestUtils.waitForOmniboxSuggestions(locationBar);
+
+        // Hitting enter should submit the URL and kick the user to the browser.
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        ActivityMonitor browserMonitor =
+                new ActivityMonitor(ChromeTabbedActivity.class.getName(), null, false);
+        instrumentation.addMonitor(browserMonitor);
+        UrlBar urlBar = (UrlBar) searchActivity.findViewById(R.id.url_bar);
+        KeyUtils.singleKeyEventView(instrumentation, urlBar, KeyEvent.KEYCODE_ENTER);
+        waitForChromeTabbedActivityToStart(browserMonitor);
+    }
+
+    @Test
+    @SmallTest
+    public void testEnterUrlBeforeNativeIsLoaded() throws Exception {
+        // Wait for the activity to load, but don't let it load the native library.
+        mTestDelegate.shouldDelayLoadingNative = true;
+        final SearchActivity searchActivity = startSearchActivity();
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        Assert.assertEquals(0, mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
+        Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+
+        // Submit a URL before native is loaded.  The browser shouldn't start yet.
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        setUrlBarText(searchActivity, "about:blank");
+        UrlBar urlBar = (UrlBar) searchActivity.findViewById(R.id.url_bar);
+        KeyUtils.singleKeyEventView(instrumentation, urlBar, KeyEvent.KEYCODE_ENTER);
+        Assert.assertEquals(searchActivity, ApplicationStatus.getLastTrackedFocusedActivity());
+        Assert.assertFalse(searchActivity.isFinishing());
+
+        // Finish initialization.  It should notice the URL is queued up and start the browser.
+        ActivityMonitor browserMonitor =
+                new ActivityMonitor(ChromeTabbedActivity.class.getName(), null, false);
+        instrumentation.addMonitor(browserMonitor);
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                searchActivity.startNativeInitialization();
+            }
+        });
+
+        Assert.assertEquals(
+                1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
+        waitForChromeTabbedActivityToStart(browserMonitor);
+    }
+
+    @Test
+    @SmallTest
+    public void testTypeBeforeDeferredInitialization() throws Exception {
+        // Start the Activity.  It should pause and assume that a promo dialog has appeared.
+        mTestDelegate.shouldDelayDeferredInitialization = true;
+        final SearchActivity searchActivity = startSearchActivity();
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+
+        // Set some text in the search box, then continue startup.
+        setUrlBarText(searchActivity, "about:blank");
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                searchActivity.finishDeferredInitialization();
+            }
+        });
+
+        // Let the initialization finish completely.
+        Assert.assertEquals(
+                1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
+        Assert.assertEquals(1, mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
+        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
+
+        // Omnibox suggestions should appear now.
+        final SearchActivityLocationBarLayout locationBar =
+                (SearchActivityLocationBarLayout) searchActivity.findViewById(
+                        R.id.search_location_bar);
+        OmniboxTestUtils.waitForOmniboxSuggestions(locationBar);
+
+        // Hitting enter should submit the URL and kick the user to the browser.
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        ActivityMonitor browserMonitor =
+                new ActivityMonitor(ChromeTabbedActivity.class.getName(), null, false);
+        instrumentation.addMonitor(browserMonitor);
+        UrlBar urlBar = (UrlBar) searchActivity.findViewById(R.id.url_bar);
+        KeyUtils.singleKeyEventView(instrumentation, urlBar, KeyEvent.KEYCODE_ENTER);
+        waitForChromeTabbedActivityToStart(browserMonitor);
+    }
+
+    @Test
+    @SmallTest
+    public void testRealPromoDialogInterruption() throws Exception {
+        // Start the Activity.  It should pause when the promo dialog appears.
+        mTestDelegate.shouldShowRealSearchDialog = true;
+        final SearchActivity searchActivity = startSearchActivity();
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        mTestDelegate.onPromoDialogShownCallback.waitForCallback(0);
+        Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+
+        // Set some text in the search box, then select the first engine to continue startup.
+        setUrlBarText(searchActivity, "about:blank");
+        DefaultSearchEngineDialogHelperUtils.clickOnFirstEngine(
+                mTestDelegate.shownPromoDialog.findViewById(android.R.id.content));
+
+        // Let the initialization finish completely.
+        Assert.assertEquals(
+                1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
+        Assert.assertEquals(1, mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
+        mTestDelegate.onFinishDeferredInitializationCallback.waitForCallback(0);
+
+        // Omnibox suggestions should appear now.
+        final SearchActivityLocationBarLayout locationBar =
+                (SearchActivityLocationBarLayout) searchActivity.findViewById(
+                        R.id.search_location_bar);
+        OmniboxTestUtils.waitForOmniboxSuggestions(locationBar);
+
+        // Hitting enter should submit the URL and kick the user to the browser.
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        ActivityMonitor browserMonitor =
+                new ActivityMonitor(ChromeTabbedActivity.class.getName(), null, false);
+        instrumentation.addMonitor(browserMonitor);
+        UrlBar urlBar = (UrlBar) searchActivity.findViewById(R.id.url_bar);
+        KeyUtils.singleKeyEventView(instrumentation, urlBar, KeyEvent.KEYCODE_ENTER);
+        waitForChromeTabbedActivityToStart(browserMonitor);
+    }
+
+    @Test
+    @SmallTest
+    public void testRealPromoDialogDismissWithoutSelection() throws Exception {
+        // Start the Activity.  It should pause when the promo dialog appears.
+        mTestDelegate.shouldShowRealSearchDialog = true;
+        startSearchActivity();
+        mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
+        mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
+        mTestDelegate.onPromoDialogShownCallback.waitForCallback(0);
+        Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+
+        // Dismiss the dialog without acting on it.
+        mTestDelegate.shownPromoDialog.dismiss();
+
+        // SearchActivity should realize the failure case and prevent the user from using it.
+        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                return ApplicationStatus.getRunningActivities().size();
+            }
+        }));
+        Assert.assertEquals(
+                1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
+        Assert.assertEquals(1, mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
+        Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+    }
+
+    @Test
+    @SmallTest
+    public void testNewIntentDiscardsQuery() throws Exception {
+        final SearchActivity searchActivity = startSearchActivity();
+        setUrlBarText(searchActivity, "first query");
+        final SearchActivityLocationBarLayout locationBar =
+                (SearchActivityLocationBarLayout) searchActivity.findViewById(
+                        R.id.search_location_bar);
+        OmniboxTestUtils.waitForOmniboxSuggestions(locationBar);
+
+        // Start the Activity again by firing another copy of the same Intent.
+        SearchActivity restartedActivity = startSearchActivity(1);
+        Assert.assertEquals(searchActivity, restartedActivity);
+
+        // The query should be wiped.
+        CriteriaHelper.pollUiThread(new Criteria() {
+            @Override
+            public boolean isSatisfied() {
+                UrlBar urlBar = (UrlBar) searchActivity.findViewById(R.id.url_bar);
+                return TextUtils.isEmpty(urlBar.getText());
+            }
+        });
+    }
+
+    private SearchActivity startSearchActivity() throws Exception {
+        return startSearchActivity(0);
+    }
+
+    private SearchActivity startSearchActivity(int expectedCallCount) throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        ActivityMonitor searchMonitor =
+                new ActivityMonitor(SearchActivity.class.getName(), null, false);
+        instrumentation.addMonitor(searchMonitor);
+
+        // The SearchActivity shouldn't have started yet.
+        Assert.assertEquals(expectedCallCount,
+                mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
+        Assert.assertEquals(expectedCallCount,
+                mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
+        Assert.assertEquals(expectedCallCount,
+                mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
+
+        // Fire the Intent to start up the SearchActivity.
+        Intent intent = new Intent();
+        SearchWidgetProvider.startSearchActivity(intent, false);
+        Activity searchActivity = instrumentation.waitForMonitorWithTimeout(
+                searchMonitor, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
+        Assert.assertNotNull("Activity didn't start", searchActivity);
+        Assert.assertTrue("Wrong activity started", searchActivity instanceof SearchActivity);
+        instrumentation.removeMonitor(searchMonitor);
+        return (SearchActivity) searchActivity;
+    }
+
+    private void waitForChromeTabbedActivityToStart(ActivityMonitor browserMonitor)
+            throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         final Activity browserActivity = instrumentation.waitForMonitorWithTimeout(
                 browserMonitor, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
         Assert.assertNotNull("Activity didn't start", browserActivity);
@@ -140,35 +425,6 @@
         });
     }
 
-    private Activity fullyStartSearchActivity() throws Exception {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-
-        ActivityMonitor searchMonitor =
-                new ActivityMonitor(SearchActivity.class.getName(), null, false);
-        instrumentation.addMonitor(searchMonitor);
-
-        // The SearchActivity shouldn't have started yet.
-        Assert.assertEquals(0, mTestObserver.onSetContentViewCallback.getCallCount());
-        Assert.assertEquals(0, mTestObserver.onFinishNativeInitializationCallback.getCallCount());
-        Assert.assertEquals(0, mTestObserver.onFinishDeferredInitializationCallback.getCallCount());
-
-        // Fire the Intent to start up the SearchActivity.
-        Intent intent = new Intent();
-        SearchWidgetProvider.startSearchActivity(intent, false);
-        Activity searchActivity = instrumentation.waitForMonitorWithTimeout(
-                searchMonitor, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
-        Assert.assertNotNull("Activity didn't start", searchActivity);
-        Assert.assertTrue("Wrong activity started", searchActivity instanceof SearchActivity);
-
-        // Wait for the Activity fully load.
-        mTestObserver.onSetContentViewCallback.waitForCallback(0);
-        mTestObserver.onFinishNativeInitializationCallback.waitForCallback(0);
-        mTestObserver.onFinishDeferredInitializationCallback.waitForCallback(0);
-
-        instrumentation.removeMonitor(searchMonitor);
-        return searchActivity;
-    }
-
     @SuppressLint("SetTextI18n")
     private void setUrlBarText(final Activity activity, final String url) {
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
index c7f970fb..5429068d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
@@ -27,6 +27,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.firstrun.FirstRunActivity;
+import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityDelegate;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.test.util.ApplicationTestUtils;
 import org.chromium.content.browser.test.util.CriteriaHelper;
@@ -39,6 +40,13 @@
  */
 @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
 public class SearchWidgetProviderTest extends InstrumentationTestCase {
+    private static class TestSearchDelegate extends SearchActivityDelegate {
+        @Override
+        public boolean isActivityDisabledForTests() {
+            return true;
+        }
+    }
+
     private static final class TestDelegate
             extends SearchWidgetProvider.SearchWidgetProviderDelegate {
         public static final int[] ALL_IDS = {11684, 20170525};
@@ -91,11 +99,11 @@
     public void setUp() throws Exception {
         super.setUp();
         ApplicationTestUtils.setUp(getInstrumentation().getTargetContext(), true);
-        SearchActivity.disableForTests();
+        SearchActivity.setDelegateForTests(new TestSearchDelegate());
 
         mContext = new TestContext();
         mDelegate = new TestDelegate(mContext);
-        SearchWidgetProvider.setDelegateForTest(mDelegate);
+        SearchWidgetProvider.setActivityDelegateForTest(mDelegate);
     }
 
     @Override
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 62ceb693..9b02091c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -68,6 +68,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/tracing/common/tracing_switches.h"
+#include "components/translate/core/browser/translate_infobar_delegate.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/browser/translate_prefs.h"
 #include "components/version_info/version_info.h"
@@ -1636,6 +1637,9 @@
      flag_descriptions::kPullToRefreshEffectName,
      flag_descriptions::kPullToRefreshEffectDescription, kOsAndroid,
      SINGLE_DISABLE_VALUE_TYPE(switches::kDisablePullToRefreshEffect)},
+    {"translate-compact-infobar", flag_descriptions::kTranslateCompactUIName,
+     flag_descriptions::kTranslateCompactUIDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(translate::kTranslateCompactUI)},
 #endif  // OS_ANDROID
 #if defined(OS_MACOSX)
     {"enable-translate-new-ux", flag_descriptions::kTranslateNewUxName,
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index e98b434fc..cd466e5 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -271,6 +271,8 @@
         <include name="IDR_MD_BOOKMARKS_APP_HTML" file="resources\md_bookmarks\app.html" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_APP_JS" file="resources\md_bookmarks\app.js" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_BOOKMARKS_HTML" file="resources\md_bookmarks\bookmarks.html" type="BINDATA" />
+        <include name="IDR_MD_BOOKMARKS_COMMAND_MANAGER_HTML" file="resources\md_bookmarks\command_manager.html" type="BINDATA" />
+        <include name="IDR_MD_BOOKMARKS_COMMAND_MANAGER_JS" file="resources\md_bookmarks\command_manager.js" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_CONSTANTS_HTML" file="resources\md_bookmarks\constants.html" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_CONSTANTS_JS" file="resources\md_bookmarks\constants.js" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_DND_MANAGER_HTML" file="resources\md_bookmarks\dnd_manager.html" type="BINDATA" />
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index b7dca30..48e50e0 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1347,6 +1347,11 @@
 const char kPullToRefreshEffectDescription[] =
     "Page reloads triggered by vertically overscrolling content.";
 
+const char kTranslateCompactUIName[] = "New Translate Infobar";
+
+const char kTranslateCompactUIDescription[] =
+    "Enable the new Translate compact infobar UI.";
+
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_MACOSX)
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index dba02b3..f8dd1a1 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1495,6 +1495,12 @@
 // Description of the flag for the pull-to-refresh effect.
 extern const char kPullToRefreshEffectDescription[];
 
+// Name of the flag for the translate compact infobar.
+extern const char kTranslateCompactUIName[];
+
+// Description of the flag for translate compact infobar.
+extern const char kTranslateCompactUIDescription[];
+
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_MACOSX)
diff --git a/chrome/browser/resources/md_bookmarks/app.html b/chrome/browser/resources/md_bookmarks/app.html
index faea753..4d997e4 100644
--- a/chrome/browser/resources/md_bookmarks/app.html
+++ b/chrome/browser/resources/md_bookmarks/app.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://resources/html/cr/ui/splitter.html">
 <link rel="import" href="chrome://bookmarks/api_listener.html">
+<link rel="import" href="chrome://bookmarks/command_manager.html">
 <link rel="import" href="chrome://bookmarks/constants.html">
 <link rel="import" href="chrome://bookmarks/dnd_manager.html">
 <link rel="import" href="chrome://bookmarks/list.html">
@@ -64,6 +65,7 @@
       <bookmarks-list></bookmarks-list>
     </div>
     <bookmarks-router></bookmarks-router>
+    <bookmarks-command-manager></bookmarks-command-manager>
   </template>
   <script src="chrome://bookmarks/app.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/md_bookmarks/command_manager.html b/chrome/browser/resources/md_bookmarks/command_manager.html
new file mode 100644
index 0000000..2ec008d
--- /dev/null
+++ b/chrome/browser/resources/md_bookmarks/command_manager.html
@@ -0,0 +1,35 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
+<link rel="import" href="chrome://bookmarks/edit_dialog.html">
+<link rel="import" href="chrome://bookmarks/store_client.html">
+
+<dom-module id="bookmarks-command-manager">
+  <template>
+    <dialog is="cr-action-menu" id="dropdown" on-mousedown="onMenuMousedown_">
+      <button class="dropdown-item"
+          command="edit"
+          hidden$="[[!canExecute('edit', menuIds_)]]"
+          on-tap="onCommandClick_">
+        [[getEditActionLabel_(menuIds_)]]
+      </button>
+      <button class="dropdown-item"
+          command="copy"
+          hidden$="[[!canExecute('copy', menuIds_)]]"
+          on-tap="onCommandClick_">
+        $i18n{menuCopyURL}
+      </button>
+      <button class="dropdown-item"
+          command="delete"
+          hidden$="[[!canExecute('delete', menuIds_)]]"
+          on-tap="onCommandClick_">
+        $i18n{menuDelete}
+      </button>
+    </dialog>
+    <template is="cr-lazy-render" id="editDialog">
+      <bookmarks-edit-dialog></bookmarks-edit-dialog>
+    </template>
+  </template>
+  <script src="chrome://bookmarks/command_manager.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/md_bookmarks/command_manager.js b/chrome/browser/resources/md_bookmarks/command_manager.js
new file mode 100644
index 0000000..9c5e564
--- /dev/null
+++ b/chrome/browser/resources/md_bookmarks/command_manager.js
@@ -0,0 +1,200 @@
+// 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.
+
+Polymer({
+  is: 'bookmarks-command-manager',
+
+  behaviors: [
+    bookmarks.StoreClient,
+  ],
+
+  properties: {
+    /** @type {Set<string>} */
+    menuIds_: Object,
+  },
+
+  attached: function() {
+    /** @private {function(!Event)} */
+    this.boundOnOpenItemMenu_ = this.onOpenItemMenu_.bind(this);
+    document.addEventListener('open-item-menu', this.boundOnOpenItemMenu_);
+
+    /** @private {function(!Event)} */
+    this.boundOnKeydown_ = this.onKeydown_.bind(this);
+    document.addEventListener('keydown', this.boundOnKeydown_);
+
+    /** @private {Object<Command, string>} */
+    this.shortcuts_ = {};
+    this.shortcuts_[Command.EDIT] = cr.isMac ? 'enter' : 'f2';
+    this.shortcuts_[Command.COPY] = cr.isMac ? 'meta+c' : 'ctrl+c';
+    this.shortcuts_[Command.DELETE] = cr.isMac ? 'delete backspace' : 'delete';
+  },
+
+  detached: function() {
+    document.removeEventListener('open-item-menu', this.boundOnOpenItemMenu_);
+    document.removeEventListener('keydown', this.boundOnKeydown_);
+  },
+
+  /**
+   * Display the command context menu at (|x|, |y|) in window co-ordinates.
+   * Commands will execute on the currently selected items.
+   * @param {number} x
+   * @param {number} y
+   */
+  openCommandMenuAtPosition: function(x, y) {
+    this.menuIds_ = this.getState().selection.items;
+    /** @type {!CrActionMenuElement} */ (this.$.dropdown)
+        .showAtPosition({top: y, left: x});
+  },
+
+  /**
+   * Display the command context menu positioned to cover the |target|
+   * element. Commands will execute on the currently selected items.
+   * @param {!Element} target
+   */
+  openCommandMenuAtElement: function(target) {
+    this.menuIds_ = this.getState().selection.items;
+    /** @type {!CrActionMenuElement} */ (this.$.dropdown).showAt(target);
+  },
+
+  closeCommandMenu: function() {
+    /** @type {!CrActionMenuElement} */ (this.$.dropdown).close();
+  },
+
+  ////////////////////////////////////////////////////////////////////////////
+  // Command handlers:
+
+  /**
+   * @param {Command} command
+   * @param {!Set<string>} itemIds
+   * @return {boolean}
+   */
+  canExecute: function(command, itemIds) {
+    switch (command) {
+      case Command.EDIT:
+        return itemIds.size == 1;
+      case Command.COPY:
+        return itemIds.size == 1 &&
+            this.containsMatchingNode_(itemIds, function(node) {
+              return !!node.url;
+            });
+      case Command.DELETE:
+        return itemIds.size > 0;
+      default:
+        return false;
+    }
+  },
+
+  /**
+   * @param {Command} command
+   * @param {!Set<string>} itemIds
+   */
+  handle: function(command, itemIds) {
+    switch (command) {
+      case Command.EDIT:
+        var id = Array.from(itemIds)[0];
+        /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
+            .showEditDialog(this.getState().nodes[id]);
+        break;
+      case Command.COPY:
+        var idList = Array.from(itemIds);
+        chrome.bookmarkManagerPrivate.copy(idList, function() {
+          // TODO(jiaxi): Add toast later.
+        });
+        break;
+      case Command.DELETE:
+        // TODO(tsergeant): Filter IDs so we don't try to delete children of
+        // something else already being deleted.
+        chrome.bookmarkManagerPrivate.removeTrees(
+            Array.from(itemIds), function() {
+              // TODO(jiaxi): Add toast later.
+            });
+        break;
+    }
+
+  },
+
+  ////////////////////////////////////////////////////////////////////////////
+  // Private functions:
+
+  /**
+   * @param {!Set<string>} itemIds
+   * @param {function(BookmarkNode):boolean} predicate
+   * @return {boolean} True if any node in |itemIds| returns true for
+   *     |predicate|.
+   */
+  containsMatchingNode_: function(itemIds, predicate) {
+    var nodes = this.getState().nodes;
+
+    return Array.from(itemIds).some(function(id) {
+      return predicate(nodes[id]);
+    });
+  },
+
+  /**
+   * @param {Event} e
+   * @private
+   */
+  onOpenItemMenu_: function(e) {
+    if (e.detail.targetElement) {
+      this.openCommandMenuAtElement(e.detail.targetElement);
+    } else {
+      this.openCommandMenuAtPosition(e.detail.x, e.detail.y);
+    }
+  },
+
+  /**
+   * @param {Event} e
+   * @private
+   */
+  onCommandClick_: function(e) {
+    this.closeCommandMenu();
+    this.handle(e.target.getAttribute('command'), assert(this.menuIds_));
+  },
+
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onKeydown_: function(e) {
+    var selection = this.getState().selection.items;
+    // TODO(tsergeant): Prevent keyboard shortcuts when a dialog is open or text
+    // field is focused.
+    for (var commandName in this.shortcuts_) {
+      var shortcut = this.shortcuts_[commandName];
+      if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(e, shortcut) &&
+          this.canExecute(commandName, selection)) {
+        this.handle(commandName, selection);
+
+        e.stopPropagation();
+        e.preventDefault();
+        return;
+      }
+    }
+  },
+
+  /**
+   * Close the menu on mousedown so clicks can propagate to the underlying UI.
+   * This allows the user to right click the list while a context menu is
+   * showing and get another context menu.
+   * @param {Event} e
+   * @private
+   */
+  onMenuMousedown_: function(e) {
+    if (e.path[0] != this.$.dropdown)
+      return;
+
+    this.$.dropdown.close();
+  },
+
+  /** @private */
+  getEditActionLabel_: function() {
+    if (this.menuIds_.size > 1)
+      return;
+
+    var id = Array.from(this.menuIds_)[0];
+    var itemUrl = this.getState().nodes[id].url;
+    var label = itemUrl ? 'menuEdit' : 'menuRename';
+    return loadTimeData.getString(label);
+  },
+});
diff --git a/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp b/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp
index 957eacb..8bdfa9f 100644
--- a/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp
+++ b/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp
@@ -38,6 +38,19 @@
       'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
     {
+      'target_name': 'command_manager',
+      'dependencies': [
+        '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior/compiled_resources2.gyp:iron-a11y-keys-behavior-extracted',
+        '<(DEPTH)/ui/webui/resources/cr_elements/cr_action_menu/compiled_resources2.gyp:cr_action_menu',
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
+        '<(EXTERNS_GYP):bookmark_manager_private',
+        'edit_dialog',
+        'store_client',
+      ],
+      'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
+    },
+    {
       'target_name': 'constants',
       'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi']
     },
@@ -79,6 +92,7 @@
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:icon',
         '<(EXTERNS_GYP):chrome_extensions',
         'actions',
+        'command_manager',
         'store_client',
       ],
       'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
@@ -86,12 +100,8 @@
     {
       'target_name': 'list',
       'dependencies': [
-        '<(DEPTH)/ui/webui/resources/cr_elements/cr_action_menu/compiled_resources2.gyp:cr_action_menu',
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
-        '<(EXTERNS_GYP):bookmark_manager_private',
-        '<(EXTERNS_GYP):chrome_extensions',
         'actions',
-        'edit_dialog',
         'store_client',
       ],
       'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
diff --git a/chrome/browser/resources/md_bookmarks/constants.js b/chrome/browser/resources/md_bookmarks/constants.js
index 95949765..692cd352 100644
--- a/chrome/browser/resources/md_bookmarks/constants.js
+++ b/chrome/browser/resources/md_bookmarks/constants.js
@@ -15,6 +15,16 @@
   BELOW: 4,
 };
 
+/**
+ * @enum {string}
+ * @const
+ */
+var Command = {
+  EDIT: 'edit',
+  COPY: 'copy',
+  DELETE: 'delete',
+};
+
 /** @const */
 var LOCAL_STORAGE_CLOSED_FOLDERS_KEY = 'closedState';
 
diff --git a/chrome/browser/resources/md_bookmarks/item.js b/chrome/browser/resources/md_bookmarks/item.js
index d04e0ac..c0be484 100644
--- a/chrome/browser/resources/md_bookmarks/item.js
+++ b/chrome/browser/resources/md_bookmarks/item.js
@@ -71,7 +71,6 @@
     this.fire('open-item-menu', {
       x: e.clientX,
       y: e.clientY,
-      item: this.item_,
     });
   },
 
@@ -85,7 +84,6 @@
         this.itemId, false, false, this.getState()));
     this.fire('open-item-menu', {
       targetElement: e.target,
-      item: this.item_,
     });
   },
 
diff --git a/chrome/browser/resources/md_bookmarks/list.html b/chrome/browser/resources/md_bookmarks/list.html
index 226eec07..a408a4b8 100644
--- a/chrome/browser/resources/md_bookmarks/list.html
+++ b/chrome/browser/resources/md_bookmarks/list.html
@@ -1,9 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/shadow.html">
-<link rel="import" href="chrome://bookmarks/edit_dialog.html">
 <link rel="import" href="chrome://bookmarks/item.html">
 <link rel="import" href="chrome://bookmarks/shared_style.html">
 <link rel="import" href="chrome://bookmarks/store_client.html">
@@ -36,18 +33,6 @@
         justify-content: center;
       }
     </style>
-    <dialog is="cr-action-menu" id="dropdown" on-mousedown="onMenuMousedown_">
-      <button class="dropdown-item" on-tap="onEditTap_">
-        [[getEditActionLabel_(menuItem_)]]
-      </button>
-      <button class="dropdown-item" on-tap="onCopyURLTap_"
-          hidden$="[[!menuItem_.url]]">
-        $i18n{menuCopyURL}
-      </button>
-      <button class="dropdown-item" on-tap="onDeleteTap_">
-         $i18n{menuDelete}
-      </button>
-    </dialog>
     <div id="bookmarksCard" hidden$="[[isEmptyList_(displayedList_.length)]]">
       <template is="dom-repeat" items="[[displayedList_]]" as="id">
         <bookmarks-item item-id="[[id]]" draggable="true">
@@ -58,9 +43,6 @@
         hidden$="[[!isEmptyList_(displayedList_.length)]]">
       [[emptyListMessage_(searchTerm_)]]
     </div>
-    <template is="cr-lazy-render" id="editDialog">
-      <bookmarks-edit-dialog></bookmarks-edit-dialog>
-    </template>
   </template>
   <script src="chrome://bookmarks/list.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/md_bookmarks/list.js b/chrome/browser/resources/md_bookmarks/list.js
index c38b6da..365487a 100644
--- a/chrome/browser/resources/md_bookmarks/list.js
+++ b/chrome/browser/resources/md_bookmarks/list.js
@@ -10,9 +10,6 @@
   ],
 
   properties: {
-    /** @type {BookmarkNode} */
-    menuItem_: Object,
-
     /** @private {Array<string>} */
     displayedList_: {
       type: Array,
@@ -29,7 +26,6 @@
 
   listeners: {
     'click': 'deselectItems_',
-    'open-item-menu': 'onOpenItemMenu_',
   },
 
   attached: function() {
@@ -46,81 +42,6 @@
     return this.$.message;
   },
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onOpenItemMenu_: function(e) {
-    this.menuItem_ = e.detail.item;
-    var menu = /** @type {!CrActionMenuElement} */ (
-        this.$.dropdown);
-    if (e.detail.targetElement) {
-      menu.showAt(/** @type {!Element} */ (e.detail.targetElement));
-    } else {
-      menu.showAtPosition({
-        top: e.detail.y,
-        left: e.detail.x,
-      });
-    }
-  },
-
-  /** @private */
-  onEditTap_: function() {
-    this.closeDropdownMenu_();
-    /** @type {BookmarksEditDialogElement} */ (this.$.editDialog.get())
-        .showEditDialog(this.menuItem_);
-  },
-
-  /** @private */
-  onCopyURLTap_: function() {
-    var idList = [this.menuItem_.id];
-    chrome.bookmarkManagerPrivate.copy(idList, function() {
-      // TODO(jiaxi): Add toast later.
-    });
-    this.closeDropdownMenu_();
-  },
-
-  /** @private */
-  onDeleteTap_: function() {
-    if (this.menuItem_.url) {
-      chrome.bookmarks.remove(this.menuItem_.id, function() {
-        // TODO(jiaxi): Add toast later.
-      }.bind(this));
-    } else {
-      chrome.bookmarks.removeTree(this.menuItem_.id, function() {
-        // TODO(jiaxi): Add toast later.
-      }.bind(this));
-    }
-    this.closeDropdownMenu_();
-  },
-
-  /**
-   * Close the menu on mousedown so clicks can propagate to the underlying UI.
-   * This allows the user to right click the list while a context menu is
-   * showing and get another context menu.
-   * @param {Event} e
-   * @private
-   */
-  onMenuMousedown_: function(e) {
-    if (e.path[0] != this.$.dropdown)
-      return;
-
-    this.closeDropdownMenu_();
-  },
-
-  /** @private */
-  closeDropdownMenu_: function() {
-    var menu = /** @type {!CrActionMenuElement} */ (
-        this.$.dropdown);
-    menu.close();
-  },
-
-  /** @private */
-  getEditActionLabel_: function() {
-    var label = this.menuItem_.url ? 'menuEdit' : 'menuRename';
-    return loadTimeData.getString(label);
-  },
-
   /** @private */
   emptyListMessage_: function() {
     var emptyListMessage = this.searchTerm_ ? 'noSearchResults' : 'emptyList';
diff --git a/chrome/browser/safe_browsing/protocol_manager.cc b/chrome/browser/safe_browsing/protocol_manager.cc
index 770e6cf..46e5382 100644
--- a/chrome/browser/safe_browsing/protocol_manager.cc
+++ b/chrome/browser/safe_browsing/protocol_manager.cc
@@ -29,6 +29,7 @@
 #include "net/base/net_errors.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/url_request/url_request_status.h"
@@ -76,6 +77,37 @@
   return base::TimeDelta::FromMinutes(finch_next_update_interval_minutes);
 }
 
+constexpr net::NetworkTrafficAnnotationTag
+    kChunkBackupRequestTrafficAnnotation = net::DefineNetworkTrafficAnnotation(
+        "safe_browsing_chunk_backup_request",
+        R"(
+        semantics {
+          sender: "Safe Browsing"
+          description:
+            "Safe Browsing updates its local database of bad sites every 30 "
+            "minutes or so. It aims to keep all users up-to-date with the same "
+            "set of hash-prefixes of bad URLs."
+          trigger:
+            "On a timer, approximately every 30 minutes."
+          data:
+            "The state of the local DB is sent so the server can send just the "
+            "changes. This doesn't include any user data."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: true
+          cookies_store: "Safe Browsing cookie store"
+          setting:
+            "Users can disable Safe Browsing by unchecking 'Protect you and "
+            "your device from dangerous sites' in Chromium settings under "
+            "Privacy. The feature is enabled by default."
+          chrome_policy {
+            SafeBrowsingEnabled {
+              policy_options {mode: MANDATORY}
+              SafeBrowsingEnabled: false
+            }
+          }
+        })");
 }  // namespace
 
 namespace safe_browsing {
@@ -219,8 +251,41 @@
     return;
   }
   GURL gethash_url = GetHashUrl(reporting_level);
-  std::unique_ptr<net::URLFetcher> fetcher_ptr = net::URLFetcher::Create(
-      url_fetcher_id_++, gethash_url, net::URLFetcher::POST, this);
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("safe_browsing_get_full_hash", R"(
+        semantics {
+          sender: "Safe Browsing"
+          description:
+            "When Safe Browsing detects that a URL might be dangerous based on "
+            "its local database, it sends a partial hash of that URL to Google "
+            "to verify it before showing a warning to the user. This partial "
+            "hash does not expose the URL to Google."
+          trigger:
+            "When a resource URL matches the local hash-prefix database of "
+            "potential threats (malware, phishing etc), and the full-hash "
+            "result is not already cached, this will be sent."
+          data:
+             "The 32-bit hash prefix of any potentially bad URLs. The URLs "
+             "themselves are not sent."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: true
+          cookies_store: "Safe Browsing cookie store"
+          setting:
+            "Users can disable Safe Browsing by unchecking 'Protect you and "
+            "your device from dangerous sites' in Chromium settings under "
+            "Privacy. The feature is enabled by default."
+          chrome_policy {
+            SafeBrowsingEnabled {
+              policy_options {mode: MANDATORY}
+              SafeBrowsingEnabled: false
+            }
+          }
+        })");
+  std::unique_ptr<net::URLFetcher> fetcher_ptr =
+      net::URLFetcher::Create(url_fetcher_id_++, gethash_url,
+                              net::URLFetcher::POST, this, traffic_annotation);
   net::URLFetcher* fetcher = fetcher_ptr.get();
   data_use_measurement::DataUseUserData::AttachToFetcher(
       fetcher, data_use_measurement::DataUseUserData::SAFE_BROWSING);
@@ -588,8 +653,37 @@
   backup_update_reason_ = backup_update_reason;
 
   GURL backup_update_url = BackupUpdateUrl(backup_update_reason);
-  request_ = net::URLFetcher::Create(url_fetcher_id_++, backup_update_url,
-                                     net::URLFetcher::POST, this);
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("safe_browsing_backup_request", R"(
+        semantics {
+          sender: "Safe Browsing"
+          description:
+            "Safe Browsing issues multi-step update requests to Google every "
+            "30 minutes or so to get the latest database of hashes of bad URLs."
+          trigger:
+            "On a timer, approximately every 30 minutes."
+          data:
+            "The state of the local DB is sent so the server can send just the "
+            "changes. This doesn't include any user data."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: true
+          cookies_store: "Safe Browsing cookie store"
+          setting:
+            "Users can disable Safe Browsing by unchecking 'Protect you and "
+            "your device from dangerous sites' in Chromium settings under "
+            "Privacy. The feature is enabled by default."
+          chrome_policy {
+            SafeBrowsingEnabled {
+              policy_options {mode: MANDATORY}
+              SafeBrowsingEnabled: false
+            }
+          }
+        })");
+  request_ =
+      net::URLFetcher::Create(url_fetcher_id_++, backup_update_url,
+                              net::URLFetcher::POST, this, traffic_annotation);
   data_use_measurement::DataUseUserData::AttachToFetcher(
       request_.get(), data_use_measurement::DataUseUserData::SAFE_BROWSING);
   request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
@@ -617,7 +711,8 @@
   GURL chunk_url = NextChunkUrl(next_chunk.url);
   request_type_ = CHUNK_REQUEST;
   request_ = net::URLFetcher::Create(url_fetcher_id_++, chunk_url,
-                                     net::URLFetcher::GET, this);
+                                     net::URLFetcher::GET, this,
+                                     kChunkBackupRequestTrafficAnnotation);
   data_use_measurement::DataUseUserData::AttachToFetcher(
       request_.get(), data_use_measurement::DataUseUserData::SAFE_BROWSING);
   request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
@@ -670,7 +765,8 @@
 
   GURL update_url = UpdateUrl(extended_reporting_level);
   request_ = net::URLFetcher::Create(url_fetcher_id_++, update_url,
-                                     net::URLFetcher::POST, this);
+                                     net::URLFetcher::POST, this,
+                                     kChunkBackupRequestTrafficAnnotation);
   data_use_measurement::DataUseUserData::AttachToFetcher(
       request_.get(), data_use_measurement::DataUseUserData::SAFE_BROWSING);
   request_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
diff --git a/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc b/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
index f0f3c50..6dc90f2e 100644
--- a/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
+++ b/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
@@ -80,6 +80,10 @@
   source->AddResourcePath("api_listener.js", IDR_MD_BOOKMARKS_API_LISTENER_JS);
   source->AddResourcePath("app.html", IDR_MD_BOOKMARKS_APP_HTML);
   source->AddResourcePath("app.js", IDR_MD_BOOKMARKS_APP_JS);
+  source->AddResourcePath("command_manager.html",
+                          IDR_MD_BOOKMARKS_COMMAND_MANAGER_HTML);
+  source->AddResourcePath("command_manager.js",
+                          IDR_MD_BOOKMARKS_COMMAND_MANAGER_JS);
   source->AddResourcePath("constants.html", IDR_MD_BOOKMARKS_CONSTANTS_HTML);
   source->AddResourcePath("constants.js", IDR_MD_BOOKMARKS_CONSTANTS_JS);
   source->AddResourcePath("dnd_manager.html",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f3d8c032..4dd9237 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -157,7 +157,6 @@
     "//skia",
     "//sql",
     "//sql:test_support",
-    "//sql:test_support",
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/leveldatabase",
@@ -3342,7 +3341,6 @@
     "//components/browser_sync:test_support",
     "//components/component_updater:test_support",
     "//components/content_settings/core/test:test_support",
-    "//components/content_settings/core/test:test_support",
     "//components/data_reduction_proxy/core/browser:test_support",
     "//components/data_use_measurement/core",
     "//components/metrics/proto",
diff --git a/chrome/test/data/webui/md_bookmarks/command_manager_test.js b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
new file mode 100644
index 0000000..07e414cb
--- /dev/null
+++ b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
@@ -0,0 +1,114 @@
+// 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.
+
+suite('<bookmarks-command-manager>', function() {
+  var commandManager;
+  var store;
+  var lastCommand;
+  var lastCommandIds;
+
+  function assertLastCommand(command, ids) {
+    assertEquals(lastCommand, command);
+    if (ids)
+      assertDeepEquals(normalizeSet(lastCommandIds), ids);
+    lastCommand = null;
+    lastCommandIds = null;
+  }
+
+  suiteSetup(function() {
+    // Overwrite bookmarkManagerPrivate APIs which will crash if called with
+    // fake data.
+    chrome.bookmarkManagerPrivate.copy = function() {};
+    chrome.bookmarkManagerPrivate.removeTrees = function() {};
+  });
+
+  setup(function() {
+    store = new bookmarks.TestStore({
+      nodes: testTree(createFolder(
+          '1',
+          [
+            createFolder('11', []),
+            createFolder('12', []),
+            createItem('13'),
+          ])),
+    });
+    bookmarks.Store.instance_ = store;
+
+    commandManager = document.createElement('bookmarks-command-manager');
+
+    var realHandle = commandManager.handle.bind(commandManager);
+    commandManager.handle = function(command, itemIds) {
+      lastCommand = command;
+      lastCommandIds = itemIds;
+      realHandle(command, itemIds);
+    };
+    replaceBody(commandManager);
+  });
+
+  test('can only copy single URL items', function() {
+    assertFalse(commandManager.canExecute(Command.COPY, new Set(['11'])));
+    assertFalse(commandManager.canExecute(Command.COPY, new Set(['11', '13'])));
+    assertTrue(commandManager.canExecute(Command.COPY, new Set(['13'])));
+  });
+
+  test('context menu hides invalid commands', function() {
+    store.data.selection.items = new Set(['11', '13']);
+    store.notifyObservers();
+
+    commandManager.openCommandMenuAtPosition(0, 0);
+    var commandHidden = {};
+    commandManager.root.querySelectorAll('.dropdown-item').forEach(element => {
+      commandHidden[element.getAttribute('command')] = element.hidden;
+    });
+
+    // With a folder and an item selected, the only available context menu item
+    // is 'Delete'.
+    assertTrue(commandHidden['edit']);
+    assertTrue(commandHidden['copy']);
+    assertFalse(commandHidden['delete']);
+  });
+
+  test('keyboard shortcuts trigger when valid', function() {
+    var modifier = cr.isMac ? 'meta' : 'ctrl';
+
+    store.data.selection.items = new Set(['13']);
+    store.notifyObservers();
+
+    MockInteractions.pressAndReleaseKeyOn(document, 67, modifier, 'c');
+    assertLastCommand('copy', ['13']);
+
+    // Doesn't trigger when a folder is selected.
+    store.data.selection.items = new Set(['11']);
+    store.notifyObservers();
+
+    MockInteractions.pressAndReleaseKeyOn(document, 67, modifier, 'c');
+    assertLastCommand(null);
+
+    // Doesn't trigger when nothing is selected.
+    store.data.selection.items = new Set();
+    store.notifyObservers();
+
+    MockInteractions.pressAndReleaseKeyOn(document, 67, modifier, 'c');
+    assertLastCommand(null);
+  });
+
+  test('delete command triggers', function() {
+    store.data.selection.items = new Set(['12', '13']);
+    store.notifyObservers();
+
+    MockInteractions.pressAndReleaseKeyOn(document, 46, '', 'Delete');
+    assertLastCommand('delete', ['12', '13']);
+  });
+
+  test('edit command triggers', function() {
+    var key = cr.isMac ? 'Enter' : 'F2';
+    var keyCode = cr.isMac ? 13 : 113;
+
+    store.data.selection.items = new Set(['11']);
+    store.notifyObservers();
+
+    MockInteractions.pressAndReleaseKeyOn(document, keyCode, '', key);
+    assertLastCommand('edit', ['11']);
+  });
+});
diff --git a/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js b/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js
index 745cf0b..9c017d5 100644
--- a/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js
+++ b/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js
@@ -55,6 +55,20 @@
   mocha.run();
 });
 
+function MaterialBookmarksCommandManagerTest() {}
+
+MaterialBookmarksCommandManagerTest.prototype = {
+  __proto__: MaterialBookmarksBrowserTest.prototype,
+
+  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+    'command_manager_test.js',
+  ]),
+};
+
+TEST_F('MaterialBookmarksCommandManagerTest', 'All', function() {
+  mocha.run();
+});
+
 function MaterialBookmarksDNDManagerTest() {}
 
 MaterialBookmarksDNDManagerTest.prototype = {
diff --git a/chromecast/renderer/BUILD.gn b/chromecast/renderer/BUILD.gn
index bb5a1c4..ea4bd19f 100644
--- a/chromecast/renderer/BUILD.gn
+++ b/chromecast/renderer/BUILD.gn
@@ -30,7 +30,6 @@
     "//chromecast/common/media",
     "//chromecast/crash",
     "//chromecast/media",
-    "//chromecast/media",
     "//components/network_hints/renderer",
     "//content/public/common",
     "//content/public/renderer",
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
index 9ffb7f87..ba98d5c 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
@@ -249,7 +249,9 @@
     data_use_measurement::DataUseUserData::AttachToFetcher(
         fetcher_.get(),
         data_use_measurement::DataUseUserData::DATA_REDUCTION_PROXY);
-    fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY);
+    fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY |
+                           net::LOAD_DO_NOT_SEND_COOKIES |
+                           net::LOAD_DO_NOT_SAVE_COOKIES);
     fetcher_->SetRequestContext(url_request_context_getter_.get());
     // Configure max retries to be at most kMaxRetries times for 5xx errors.
     static const int kMaxRetries = 5;
@@ -304,7 +306,9 @@
     data_use_measurement::DataUseUserData::AttachToFetcher(
         fetcher_.get(),
         data_use_measurement::DataUseUserData::DATA_REDUCTION_PROXY);
-    fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE);
+    fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
+                           net::LOAD_DO_NOT_SEND_COOKIES |
+                           net::LOAD_DO_NOT_SAVE_COOKIES);
     fetcher_->SetRequestContext(url_request_context_getter_.get());
     // |fetcher| should not retry on 5xx errors.
     fetcher_->SetAutomaticallyRetryOn5xx(false);
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
index 2b58c6b1..26f38eb5 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
@@ -412,7 +412,8 @@
   data_use_measurement::DataUseUserData::AttachToFetcher(
       fetcher.get(),
       data_use_measurement::DataUseUserData::DATA_REDUCTION_PROXY);
-  fetcher->SetLoadFlags(net::LOAD_BYPASS_PROXY);
+  fetcher->SetLoadFlags(net::LOAD_BYPASS_PROXY | net::LOAD_DO_NOT_SEND_COOKIES |
+                        net::LOAD_DO_NOT_SAVE_COOKIES);
   fetcher->SetUploadData("application/x-protobuf", request_body);
   DCHECK(url_request_context_getter_);
   fetcher->SetRequestContext(url_request_context_getter_);
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc
index 4d21e66..fb72e14 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc
@@ -196,7 +196,9 @@
   data_use_measurement::DataUseUserData::AttachToFetcher(
       current_fetcher_.get(),
       data_use_measurement::DataUseUserData::DATA_REDUCTION_PROXY);
-  current_fetcher_->SetLoadFlags(net::LOAD_BYPASS_PROXY);
+  current_fetcher_->SetLoadFlags(net::LOAD_BYPASS_PROXY |
+                                 net::LOAD_DO_NOT_SEND_COOKIES |
+                                 net::LOAD_DO_NOT_SAVE_COOKIES);
   current_fetcher_->SetUploadData("application/x-protobuf", serialized_request);
   current_fetcher_->SetRequestContext(url_request_context_);
   // |current_fetcher_| should not retry on 5xx errors since the server may
diff --git a/components/safe_browsing_db/v4_get_hash_protocol_manager.cc b/components/safe_browsing_db/v4_get_hash_protocol_manager.cc
index eba742b..77488c2c 100644
--- a/components/safe_browsing_db/v4_get_hash_protocol_manager.cc
+++ b/components/safe_browsing_db/v4_get_hash_protocol_manager.cc
@@ -17,6 +17,7 @@
 #include "net/base/load_flags.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_request_context_getter.h"
 
@@ -301,8 +302,41 @@
   net::HttpRequestHeaders headers;
   GetHashUrlAndHeaders(req_base64, &gethash_url, &headers);
 
-  std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create(
-      url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this);
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("safe_browsing_v4_get_hash", R"(
+        semantics {
+          sender: "Safe Browsing"
+          description:
+            "When Safe Browsing detects that a URL might be dangerous based on "
+            "its local database, it sends a partial hash of that URL to Google "
+            "to verify it before showing a warning to the user. This partial "
+            "hash does not expose the URL to Google."
+          trigger:
+            "When a resource URL matches the local hash-prefix database of "
+            "potential threats (malware, phishing etc), and the full-hash "
+            "result is not already cached, this will be sent."
+          data:
+             "The 32-bit hash prefix of any potentially bad URLs. The URLs "
+             "themselves are not sent."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: true
+          cookies_store: "Safe Browsing cookie store"
+          setting:
+            "Users can disable Safe Browsing by unchecking 'Protect you and "
+            "your device from dangerous sites' in Chromium settings under "
+            "Privacy. The feature is enabled by default."
+          chrome_policy {
+            SafeBrowsingEnabled {
+              policy_options {mode: MANDATORY}
+              SafeBrowsingEnabled: false
+            }
+          }
+        })");
+  std::unique_ptr<net::URLFetcher> owned_fetcher =
+      net::URLFetcher::Create(url_fetcher_id_++, gethash_url,
+                              net::URLFetcher::GET, this, traffic_annotation);
   net::URLFetcher* fetcher = owned_fetcher.get();
   pending_hash_requests_[fetcher].reset(new FullHashCallbackInfo(
       cached_full_hash_infos, prefixes_to_request, std::move(owned_fetcher),
diff --git a/components/safe_browsing_db/v4_update_protocol_manager.cc b/components/safe_browsing_db/v4_update_protocol_manager.cc
index e72eb44..3d485bdd 100644
--- a/components/safe_browsing_db/v4_update_protocol_manager.cc
+++ b/components/safe_browsing_db/v4_update_protocol_manager.cc
@@ -17,6 +17,7 @@
 #include "net/base/load_flags.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_request_context_getter.h"
 
@@ -311,8 +312,37 @@
   net::HttpRequestHeaders headers;
   GetUpdateUrlAndHeaders(req_base64, &update_url, &headers);
 
-  std::unique_ptr<net::URLFetcher> fetcher = net::URLFetcher::Create(
-      url_fetcher_id_++, update_url, net::URLFetcher::GET, this);
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("safe_browsing_g4_update", R"(
+        semantics {
+          sender: "Safe Browsing"
+          description:
+            "Safe Browsing issues a request to Google every 30 minutes or so "
+            "to get the latest database of hashes of bad URLs."
+          trigger:
+            "On a timer, approximately every 30 minutes."
+          data:
+             "The state of the local DB is sent so the server can send just "
+             "the changes. This doesn't include any user data."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: true
+          cookies_store: "Safe Browsing cookie store"
+          setting:
+            "Users can disable Safe Browsing by unchecking 'Protect you and "
+            "your device from dangerous sites' in Chromium settings under "
+            "Privacy. The feature is enabled by default."
+          chrome_policy {
+            SafeBrowsingEnabled {
+              policy_options {mode: MANDATORY}
+              SafeBrowsingEnabled: false
+            }
+          }
+        })");
+  std::unique_ptr<net::URLFetcher> fetcher =
+      net::URLFetcher::Create(url_fetcher_id_++, update_url,
+                              net::URLFetcher::GET, this, traffic_annotation);
   fetcher->SetExtraRequestHeaders(headers.ToString());
   data_use_measurement::DataUseUserData::AttachToFetcher(
       fetcher.get(), data_use_measurement::DataUseUserData::SAFE_BROWSING);
diff --git a/components/translate/core/browser/translate_infobar_delegate.cc b/components/translate/core/browser/translate_infobar_delegate.cc
index c945a5c692..5fa42a5c 100644
--- a/components/translate/core/browser/translate_infobar_delegate.cc
+++ b/components/translate/core/browser/translate_infobar_delegate.cc
@@ -28,10 +28,6 @@
 
 namespace {
 
-// Feature flag for "Translate UI Redesign" project.
-const base::Feature kTranslateCompactUI{"TranslateCompactUI",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Counts used to decide whether infobars should be shown.
 // Android and iOS implementations do not offer a drop down (for space reasons),
 // so we are more aggressive about showing the shortcut to never translate.
@@ -52,6 +48,9 @@
 
 }  // namespace
 
+const base::Feature kTranslateCompactUI{"TranslateCompactUI",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 const size_t TranslateInfoBarDelegate::kNoIndex = TranslateUIDelegate::kNoIndex;
 
 TranslateInfoBarDelegate::~TranslateInfoBarDelegate() {
diff --git a/components/translate/core/browser/translate_infobar_delegate.h b/components/translate/core/browser/translate_infobar_delegate.h
index eb74271..1e48a64 100644
--- a/components/translate/core/browser/translate_infobar_delegate.h
+++ b/components/translate/core/browser/translate_infobar_delegate.h
@@ -29,6 +29,9 @@
 
 namespace translate {
 
+// Feature flag for "Translate Compact Infobar UI" project.
+extern const base::Feature kTranslateCompactUI;
+
 class TranslateDriver;
 class TranslateManager;
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 16803c32..47c55207 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1293,6 +1293,8 @@
     "resource_context_impl.h",
     "screen_orientation/screen_orientation_provider.cc",
     "screen_orientation/screen_orientation_provider.h",
+    "service_manager/common_browser_interfaces.cc",
+    "service_manager/common_browser_interfaces.h",
     "service_manager/merge_dictionary.cc",
     "service_manager/merge_dictionary.h",
     "service_manager/service_manager_context.cc",
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index 509baef..3f68fd7d 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -29,6 +29,7 @@
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/loader/resource_dispatcher_host_impl.h"
 #include "content/browser/push_messaging/push_messaging_router.h"
+#include "content/browser/service_manager/common_browser_interfaces.h"
 #include "content/browser/storage_partition_impl_map.h"
 #include "content/common/child_process_host_impl.h"
 #include "content/public/browser/blob_handle.h"
@@ -492,6 +493,8 @@
     for (const auto& entry : services) {
       connection->AddEmbeddedService(entry.first, entry.second);
     }
+
+    RegisterCommonBrowserInterfaces(connection);
     connection->Start();
   }
 }
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 4977be2..da5a242b 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -346,12 +346,6 @@
     auto task_runner = BrowserThread::GetTaskRunnerForThread(BrowserThread::UI);
     registry_.AddInterface(base::Bind(&FieldTrialRecorder::Create),
                            task_runner);
-    registry_.AddInterface(
-        base::Bind(
-            &memory_instrumentation::CoordinatorImpl::BindCoordinatorRequest,
-            base::Unretained(
-                memory_instrumentation::CoordinatorImpl::GetInstance())),
-        task_runner);
 #if defined(OS_ANDROID)
     registry_.AddInterface(
         base::Bind(&BindJavaInterface<media::mojom::AndroidOverlayProvider>),
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 3121685..5731df87c 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -52,7 +52,6 @@
 #include "build/build_config.h"
 #include "cc/base/switches.h"
 #include "cc/output/buffer_to_texture_target_map.h"
-#include "components/discardable_memory/service/discardable_shared_memory_manager.h"
 #include "components/metrics/single_sample_metrics.h"
 #include "components/tracing/common/tracing_switches.h"
 #include "content/browser/appcache/appcache_dispatcher_host.h"
@@ -183,7 +182,6 @@
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "ppapi/features/features.h"
-#include "services/resource_coordinator/memory/coordinator/coordinator_impl.h"
 #include "services/service_manager/embedder/switches.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -1341,25 +1339,8 @@
                        base::Bind(&WebSocketManager::CreateWebSocket, GetID(),
                                   MSG_ROUTING_NONE));
 
-  // Chrome browser process only provides DiscardableSharedMemory service when
-  // Chrome is not running in mus+ash.
-  if (!service_manager::ServiceManagerIsRemote()) {
-    discardable_memory::DiscardableSharedMemoryManager* manager =
-        BrowserMainLoop::GetInstance()->discardable_shared_memory_manager();
-    registry->AddInterface(
-        base::Bind(&discardable_memory::DiscardableSharedMemoryManager::Bind,
-                   base::Unretained(manager)));
-  }
-
   AddUIThreadInterface(registry.get(), base::Bind(&FieldTrialRecorder::Create));
 
-  AddUIThreadInterface(
-      registry.get(),
-      base::Bind(
-          &memory_instrumentation::CoordinatorImpl::BindCoordinatorRequest,
-          base::Unretained(
-              memory_instrumentation::CoordinatorImpl::GetInstance())));
-
   associated_interfaces_.reset(new AssociatedInterfaceRegistryImpl());
   GetContentClient()->browser()->ExposeInterfacesToRenderer(
       registry.get(), associated_interfaces_.get(), this);
diff --git a/content/browser/service_manager/common_browser_interfaces.cc b/content/browser/service_manager/common_browser_interfaces.cc
new file mode 100644
index 0000000..afc81bb
--- /dev/null
+++ b/content/browser/service_manager/common_browser_interfaces.cc
@@ -0,0 +1,98 @@
+// 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.
+
+#include "content/browser/service_manager/common_browser_interfaces.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "components/discardable_memory/service/discardable_shared_memory_manager.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/public/common/connection_filter.h"
+#include "content/public/common/service_manager_connection.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/resource_coordinator/memory/coordinator/coordinator_impl.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+
+namespace content {
+
+namespace {
+
+void BindMemoryCoordinatorRequest(
+    const service_manager::BindSourceInfo& source_info,
+    memory_instrumentation::mojom::CoordinatorRequest request) {
+  auto* coordinator = memory_instrumentation::CoordinatorImpl::GetInstance();
+  if (coordinator)
+    coordinator->BindCoordinatorRequest(source_info, std::move(request));
+}
+
+class ConnectionFilterImpl : public ConnectionFilter {
+ public:
+  ConnectionFilterImpl()
+      : main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+    RegisterMainThreadInterface(base::Bind(&BindMemoryCoordinatorRequest));
+
+    auto* browser_main_loop = BrowserMainLoop::GetInstance();
+    if (browser_main_loop) {
+      auto* manager = browser_main_loop->discardable_shared_memory_manager();
+      if (manager) {
+        registry_.AddInterface(base::Bind(
+            &discardable_memory::DiscardableSharedMemoryManager::Bind,
+            base::Unretained(manager)));
+      }
+    }
+  }
+
+  ~ConnectionFilterImpl() override {}
+
+ private:
+  template <typename Interface>
+  using InterfaceBinder =
+      base::Callback<void(const service_manager::BindSourceInfo&,
+                          mojo::InterfaceRequest<Interface>)>;
+
+  // ConnectionFilter:
+  void OnBindInterface(const service_manager::BindSourceInfo& source_info,
+                       const std::string& interface_name,
+                       mojo::ScopedMessagePipeHandle* interface_pipe,
+                       service_manager::Connector* connector) override {
+    if (registry_.CanBindInterface(interface_name)) {
+      registry_.BindInterface(source_info, interface_name,
+                              std::move(*interface_pipe));
+    }
+  }
+
+  template <typename Interface>
+  static void BindOnTaskRunner(
+      const scoped_refptr<base::TaskRunner>& task_runner,
+      const InterfaceBinder<Interface>& binder,
+      const service_manager::BindSourceInfo& source_info,
+      mojo::InterfaceRequest<Interface> request) {
+    task_runner->PostTask(
+        FROM_HERE, base::BindOnce(binder, source_info, std::move(request)));
+  }
+
+  template <typename Interface>
+  void RegisterMainThreadInterface(const InterfaceBinder<Interface>& binder) {
+    registry_.AddInterface(base::Bind(&BindOnTaskRunner<Interface>,
+                                      main_thread_task_runner_, binder));
+  }
+
+  const scoped_refptr<base::TaskRunner> main_thread_task_runner_;
+  service_manager::BinderRegistry registry_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectionFilterImpl);
+};
+
+}  // namespace
+
+void RegisterCommonBrowserInterfaces(ServiceManagerConnection* connection) {
+  connection->AddConnectionFilter(base::MakeUnique<ConnectionFilterImpl>());
+}
+
+}  // namespace content
diff --git a/content/browser/service_manager/common_browser_interfaces.h b/content/browser/service_manager/common_browser_interfaces.h
new file mode 100644
index 0000000..0e826dec
--- /dev/null
+++ b/content/browser/service_manager/common_browser_interfaces.h
@@ -0,0 +1,18 @@
+// 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 CONTENT_BROWSER_SERVICE_MANAGER_COMMON_BROWSER_INTERFACES_H_
+#define CONTENT_BROWSER_SERVICE_MANAGER_COMMON_BROWSER_INTERFACES_H_
+
+namespace content {
+
+class ServiceManagerConnection;
+
+// Registers interface binders for browser-side interfaces that are common to
+// all child process types.
+void RegisterCommonBrowserInterfaces(ServiceManagerConnection* connection);
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SERVICE_MANAGER_COMMON_BROWSER_INTERFACES_H_
diff --git a/content/browser/service_manager/service_manager_context.cc b/content/browser/service_manager/service_manager_context.cc
index 98372cb..eeb81396 100644
--- a/content/browser/service_manager/service_manager_context.cc
+++ b/content/browser/service_manager/service_manager_context.cc
@@ -19,6 +19,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "content/browser/child_process_launcher.h"
 #include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/service_manager/common_browser_interfaces.h"
 #include "content/browser/service_manager/merge_dictionary.h"
 #include "content/browser/wake_lock/wake_lock_context_host.h"
 #include "content/common/service_manager/service_manager_connection_impl.h"
@@ -275,6 +276,7 @@
   ServiceManagerConnection::SetForProcess(ServiceManagerConnection::Create(
       mojo::MakeRequest(&root_browser_service),
       BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)));
+  auto* browser_connection = ServiceManagerConnection::GetForProcess();
 
   service_manager::mojom::PIDReceiverPtr pid_receiver;
   packaged_services_connection_->GetConnector()->StartService(
@@ -313,8 +315,7 @@
   // This is safe to assign directly from any thread, because
   // ServiceManagerContext must be constructed before anyone can call
   // GetConnectorForIOThread().
-  g_io_thread_connector.Get() =
-      ServiceManagerConnection::GetForProcess()->GetConnector()->Clone();
+  g_io_thread_connector.Get() = browser_connection->GetConnector()->Clone();
 
   ContentBrowserClient::OutOfProcessServiceMap sandboxed_services;
   GetContentClient()
@@ -360,12 +361,14 @@
                  shape_detection::mojom::kServiceName));
 
   packaged_services_connection_->Start();
-  ServiceManagerConnection::GetForProcess()->Start();
+
+  RegisterCommonBrowserInterfaces(browser_connection);
+  browser_connection->Start();
 
   if (network_service_enabled) {
     // Start the network service process as soon as possible, since it is
     // critical to start up performance.
-    ServiceManagerConnection::GetForProcess()->GetConnector()->StartService(
+    browser_connection->GetConnector()->StartService(
         mojom::kNetworkServiceName);
   }
 }
diff --git a/content/child/DEPS b/content/child/DEPS
index da61fe55..270ae0dd 100644
--- a/content/child/DEPS
+++ b/content/child/DEPS
@@ -14,7 +14,6 @@
   "+services/device/public/interfaces",
   "+services/resource_coordinator",
   "+services/service_manager",
-  "+services/service_manager",
   "+v8/include/v8.h"
 ]
 
diff --git a/content/renderer/gpu/render_widget_compositor.cc b/content/renderer/gpu/render_widget_compositor.cc
index e3450e5..9733a8c 100644
--- a/content/renderer/gpu/render_widget_compositor.cc
+++ b/content/renderer/gpu/render_widget_compositor.cc
@@ -87,6 +87,57 @@
 namespace content {
 namespace {
 
+using ReportTimeCallback = base::Callback<void(bool, double)>;
+
+double MonotonicallyIncreasingTime() {
+  return static_cast<double>(base::TimeTicks::Now().ToInternalValue()) /
+         base::Time::kMicrosecondsPerSecond;
+}
+
+class ReportTimeSwapPromise : public cc::SwapPromise {
+ public:
+  ReportTimeSwapPromise(
+      ReportTimeCallback callback,
+      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+  ~ReportTimeSwapPromise() override;
+
+  void DidActivate() override {}
+  void WillSwap(cc::CompositorFrameMetadata* metadata) override {}
+  void DidSwap() override;
+  DidNotSwapAction DidNotSwap(DidNotSwapReason reason) override;
+
+  int64_t TraceId() const override;
+
+ private:
+  ReportTimeCallback callback_;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportTimeSwapPromise);
+};
+
+ReportTimeSwapPromise::ReportTimeSwapPromise(
+    ReportTimeCallback callback,
+    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+    : callback_(callback), task_runner_(task_runner) {}
+
+ReportTimeSwapPromise::~ReportTimeSwapPromise() {}
+
+void ReportTimeSwapPromise::DidSwap() {
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(callback_, true, MonotonicallyIncreasingTime()));
+}
+
+cc::SwapPromise::DidNotSwapAction ReportTimeSwapPromise::DidNotSwap(
+    cc::SwapPromise::DidNotSwapReason reason) {
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(callback_, false, 0));
+  return cc::SwapPromise::DidNotSwapAction::BREAK_PROMISE;
+}
+
+int64_t ReportTimeSwapPromise::TraceId() const {
+  return 0;
+}
+
 bool GetSwitchValueAsInt(const base::CommandLine& command_line,
                          const std::string& switch_string,
                          int min_value,
@@ -1152,4 +1203,9 @@
   layer_tree_host_->SetLocalSurfaceId(local_surface_id);
 }
 
+void RenderWidgetCompositor::NotifySwapTime(ReportTimeCallback callback) {
+  QueueSwapPromise(base::MakeUnique<ReportTimeSwapPromise>(
+      std::move(callback), base::ThreadTaskRunnerHandle::Get()));
+}
+
 }  // namespace content
diff --git a/content/renderer/gpu/render_widget_compositor.h b/content/renderer/gpu/render_widget_compositor.h
index a36b5f02..a2692a09 100644
--- a/content/renderer/gpu/render_widget_compositor.h
+++ b/content/renderer/gpu/render_widget_compositor.h
@@ -53,6 +53,8 @@
     : NON_EXPORTED_BASE(public blink::WebLayerTreeView),
       NON_EXPORTED_BASE(public cc::LayerTreeHostClient),
       NON_EXPORTED_BASE(public cc::LayerTreeHostSingleThreadClient) {
+  using ReportTimeCallback = base::Callback<void(bool, double)>;
+
  public:
   // Attempt to construct and initialize a compositor instance for the widget
   // with the given settings. Returns NULL if initialization fails.
@@ -167,6 +169,7 @@
   void SetShowPaintRects(bool show) override;
   void SetShowDebugBorders(bool show) override;
   void SetShowScrollBottleneckRects(bool show) override;
+  void NotifySwapTime(ReportTimeCallback callback) override;
 
   void UpdateBrowserControlsState(blink::WebBrowserControlsState constraints,
                                   blink::WebBrowserControlsState current,
@@ -240,6 +243,8 @@
   cc::FrameSinkId frame_sink_id_;
 
   base::WeakPtrFactory<RenderWidgetCompositor> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(RenderWidgetCompositor);
 };
 
 }  // namespace content
diff --git a/content/renderer/pepper/pepper_device_enumeration_host_helper.cc b/content/renderer/pepper/pepper_device_enumeration_host_helper.cc
index 2fd9a3b..dc4967c 100644
--- a/content/renderer/pepper/pepper_device_enumeration_host_helper.cc
+++ b/content/renderer/pepper/pepper_device_enumeration_host_helper.cc
@@ -35,6 +35,9 @@
     if (!owner->document_url_.is_valid())
       return;
 
+    if (!owner->delegate_)
+      return;
+
     requested_ = true;
 
     // Note that the callback passed into
@@ -44,7 +47,6 @@
     // EnumerateDevicesCallbackBody() to ensure that we always call |callback|
     // asynchronously.
     sync_call_ = true;
-    DCHECK(owner->delegate_);
     owner->delegate_->EnumerateDevices(
         owner->device_type_, owner->document_url_,
         base::Bind(&ScopedEnumerationRequest::EnumerateDevicesCallbackBody,
@@ -91,9 +93,11 @@
     if (!owner_->document_url_.is_valid())
       return;
 
+    if (!owner->delegate_)
+      return;
+
     requested_ = true;
 
-    DCHECK(owner_->delegate_);
     // |callback| is never called synchronously by StartMonitoringDevices(),
     // so it is OK to pass it directly, even if |callback| destroys |this|.
     subscription_id_ = owner_->delegate_->StartMonitoringDevices(
diff --git a/content/utility/DEPS b/content/utility/DEPS
index 26456d68..95eef3e 100644
--- a/content/utility/DEPS
+++ b/content/utility/DEPS
@@ -5,7 +5,6 @@
   "+content/public/utility",
   "+services/data_decoder",
   "+services/service_manager",
-  "+services/service_manager",
   "+services/shape_detection",
   "+sandbox/win/src",
 ]
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index 7b3788b2..57342a3 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 import("//build/config/chrome_build.gni")
-import("//headless/headless.gni")
 import("//build/util/process_version.gni")
+import("//headless/headless.gni")
 import("//mojo/public/tools/bindings/mojom.gni")
 import("//printing/features/features.gni")
 import("//testing/test.gni")
@@ -21,7 +21,7 @@
 
 group("headless") {
   deps = [
-    "//headless:headless_lib",
+    ":headless_lib",
   ]
 }
 
@@ -69,7 +69,7 @@
 
 action("embed_resources") {
   # TODO(altimin): Consider zipping file here, it can reduce size up to 80%.
-  script = "//headless/lib/embed_data.py"
+  script = "lib/embed_data.py"
 
   inputs = [
     "$root_out_dir/headless_lib.pak",
@@ -168,7 +168,7 @@
 }
 
 action("gen_devtools_client_api") {
-  script = "//headless/lib/browser/devtools_api/client_api_generator.py"
+  script = "lib/browser/devtools_api/client_api_generator.py"
   deps = [
     "//third_party/WebKit/Source/core/inspector:protocol_version",
   ]
@@ -484,9 +484,9 @@
   deps = [
     ":embedder_mojo_for_testing",
     ":headless_browser_tests_pak",
+    ":headless_lib",
     "//base",
     "//content/test:test_support",
-    "//headless:headless_lib",
     "//testing/gmock",
     "//testing/gtest",
   ]
@@ -504,7 +504,7 @@
   ]
 
   deps = [
-    "//headless:headless_lib",
+    ":headless_lib",
   ]
 
   configs += [ ":headless_implementation" ]
@@ -516,7 +516,7 @@
   ]
 
   deps = [
-    "//headless:headless_shell_lib",
+    ":headless_shell_lib",
   ]
 
   if (is_win) {
@@ -545,6 +545,6 @@
   ]
 
   deps = [
-    "//headless:headless_shell_lib",
+    ":headless_shell_lib",
   ]
 }
diff --git a/ios/chrome/browser/tabs/BUILD.gn b/ios/chrome/browser/tabs/BUILD.gn
index 0555e75..113d24f 100644
--- a/ios/chrome/browser/tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/BUILD.gn
@@ -153,7 +153,6 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/find_in_page",
-    "//ios/chrome/browser/find_in_page",
     "//ios/chrome/browser/history",
     "//ios/chrome/browser/infobars",
     "//ios/chrome/browser/reading_list",
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 9d053cd..334aee2 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -80,7 +80,6 @@
   ":packed_resources",
   "//base",
   "//components/infobars/core",
-  "//components/infobars/core",
   "//components/keyed_service/core",
   "//components/keyed_service/ios",
   "//components/pref_registry",
@@ -90,17 +89,14 @@
   "//components/translate/ios/browser",
   "//google_apis",
   "//ios/net",
-  "//ios/net",
   "//ios/web",
   "//ios/web:reload_type",
   "//ios/web:user_agent",
-  "//ios/web:user_agent",
   "//ios/web/public/app",
   "//net",
   "//net:extras",
   "//ui/base",
   "//url",
-  "//url",
 ]
 
 ios_framework_bundle("web_view") {
diff --git a/mash/catalog_viewer/BUILD.gn b/mash/catalog_viewer/BUILD.gn
index cd8d1f4..71979a3 100644
--- a/mash/catalog_viewer/BUILD.gn
+++ b/mash/catalog_viewer/BUILD.gn
@@ -19,7 +19,6 @@
     "//mojo/public/cpp/bindings",
     "//services/catalog/public/interfaces",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//services/service_manager/public/interfaces",
     "//ui/resources",
     "//ui/views",
@@ -41,7 +40,6 @@
     "//base",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//services/service_manager/public/interfaces",
     "//ui/views/mus:for_mojo_application",
   ]
diff --git a/mash/example/views_examples/BUILD.gn b/mash/example/views_examples/BUILD.gn
index 1da75c5..c531369 100644
--- a/mash/example/views_examples/BUILD.gn
+++ b/mash/example/views_examples/BUILD.gn
@@ -20,7 +20,6 @@
     "//mash/public/interfaces",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//services/ui/public/interfaces",
     "//skia",
     "//ui/gfx",
diff --git a/mash/session/BUILD.gn b/mash/session/BUILD.gn
index b2052a79..5aa3ecf4 100644
--- a/mash/session/BUILD.gn
+++ b/mash/session/BUILD.gn
@@ -22,7 +22,6 @@
     "//mojo/common",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
   ]
 
   data_deps = [
@@ -47,7 +46,6 @@
     "//mojo/common",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
   ]
 }
 
diff --git a/mash/task_viewer/BUILD.gn b/mash/task_viewer/BUILD.gn
index d0cec0e..790e6b0 100644
--- a/mash/task_viewer/BUILD.gn
+++ b/mash/task_viewer/BUILD.gn
@@ -36,7 +36,6 @@
     "//base",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
-    "//services/service_manager/public/cpp",
     "//services/service_manager/public/interfaces",
     "//ui/views/mus:for_mojo_application",
   ]
diff --git a/pdf/DEPS b/pdf/DEPS
index c62be37..b7a8a17 100644
--- a/pdf/DEPS
+++ b/pdf/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
   "+chrome/common/content_restriction.h",
-  "+chrome/common/url_constants.h",
   "+net",
   "+ppapi",
   "+ui/base/window_open_disposition.h",
diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc
index f9c2e98d..8a597e5 100644
--- a/pdf/out_of_process_instance.cc
+++ b/pdf/out_of_process_instance.cc
@@ -21,7 +21,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/common/content_restriction.h"
-#include "chrome/common/url_constants.h"
 #include "net/base/escape.h"
 #include "pdf/pdf.h"
 #include "ppapi/c/dev/ppb_cursor_control_dev.h"
@@ -49,6 +48,7 @@
 
 namespace {
 
+const char kChromePrint[] = "chrome://print/";
 const char kChromeExtension[] =
     "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai";
 
@@ -221,8 +221,8 @@
 int ExtractPrintPreviewPageIndex(base::StringPiece src_url) {
   // Sample |src_url| format: chrome://print/id/page_index/print.pdf
   std::vector<base::StringPiece> url_substr =
-      base::SplitStringPiece(src_url.substr(strlen(chrome::kChromeUIPrintURL)),
-                             "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+      base::SplitStringPiece(src_url.substr(strlen(kChromePrint)), "/",
+                             base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
   if (url_substr.size() != 3)
     return -1;
 
@@ -236,7 +236,7 @@
 }
 
 bool IsPrintPreviewUrl(base::StringPiece url) {
-  return url.starts_with(chrome::kChromeUIPrintURL);
+  return url.starts_with(kChromePrint);
 }
 
 void ScalePoint(float scale, pp::Point* point) {
diff --git a/services/service_manager/public/cpp/service_context.cc b/services/service_manager/public/cpp/service_context.cc
index 74683c57..4e81468a 100644
--- a/services/service_manager/public/cpp/service_context.cc
+++ b/services/service_manager/public/cpp/service_context.cc
@@ -41,12 +41,7 @@
 ServiceContext::~ServiceContext() {}
 
 void ServiceContext::SetQuitClosure(const base::Closure& closure) {
-  if (service_quit_) {
-    // CAUTION: May delete |this|.
-    closure.Run();
-  } else {
-    quit_closure_ = closure;
-  }
+  quit_closure_ = closure;
 }
 
 void ServiceContext::RequestQuit() {
@@ -61,7 +56,6 @@
 }
 
 void ServiceContext::QuitNow() {
-  service_quit_ = true;
   if (binding_.is_bound())
     binding_.Close();
   if (!quit_closure_.is_null()) {
diff --git a/services/service_manager/public/cpp/service_context.h b/services/service_manager/public/cpp/service_context.h
index bec2ba18..451f1ad 100644
--- a/services/service_manager/public/cpp/service_context.h
+++ b/services/service_manager/public/cpp/service_context.h
@@ -129,14 +129,6 @@
   // is unbound and therefore invalid until OnStart() is called.
   mojom::ServiceControlAssociatedPtr service_control_;
 
-  // The Service may call QuitNow() before SetConnectionLostClosure(), and the
-  // latter is expected to invoke the closure immediately in that case. This is
-  // used to track that condition.
-  //
-  // TODO(rockot): Figure out who depends on this behavior and make them stop.
-  // It's weird and shouldn't be necessary.
-  bool service_quit_ = false;
-
   // The closure to run when QuitNow() is invoked. May delete |this|.
   base::Closure quit_closure_;
 
diff --git a/services/service_manager/tests/lifecycle/package.cc b/services/service_manager/tests/lifecycle/package.cc
index 799cf1d..ece551b 100644
--- a/services/service_manager/tests/lifecycle/package.cc
+++ b/services/service_manager/tests/lifecycle/package.cc
@@ -72,8 +72,7 @@
     service_manager_connection_closed_callback_.Run();
     context()->QuitNow();
     // This only closed our relationship with the service manager, existing
-    // |bindings_|
-    // remain active.
+    // |bindings_| remain active.
   }
 
   void BindingLost() {
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 7dd724db..ad0f0ac 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -2340,26 +2340,14 @@
 crbug.com/698077 inspector/sources/debugger/debug-inlined-scripts.html [ NeedsManualRebaseline ]
 
 # Working on getting the CSP tests going:
-crbug.com/694525 external/wpt/content-security-policy [ Skip ]
-crbug.com/694525 external/wpt/content-security-policy/child-src [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/connect-src [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/embedded-enforcement [ Pass ]
+crbug.com/694525 external/wpt/content-security-policy/base-uri [ Skip ]
+crbug.com/694525 external/wpt/content-security-policy/blink-contrib [ Skip ]
+crbug.com/694525 external/wpt/content-security-policy/blink-contrib-2 [ Skip ]
 crbug.com/694525 external/wpt/content-security-policy/embedded-enforcement/embedding_csp-header-invalid-format.html [ Skip ]
-crbug.com/694525 external/wpt/content-security-policy/font-src [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/frame-ancestors [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/generic [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/img-src [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/inside-worker [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/media-src [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/meta [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/navigation [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/script-src [ Pass ]
+crbug.com/694525 external/wpt/content-security-policy/object-src [ Skip ]
+crbug.com/694525 external/wpt/content-security-policy/reporting [ Skip ]
 crbug.com/694525 external/wpt/content-security-policy/script-src/script-src-1_10.html [ Skip ]
 crbug.com/694525 external/wpt/content-security-policy/script-src/script-src-strict_dynamic_in_img-src.html [ Skip ]
-crbug.com/694525 external/wpt/content-security-policy/securitypolicyviolation [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/style-src [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/svg [ Pass ]
-crbug.com/694525 external/wpt/content-security-policy/worker-src [ Pass ]
 
 # These policies are not implemented yet.
 crbug.com/627968 external/wpt/referrer-policy/same-origin/ [ Skip ]
@@ -2471,9 +2459,6 @@
 crbug.com/626703 external/wpt/XMLHttpRequest/abort-after-stop.htm [ Timeout ]
 crbug.com/626703 external/wpt/XMLHttpRequest/event-readystatechange-loaded.htm [ Failure Timeout ]
 crbug.com/626703 external/wpt/XMLHttpRequest/send-authentication-existing-session-manual.htm [ Skip ]
-crbug.com/626703 external/wpt/content-security-policy/inside-worker/shared-inheritance.html [ Timeout ]
-crbug.com/626703 external/wpt/content-security-policy/inside-worker/shared-script.html [ Timeout ]
-crbug.com/626703 external/wpt/content-security-policy/securitypolicyviolation/inside-shared-worker.html [ Timeout ]
 crbug.com/626703 crbug.com/717157 external/wpt/fetch/api/basic/integrity-sharedworker.html [ Crash Timeout ]
 crbug.com/626703 external/wpt/html/browsers/offline/no-appcache-in-shared-workers-historical.html [ Timeout ]
 crbug.com/626703 external/wpt/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html [ Timeout ]
diff --git a/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt b/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
index c6197ad..29f90cea 100644
--- a/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
@@ -94,6 +94,9 @@
 CONSOLE MESSAGE: line 138:     getter y
 CONSOLE MESSAGE: line 138:     getter z
 CONSOLE MESSAGE: line 138:     method constructor
+CONSOLE MESSAGE: line 138:     setter x
+CONSOLE MESSAGE: line 138:     setter y
+CONSOLE MESSAGE: line 138:     setter z
 CONSOLE MESSAGE: line 138: interface CSSSimpleLength : CSSLengthValue
 CONSOLE MESSAGE: line 138:     getter type
 CONSOLE MESSAGE: line 138:     getter value
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
index 30ff36b..cb8ed48 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
@@ -91,4 +91,13 @@
   }
 }, "Test that asMatrix is constructed correctly for CSSScale.");
 
+test(function() {
+  var actual = new CSSScale(0, 0, 0);
+  actual.x = 1;
+  actual.y = 2;
+  actual.z = 3;
+  assert_equals(actual.x, 1);
+  assert_equals(actual.y, 2);
+  assert_equals(actual.z, 3);
+}, "Test that x, y, z are mutable attributes.");
 </script>
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
index c5afde5..7fe4d666 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
@@ -682,6 +682,9 @@
     getter y
     getter z
     method constructor
+    setter x
+    setter y
+    setter z
 interface CSSSimpleLength : CSSLengthValue
     attribute @@toStringTag
     getter type
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index 245d07c8..c3d44eb 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -682,6 +682,9 @@
     getter y
     getter z
     method constructor
+    setter x
+    setter y
+    setter z
 interface CSSSimpleLength : CSSLengthValue
     attribute @@toStringTag
     getter type
diff --git a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyAPI.h.tmpl b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyAPI.h.tmpl
index 1fc68266..40ee4e918 100644
--- a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyAPI.h.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyAPI.h.tmpl
@@ -14,20 +14,21 @@
 class CSSParserContext;
 class CSSParserTokenRange;
 
-// We will use this API to represent all functions used for property-specific
-// logic inside the blink style engine. All specific properties are subclasses
-// of CSSPropertyAPI.
+// Base class for property APIs that contain logic for handling individual
+// properties.
+// The methods declared here are standard methods among property APIs, and can
+// be called on a particular property API through a CSSPropertyDescriptor
+// object.
+// Note that not all methods are implemented by all property APIs. Methods that
+// are not implemented on a property API will return nullptr when accessed
+// through a CSSPropertyDescriptor.
+// See the api_methods field on properties defined in CSSProperties.json5 for a
+// definition of which methods are implemented on a property.
 //
-// To add a new implementation of this API for a property:
-// - Make a class that implements CSSPropertyAPI.
-// - For each method that you wish to implement in this class, add this method
-//   name to the api_methods flag in CSSProperties.json5.
-// - Implement these methods in the .cpp file.
-//
-// To add new functions to this API:
-// - Add the function to the struct below.
-// - Add the function name to the valid_values field for api_methods in
-//   CSSProperties.json5.
+// Status (5th May 2017): Eventually, all property specific logic will be
+// contained within property APIs that inherit from CSSPropertyAPI.
+// Currently, the code base is in a transitional state and property specific
+// logic is still scattered around the code base.
 class CSSPropertyAPI {
   STATIC_ONLY(CSSPropertyAPI);
 
diff --git a/third_party/WebKit/Source/core/css/BUILD.gn b/third_party/WebKit/Source/core/css/BUILD.gn
index 565344f..fff8235 100644
--- a/third_party/WebKit/Source/core/css/BUILD.gn
+++ b/third_party/WebKit/Source/core/css/BUILD.gn
@@ -507,6 +507,8 @@
     "properties/CSSPropertyPositionUtils.h",
     "properties/CSSPropertyShapeUtils.cpp",
     "properties/CSSPropertyShapeUtils.h",
+    "properties/CSSPropertyWebkitBorderWidthUtils.cpp",
+    "properties/CSSPropertyWebkitBorderWidthUtils.h",
     "resolver/AnimatedStyleBuilder.cpp",
     "resolver/AnimatedStyleBuilder.h",
     "resolver/CSSPropertyPriority.h",
diff --git a/third_party/WebKit/Source/core/css/CSSProperties.json5 b/third_party/WebKit/Source/core/css/CSSProperties.json5
index 8c214fa..c9d2a8ed 100644
--- a/third_party/WebKit/Source/core/css/CSSProperties.json5
+++ b/third_party/WebKit/Source/core/css/CSSProperties.json5
@@ -2524,6 +2524,7 @@
     {
       name: "-webkit-border-end-width",
       api_class: "CSSPropertyAPIWebkitBorderWidth",
+      api_methods: ["parseSingleValue"],
       direction_aware: true,
     },
     {
@@ -2539,6 +2540,7 @@
     {
       name: "-webkit-border-start-width",
       api_class: "CSSPropertyAPIWebkitBorderWidth",
+      api_methods: ["parseSingleValue"],
       direction_aware: true,
     },
     {
@@ -2554,6 +2556,7 @@
     {
       name: "-webkit-border-before-width",
       api_class: "CSSPropertyAPIWebkitBorderWidth",
+      api_methods: ["parseSingleValue"],
       direction_aware: true,
     },
     {
@@ -2569,6 +2572,7 @@
     {
       name: "-webkit-border-after-width",
       api_class: "CSSPropertyAPIWebkitBorderWidth",
+      api_methods: ["parseSingleValue"],
       direction_aware: true,
     },
     {
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSScale.h b/third_party/WebKit/Source/core/css/cssom/CSSScale.h
index 8f509db5..5f15568 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSScale.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSScale.h
@@ -27,6 +27,10 @@
   double y() const { return y_; }
   double z() const { return z_; }
 
+  void setX(double x) { x_ = x; }
+  void setY(double y) { y_ = y; }
+  void setZ(double z) { z_ = z; }
+
   TransformComponentType GetType() const override {
     return is2d_ ? kScaleType : kScale3DType;
   }
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSScale.idl b/third_party/WebKit/Source/core/css/cssom/CSSScale.idl
index 0971515..f1d029f9 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSScale.idl
+++ b/third_party/WebKit/Source/core/css/cssom/CSSScale.idl
@@ -8,7 +8,7 @@
     Exposed=(Window,PaintWorklet),
     RuntimeEnabled=CSSTypedOM
 ] interface CSSScale : CSSTransformComponent {
-    readonly attribute double x;
-    readonly attribute double y;
-    readonly attribute double z;
+    attribute double x;
+    attribute double y;
+    attribute double z;
 };
diff --git a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
index 615c20f8..370f3736 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
@@ -49,6 +49,7 @@
 #include "core/css/properties/CSSPropertyOffsetPathUtils.h"
 #include "core/css/properties/CSSPropertyPositionUtils.h"
 #include "core/css/properties/CSSPropertyShapeUtils.h"
+#include "core/css/properties/CSSPropertyWebkitBorderWidthUtils.h"
 #include "core/frame/UseCounter.h"
 #include "core/layout/LayoutTheme.h"
 #include "platform/wtf/text/StringBuilder.h"
@@ -750,12 +751,6 @@
   return true;
 }
 
-static CSSValue* ConsumeBorderWidth(CSSParserTokenRange& range,
-                                    CSSParserMode css_parser_mode,
-                                    UnitlessQuirk unitless) {
-  return ConsumeLineWidth(range, css_parser_mode, unitless);
-}
-
 static CSSValue* ConsumeNoneOrURI(CSSParserTokenRange& range,
                                   const CSSParserContext* context) {
   if (range.Peek().Id() == CSSValueNone)
@@ -1710,12 +1705,6 @@
     case CSSPropertyColor:
     case CSSPropertyBackgroundColor:
       return ConsumeColor(range_, context_->Mode(), InQuirksMode());
-    case CSSPropertyWebkitBorderStartWidth:
-    case CSSPropertyWebkitBorderEndWidth:
-    case CSSPropertyWebkitBorderBeforeWidth:
-    case CSSPropertyWebkitBorderAfterWidth:
-      return ConsumeBorderWidth(range_, context_->Mode(),
-                                UnitlessQuirk::kForbid);
     case CSSPropertyBorderBottomColor:
     case CSSPropertyBorderLeftColor:
     case CSSPropertyBorderRightColor:
@@ -1734,7 +1723,8 @@
                              current_shorthand == CSSPropertyBorderWidth);
       UnitlessQuirk unitless =
           allow_quirky_lengths ? UnitlessQuirk::kAllow : UnitlessQuirk::kForbid;
-      return ConsumeBorderWidth(range_, context_->Mode(), unitless);
+      return CSSPropertyWebkitBorderWidthUtils::ConsumeBorderWidth(
+          range_, context_->Mode(), unitless);
     }
     case CSSPropertyTextShadow:
       return ConsumeShadow(range_, context_->Mode(), false);
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIWebkitBorderWidth.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIWebkitBorderWidth.cpp
index 399f5f4..8cffddd 100644
--- a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIWebkitBorderWidth.cpp
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIWebkitBorderWidth.cpp
@@ -4,4 +4,18 @@
 
 #include "core/css/properties/CSSPropertyAPIWebkitBorderWidth.h"
 
-namespace blink {}  // namespace blink
+#include "core/css/parser/CSSParserContext.h"
+#include "core/css/parser/CSSPropertyParserHelpers.h"
+#include "core/css/properties/CSSPropertyWebkitBorderWidthUtils.h"
+
+namespace blink {
+
+const CSSValue* CSSPropertyAPIWebkitBorderWidth::parseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    CSSPropertyID) {
+  return CSSPropertyWebkitBorderWidthUtils::ConsumeBorderWidth(
+      range, context.Mode(), CSSPropertyParserHelpers::UnitlessQuirk::kForbid);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyWebkitBorderWidthUtils.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyWebkitBorderWidthUtils.cpp
new file mode 100644
index 0000000..cf8e4c3
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyWebkitBorderWidthUtils.cpp
@@ -0,0 +1,17 @@
+// 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.
+
+#include "core/css/properties/CSSPropertyWebkitBorderWidthUtils.h"
+
+namespace blink {
+
+CSSValue* CSSPropertyWebkitBorderWidthUtils::ConsumeBorderWidth(
+    CSSParserTokenRange& range,
+    CSSParserMode css_parser_mode,
+    CSSPropertyParserHelpers::UnitlessQuirk unitless) {
+  return CSSPropertyParserHelpers::ConsumeLineWidth(range, css_parser_mode,
+                                                    unitless);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyWebkitBorderWidthUtils.h b/third_party/WebKit/Source/core/css/properties/CSSPropertyWebkitBorderWidthUtils.h
new file mode 100644
index 0000000..264a023
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyWebkitBorderWidthUtils.h
@@ -0,0 +1,23 @@
+// 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 CSSPropertyWebkitBorderWidthUtils_h
+#define CSSPropertyWebkitBorderWidthUtils_h
+
+#include "core/css/parser/CSSPropertyParserHelpers.h"
+#include "platform/wtf/Allocator.h"
+
+namespace blink {
+
+class CSSPropertyWebkitBorderWidthUtils {
+  STATIC_ONLY(CSSPropertyWebkitBorderWidthUtils);
+
+  static CSSValue* ConsumeBorderWidth(CSSParserTokenRange&,
+                                      CSSParserMode,
+                                      CSSPropertyParserHelpers::UnitlessQuirk);
+};
+
+}  // namespace blink
+
+#endif  // CSSPropertyWebkitBorderWidthUtils_h
diff --git a/third_party/WebKit/Source/core/exported/WebViewBase.h b/third_party/WebKit/Source/core/exported/WebViewBase.h
index 64a9b2a..ebe4165 100644
--- a/third_party/WebKit/Source/core/exported/WebViewBase.h
+++ b/third_party/WebKit/Source/core/exported/WebViewBase.h
@@ -66,9 +66,6 @@
   virtual Page* GetPage() const = 0;
   virtual Frame* FocusedCoreFrame() const = 0;
 
-  // PLATFORM_EXPORT static WebViewBase* Create(WebViewClient*,
-  //                                            WebPageVisibilityState);
-
   static WebViewBase* FromPage(Page*);
   static HashSet<WebViewBase*>& AllInstances();
 
diff --git a/third_party/WebKit/Source/core/paint/PaintTiming.cpp b/third_party/WebKit/Source/core/paint/PaintTiming.cpp
index fae6893..d0d77477 100644
--- a/third_party/WebKit/Source/core/paint/PaintTiming.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintTiming.cpp
@@ -9,10 +9,13 @@
 #include "core/frame/LocalDOMWindow.h"
 #include "core/frame/LocalFrame.h"
 #include "core/loader/DocumentLoader.h"
+#include "core/page/ChromeClient.h"
+#include "core/page/Page.h"
 #include "core/timing/DOMWindowPerformance.h"
 #include "core/timing/Performance.h"
 #include "platform/WebFrameScheduler.h"
 #include "platform/instrumentation/tracing/TraceEvent.h"
+#include "public/platform/WebLayerTreeView.h"
 
 namespace blink {
 
@@ -100,6 +103,7 @@
       TraceEvent::ToTraceTimestamp(first_meaningful_paint_), "frame",
       GetFrame());
   NotifyPaintTimingChanged();
+  RegisterNotifySwapTime(PaintEvent::kFirstMeaningfulPaint);
 }
 
 void PaintTiming::NotifyPaint(bool is_first_paint,
@@ -142,6 +146,7 @@
 
   TRACE_EVENT_INSTANT1("loading,rail,devtools.timeline", "firstPaint",
                        TRACE_EVENT_SCOPE_PROCESS, "frame", GetFrame());
+  RegisterNotifySwapTime(PaintEvent::kFirstPaint);
 }
 
 void PaintTiming::SetFirstContentfulPaint(double stamp) {
@@ -152,8 +157,43 @@
   Performance* performance = GetPerformanceInstance(GetFrame());
   if (performance)
     performance->AddFirstContentfulPaintTiming(first_contentful_paint_);
+
   TRACE_EVENT_INSTANT1("loading,rail,devtools.timeline", "firstContentfulPaint",
                        TRACE_EVENT_SCOPE_PROCESS, "frame", GetFrame());
+  RegisterNotifySwapTime(PaintEvent::kFirstContentfulPaint);
+}
+
+void PaintTiming::RegisterNotifySwapTime(PaintEvent event) {
+  // ReportSwapTime on layerTreeView will queue a swap-promise, the callback is
+  // called when the swap for current render frame completes or fails to happen.
+  if (!GetFrame() || !GetFrame()->GetPage())
+    return;
+  if (WebLayerTreeView* layerTreeView =
+          GetFrame()->GetPage()->GetChromeClient().GetWebLayerTreeView(
+              GetFrame())) {
+    layerTreeView->NotifySwapTime(ConvertToBaseCallback(
+        WTF::Bind(&PaintTiming::ReportSwapTime,
+                  WrapCrossThreadWeakPersistent(this), event)));
+  }
+}
+
+void PaintTiming::ReportSwapTime(PaintEvent event,
+                                 bool did_swap,
+                                 double timestamp) {
+  if (!did_swap)
+    return;
+  switch (event) {
+    case PaintEvent::kFirstPaint:
+      first_paint_swap_ = timestamp;
+      return;
+    case PaintEvent::kFirstContentfulPaint:
+      first_contentful_paint_swap_ = timestamp;
+      return;
+    case PaintEvent::kFirstMeaningfulPaint:
+      first_meaningful_paint_swap_ = timestamp;
+      return;
+  }
+  NOTREACHED();
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/PaintTiming.h b/third_party/WebKit/Source/core/paint/PaintTiming.h
index ba2d02f7..a9105d5 100644
--- a/third_party/WebKit/Source/core/paint/PaintTiming.h
+++ b/third_party/WebKit/Source/core/paint/PaintTiming.h
@@ -26,6 +26,12 @@
  public:
   virtual ~PaintTiming() {}
 
+  enum class PaintEvent {
+    kFirstPaint,
+    kFirstContentfulPaint,
+    kFirstMeaningfulPaint
+  };
+
   static PaintTiming& From(Document&);
 
   // mark*() methods record the time for the given paint event, record a trace
@@ -82,6 +88,8 @@
     return *fmp_detector_;
   }
 
+  void ReportSwapTime(PaintEvent, bool did_swap, double timestamp);
+
   DECLARE_VIRTUAL_TRACE();
 
  private:
@@ -101,11 +109,16 @@
   // time has not yet been recorded.
   void SetFirstContentfulPaint(double stamp);
 
+  void RegisterNotifySwapTime(PaintEvent);
+
   double first_paint_ = 0.0;
+  double first_paint_swap_ = 0.0;
   double first_text_paint_ = 0.0;
   double first_image_paint_ = 0.0;
   double first_contentful_paint_ = 0.0;
+  double first_contentful_paint_swap_ = 0.0;
   double first_meaningful_paint_ = 0.0;
+  double first_meaningful_paint_swap_ = 0.0;
   double first_meaningful_paint_candidate_ = 0.0;
 
   Member<FirstMeaningfulPaintDetector> fmp_detector_;
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/SourceMap.js b/third_party/WebKit/Source/devtools/front_end/sdk/SourceMap.js
index c8239016..53a38625 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/SourceMap.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/SourceMap.js
@@ -320,26 +320,10 @@
    * @return {?SDK.SourceMapEntry}
    */
   findEntry(lineNumber, columnNumber) {
-    var first = 0;
     var mappings = this.mappings();
-    var count = mappings.length;
-    while (count > 1) {
-      var step = count >> 1;
-      var middle = first + step;
-      var mapping = mappings[middle];
-      if (lineNumber < mapping.lineNumber ||
-          (lineNumber === mapping.lineNumber && columnNumber < mapping.columnNumber)) {
-        count = step;
-      } else {
-        first = middle;
-        count -= step;
-      }
-    }
-    var entry = mappings[first];
-    if (!first && entry &&
-        (lineNumber < entry.lineNumber || (lineNumber === entry.lineNumber && columnNumber < entry.columnNumber)))
-      return null;
-    return entry;
+    var index = mappings.upperBound(
+        undefined, (unused, entry) => lineNumber - entry.lineNumber || columnNumber - entry.columnNumber);
+    return index ? mappings[index - 1] : null;
   }
 
   /**
diff --git a/third_party/WebKit/public/platform/DEPS b/third_party/WebKit/public/platform/DEPS
index cf0e7d8a..3824dc7 100644
--- a/third_party/WebKit/public/platform/DEPS
+++ b/third_party/WebKit/public/platform/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+    "+base/callback.h",
     "+base/callback_forward.h",
     "+base/location.h",
     "+base/logging.h",
diff --git a/third_party/WebKit/public/platform/WebLayerTreeView.h b/third_party/WebKit/public/platform/WebLayerTreeView.h
index 35b27082..19e128c1 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 "base/callback.h"
 #include "cc/surfaces/frame_sink_id.h"
 
 namespace cc {
@@ -48,6 +49,8 @@
 class WebSelection;
 
 class WebLayerTreeView {
+  using ReportTimeCallback = base::Callback<void(bool, double)>;
+
  public:
   virtual ~WebLayerTreeView() {}
 
@@ -181,6 +184,10 @@
 
   // Toggles scroll bottleneck rects on the HUD layer
   virtual void SetShowScrollBottleneckRects(bool) {}
+
+  // ReportTimeCallback is a callback that should be fired when the
+  // corresponding Swap completes (either with DidSwap or DidNotSwap).
+  virtual void NotifySwapTime(ReportTimeCallback callback) {}
 };
 
 }  // namespace blink
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a436e705..acef7c5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21997,6 +21997,7 @@
   <int value="1586022426" label="AutofillCreditCardAssist:enabled"/>
   <int value="1589341623" label="disable-easy-unlock"/>
   <int value="1594247626" label="ContentSuggestionsSettings:enabled"/>
+  <int value="1600926040" label="TranslateCompactUI:enabled"/>
   <int value="1605611615" label="enable-webrtc-srtp-aes-gcm"/>
   <int value="1612446645" label="enable-weak-memorycache"/>
   <int value="1612871297" label="WebPayments:disabled"/>
@@ -22040,6 +22041,7 @@
   <int value="1783837132" label="enable-threaded-gpu-rasterization"/>
   <int value="1785093465" label="enable-document-passive-event-listeners"/>
   <int value="1786229999" label="disable-md-downloads"/>
+  <int value="1786386775" label="TranslateCompactUI:disabled"/>
   <int value="1791904609" label="disable-autofill-keyboard-accessory-view"/>
   <int value="1803465156" label="enable-zero-suggest-most-visited"/>
   <int value="1809940714" label="SpeculativeLaunchServiceWorker:disabled"/>