diff --git a/DEPS b/DEPS
index e43bb64..8e0c0bde 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '7dda9f883e06f3f00bbcec93601877ea7b9b6c65',
+  'v8_revision': '3d7c892d19ff27d4b8c046c50b68116814c4d746',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
index 1b6f76e..788074ac 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
@@ -17,6 +17,7 @@
 import org.chromium.android_webview.test.util.AwTestTouchUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.RetryOnFailure;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -158,9 +159,11 @@
 
     /*
      * Verify that when the AwContentsClient calls handleJsBeforeUnload
+     * Flaky (crbug/719308)
      */
     @MediumTest
     @Feature({"AndroidWebView"})
+    @RetryOnFailure
     public void testOverrideBeforeUnloadHandling() throws Throwable {
         final CallbackHelper jsBeforeUnloadHelper = new CallbackHelper();
         TestAwContentsClient client = new TestAwContentsClient() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index d6d3b11..6c78fa4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -56,6 +56,7 @@
 import org.chromium.mojo.system.MojoException;
 import org.chromium.payments.mojom.CanMakePaymentQueryResult;
 import org.chromium.payments.mojom.PaymentComplete;
+import org.chromium.payments.mojom.PaymentCurrencyAmount;
 import org.chromium.payments.mojom.PaymentDetails;
 import org.chromium.payments.mojom.PaymentDetailsModifier;
 import org.chromium.payments.mojom.PaymentErrorReason;
@@ -321,7 +322,7 @@
     private boolean mMerchantSupportsAutofillPaymentInstruments;
     private ContactEditor mContactEditor;
     private boolean mHasRecordedAbortReason;
-    private CurrencyFormatter mCurrencyFormatter;
+    private Map<String, CurrencyFormatter> mCurrencyFormatterMap;
     private TabModelSelector mObservedTabModelSelector;
     private TabModel mObservedTabModel;
 
@@ -390,15 +391,18 @@
 
         if (sCanMakePaymentQueries == null) sCanMakePaymentQueries = new ArrayMap<>();
 
+        mCurrencyFormatterMap = new HashMap<>();
+
         recordSuccessFunnelHistograms("Initiated");
     }
 
     @Override
     protected void finalize() throws Throwable {
         super.finalize();
-        if (mCurrencyFormatter != null) {
+        for (CurrencyFormatter formatter : mCurrencyFormatterMap.values()) {
+            assert formatter != null;
             // Ensures the native implementation of currency formatter does not leak.
-            mCurrencyFormatter.destroy();
+            formatter.destroy();
         }
     }
 
@@ -424,6 +428,16 @@
         mRequestPayerEmail = options != null && options.requestPayerEmail;
         mShippingType = options == null ? PaymentShippingType.SHIPPING : options.shippingType;
 
+        if (!OriginSecurityChecker.isSchemeCryptographic(mWebContents.getLastCommittedUrl())
+                && !OriginSecurityChecker.isOriginLocalhostOrFile(
+                           mWebContents.getLastCommittedUrl())) {
+            Log.d(TAG, "Only localhost, file://, and cryptographic scheme origins allowed");
+            // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
+            // "NotSupportedError".
+            onAllPaymentAppsCreated();
+            return;
+        }
+
         PaymentRequestMetrics.recordRequestedInformationHistogram(
                 mRequestPayerEmail, mRequestPayerPhone, mRequestShipping, mRequestPayerName);
 
@@ -778,28 +792,25 @@
             return false;
         }
 
-        if (mCurrencyFormatter == null) {
-            mCurrencyFormatter = new CurrencyFormatter(details.total.amount.currency,
-                    details.total.amount.currencySystem, Locale.getDefault());
-        }
-
         if (details.total != null) {
             mRawTotal = details.total;
         }
 
+        loadCurrencyFormattersForPaymentDetails(details);
+
         if (mRawTotal != null) {
             // Total is never pending.
-            LineItem uiTotal = new LineItem(mRawTotal.label,
-                    mCurrencyFormatter.getFormattedCurrencyCode(),
-                    mCurrencyFormatter.format(mRawTotal.amount.value), /* isPending */ false);
+            CurrencyFormatter formatter = getOrCreateCurrencyFormatter(mRawTotal.amount);
+            LineItem uiTotal = new LineItem(mRawTotal.label, formatter.getFormattedCurrencyCode(),
+                    formatter.format(mRawTotal.amount.value), /* isPending */ false);
 
-            List<LineItem> uiLineItems = getLineItems(details.displayItems, mCurrencyFormatter);
+            List<LineItem> uiLineItems = getLineItems(details.displayItems);
 
             mUiShoppingCart = new ShoppingCart(uiTotal, uiLineItems);
         }
         mRawLineItems = Collections.unmodifiableList(Arrays.asList(details.displayItems));
 
-        mUiShippingOptions = getShippingOptions(details.shippingOptions, mCurrencyFormatter);
+        mUiShippingOptions = getShippingOptions(details.shippingOptions);
 
         for (int i = 0; i < details.modifiers.length; i++) {
             PaymentDetailsModifier modifier = details.modifiers[i];
@@ -826,7 +837,8 @@
             PaymentDetailsModifier modifier = getModifier(instrument);
             instrument.setModifiedTotal(modifier == null || modifier.total == null
                             ? null
-                            : mCurrencyFormatter.format(modifier.total.amount.value));
+                            : getOrCreateCurrencyFormatter(modifier.total.amount)
+                                      .format(modifier.total.amount.value));
         }
 
         updateOrderSummary((PaymentInstrument) mPaymentMethodsSection.getSelectedItem());
@@ -840,12 +852,11 @@
         PaymentItem total = modifier == null ? null : modifier.total;
         if (total == null) total = mRawTotal;
 
-        mUiShoppingCart.setTotal(
-                new LineItem(total.label, mCurrencyFormatter.getFormattedCurrencyCode(),
-                        mCurrencyFormatter.format(total.amount.value), false /* isPending */));
-        mUiShoppingCart.setAdditionalContents(modifier == null
-                        ? null
-                        : getLineItems(modifier.additionalDisplayItems, mCurrencyFormatter));
+        CurrencyFormatter formatter = getOrCreateCurrencyFormatter(total.amount);
+        mUiShoppingCart.setTotal(new LineItem(total.label, formatter.getFormattedCurrencyCode(),
+                formatter.format(total.amount.value), false /* isPending */));
+        mUiShoppingCart.setAdditionalContents(
+                modifier == null ? null : getLineItems(modifier.additionalDisplayItems));
         mUI.updateOrderSummarySection(mUiShoppingCart);
     }
 
@@ -861,20 +872,19 @@
      * Converts a list of payment items and returns their parsed representation.
      *
      * @param items     The payment items to parse. Can be null.
-     * @param formatter A formatter for the currency amount value.
      * @return A list of valid line items.
      */
-    private static List<LineItem> getLineItems(
-            @Nullable PaymentItem[] items, CurrencyFormatter formatter) {
+    private List<LineItem> getLineItems(@Nullable PaymentItem[] items) {
         // Line items are optional.
         if (items == null) return new ArrayList<>();
 
         List<LineItem> result = new ArrayList<>(items.length);
         for (int i = 0; i < items.length; i++) {
             PaymentItem item = items[i];
-
-            result.add(new LineItem(
-                    item.label, "", formatter.format(item.amount.value), item.pending));
+            CurrencyFormatter formatter = getOrCreateCurrencyFormatter(item.amount);
+            result.add(new LineItem(item.label,
+                    isMixedOrChangedCurrency() ? formatter.getFormattedCurrencyCode() : "",
+                    formatter.format(item.amount.value), item.pending));
         }
 
         return Collections.unmodifiableList(result);
@@ -884,11 +894,9 @@
      * Converts a list of shipping options and returns their parsed representation.
      *
      * @param options   The raw shipping options to parse. Can be null.
-     * @param formatter A formatter for the currency amount value.
      * @return The UI representation of the shipping options.
      */
-    private static SectionInformation getShippingOptions(
-            @Nullable PaymentShippingOption[] options, CurrencyFormatter formatter) {
+    private SectionInformation getShippingOptions(@Nullable PaymentShippingOption[] options) {
         // Shipping options are optional.
         if (options == null || options.length == 0) {
             return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS);
@@ -898,8 +906,12 @@
         int selectedItemIndex = SectionInformation.NO_SELECTION;
         for (int i = 0; i < options.length; i++) {
             PaymentShippingOption option = options[i];
+            CurrencyFormatter formatter = getOrCreateCurrencyFormatter(option.amount);
+            String currencyPrefix = isMixedOrChangedCurrency()
+                    ? formatter.getFormattedCurrencyCode() + "\u0020"
+                    : "";
             result.add(new PaymentOption(option.id, option.label,
-                    formatter.format(option.amount.value), null));
+                    currencyPrefix + formatter.format(option.amount.value), null));
             if (option.selected) selectedItemIndex = i;
         }
 
@@ -908,6 +920,62 @@
     }
 
     /**
+     * Load required currency formatter for a given PaymentDetails.
+     *
+     * Note that the cache (mCurrencyFormatterMap) is not cleared for
+     * updated payment details so as to indicate the currency has been changed.
+     *
+     * @param details The given payment details.
+     */
+    private void loadCurrencyFormattersForPaymentDetails(PaymentDetails details) {
+        if (details.total != null) {
+            getOrCreateCurrencyFormatter(details.total.amount);
+        }
+
+        if (details.displayItems != null) {
+            for (PaymentItem item : details.displayItems) {
+                getOrCreateCurrencyFormatter(item.amount);
+            }
+        }
+
+        if (details.shippingOptions != null) {
+            for (PaymentShippingOption option : details.shippingOptions) {
+                getOrCreateCurrencyFormatter(option.amount);
+            }
+        }
+
+        if (details.modifiers != null) {
+            for (PaymentDetailsModifier modifier : details.modifiers) {
+                getOrCreateCurrencyFormatter(modifier.total.amount);
+                for (PaymentItem displayItem : modifier.additionalDisplayItems) {
+                    getOrCreateCurrencyFormatter(displayItem.amount);
+                }
+            }
+        }
+    }
+
+    private boolean isMixedOrChangedCurrency() {
+        return mCurrencyFormatterMap.size() > 1;
+    }
+
+    /**
+     * Gets currency formatter for a given PaymentCurrencyAmount,
+     * creates one if no existing instance is found.
+     *
+     * @amount The given payment amount.
+     */
+    private CurrencyFormatter getOrCreateCurrencyFormatter(PaymentCurrencyAmount amount) {
+        String key = amount.currency + amount.currencySystem;
+        CurrencyFormatter formatter = mCurrencyFormatterMap.get(key);
+        if (formatter == null) {
+            formatter = new CurrencyFormatter(
+                    amount.currency, amount.currencySystem, Locale.getDefault());
+            mCurrencyFormatterMap.put(key, formatter);
+        }
+        return formatter;
+    }
+
+    /**
      * Called to retrieve the data to show in the initial PaymentRequest UI.
      */
     @Override
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 261d895..0392559 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1483,6 +1483,7 @@
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBasicCardTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressWithoutPhoneTest.java",
+  "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentMetricsTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java",
@@ -1492,6 +1493,7 @@
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsAndFreeShippingTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsSectionUnitTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java",
+  "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDataUrlTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingMultipleAddressesTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java
new file mode 100644
index 0000000..844d51f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBlobUrlTest.java
@@ -0,0 +1,32 @@
+// 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.payments;
+
+import android.support.test.filters.MediumTest;
+
+import org.chromium.base.test.util.Feature;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/** Web payments test for blob URL.  */
+public class PaymentRequestBlobUrlTest extends PaymentRequestTestBase {
+    public PaymentRequestBlobUrlTest() {
+        super("payment_request_blob_url_test.html");
+    }
+
+    @Override
+    public void onMainActivityStarted()
+            throws InterruptedException, ExecutionException, TimeoutException {}
+
+    @MediumTest
+    @Feature({"Payments"})
+    public void test() throws InterruptedException, ExecutionException, TimeoutException {
+        openPageAndClickNode("buy");
+        assertWaitForPageScaleFactorMatch(2);
+        expectResultContains(new String[] {"SecurityError: Failed to construct 'PaymentRequest': "
+                + "Must be in a secure context"});
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDataUrlTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDataUrlTest.java
new file mode 100644
index 0000000..4e23257
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDataUrlTest.java
@@ -0,0 +1,39 @@
+// 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.payments;
+
+import android.support.test.filters.MediumTest;
+
+import org.chromium.base.test.util.Feature;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/** Web payments test for data URL.  */
+public class PaymentRequestDataUrlTest extends PaymentRequestTestBase {
+    public PaymentRequestDataUrlTest() {
+        super("data:text/html,<html><head>"
+                + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, "
+                + "maximum-scale=1\"></head><body><button id=\"buy\" onclick=\"try { "
+                + "(new PaymentRequest([{supportedMethods: ['basic-card']}], "
+                + "{total: {label: 'Total', "
+                + " amount: {currency: 'USD', value: '1.00'}}})).show(); "
+                + "} catch(e) { "
+                + "document.getElementById('result').innerHTML = e; "
+                + "}\">Data URL Test</button><div id='result'></div></body></html>");
+    }
+
+    @Override
+    public void onMainActivityStarted()
+            throws InterruptedException, ExecutionException, TimeoutException {}
+
+    @MediumTest
+    @Feature({"Payments"})
+    public void test() throws InterruptedException, ExecutionException, TimeoutException {
+        openPageAndClickNode("buy");
+        expectResultContains(new String[] {"SecurityError: Failed to construct 'PaymentRequest': "
+                + "Must be in a secure context"});
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
index 02bae87..c29b24c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
@@ -128,8 +128,10 @@
         mCanMakePaymentQueryResponded = new CallbackHelper();
         mViewCoreRef = new AtomicReference<>();
         mWebContentsRef = new AtomicReference<>();
-        mTestFilePath = UrlUtils.getIsolatedTestFilePath(
-                String.format("chrome/test/data/payments/%s", testFileName));
+        mTestFilePath = testFileName.startsWith("data:")
+                ? testFileName
+                : UrlUtils.getIsolatedTestFilePath(
+                          String.format("chrome/test/data/payments/%s", testFileName));
     }
 
     @Override
@@ -159,6 +161,17 @@
 
     protected void openPageAndClickNodeAndWait(String nodeId, CallbackHelper helper)
             throws InterruptedException, ExecutionException, TimeoutException {
+        openPage();
+        clickNodeAndWait(nodeId, helper);
+    }
+
+    protected void openPageAndClickNode(String nodeId)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        openPage();
+        DOMUtils.clickNode(mViewCoreRef.get(), nodeId);
+    }
+
+    private void openPage() throws InterruptedException, ExecutionException, TimeoutException {
         onMainActivityStarted();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
@@ -171,7 +184,6 @@
             }
         });
         assertWaitForPageScaleFactorMatch(1);
-        clickNodeAndWait(nodeId, helper);
     }
 
     protected void reTriggerUIAndWait(
diff --git a/chrome/app/nibs/BUILD.gn b/chrome/app/nibs/BUILD.gn
index 9f7f106..6b0caef 100644
--- a/chrome/app/nibs/BUILD.gn
+++ b/chrome/app/nibs/BUILD.gn
@@ -23,7 +23,6 @@
   "ContentBlockedPopups.xib",
   "ContentBlockedSimple.xib",
   "ContentProtocolHandlers.xib",
-  "ContentSubresourceFilter.xib",
   "CookieDetailsView.xib",
   "DownloadItem.xib",
   "DownloadShelf.xib",
diff --git a/chrome/app/nibs/ContentSubresourceFilter.xib b/chrome/app/nibs/ContentSubresourceFilter.xib
deleted file mode 100644
index 818d9f6..0000000
--- a/chrome/app/nibs/ContentSubresourceFilter.xib
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5053" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
-    <dependencies>
-        <deployment version="1090" identifier="macosx"/>
-        <development version="5100" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5053"/>
-    </dependencies>
-    <objects>
-        <customObject id="-2" userLabel="File's Owner" customClass="ContentSettingBubbleController">
-            <connections>
-                <outlet property="bubble_" destination="4" id="5"/>
-                <outlet property="doneButton_" destination="17" id="32"/>
-                <outlet property="manageButton_" destination="15" id="31"/>
-                <outlet property="messageLabel_" destination="dgF-Po-kkg" id="23a-5O-Xno"/>
-                <outlet property="titleLabel_" destination="7" id="30"/>
-                <outlet property="window" destination="1" id="3"/>
-            </connections>
-        </customObject>
-        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
-        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
-        <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="1" customClass="InfoBubbleWindow">
-            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
-            <windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
-            <rect key="contentRect" x="196" y="376" width="316" height="134"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
-            <view key="contentView" id="2">
-                <rect key="frame" x="0.0" y="0.0" width="316" height="134"/>
-                <autoresizingMask key="autoresizingMask"/>
-                <subviews>
-                    <customView id="4" customClass="InfoBubbleView">
-                        <rect key="frame" x="0.0" y="0.0" width="316" height="134"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <subviews>
-                            <customView id="29" customClass="GTMWidthBasedTweaker">
-                                <rect key="frame" x="193" y="0.0" width="123" height="41"/>
-                                <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxY="YES"/>
-                                <subviews>
-                                    <button verticalHuggingPriority="750" id="17">
-                                        <rect key="frame" x="39" y="13" width="70" height="28"/>
-                                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
-                                        <buttonCell key="cell" type="push" title="^IDS_OK" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="18">
-                                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                                            <font key="font" metaFont="smallSystem"/>
-                                            <string key="keyEquivalent" base64-UTF8="YES">
-DQ
-</string>
-                                        </buttonCell>
-                                        <connections>
-                                            <action selector="closeBubble:" target="-2" id="26"/>
-                                        </connections>
-                                    </button>
-                                </subviews>
-                            </customView>
-                            <customView id="28" customClass="GTMWidthBasedTweaker">
-                                <rect key="frame" x="0.0" y="0.0" width="192" height="41"/>
-                                <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                                <subviews>
-                                    <button verticalHuggingPriority="750" id="15">
-                                        <rect key="frame" x="15" y="13" width="115" height="28"/>
-                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                                        <buttonCell key="cell" type="push" title="^IDS_FILTERED_DECEPTIVE_CONTENT_PROMPT_RELOAD" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="16">
-                                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                                            <font key="font" metaFont="smallSystem"/>
-                                        </buttonCell>
-                                        <connections>
-                                            <action selector="manageBlocking:" target="-2" id="25"/>
-                                        </connections>
-                                    </button>
-                                </subviews>
-                            </customView>
-                            <textField verticalHuggingPriority="750" id="dgF-Po-kkg">
-                                <rect key="frame" x="18" y="53" width="282" height="28"/>
-                                <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                                <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" title="^IDS_FILTERED_DECEPTIVE_CONTENT_PROMPT_TITLE" id="AxL-Md-rce">
-                                    <font key="font" metaFont="smallSystem"/>
-                                    <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
-                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
-                                    <connections>
-                                        <binding destination="-2" name="displayPatternValue1" keyPath="self.messageLabel_" id="wgi-Vp-Zyf">
-                                            <dictionary key="options">
-                                                <string key="NSDisplayPattern">%{value1}@</string>
-                                            </dictionary>
-                                        </binding>
-                                    </connections>
-                                </textFieldCell>
-                            </textField>
-                            <textField verticalHuggingPriority="750" id="7">
-                                <rect key="frame" x="17" y="100" width="282" height="14"/>
-                                <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                                <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" title="^IDS_FILTERED_DECEPTIVE_CONTENT_PROMPT_EXPLANATION" id="8">
-                                    <font key="font" metaFont="smallSystem"/>
-                                    <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
-                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
-                                </textFieldCell>
-                            </textField>
-                        </subviews>
-                    </customView>
-                </subviews>
-            </view>
-            <connections>
-                <outlet property="delegate" destination="-2" id="6"/>
-            </connections>
-        </window>
-        <customObject id="20" customClass="ChromeUILocalizer"/>
-        <customObject id="21" customClass="GTMUILocalizerAndLayoutTweaker">
-            <connections>
-                <outlet property="localizer_" destination="20" id="22"/>
-                <outlet property="uiObject_" destination="1" id="23"/>
-            </connections>
-        </customObject>
-    </objects>
-</document>
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 582f824..0c2a753 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -207,7 +207,7 @@
     "UserMediaScreenCapturing", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kVideoPersistence{"VideoPersistence",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kWebPaymentsModifiers{"WebPaymentsModifiers",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/vr_shell/ui_elements/textured_element.cc b/chrome/browser/android/vr_shell/ui_elements/textured_element.cc
index 3623c1e45..f0235fe 100644
--- a/chrome/browser/android/vr_shell/ui_elements/textured_element.cc
+++ b/chrome/browser/android/vr_shell/ui_elements/textured_element.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/android/vr_shell/ui_elements/textured_element.h"
 
+#include "base/trace_event/trace_event.h"
 #include "cc/paint/skia_paint_canvas.h"
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
 #include "chrome/browser/android/vr_shell/vr_shell_renderer.h"
@@ -17,6 +18,7 @@
 TexturedElement::~TexturedElement() = default;
 
 void TexturedElement::Initialize() {
+  TRACE_EVENT0("gpu", "TexturedElement::Initialize");
   glGenTextures(1, &texture_handle_);
   DCHECK(GetTexture() != nullptr);
   texture_size_ = GetTexture()->GetPreferredTextureSize(maximum_width_);
diff --git a/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc b/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc
index c3bc7a1..0917914 100644
--- a/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc
@@ -182,7 +182,8 @@
 
 TEST_F(NotificationPlatformBridgeLinuxTest, SetUpAndTearDown) {}
 
-TEST_F(NotificationPlatformBridgeLinuxTest, NotifyAndCloseFormat) {
+// Frequently triggers a data race. Disabled as flaky: https://crbug.com/719485
+TEST_F(NotificationPlatformBridgeLinuxTest, DISABLED_NotifyAndCloseFormat) {
   EXPECT_CALL(*mock_notification_proxy_.get(),
               MockCallMethodAndBlock(Calls("Notify"), _))
       .WillOnce(OnNotify(1));
diff --git a/chrome/browser/sessions/better_session_restore_browsertest.cc b/chrome/browser/sessions/better_session_restore_browsertest.cc
index 4c5d9aa..7af994f 100644
--- a/chrome/browser/sessions/better_session_restore_browsertest.cc
+++ b/chrome/browser/sessions/better_session_restore_browsertest.cc
@@ -645,27 +645,14 @@
   CheckReloadedPageRestored();
 }
 
-// TODO(crbug.com/717740): This test is flaky on Mac.
-#if defined(OS_MACOSX)
-#define MAYBE_PRE_LocalStorageClearedOnExit \
-  DISABLED_PRE_LocalStorageClearedOnExit
-#else
-#define MAYBE_PRE_LocalStorageClearedOnExit PRE_LocalStorageClearedOnExit
-#endif
-IN_PROC_BROWSER_TEST_F(RestartTest, MAYBE_PRE_LocalStorageClearedOnExit) {
+IN_PROC_BROWSER_TEST_F(RestartTest, PRE_LocalStorageClearedOnExit) {
   StoreDataWithPage("local_storage.html");
   CookieSettingsFactory::GetForProfile(browser()->profile())
       ->SetDefaultCookieSetting(CONTENT_SETTING_SESSION_ONLY);
   Restart();
 }
 
-// TODO(crbug.com/717740): This test is flaky on Mac.
-#if defined(OS_MACOSX)
-#define MAYBE_LocalStorageClearedOnExit DISABLED_LocalStorageClearedOnExit
-#else
-#define MAYBE_LocalStorageClearedOnExit LocalStorageClearedOnExit
-#endif
-IN_PROC_BROWSER_TEST_F(RestartTest, MAYBE_LocalStorageClearedOnExit) {
+IN_PROC_BROWSER_TEST_F(RestartTest, LocalStorageClearedOnExit) {
   CheckReloadedPageRestored();
 }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a2f8419..e6c3445 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2975,6 +2975,8 @@
         "cocoa/styled_text_field.mm",
         "cocoa/styled_text_field_cell.h",
         "cocoa/styled_text_field_cell.mm",
+        "cocoa/subresource_filter/subresource_filter_bubble_controller.h",
+        "cocoa/subresource_filter/subresource_filter_bubble_controller.mm",
         "cocoa/tab_contents/favicon_util_mac.h",
         "cocoa/tab_contents/favicon_util_mac.mm",
         "cocoa/tab_contents/overlayable_contents_controller.h",
diff --git a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h
index a7c7fb25..1c834c8 100644
--- a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h
+++ b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h
@@ -55,7 +55,7 @@
 
 // Manages a "content blocked" bubble.
 @interface ContentSettingBubbleController : OmniboxDecorationBubbleController {
- @private
+ @protected
   IBOutlet NSTextField* titleLabel_;
   IBOutlet NSTextField* messageLabel_;
   IBOutlet NSMatrix* allowBlockRadioGroup_;
@@ -64,12 +64,14 @@
   IBOutlet NSButton* doneButton_;
   IBOutlet NSButton* loadButton_;
 
+  std::unique_ptr<ContentSettingBubbleModel> contentSettingBubbleModel_;
+
+ @private
   // The container for the bubble contents of the geolocation bubble.
   IBOutlet NSView* contentsContainer_;
 
   IBOutlet NSTextField* blockedResourcesField_;
 
-  std::unique_ptr<ContentSettingBubbleModel> contentSettingBubbleModel_;
   std::unique_ptr<ContentSettingBubbleWebContentsObserverBridge>
       observerBridge_;
   content_setting_bubble::PopupLinks popupLinks_;
@@ -79,6 +81,17 @@
   ContentSettingDecoration* decoration_;  // weak
 }
 
+// Initializes the controller using the model. Takes ownership of
+// |settingsBubbleModel| but not of the other objects. This is intended to be
+// invoked by subclasses and most callers should invoke the showForModel
+// convenience constructor.
+- (id)initWithModel:(ContentSettingBubbleModel*)settingsBubbleModel
+        webContents:(content::WebContents*)webContents
+             window:(NSWindow*)window
+       parentWindow:(NSWindow*)parentWindow
+         decoration:(ContentSettingDecoration*)decoration
+         anchoredAt:(NSPoint)anchoredAt;
+
 // Creates and shows a content blocked bubble. Takes ownership of
 // |contentSettingBubbleModel| but not of the other objects.
 + (ContentSettingBubbleController*)
@@ -88,6 +101,9 @@
   decoration:(ContentSettingDecoration*)decoration
   anchoredAt:(NSPoint)anchoredAt;
 
+// Initializes the layout of all the UI elements.
+- (void)layoutView;
+
 // Callback for the "don't block / continue blocking" radio group.
 - (IBAction)allowBlockToggled:(id)sender;
 
diff --git a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm
index 41cd17d..c10a6631 100644
--- a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm
+++ b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.mm
@@ -16,9 +16,10 @@
 #include "chrome/browser/plugins/plugin_metadata.h"
 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
 #import "chrome/browser/ui/cocoa/l10n_util.h"
+#import "chrome/browser/ui/cocoa/location_bar/content_setting_decoration.h"
+#import "chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.h"
 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
-#import "chrome/browser/ui/cocoa/location_bar/content_setting_decoration.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "content/public/browser/navigation_handle.h"
@@ -205,13 +206,15 @@
   DISALLOW_COPY_AND_ASSIGN(ContentSettingBubbleWebContentsObserverBridge);
 };
 
-@interface ContentSettingBubbleController(Private)
+@interface ContentSettingBubbleController (Private)
 - (id)initWithModel:(ContentSettingBubbleModel*)settingsBubbleModel
         webContents:(content::WebContents*)webContents
        parentWindow:(NSWindow*)parentWindow
          decoration:(ContentSettingDecoration*)decoration
          anchoredAt:(NSPoint)anchoredAt;
-- (NSString*)getNibPathForModel:(ContentSettingBubbleModel*)model;
++ (NSString*)getNibPathForModel:(ContentSettingBubbleModel*)model;
++ (ContentSettingBubbleController*)allocControllerForModel:
+    (ContentSettingBubbleModel*)model;
 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
                                 title:(NSString*)title
                                  icon:(NSImage*)icon
@@ -241,12 +244,16 @@
   decoration:(ContentSettingDecoration*)decoration
   anchoredAt:(NSPoint)anchor {
   // Autoreleases itself on bubble close.
-  return [[ContentSettingBubbleController alloc]
-      initWithModel:contentSettingBubbleModel
-        webContents:webContents
-       parentWindow:parentWindow
-         decoration:decoration
-         anchoredAt:anchor];
+  ContentSettingBubbleController* controller =
+      [self allocControllerForModel:contentSettingBubbleModel];
+
+  DCHECK(controller);
+
+  return [controller initWithModel:contentSettingBubbleModel
+                       webContents:webContents
+                      parentWindow:parentWindow
+                        decoration:decoration
+                        anchoredAt:anchor];
 }
 
 struct ContentTypeToNibPath {
@@ -278,7 +285,8 @@
   observerBridge_.reset(
     new ContentSettingBubbleWebContentsObserverBridge(webContents, self));
 
-  NSString* nibPath = [self getNibPathForModel:model.get()];
+  NSString* nibPath =
+      [ContentSettingBubbleController getNibPathForModel:model.get()];
 
   DCHECK_NE(0u, [nibPath length]);
 
@@ -292,7 +300,30 @@
   return self;
 }
 
-- (NSString*)getNibPathForModel:(ContentSettingBubbleModel*)model {
+- (id)initWithModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
+        webContents:(content::WebContents*)webContents
+             window:(NSWindow*)window
+       parentWindow:(NSWindow*)parentWindow
+         decoration:(ContentSettingDecoration*)decoration
+         anchoredAt:(NSPoint)anchoredAt {
+  // This method takes ownership of |contentSettingBubbleModel| in all cases.
+  std::unique_ptr<ContentSettingBubbleModel> model(contentSettingBubbleModel);
+  DCHECK(model.get());
+  observerBridge_.reset(
+      new ContentSettingBubbleWebContentsObserverBridge(webContents, self));
+
+  contentSettingBubbleModel_ = std::move(model);
+
+  if ((self = [super initWithWindow:window
+                       parentWindow:parentWindow
+                         anchoredAt:anchoredAt])) {
+    decoration_ = decoration;
+    [self showWindow:nil];
+  }
+  return self;
+}
+
++ (NSString*)getNibPathForModel:(ContentSettingBubbleModel*)model {
   NSString* nibPath = @"";
 
   ContentSettingSimpleBubbleModel* simple_bubble = model->AsSimpleBubbleModel();
@@ -310,14 +341,27 @@
   if (model->AsMediaStreamBubbleModel())
     nibPath = @"ContentBlockedMedia";
 
-  if (model->AsSubresourceFilterBubbleModel())
-    nibPath = @"ContentSubresourceFilter";
-
   if (model->AsDownloadsBubbleModel())
     nibPath = @"ContentBlockedDownloads";
   return nibPath;
 }
 
++ (ContentSettingBubbleController*)allocControllerForModel:
+    (ContentSettingBubbleModel*)model {
+  // Check if the view is expressed in xib file or not.
+  NSString* nibPath = [self getNibPathForModel:model];
+
+  // Autoreleases itself on bubble close.
+
+  if ([nibPath length] > 0u)
+    return [ContentSettingBubbleController alloc];
+
+  if (model->AsSubresourceFilterBubbleModel())
+    return [SubresourceFilterBubbleController alloc];
+
+  return nil;
+}
+
 - (void)initializeTitle {
   if (!titleLabel_)
     return;
@@ -767,14 +811,31 @@
 }
 
 - (void)initManageDoneButtons {
+  if (!manageButton_ && !doneButton_)
+    return;
+
   const ContentSettingBubbleModel::BubbleContent& content =
       contentSettingBubbleModel_->bubble_content();
-  [manageButton_ setTitle:base::SysUTF16ToNSString(content.manage_text)];
-  [GTMUILocalizerAndLayoutTweaker sizeToFitView:[manageButton_ superview]];
+
+  CGFloat requiredWidthForManageButton = 0.0;
+  if (manageButton_) {
+    [manageButton_ setTitle:base::SysUTF16ToNSString(content.manage_text)];
+    [GTMUILocalizerAndLayoutTweaker sizeToFitView:[manageButton_ superview]];
+    requiredWidthForManageButton =
+        NSMaxX([manageButton_ frame]) + kManageDonePadding;
+  }
+
+  if (!doneButton_)
+    return;
+
+  NSString* doneLabel = base::SysUTF16ToNSString(content.done_button_text);
+  if ([doneLabel length] > 0u)
+    [doneButton_ setTitle:doneLabel];
 
   CGFloat actualWidth = NSWidth([[[self window] contentView] frame]);
-  CGFloat requiredWidth = NSMaxX([manageButton_ frame]) + kManageDonePadding +
-      NSWidth([[doneButton_ superview] frame]) - NSMinX([doneButton_ frame]);
+  CGFloat requiredWidth = requiredWidthForManageButton +
+                          NSWidth([[doneButton_ superview] frame]) -
+                          NSMinX([doneButton_ frame]);
   if (requiredWidth <= actualWidth || !doneButton_ || !manageButton_)
     return;
 
@@ -789,7 +850,10 @@
 
 - (void)awakeFromNib {
   [super awakeFromNib];
+  [self layoutView];
+}
 
+- (void)layoutView {
   ContentSettingSimpleBubbleModel* simple_bubble =
       contentSettingBubbleModel_->AsSimpleBubbleModel();
 
@@ -797,8 +861,10 @@
 
   // Adapt window size to bottom buttons. Do this before all other layouting.
   if ((simple_bubble && !simple_bubble->bubble_content().manage_text.empty()) ||
-      contentSettingBubbleModel_->AsDownloadsBubbleModel())
+      contentSettingBubbleModel_->AsDownloadsBubbleModel() ||
+      contentSettingBubbleModel_->AsSubresourceFilterBubbleModel()) {
     [self initManageDoneButtons];
+  }
 
   [self initializeTitle];
   [self initializeMessage];
diff --git a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa_browsertest.mm
index c53d1a3..806c4002 100644
--- a/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa_browsertest.mm
@@ -6,19 +6,23 @@
 
 #import <Cocoa/Cocoa.h>
 
+#include "base/mac/foundation_util.h"
 #include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/content_settings/tab_specific_content_settings.h"
 #include "chrome/browser/download/download_request_limiter.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/ui/browser.h"
+#import "chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.h"
 #import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
 #include "chrome/browser/ui/content_settings/content_setting_image_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "components/strings/grit/components_strings.h"
 #include "content/public/common/media_stream_request.h"
 #include "testing/gtest_mac.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -117,3 +121,66 @@
 
  [parent_ close];
 }
+
+// Subresource Filter bubble.
+
+IN_PROC_BROWSER_TEST_F(ContentSettingBubbleControllerTest,
+                       InitSubresourceFilter) {
+  ContentSettingBubbleController* controller =
+      CreateBubbleController(new ContentSettingSubresourceFilterBubbleModel(
+          nullptr, web_contents(), profile()));
+  EXPECT_TRUE(controller);
+
+  SubresourceFilterBubbleController* filterController =
+      base::mac::ObjCCast<SubresourceFilterBubbleController>(controller);
+
+  EXPECT_TRUE([filterController titleLabel]);
+  NSString* label = base::SysUTF16ToNSString(
+      l10n_util::GetStringUTF16(IDS_FILTERED_DECEPTIVE_CONTENT_PROMPT_TITLE));
+  EXPECT_NSEQ([[filterController titleLabel] stringValue], label);
+
+  EXPECT_TRUE([filterController messageLabel]);
+  label = base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
+      IDS_FILTERED_DECEPTIVE_CONTENT_PROMPT_EXPLANATION));
+  EXPECT_NSEQ([[filterController messageLabel] stringValue], label);
+
+  EXPECT_TRUE([filterController manageCheckbox]);
+  label = base::SysUTF16ToNSString(
+      l10n_util::GetStringUTF16(IDS_FILTERED_DECEPTIVE_CONTENT_PROMPT_RELOAD));
+  EXPECT_NSEQ([[filterController manageCheckbox] title], label);
+
+  EXPECT_TRUE([filterController doneButton]);
+  label = base::SysUTF16ToNSString(l10n_util::GetStringUTF16(IDS_OK));
+  EXPECT_NSEQ([[filterController doneButton] title], label);
+
+  [parent_ close];
+}
+
+IN_PROC_BROWSER_TEST_F(ContentSettingBubbleControllerTest,
+                       ManageCheckboxSubresourceFilter) {
+  ContentSettingSubresourceFilterBubbleModel* model =
+      new ContentSettingSubresourceFilterBubbleModel(nullptr, web_contents(),
+                                                     profile());
+  ContentSettingBubbleController* controller = CreateBubbleController(model);
+  EXPECT_TRUE(controller);
+
+  SubresourceFilterBubbleController* filterController =
+      base::mac::ObjCCast<SubresourceFilterBubbleController>(controller);
+  NSButton* manageCheckbox = [filterController manageCheckbox];
+  NSButton* doneButton = [filterController doneButton];
+
+  EXPECT_EQ([manageCheckbox state], NSOffState);
+
+  NSString* label = base::SysUTF16ToNSString(l10n_util::GetStringUTF16(IDS_OK));
+  EXPECT_NSEQ([doneButton title], label);
+
+  [manageCheckbox setState:NSOnState];
+  [filterController manageCheckboxChecked:manageCheckbox];
+  EXPECT_EQ([manageCheckbox state], NSOnState);
+
+  label =
+      base::SysUTF16ToNSString(l10n_util::GetStringUTF16(IDS_APP_MENU_RELOAD));
+  EXPECT_NSEQ([doneButton title], label);
+
+  [parent_ close];
+}
diff --git a/chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.h b/chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.h
new file mode 100644
index 0000000..f23ef52f
--- /dev/null
+++ b/chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.h
@@ -0,0 +1,33 @@
+// 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 CHROME_BROWSER_UI_COCOA_SUBRESOURCE_FILTER_SUBRESOURCE_FILTER_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COCOA_SUBRESOURCE_FILTER_SUBRESOURCE_FILTER_BUBBLE_CONTROLLER_H_
+
+#import <Cocoa/Cocoa.h>
+
+#import "chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h"
+
+// Displays the content filtering bubble. This is a bubble which is shown
+// through content settings when the user proceeds through a Safe Browsing
+// warning interstitial that is displayed when the site ahead contains deceptive
+// embedded content. It explains to the user that some subresources were
+// filtered and presents the checkbox to reload the page. If the check box is
+// checked, the OK button's text is replaced with 'Reload' giving the user
+// the ability to reload the page with filtering disabled.
+@interface SubresourceFilterBubbleController : ContentSettingBubbleController
+
+@end
+
+// The methods on this category are used internally by the controller and are
+// only exposed for testing purposes. DO NOT USE OTHERWISE.
+@interface SubresourceFilterBubbleController (ExposedForTesting)
+- (void)manageCheckboxChecked:(id)sender;
+- (id)titleLabel;
+- (id)messageLabel;
+- (id)manageCheckbox;
+- (id)doneButton;
+@end
+
+#endif  // CHROME_BROWSER_UI_COCOA_SUBRESOURCE_FILTER_SUBRESOURCE_FILTER_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.mm b/chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.mm
new file mode 100644
index 0000000..9712dbc
--- /dev/null
+++ b/chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.mm
@@ -0,0 +1,130 @@
+// 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.
+
+#import "chrome/browser/ui/cocoa/subresource_filter/subresource_filter_bubble_controller.h"
+
+#include "base/strings/sys_string_conversions.h"
+#import "chrome/browser/ui/cocoa/info_bubble_window.h"
+#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
+#include "components/strings/grit/components_strings.h"
+#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+@interface SubresourceFilterBubbleController () {
+  NSButton* manageCheckbox_;
+}
+@end
+
+@implementation SubresourceFilterBubbleController
+
+- (id)initWithModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
+        webContents:(content::WebContents*)webContents
+       parentWindow:(NSWindow*)parentWindow
+         decoration:(ContentSettingDecoration*)decoration
+         anchoredAt:(NSPoint)anchoredAt {
+  NSRect contentRect = NSMakeRect(196, 376, 316, 154);
+  base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
+      initWithContentRect:contentRect
+                styleMask:NSBorderlessWindowMask
+                  backing:NSBackingStoreBuffered
+                    defer:NO]);
+
+  // Disable animations - otherwise, the window/controller will outlive the web
+  // contents it's associated with.
+  [window setAllowedAnimations:info_bubble::kAnimateNone];
+
+  [super initWithModel:contentSettingBubbleModel
+           webContents:webContents
+                window:window
+          parentWindow:parentWindow
+            decoration:decoration
+            anchoredAt:anchoredAt];
+  return self;
+}
+
+- (void)awakeFromNib {
+  [self loadView];
+  [super awakeFromNib];
+}
+
+- (void)layoutView {
+  [super layoutView];
+  [self initializeManageCheckbox];
+}
+
+- (void)loadView {
+  titleLabel_ =
+      [[NSTextField alloc] initWithFrame:NSMakeRect(18, 120, 282, 14)];
+  [titleLabel_ setEditable:NO];
+  [titleLabel_ setBordered:NO];
+  [self.window.contentView addSubview:titleLabel_];
+  [titleLabel_ release];
+
+  messageLabel_ =
+      [[NSTextField alloc] initWithFrame:NSMakeRect(18, 85, 282, 28)];
+  [messageLabel_ setEditable:NO];
+  [messageLabel_ setBordered:NO];
+  [self.window.contentView addSubview:messageLabel_];
+  [messageLabel_ release];
+
+  manageCheckbox_ =
+      [[NSButton alloc] initWithFrame:NSMakeRect(18, 35, 282, 28)];
+  [manageCheckbox_ setButtonType:NSSwitchButton];
+  [manageCheckbox_ setState:NSOffState];
+  [self.window.contentView addSubview:manageCheckbox_];
+  [manageCheckbox_ setAction:@selector(manageCheckboxChecked:)];
+  [manageCheckbox_ release];
+
+  doneButton_ = [[NSButton alloc] initWithFrame:NSMakeRect(210, 10, 90, 28)];
+  [doneButton_ setBezelStyle:NSRoundedBezelStyle];
+  [doneButton_ highlight:YES];
+  [doneButton_ setTitle:l10n_util::GetNSString(IDS_OK)];
+  [self.window.contentView addSubview:doneButton_];
+  [doneButton_ setAction:@selector(closeBubble:)];
+  [doneButton_ release];
+}
+
+- (void)initializeManageCheckbox {
+  if (!manageCheckbox_)
+    return;
+
+  NSString* label = base::SysUTF16ToNSString(
+      contentSettingBubbleModel_->bubble_content().manage_text);
+  [manageCheckbox_ setTitle:label];
+
+  CGFloat deltaY =
+      [GTMUILocalizerAndLayoutTweaker sizeToFitView:manageCheckbox_].height;
+  NSRect windowFrame = [[self window] frame];
+  windowFrame.size.height += deltaY;
+  [[self window] setFrame:windowFrame display:NO];
+  NSRect manageCheckboxFrame = [manageCheckbox_ frame];
+  manageCheckboxFrame.origin.y -= deltaY;
+  [manageCheckbox_ setFrame:manageCheckboxFrame];
+}
+
+// Callback for "manage" checkbox button.
+- (void)manageCheckboxChecked:(id)sender {
+  bool isChecked = [sender state] == NSOnState;
+  contentSettingBubbleModel_->OnManageCheckboxChecked(isChecked);
+  [self layoutView];
+}
+
+// For testing.
+
+- (id)titleLabel {
+  return titleLabel_;
+}
+
+- (id)messageLabel {
+  return messageLabel_;
+}
+
+- (id)manageCheckbox {
+  return manageCheckbox_;
+}
+
+- (id)doneButton {
+  return doneButton_;
+}
+@end
diff --git a/chrome/browser/ui/views/payments/payment_request_blob_url_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_blob_url_browsertest.cc
new file mode 100644
index 0000000..5fdfc5ad
--- /dev/null
+++ b/chrome/browser/ui/views/payments/payment_request_blob_url_browsertest.cc
@@ -0,0 +1,27 @@
+// 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 "chrome/browser/ui/views/payments/payment_request_browsertest_base.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace payments {
+
+class PaymentRequestBlobUrlTest : public PaymentRequestBrowserTestBase {
+ protected:
+  PaymentRequestBlobUrlTest()
+      : PaymentRequestBrowserTestBase("/payment_request_blob_url_test.html") {}
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestBlobUrlTest, ConnectionTerminated) {
+  ResetEventObserver(DialogEvent::DIALOG_CLOSED);
+  ASSERT_TRUE(content::ExecuteScript(
+      GetActiveWebContents(),
+      "(function() { document.getElementById('buy').click(); })();"));
+  WaitForObservedEvent();
+  ExpectBodyContains({"Rejected: UnknownError: Request failed"});
+}
+
+}  // namespace payments
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
index 34b5bce..e556570 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/payments/payment_request_browsertest_base.h"
 
 #include <algorithm>
+#include <iostream>
 #include <memory>
 #include <string>
 #include <utility>
@@ -81,7 +82,10 @@
 }
 
 void PaymentRequestBrowserTestBase::NavigateTo(const std::string& file_path) {
-  ui_test_utils::NavigateToURL(browser(), https_server()->GetURL(file_path));
+  ui_test_utils::NavigateToURL(browser(),
+                               file_path.find("data:") == 0U
+                                   ? GURL(file_path)
+                                   : https_server()->GetURL(file_path));
 }
 
 void PaymentRequestBrowserTestBase::SetIncognito() {
@@ -102,6 +106,11 @@
     event_observer_->Observe(DialogEvent::NOT_SUPPORTED_ERROR);
 }
 
+void PaymentRequestBrowserTestBase::OnConnectionTerminated() {
+  if (event_observer_)
+    event_observer_->Observe(DialogEvent::DIALOG_CLOSED);
+}
+
 void PaymentRequestBrowserTestBase::OnDialogOpened() {
   if (event_observer_)
     event_observer_->Observe(DialogEvent::DIALOG_OPENED);
@@ -177,11 +186,6 @@
     event_observer_->Observe(DialogEvent::CVC_PROMPT_SHOWN);
 }
 
-void PaymentRequestBrowserTestBase::OnWidgetDestroyed(views::Widget* widget) {
-  if (event_observer_)
-    event_observer_->Observe(DialogEvent::DIALOG_CLOSED);
-}
-
 void PaymentRequestBrowserTestBase::InvokePaymentRequestUI() {
   ResetEventObserver(DialogEvent::DIALOG_OPENED);
 
@@ -210,7 +214,8 @@
       web_contents, extract_contents_js, &contents));
   for (const std::string& expected_string : expected_strings) {
     EXPECT_NE(std::string::npos, contents.find(expected_string))
-        << "String not present: " << expected_string;
+        << "String \"" << expected_string
+        << "\" is not present in the content \"" << contents << "\"";
   }
 }
 
@@ -406,8 +411,7 @@
   DCHECK(web_contents);
   std::unique_ptr<TestChromePaymentRequestDelegate> delegate =
       base::MakeUnique<TestChromePaymentRequestDelegate>(
-          web_contents, this /* observer */, this /* widget_observer */,
-          is_incognito_, is_valid_ssl_);
+          web_contents, this /* observer */, is_incognito_, is_valid_ssl_);
   delegate_ = delegate.get();
   PaymentRequestWebContentsManager::GetOrCreateForWebContents(web_contents)
       ->CreatePaymentRequest(web_contents, std::move(delegate),
@@ -661,3 +665,66 @@
 }
 
 }  // namespace payments
+
+std::ostream& operator<<(
+    std::ostream& out,
+    payments::PaymentRequestBrowserTestBase::DialogEvent event) {
+  using DialogEvent = payments::PaymentRequestBrowserTestBase::DialogEvent;
+  switch (event) {
+    case DialogEvent::DIALOG_OPENED:
+      out << "DIALOG_OPENED";
+      break;
+    case DialogEvent::DIALOG_CLOSED:
+      out << "DIALOG_CLOSED";
+      break;
+    case DialogEvent::ORDER_SUMMARY_OPENED:
+      out << "ORDER_SUMMARY_OPENED";
+      break;
+    case DialogEvent::PAYMENT_METHOD_OPENED:
+      out << "PAYMENT_METHOD_OPENED";
+      break;
+    case DialogEvent::SHIPPING_ADDRESS_SECTION_OPENED:
+      out << "SHIPPING_ADDRESS_SECTION_OPENED";
+      break;
+    case DialogEvent::SHIPPING_OPTION_SECTION_OPENED:
+      out << "SHIPPING_OPTION_SECTION_OPENED";
+      break;
+    case DialogEvent::CREDIT_CARD_EDITOR_OPENED:
+      out << "CREDIT_CARD_EDITOR_OPENED";
+      break;
+    case DialogEvent::SHIPPING_ADDRESS_EDITOR_OPENED:
+      out << "SHIPPING_ADDRESS_EDITOR_OPENED";
+      break;
+    case DialogEvent::CONTACT_INFO_EDITOR_OPENED:
+      out << "CONTACT_INFO_EDITOR_OPENED";
+      break;
+    case DialogEvent::BACK_NAVIGATION:
+      out << "BACK_NAVIGATION";
+      break;
+    case DialogEvent::BACK_TO_PAYMENT_SHEET_NAVIGATION:
+      out << "BACK_TO_PAYMENT_SHEET_NAVIGATION";
+      break;
+    case DialogEvent::CONTACT_INFO_OPENED:
+      out << "CONTACT_INFO_OPENED";
+      break;
+    case DialogEvent::EDITOR_VIEW_UPDATED:
+      out << "EDITOR_VIEW_UPDATED";
+      break;
+    case DialogEvent::CAN_MAKE_PAYMENT_CALLED:
+      out << "CAN_MAKE_PAYMENT_CALLED";
+      break;
+    case DialogEvent::ERROR_MESSAGE_SHOWN:
+      out << "ERROR_MESSAGE_SHOWN";
+      break;
+    case DialogEvent::SPEC_DONE_UPDATING:
+      out << "SPEC_DONE_UPDATING";
+      break;
+    case DialogEvent::CVC_PROMPT_SHOWN:
+      out << "CVC_PROMPT_SHOWN";
+      break;
+    case DialogEvent::NOT_SUPPORTED_ERROR:
+      out << "NOT_SUPPORTED_ERROR";
+      break;
+  }
+  return out;
+}
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.h b/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
index 38dc231..8365bff 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_REQUEST_BROWSERTEST_BASE_H_
 #define CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_REQUEST_BROWSERTEST_BASE_H_
 
+#include <iosfwd>
 #include <list>
 #include <memory>
 #include <string>
@@ -23,7 +24,6 @@
 #include "components/payments/mojom/payment_request.mojom.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "ui/views/widget/widget_observer.h"
 
 namespace autofill {
 class AutofillProfile;
@@ -38,10 +38,6 @@
 struct BindSourceInfo;
 }
 
-namespace views {
-class Widget;
-}
-
 namespace payments {
 
 enum class DialogViewID;
@@ -64,8 +60,30 @@
 class PaymentRequestBrowserTestBase
     : public InProcessBrowserTest,
       public PaymentRequest::ObserverForTest,
-      public PaymentRequestDialogView::ObserverForTest,
-      public views::WidgetObserver {
+      public PaymentRequestDialogView::ObserverForTest {
+ public:
+  // Various events that can be waited on by the DialogEventObserver.
+  enum DialogEvent : int {
+    DIALOG_OPENED,
+    DIALOG_CLOSED,
+    ORDER_SUMMARY_OPENED,
+    PAYMENT_METHOD_OPENED,
+    SHIPPING_ADDRESS_SECTION_OPENED,
+    SHIPPING_OPTION_SECTION_OPENED,
+    CREDIT_CARD_EDITOR_OPENED,
+    SHIPPING_ADDRESS_EDITOR_OPENED,
+    CONTACT_INFO_EDITOR_OPENED,
+    BACK_NAVIGATION,
+    BACK_TO_PAYMENT_SHEET_NAVIGATION,
+    CONTACT_INFO_OPENED,
+    EDITOR_VIEW_UPDATED,
+    CAN_MAKE_PAYMENT_CALLED,
+    ERROR_MESSAGE_SHOWN,
+    SPEC_DONE_UPDATING,
+    CVC_PROMPT_SHOWN,
+    NOT_SUPPORTED_ERROR,
+  };
+
  protected:
   // Test will open a browser window to |test_file_path| (relative to
   // chrome/test/data/payments).
@@ -82,6 +100,7 @@
   // PaymentRequest::ObserverForTest:
   void OnCanMakePaymentCalled() override;
   void OnNotSupportedError() override;
+  void OnConnectionTerminated() override;
 
   // PaymentRequestDialogView::ObserverForTest:
   void OnDialogOpened() override;
@@ -100,10 +119,6 @@
   void OnSpecDoneUpdating() override;
   void OnCvcPromptShown() override;
 
-  // views::WidgetObserver
-  // Effective way to be warned of all dialog closures.
-  void OnWidgetDestroyed(views::Widget* widget) override;
-
   // Will call JavaScript to invoke the PaymentRequest dialog and verify that
   // it's open.
   void InvokePaymentRequestUI();
@@ -199,28 +214,6 @@
     delegate_->SetRegionDataLoader(region_data_loader);
   }
 
-  // Various events that can be waited on by the DialogEventObserver.
-  enum DialogEvent : int {
-    DIALOG_OPENED,
-    DIALOG_CLOSED,
-    ORDER_SUMMARY_OPENED,
-    PAYMENT_METHOD_OPENED,
-    SHIPPING_ADDRESS_SECTION_OPENED,
-    SHIPPING_OPTION_SECTION_OPENED,
-    CREDIT_CARD_EDITOR_OPENED,
-    SHIPPING_ADDRESS_EDITOR_OPENED,
-    CONTACT_INFO_EDITOR_OPENED,
-    BACK_NAVIGATION,
-    BACK_TO_PAYMENT_SHEET_NAVIGATION,
-    CONTACT_INFO_OPENED,
-    EDITOR_VIEW_UPDATED,
-    CAN_MAKE_PAYMENT_CALLED,
-    ERROR_MESSAGE_SHOWN,
-    SPEC_DONE_UPDATING,
-    CVC_PROMPT_SHOWN,
-    NOT_SUPPORTED_ERROR,
-  };
-
   // DialogEventObserver is used to wait on specific events that may have
   // occured before the call to Wait(), or after, in which case a RunLoop is
   // used.
@@ -273,4 +266,8 @@
 
 }  // namespace payments
 
+std::ostream& operator<<(
+    std::ostream& out,
+    payments::PaymentRequestBrowserTestBase::DialogEvent event);
+
 #endif  // CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_REQUEST_BROWSERTEST_BASE_H_
diff --git a/chrome/browser/ui/views/payments/payment_request_data_url_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_data_url_browsertest.cc
new file mode 100644
index 0000000..a2e6e05
--- /dev/null
+++ b/chrome/browser/ui/views/payments/payment_request_data_url_browsertest.cc
@@ -0,0 +1,35 @@
+// 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 "chrome/browser/ui/views/payments/payment_request_browsertest_base.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace payments {
+
+class PaymentRequestDataUrlTest : public PaymentRequestBrowserTestBase {
+ protected:
+  PaymentRequestDataUrlTest()
+      : PaymentRequestBrowserTestBase(
+            "data:text/html,<html><head><meta name=\"viewport\" "
+            "content=\"width=device-width, initial-scale=1, "
+            "maximum-scale=1\"></head><body><button id=\"buy\" onclick=\"try { "
+            "(new PaymentRequest([{supportedMethods: ['basic-card']}], {total: "
+            "{label: 'Total',  amount: {currency: 'USD', value: "
+            "'1.00'}}})).show(); } catch(e) { "
+            "document.getElementById('result').innerHTML = e; }\">Data URL "
+            "Test</button><div id='result'></div></body></html>") {}
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestDataUrlTest, SecurityError) {
+  ASSERT_TRUE(content::ExecuteScript(
+      GetActiveWebContents(),
+      "(function() { document.getElementById('buy').click(); })();"));
+  ExpectBodyContains(
+      {"SecurityError: Failed to construct 'PaymentRequest': Must be in a "
+       "secure context"});
+}
+
+}  // namespace payments
diff --git a/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.cc b/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.cc
index e5c8144..5ea6cb0 100644
--- a/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.cc
+++ b/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.cc
@@ -7,21 +7,17 @@
 #include <utility>
 
 #include "content/public/browser/web_contents.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/widget/widget_observer.h"
 
 namespace payments {
 
 TestChromePaymentRequestDelegate::TestChromePaymentRequestDelegate(
     content::WebContents* web_contents,
     PaymentRequestDialogView::ObserverForTest* observer,
-    views::WidgetObserver* widget_observer,
     bool is_incognito,
     bool is_valid_ssl)
     : ChromePaymentRequestDelegate(web_contents),
       region_data_loader_(nullptr),
       observer_(observer),
-      widget_observer_(widget_observer),
       is_incognito_(is_incognito),
       is_valid_ssl_(is_valid_ssl) {}
 
@@ -29,11 +25,6 @@
   PaymentRequestDialogView* dialog_view =
       new PaymentRequestDialogView(request, observer_);
   dialog_view->ShowDialog();
-
-  // The widget is now valid, so register its observer.
-  views::Widget* widget = dialog_view->GetWidget();
-  widget->AddObserver(widget_observer_);
-
   dialog_ = std::move(dialog_view);
 }
 
diff --git a/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.h b/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.h
index f2534db..7ed2d98 100644
--- a/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.h
+++ b/chrome/browser/ui/views/payments/test_chrome_payment_request_delegate.h
@@ -15,10 +15,6 @@
 class WebContents;
 }
 
-namespace views {
-class WidgetObserver;
-}
-
 namespace payments {
 
 class PaymentRequest;
@@ -29,7 +25,6 @@
   TestChromePaymentRequestDelegate(
       content::WebContents* web_contents,
       PaymentRequestDialogView::ObserverForTest* observer,
-      views::WidgetObserver* widget_observer,
       bool is_incognito,
       bool is_valid_ssl);
 
@@ -52,7 +47,6 @@
   autofill::RegionDataLoader* region_data_loader_;
 
   PaymentRequestDialogView::ObserverForTest* observer_;
-  views::WidgetObserver* widget_observer_;
   bool is_incognito_;
   bool is_valid_ssl_;
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 129e8c84..cc1bd501 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1981,11 +1981,13 @@
         "../browser/ui/views/payments/error_message_view_controller_browsertest.cc",
         "../browser/ui/views/payments/order_summary_view_controller_browsertest.cc",
         "../browser/ui/views/payments/payment_method_view_controller_browsertest.cc",
+        "../browser/ui/views/payments/payment_request_blob_url_browsertest.cc",
         "../browser/ui/views/payments/payment_request_browsertest.cc",
         "../browser/ui/views/payments/payment_request_browsertest_base.cc",
         "../browser/ui/views/payments/payment_request_browsertest_base.h",
         "../browser/ui/views/payments/payment_request_can_make_payment_browsertest.cc",
         "../browser/ui/views/payments/payment_request_can_make_payment_metrics_browsertest.cc",
+        "../browser/ui/views/payments/payment_request_data_url_browsertest.cc",
         "../browser/ui/views/payments/payment_request_payment_app_browsertest.cc",
         "../browser/ui/views/payments/payment_request_payment_response_browsertest.cc",
         "../browser/ui/views/payments/payment_sheet_view_controller_browsertest.cc",
diff --git a/chrome/test/data/payments/blob_url.js b/chrome/test/data/payments/blob_url.js
new file mode 100644
index 0000000..5e6715de
--- /dev/null
+++ b/chrome/test/data/payments/blob_url.js
@@ -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.
+ */
+
+/** Requests payment via a blob URL. */
+function buy() { // eslint-disable-line no-unused-vars
+  const spoof = function() {
+    const payload = 'PGh0bWw+PGhlYWQ+PG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0yLCBtYXhpbXVtLXNjYWxlPTIiPjwvaGVhZD48Ym9keT48ZGl2IGlkPSJyZXN1bHQiPjwvZGl2PjxzY3JpcHQ+dHJ5IHsgIG5ldyBQYXltZW50UmVxdWVzdChbe3N1cHBvcnRlZE1ldGhvZHM6IFsiYmFzaWMtY2FyZCJdfV0sICAgIHt0b3RhbDoge2xhYmVsOiAiVCIsIGFtb3VudDoge2N1cnJlbmN5OiAiVVNEIiwgdmFsdWU6ICIxLjAwIn19fSkgIC5zaG93KCkgIC50aGVuKGZ1bmN0aW9uKGluc3RydW1lbnRSZXNwb25zZSkgeyAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgicmVzdWx0IikuaW5uZXJIVE1MID0gIlJlc29sdmVkIjsgIH0pLmNhdGNoKGZ1bmN0aW9uKGUpIHsgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInJlc3VsdCIpLmlubmVySFRNTCA9ICJSZWplY3RlZDogIiArIGU7ICB9KTt9IGNhdGNoKGUpIHsgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJyZXN1bHQiKS5pbm5lckhUTUwgPSAiRXhjZXB0aW9uOiAiICsgZTt9PC9zY3JpcHQ+PC9ib2R5PjwvaHRtbD4=';
+    document.write(atob(payload));
+  };
+  window.location.href =
+    URL.createObjectURL(new Blob(['<script>(', spoof, ')();</script>'], {
+      type: 'text/html',
+    }));
+}
diff --git a/chrome/test/data/payments/payment_request_blob_url_test.html b/chrome/test/data/payments/payment_request_blob_url_test.html
new file mode 100644
index 0000000..58b2768
--- /dev/null
+++ b/chrome/test/data/payments/payment_request_blob_url_test.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<head>
+<title>Blob URL Test</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+</head>
+<body>
+<button onclick="buy()" id="buy">Blob URL Test</button>
+<script src="blob_url.js"></script>
+</body>
+</html>
diff --git a/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc b/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc
index bdb786d..668ff41 100644
--- a/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc
+++ b/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc
@@ -50,6 +50,7 @@
 using syncer::ModelType;
 using syncer::ModelTypeChangeProcessor;
 using syncer::ModelTypeSyncBridge;
+using syncer::RecordingModelTypeChangeProcessor;
 
 namespace autofill {
 
@@ -143,8 +144,7 @@
   void ResetBridge() {
     bridge_.reset(new AutocompleteSyncBridge(
         &backend_,
-        base::Bind(&AutocompleteSyncBridgeTest::CreateModelTypeChangeProcessor,
-                   base::Unretained(this))));
+        RecordingModelTypeChangeProcessor::FactoryForBridgeTest(&processor_)));
   }
 
   void SaveSpecificsToTable(
@@ -246,12 +246,12 @@
                                   int position = 0) {
     const std::string storage_key = GetStorageKey(specifics);
     auto recorded_specifics_iterator =
-        processor()->put_multimap().find(storage_key);
+        processor().put_multimap().find(storage_key);
 
-    EXPECT_NE(processor()->put_multimap().end(), recorded_specifics_iterator);
+    EXPECT_NE(processor().put_multimap().end(), recorded_specifics_iterator);
     while (position > 0) {
       recorded_specifics_iterator++;
-      EXPECT_NE(processor()->put_multimap().end(), recorded_specifics_iterator);
+      EXPECT_NE(processor().put_multimap().end(), recorded_specifics_iterator);
       position--;
     }
 
@@ -262,20 +262,11 @@
 
   AutocompleteSyncBridge* bridge() { return bridge_.get(); }
 
-  syncer::RecordingModelTypeChangeProcessor* processor() { return processor_; }
+  const RecordingModelTypeChangeProcessor& processor() { return *processor_; }
 
   AutofillTable* table() { return &table_; }
 
  private:
-  std::unique_ptr<syncer::ModelTypeChangeProcessor>
-  CreateModelTypeChangeProcessor(syncer::ModelType type,
-                                 syncer::ModelTypeSyncBridge* bridge) {
-    auto processor =
-        base::MakeUnique<syncer::RecordingModelTypeChangeProcessor>();
-    processor_ = processor.get();
-    return std::move(processor);
-  }
-
   ScopedTempDir temp_dir_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   FakeAutofillBackend backend_;
@@ -284,7 +275,7 @@
   std::unique_ptr<AutocompleteSyncBridge> bridge_;
   // A non-owning pointer to the processor given to the bridge. Will be null
   // before being given to the bridge, to make ownership easier.
-  syncer::RecordingModelTypeChangeProcessor* processor_ = nullptr;
+  RecordingModelTypeChangeProcessor* processor_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(AutocompleteSyncBridgeTest);
 };
@@ -541,7 +532,7 @@
       {AutofillChange(AutofillChange::ADD, added_entry1.key()),
        AutofillChange(AutofillChange::ADD, added_entry2.key())});
 
-  EXPECT_EQ(2u, processor()->put_multimap().size());
+  EXPECT_EQ(2u, processor().put_multimap().size());
 
   VerifyProcessorRecordedPut(added_specifics1);
   VerifyProcessorRecordedPut(added_specifics2);
@@ -555,7 +546,7 @@
   bridge()->AutofillEntriesChanged(
       {AutofillChange(AutofillChange::ADD, added_entry.key())});
 
-  EXPECT_EQ(1u, processor()->put_multimap().size());
+  EXPECT_EQ(1u, processor().put_multimap().size());
 
   VerifyProcessorRecordedPut(added_specifics);
 
@@ -577,16 +568,15 @@
   bridge()->AutofillEntriesChanged(
       {AutofillChange(AutofillChange::REMOVE, deleted_entry.key())});
 
-  EXPECT_EQ(1u, processor()->delete_set().size());
-  EXPECT_NE(processor()->delete_set().end(),
-            processor()->delete_set().find(storage_key));
+  EXPECT_EQ(1u, processor().delete_set().size());
+  EXPECT_NE(processor().delete_set().end(),
+            processor().delete_set().find(storage_key));
 }
 
 TEST_F(AutocompleteSyncBridgeTest, LoadMetadataCalled) {
-  EXPECT_NE(nullptr, processor()->metadata());
-  EXPECT_FALSE(
-      processor()->metadata()->GetModelTypeState().initial_sync_done());
-  EXPECT_EQ(0u, processor()->metadata()->TakeAllMetadata().size());
+  EXPECT_NE(nullptr, processor().metadata());
+  EXPECT_FALSE(processor().metadata()->GetModelTypeState().initial_sync_done());
+  EXPECT_EQ(0u, processor().metadata()->TakeAllMetadata().size());
 
   ModelTypeState model_type_state;
   model_type_state.set_initial_sync_done(true);
@@ -597,17 +587,17 @@
 
   ResetBridge();
 
-  EXPECT_NE(nullptr, processor()->metadata());
-  EXPECT_TRUE(processor()->metadata()->GetModelTypeState().initial_sync_done());
-  EXPECT_EQ(1u, processor()->metadata()->TakeAllMetadata().size());
+  EXPECT_NE(nullptr, processor().metadata());
+  EXPECT_TRUE(processor().metadata()->GetModelTypeState().initial_sync_done());
+  EXPECT_EQ(1u, processor().metadata()->TakeAllMetadata().size());
 }
 
 TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataEmpty) {
   VerifyMerge(std::vector<AutofillSpecifics>());
 
   VerifyAllData(std::vector<AutofillSpecifics>());
-  EXPECT_EQ(0u, processor()->delete_set().size());
-  EXPECT_EQ(0u, processor()->put_multimap().size());
+  EXPECT_EQ(0u, processor().delete_set().size());
+  EXPECT_EQ(0u, processor().put_multimap().size());
 }
 
 TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataRemoteOnly) {
@@ -617,8 +607,8 @@
   VerifyMerge({specifics1, specifics2});
 
   VerifyAllData({specifics1, specifics2});
-  EXPECT_EQ(0u, processor()->delete_set().size());
-  EXPECT_EQ(0u, processor()->put_multimap().size());
+  EXPECT_EQ(0u, processor().delete_set().size());
+  EXPECT_EQ(0u, processor().put_multimap().size());
 }
 
 TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataLocalOnly) {
@@ -630,10 +620,10 @@
   VerifyMerge(std::vector<AutofillSpecifics>());
 
   VerifyAllData({specifics1, specifics2});
-  EXPECT_EQ(2u, processor()->put_multimap().size());
+  EXPECT_EQ(2u, processor().put_multimap().size());
   VerifyProcessorRecordedPut(specifics1);
   VerifyProcessorRecordedPut(specifics2);
-  EXPECT_EQ(0u, processor()->delete_set().size());
+  EXPECT_EQ(0u, processor().delete_set().size());
 }
 
 TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataAllMerged) {
@@ -660,12 +650,12 @@
   VerifyMerge({remote1, remote2, remote3, remote4, remote5, remote6});
 
   VerifyAllData({merged1, merged2, merged3, merged4, merged5, merged6});
-  EXPECT_EQ(4u, processor()->put_multimap().size());
+  EXPECT_EQ(4u, processor().put_multimap().size());
   VerifyProcessorRecordedPut(merged3);
   VerifyProcessorRecordedPut(merged4);
   VerifyProcessorRecordedPut(merged5);
   VerifyProcessorRecordedPut(merged6);
-  EXPECT_EQ(0u, processor()->delete_set().size());
+  EXPECT_EQ(0u, processor().delete_set().size());
 }
 
 TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataMixed) {
@@ -681,10 +671,10 @@
   VerifyMerge({remote2, specifics3, remote4});
 
   VerifyAllData({local1, remote2, specifics3, merged4});
-  EXPECT_EQ(2u, processor()->put_multimap().size());
+  EXPECT_EQ(2u, processor().put_multimap().size());
   VerifyProcessorRecordedPut(local1);
   VerifyProcessorRecordedPut(merged4);
-  EXPECT_EQ(0u, processor()->delete_set().size());
+  EXPECT_EQ(0u, processor().delete_set().size());
 }
 
 }  // namespace autofill
diff --git a/components/payments/content/payment_details_validation.cc b/components/payments/content/payment_details_validation.cc
index 1edd6fb1..e953cfb7 100644
--- a/components/payments/content/payment_details_validation.cc
+++ b/components/payments/content/payment_details_validation.cc
@@ -35,11 +35,6 @@
     return false;
   }
 
-  if (total && item->amount->currency != total->amount->currency) {
-    *error_message = "Currencies must all be equal";
-    return false;
-  }
-
   if (item->amount->currency_system.empty()) {
     *error_message = "Currency system can't be empty";
     return false;
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index 68da7ff..138b6d1 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -48,17 +48,28 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   client_ = std::move(client);
 
-  if (!OriginSecurityChecker::IsOriginSecure(
-          delegate_->GetLastCommittedURL())) {
+  const GURL last_committed_url = delegate_->GetLastCommittedURL();
+  if (!OriginSecurityChecker::IsOriginSecure(last_committed_url)) {
     LOG(ERROR) << "Not in a secure origin";
     OnConnectionTerminated();
     return;
   }
 
-  if (OriginSecurityChecker::IsSchemeCryptographic(
-          delegate_->GetLastCommittedURL()) &&
-      !delegate_->IsSslCertificateValid()) {
+  bool allowed_origin =
+      OriginSecurityChecker::IsSchemeCryptographic(last_committed_url) ||
+      OriginSecurityChecker::IsOriginLocalhostOrFile(last_committed_url);
+  if (!allowed_origin) {
+    LOG(ERROR) << "Only localhost, file://, and cryptographic scheme origins "
+                  "allowed";
+  }
+
+  bool invalid_ssl =
+      OriginSecurityChecker::IsSchemeCryptographic(last_committed_url) &&
+      !delegate_->IsSslCertificateValid();
+  if (invalid_ssl)
     LOG(ERROR) << "SSL certificate is not valid";
+
+  if (!allowed_origin || invalid_ssl) {
     // Don't show UI. Resolve .canMakepayment() with "false". Reject .show()
     // with "NotSupportedError".
     spec_ = base::MakeUnique<PaymentRequestSpec>(
@@ -99,6 +110,14 @@
     return;
   }
 
+  // A tab can display only one PaymentRequest UI at a time.
+  if (!manager_->CanShow(this)) {
+    LOG(ERROR) << "A PaymentRequest UI is already showing";
+    client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
+    OnConnectionTerminated();
+    return;
+  }
+
   if (!state_->AreRequestedMethodsSupported()) {
     client_->OnError(mojom::PaymentErrorReason::NOT_SUPPORTED);
     if (observer_for_testing_)
@@ -190,6 +209,8 @@
   // We close all bindings and ask to be destroyed.
   client_.reset();
   binding_.Close();
+  if (observer_for_testing_)
+    observer_for_testing_->OnConnectionTerminated();
   manager_->DestroyRequest(this);
 }
 
@@ -202,6 +223,8 @@
   client_.reset();
   binding_.Close();
   delegate_->CloseDialog();
+  if (observer_for_testing_)
+    observer_for_testing_->OnConnectionTerminated();
   manager_->DestroyRequest(this);
 }
 
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index c831cb91..2514a8b 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -39,6 +39,7 @@
    public:
     virtual void OnCanMakePaymentCalled() = 0;
     virtual void OnNotSupportedError() = 0;
+    virtual void OnConnectionTerminated() = 0;
 
    protected:
     virtual ~ObserverForTest() {}
diff --git a/components/payments/content/payment_request_web_contents_manager.cc b/components/payments/content/payment_request_web_contents_manager.cc
index 2f11a65..1c15003 100644
--- a/components/payments/content/payment_request_web_contents_manager.cc
+++ b/components/payments/content/payment_request_web_contents_manager.cc
@@ -40,10 +40,24 @@
 }
 
 void PaymentRequestWebContentsManager::DestroyRequest(PaymentRequest* request) {
+  if (request == showing_)
+    showing_ = nullptr;
   payment_requests_.erase(request);
 }
 
+bool PaymentRequestWebContentsManager::CanShow(PaymentRequest* request) {
+  DCHECK(request);
+  DCHECK(payment_requests_.find(request) != payment_requests_.end());
+  if (!showing_) {
+    showing_ = request;
+    return true;
+  } else {
+    return false;
+  }
+}
+
 PaymentRequestWebContentsManager::PaymentRequestWebContentsManager(
-    content::WebContents* web_contents) {}
+    content::WebContents* web_contents)
+    : showing_(nullptr) {}
 
 }  // namespace payments
diff --git a/components/payments/content/payment_request_web_contents_manager.h b/components/payments/content/payment_request_web_contents_manager.h
index be08a3d..1c000ec3 100644
--- a/components/payments/content/payment_request_web_contents_manager.h
+++ b/components/payments/content/payment_request_web_contents_manager.h
@@ -5,8 +5,8 @@
 #ifndef COMPONENTS_PAYMENTS_PAYMENT_REQUEST_WEB_CONTENTS_MANAGER_H_
 #define COMPONENTS_PAYMENTS_PAYMENT_REQUEST_WEB_CONTENTS_MANAGER_H_
 
+#include <map>
 #include <memory>
-#include <unordered_map>
 
 #include "base/macros.h"
 #include "components/payments/content/payment_request.h"
@@ -50,6 +50,12 @@
   // Destroys the given |request|.
   void DestroyRequest(PaymentRequest* request);
 
+  // Called when |request| has received the show() call. If the |request| can be
+  // shown, then returns true and assumes that |request| is now showing until
+  // DestroyRequest(|request|) is called with the same pointer. (Only one
+  // request at a time can be shown per tab.)
+  bool CanShow(PaymentRequest* request);
+
  private:
   explicit PaymentRequestWebContentsManager(content::WebContents* web_contents);
   friend class content::WebContentsUserData<PaymentRequestWebContentsManager>;
@@ -59,8 +65,11 @@
   // PaymentRequestWebContentsManager's lifetime is tied to the WebContents,
   // these requests only get destroyed when the WebContents goes away, or when
   // the requests themselves call DestroyRequest().
-  std::unordered_map<PaymentRequest*, std::unique_ptr<PaymentRequest>>
-      payment_requests_;
+  std::map<PaymentRequest*, std::unique_ptr<PaymentRequest>> payment_requests_;
+
+  // The currently displayed instance of PaymentRequest. Points to one of the
+  // elements in |payment_requests_|. Can be null.
+  PaymentRequest* showing_;
 
   DISALLOW_COPY_AND_ASSIGN(PaymentRequestWebContentsManager);
 };
diff --git a/components/suggestions/suggestions_service_impl_unittest.cc b/components/suggestions/suggestions_service_impl_unittest.cc
index ef75e2e..5643ed1 100644
--- a/components/suggestions/suggestions_service_impl_unittest.cc
+++ b/components/suggestions/suggestions_service_impl_unittest.cc
@@ -10,10 +10,10 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/feature_list.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
+#include "base/test/mock_callback.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
 #include "components/signin/core/browser/fake_signin_manager.h"
@@ -25,7 +25,6 @@
 #include "components/sync/driver/fake_sync_service.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "net/base/escape.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
 #include "net/url_request/test_url_fetcher_factory.h"
@@ -40,7 +39,6 @@
 using testing::AnyNumber;
 using testing::DoAll;
 using testing::Eq;
-using testing::NiceMock;
 using testing::Return;
 using testing::SetArgPointee;
 using testing::StrictMock;
@@ -169,21 +167,9 @@
 };
 
 class SuggestionsServiceTest : public testing::Test {
- public:
-  void CheckCallback(const SuggestionsProfile& suggestions_profile) {
-    ++suggestions_data_callback_count_;
-    if (suggestions_profile.suggestions_size() == 0)
-      ++suggestions_empty_data_count_;
-  }
-
-  int suggestions_data_callback_count_;
-  int suggestions_empty_data_count_;
-
  protected:
   SuggestionsServiceTest()
-      : suggestions_data_callback_count_(0),
-        suggestions_empty_data_count_(0),
-        signin_client_(&pref_service_),
+      : signin_client_(&pref_service_),
         signin_manager_(&signin_client_, &account_tracker_),
         factory_(nullptr, base::Bind(&CreateURLFetcher)),
         mock_thumbnail_manager_(nullptr),
@@ -255,17 +241,14 @@
 };
 
 TEST_F(SuggestionsServiceTest, FetchSuggestionsData) {
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
-
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
   // Set up net::FakeURLFetcherFactory.
   factory_.SetFakeResponse(SuggestionsServiceImpl::BuildSuggestionsURL(),
-                           suggestions_profile.SerializeAsString(),
+                           CreateSuggestionsProfile().SerializeAsString(),
                            net::HTTP_OK, net::URLRequestStatus::SUCCESS);
 
-  // Expectations.
   EXPECT_CALL(*mock_thumbnail_manager_, Initialize(_));
   EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_));
   EXPECT_CALL(*mock_blacklist_store_, GetTimeUntilReadyForUpload(_))
@@ -274,45 +257,41 @@
   // Send the request. The data should be returned to the callback.
   suggestions_service_->FetchSuggestionsData();
 
+  EXPECT_CALL(callback, Run(_));
+
   // Let the network request run.
   base::RunLoop().RunUntilIdle();
 
-  // Ensure that CheckCallback() ran once.
-  EXPECT_EQ(1, suggestions_data_callback_count_);
-
-  test_suggestions_store_->LoadSuggestions(&suggestions_profile);
-  ASSERT_EQ(1, suggestions_profile.suggestions_size());
-  EXPECT_EQ(kTestTitle, suggestions_profile.suggestions(0).title());
-  EXPECT_EQ(kTestUrl, suggestions_profile.suggestions(0).url());
-  EXPECT_EQ(kTestFaviconUrl, suggestions_profile.suggestions(0).favicon_url());
+  SuggestionsProfile suggestions;
+  test_suggestions_store_->LoadSuggestions(&suggestions);
+  ASSERT_EQ(1, suggestions.suggestions_size());
+  EXPECT_EQ(kTestTitle, suggestions.suggestions(0).title());
+  EXPECT_EQ(kTestUrl, suggestions.suggestions(0).url());
+  EXPECT_EQ(kTestFaviconUrl, suggestions.suggestions(0).favicon_url());
 }
 
 TEST_F(SuggestionsServiceTest, IgnoresNoopSyncChange) {
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
   factory_.SetFakeResponse(SuggestionsServiceImpl::BuildSuggestionsURL(),
-                           suggestions_profile.SerializeAsString(),
+                           CreateSuggestionsProfile().SerializeAsString(),
                            net::HTTP_OK, net::URLRequestStatus::SUCCESS);
 
   // An no-op change should not result in a suggestions refresh.
+  EXPECT_CALL(callback, Run(_)).Times(0);
   suggestions_service_->OnStateChanged(&mock_sync_service_);
 
   // Let any network request run (there shouldn't be one).
   base::RunLoop().RunUntilIdle();
-
-  // Ensure that we weren't called back.
-  EXPECT_EQ(0, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, IgnoresUninterestingSyncChange) {
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
   factory_.SetFakeResponse(SuggestionsServiceImpl::BuildSuggestionsURL(),
-                           suggestions_profile.SerializeAsString(),
+                           CreateSuggestionsProfile().SerializeAsString(),
                            net::HTTP_OK, net::URLRequestStatus::SUCCESS);
 
   // An uninteresting change should not result in a network request (the
@@ -321,73 +300,64 @@
       .Times(AnyNumber())
       .WillRepeatedly(Return(syncer::ModelTypeSet(
           syncer::HISTORY_DELETE_DIRECTIVES, syncer::BOOKMARKS)));
+  EXPECT_CALL(callback, Run(_)).Times(0);
   suggestions_service_->OnStateChanged(&mock_sync_service_);
 
   // Let any network request run (there shouldn't be one).
   base::RunLoop().RunUntilIdle();
-
-  // Ensure that we weren't called back.
-  EXPECT_EQ(0, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataSyncNotInitializedEnabled) {
   EXPECT_CALL(mock_sync_service_, IsSyncActive()).WillRepeatedly(Return(false));
   suggestions_service_->OnStateChanged(&mock_sync_service_);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
   // Try to fetch suggestions. Since sync is not active, no network request
   // should be sent.
+  EXPECT_CALL(callback, Run(_)).Times(0);
   suggestions_service_->FetchSuggestionsData();
 
-  // Let any network request run.
+  // Let any network request run (there shouldn't be one).
   base::RunLoop().RunUntilIdle();
 
-  // Ensure that CheckCallback() didn't run.
-  EXPECT_EQ(0, suggestions_data_callback_count_);
-
   // |test_suggestions_store_| should still contain the default values.
   SuggestionsProfile suggestions;
   test_suggestions_store_->LoadSuggestions(&suggestions);
-  EXPECT_EQ(CreateSuggestionsProfile().SerializeAsString(),
-            suggestions.SerializeAsString());
+  EXPECT_THAT(suggestions, EqualsProto(CreateSuggestionsProfile()));
 }
 
 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataSyncDisabled) {
   EXPECT_CALL(mock_sync_service_, CanSyncStart()).WillRepeatedly(Return(false));
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
   // Tell SuggestionsService that the sync state changed. The cache should be
   // cleared and empty data returned to the callback.
+  EXPECT_CALL(callback, Run(EqualsProto(SuggestionsProfile())));
   suggestions_service_->OnStateChanged(&mock_sync_service_);
 
-  // Ensure that CheckCallback ran once with empty data.
-  EXPECT_EQ(1, suggestions_data_callback_count_);
-  EXPECT_EQ(1, suggestions_empty_data_count_);
-
   // Try to fetch suggestions. Since sync is not active, no network request
   // should be sent.
   suggestions_service_->FetchSuggestionsData();
 
   // Let any network request run.
   base::RunLoop().RunUntilIdle();
-
-  // Ensure that CheckCallback didn't run again.
-  EXPECT_EQ(1, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataNoAccessToken) {
   token_service_.set_auto_post_fetch_response_on_message_loop(false);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
   EXPECT_CALL(*mock_blacklist_store_, GetTimeUntilReadyForUpload(_))
       .WillOnce(Return(false));
 
+  EXPECT_CALL(callback, Run(_)).Times(0);
+
   suggestions_service_->FetchSuggestionsData();
 
   token_service_.IssueErrorForAllPendingRequests(GoogleServiceAuthError(
@@ -396,7 +366,6 @@
   // No network request should be sent.
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(HasPendingSuggestionsRequest());
-  EXPECT_EQ(0, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, IssueRequestIfNoneOngoingError) {
@@ -439,17 +408,17 @@
 }
 
 TEST_F(SuggestionsServiceTest, BlacklistURL) {
-  base::TimeDelta no_delay = base::TimeDelta::FromSeconds(0);
+  const base::TimeDelta no_delay = base::TimeDelta::FromSeconds(0);
   suggestions_service_->set_blacklist_delay(no_delay);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  GURL blacklisted_url(kBlacklistedUrl);
-  GURL request_url(
+  const GURL blacklisted_url(kBlacklistedUrl);
+  const GURL request_url(
       SuggestionsServiceImpl::BuildSuggestionsBlacklistURL(blacklisted_url));
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
-  factory_.SetFakeResponse(request_url, suggestions_profile.SerializeAsString(),
+  factory_.SetFakeResponse(request_url,
+                           CreateSuggestionsProfile().SerializeAsString(),
                            net::HTTP_OK, net::URLRequestStatus::SUCCESS);
   EXPECT_CALL(*mock_thumbnail_manager_, Initialize(_)).Times(2);
 
@@ -465,8 +434,9 @@
   EXPECT_CALL(*mock_blacklist_store_, RemoveUrl(Eq(blacklisted_url)))
       .WillOnce(Return(true));
 
+  EXPECT_CALL(callback, Run(_)).Times(2);
+
   EXPECT_TRUE(suggestions_service_->BlacklistURL(blacklisted_url));
-  EXPECT_EQ(1, suggestions_data_callback_count_);
 
   // Wait on the upload task, the blacklist request and the next blacklist
   // scheduling task. This only works when the scheduling task is not for future
@@ -474,43 +444,40 @@
   // BlacklistStore's candidacy delay are zero).
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_EQ(2, suggestions_data_callback_count_);
-
-  test_suggestions_store_->LoadSuggestions(&suggestions_profile);
-  ASSERT_EQ(1, suggestions_profile.suggestions_size());
-  EXPECT_EQ(kTestTitle, suggestions_profile.suggestions(0).title());
-  EXPECT_EQ(kTestUrl, suggestions_profile.suggestions(0).url());
-  EXPECT_EQ(kTestFaviconUrl, suggestions_profile.suggestions(0).favicon_url());
+  SuggestionsProfile suggestions;
+  test_suggestions_store_->LoadSuggestions(&suggestions);
+  ASSERT_EQ(1, suggestions.suggestions_size());
+  EXPECT_EQ(kTestTitle, suggestions.suggestions(0).title());
+  EXPECT_EQ(kTestUrl, suggestions.suggestions(0).url());
+  EXPECT_EQ(kTestFaviconUrl, suggestions.suggestions(0).favicon_url());
 }
 
 TEST_F(SuggestionsServiceTest, BlacklistURLFails) {
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  GURL blacklisted_url(kBlacklistedUrl);
+  const GURL blacklisted_url(kBlacklistedUrl);
   EXPECT_CALL(*mock_blacklist_store_, BlacklistUrl(Eq(blacklisted_url)))
       .WillOnce(Return(false));
-
+  EXPECT_CALL(callback, Run(_)).Times(0);
   EXPECT_FALSE(suggestions_service_->BlacklistURL(blacklisted_url));
-
-  EXPECT_EQ(0, suggestions_data_callback_count_);
 }
 
 // Initial blacklist request fails, triggering a second which succeeds.
 TEST_F(SuggestionsServiceTest, BlacklistURLRequestFails) {
-  base::TimeDelta no_delay = base::TimeDelta::FromSeconds(0);
+  const base::TimeDelta no_delay = base::TimeDelta::FromSeconds(0);
   suggestions_service_->set_blacklist_delay(no_delay);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  GURL blacklisted_url(kBlacklistedUrl);
-  GURL request_url(
+  const GURL blacklisted_url(kBlacklistedUrl);
+  const GURL request_url(
       SuggestionsServiceImpl::BuildSuggestionsBlacklistURL(blacklisted_url));
-  GURL blacklisted_url_alt(kBlacklistedUrlAlt);
-  GURL request_url_alt(SuggestionsServiceImpl::BuildSuggestionsBlacklistURL(
-      blacklisted_url_alt));
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
+  const GURL blacklisted_url_alt(kBlacklistedUrlAlt);
+  const GURL request_url_alt(
+      SuggestionsServiceImpl::BuildSuggestionsBlacklistURL(
+          blacklisted_url_alt));
 
   // Note: we want to set the response for the blacklist URL to first
   // succeed, then fail. This doesn't seem possible. For simplicity of testing,
@@ -519,7 +486,7 @@
   factory_.SetFakeResponse(request_url, "irrelevant", net::HTTP_OK,
                            net::URLRequestStatus::FAILED);
   factory_.SetFakeResponse(request_url_alt,
-                           suggestions_profile.SerializeAsString(),
+                           CreateSuggestionsProfile().SerializeAsString(),
                            net::HTTP_OK, net::URLRequestStatus::SUCCESS);
 
   // Expectations.
@@ -537,9 +504,10 @@
   EXPECT_CALL(*mock_blacklist_store_, RemoveUrl(Eq(blacklisted_url_alt)))
       .WillOnce(Return(true));
 
+  EXPECT_CALL(callback, Run(_)).Times(2);
+
   // Blacklist call, first request attempt.
   EXPECT_TRUE(suggestions_service_->BlacklistURL(blacklisted_url));
-  EXPECT_EQ(1, suggestions_data_callback_count_);
 
   // Wait for the first scheduling, the first request, the second scheduling,
   // second request and the third scheduling. Again, note that calling
@@ -547,29 +515,29 @@
   // the future.
   base::RunLoop().RunUntilIdle();
 
-  test_suggestions_store_->LoadSuggestions(&suggestions_profile);
-  ASSERT_EQ(1, suggestions_profile.suggestions_size());
-  EXPECT_EQ(kTestTitle, suggestions_profile.suggestions(0).title());
-  EXPECT_EQ(kTestUrl, suggestions_profile.suggestions(0).url());
-  EXPECT_EQ(kTestFaviconUrl, suggestions_profile.suggestions(0).favicon_url());
+  SuggestionsProfile suggestions;
+  test_suggestions_store_->LoadSuggestions(&suggestions);
+  ASSERT_EQ(1, suggestions.suggestions_size());
+  EXPECT_EQ(kTestTitle, suggestions.suggestions(0).title());
+  EXPECT_EQ(kTestUrl, suggestions.suggestions(0).url());
+  EXPECT_EQ(kTestFaviconUrl, suggestions.suggestions(0).favicon_url());
 }
 
 TEST_F(SuggestionsServiceTest, UndoBlacklistURL) {
   // Ensure scheduling the request doesn't happen before undo.
-  base::TimeDelta delay = base::TimeDelta::FromHours(1);
+  const base::TimeDelta delay = base::TimeDelta::FromHours(1);
   suggestions_service_->set_blacklist_delay(delay);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
-  GURL blacklisted_url(kBlacklistedUrl);
+  const GURL blacklisted_url(kBlacklistedUrl);
 
   // Blacklist expectations.
   EXPECT_CALL(*mock_blacklist_store_, BlacklistUrl(Eq(blacklisted_url)))
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_thumbnail_manager_,
-              Initialize(EqualsProto(suggestions_profile)))
+              Initialize(EqualsProto(CreateSuggestionsProfile())))
       .Times(AnyNumber());
   EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_)).Times(AnyNumber());
   EXPECT_CALL(*mock_blacklist_store_, GetTimeUntilReadyForUpload(_))
@@ -581,22 +549,21 @@
   EXPECT_CALL(*mock_blacklist_store_, RemoveUrl(Eq(blacklisted_url)))
       .WillOnce(Return(true));
 
+  EXPECT_CALL(callback, Run(_)).Times(2);
   EXPECT_TRUE(suggestions_service_->BlacklistURL(blacklisted_url));
   EXPECT_TRUE(suggestions_service_->UndoBlacklistURL(blacklisted_url));
-
-  EXPECT_EQ(2, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, ClearBlacklist) {
   // Ensure scheduling the request doesn't happen before undo.
-  base::TimeDelta delay = base::TimeDelta::FromHours(1);
+  const base::TimeDelta delay = base::TimeDelta::FromHours(1);
   suggestions_service_->set_blacklist_delay(delay);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
-  GURL blacklisted_url(kBlacklistedUrl);
+  const SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
+  const GURL blacklisted_url(kBlacklistedUrl);
 
   factory_.SetFakeResponse(
       SuggestionsServiceImpl::BuildSuggestionsBlacklistClearURL(),
@@ -614,28 +581,26 @@
       .WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
   EXPECT_CALL(*mock_blacklist_store_, ClearBlacklist());
 
+  EXPECT_CALL(callback, Run(_)).Times(2);
   EXPECT_TRUE(suggestions_service_->BlacklistURL(blacklisted_url));
   suggestions_service_->ClearBlacklist();
-
-  EXPECT_EQ(2, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, UndoBlacklistURLFailsIfNotInBlacklist) {
   // Ensure scheduling the request doesn't happen before undo.
-  base::TimeDelta delay = base::TimeDelta::FromHours(1);
+  const base::TimeDelta delay = base::TimeDelta::FromHours(1);
   suggestions_service_->set_blacklist_delay(delay);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
-  GURL blacklisted_url(kBlacklistedUrl);
+  const GURL blacklisted_url(kBlacklistedUrl);
 
   // Blacklist expectations.
   EXPECT_CALL(*mock_blacklist_store_, BlacklistUrl(Eq(blacklisted_url)))
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_thumbnail_manager_,
-              Initialize(EqualsProto(suggestions_profile)));
+              Initialize(EqualsProto(CreateSuggestionsProfile())));
   EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_));
   EXPECT_CALL(*mock_blacklist_store_, GetTimeUntilReadyForUpload(_))
       .WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
@@ -645,28 +610,26 @@
               GetTimeUntilURLReadyForUpload(Eq(blacklisted_url), _))
       .WillOnce(Return(false));
 
+  EXPECT_CALL(callback, Run(_));
   EXPECT_TRUE(suggestions_service_->BlacklistURL(blacklisted_url));
   EXPECT_FALSE(suggestions_service_->UndoBlacklistURL(blacklisted_url));
-
-  EXPECT_EQ(1, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, UndoBlacklistURLFailsIfAlreadyCandidate) {
   // Ensure scheduling the request doesn't happen before undo.
-  base::TimeDelta delay = base::TimeDelta::FromHours(1);
+  const base::TimeDelta delay = base::TimeDelta::FromHours(1);
   suggestions_service_->set_blacklist_delay(delay);
 
-  auto subscription = suggestions_service_->AddCallback(base::Bind(
-      &SuggestionsServiceTest::CheckCallback, base::Unretained(this)));
+  base::MockCallback<SuggestionsService::ResponseCallback> callback;
+  auto subscription = suggestions_service_->AddCallback(callback.Get());
 
-  SuggestionsProfile suggestions_profile = CreateSuggestionsProfile();
-  GURL blacklisted_url(kBlacklistedUrl);
+  const GURL blacklisted_url(kBlacklistedUrl);
 
   // Blacklist expectations.
   EXPECT_CALL(*mock_blacklist_store_, BlacklistUrl(Eq(blacklisted_url)))
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_thumbnail_manager_,
-              Initialize(EqualsProto(suggestions_profile)));
+              Initialize(EqualsProto(CreateSuggestionsProfile())));
   EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_));
   EXPECT_CALL(*mock_blacklist_store_, GetTimeUntilReadyForUpload(_))
       .WillOnce(DoAll(SetArgPointee<0>(delay), Return(true)));
@@ -677,10 +640,9 @@
               GetTimeUntilURLReadyForUpload(Eq(blacklisted_url), _))
       .WillOnce(DoAll(SetArgPointee<1>(negative_delay), Return(true)));
 
+  EXPECT_CALL(callback, Run(_));
   EXPECT_TRUE(suggestions_service_->BlacklistURL(blacklisted_url));
   EXPECT_FALSE(suggestions_service_->UndoBlacklistURL(blacklisted_url));
-
-  EXPECT_EQ(1, suggestions_data_callback_count_);
 }
 
 TEST_F(SuggestionsServiceTest, GetBlacklistedUrlNotBlacklistRequest) {
@@ -695,10 +657,10 @@
 
 TEST_F(SuggestionsServiceTest, GetBlacklistedUrlBlacklistRequest) {
   // An actual blacklist request.
-  std::string blacklisted_url = "http://blacklisted.com/a?b=c&d=e";
-  std::string encoded_blacklisted_url =
+  const GURL blacklisted_url("http://blacklisted.com/a?b=c&d=e");
+  const std::string encoded_blacklisted_url =
       "http%3A%2F%2Fblacklisted.com%2Fa%3Fb%3Dc%26d%3De";
-  std::string blacklist_request_prefix(
+  const std::string blacklist_request_prefix(
       SuggestionsServiceImpl::BuildSuggestionsBlacklistURLPrefix());
   std::unique_ptr<net::FakeURLFetcher> fetcher(CreateURLFetcher(
       GURL(blacklist_request_prefix + encoded_blacklisted_url), nullptr, "",
@@ -706,11 +668,11 @@
   GURL retrieved_url;
   EXPECT_TRUE(
       SuggestionsServiceImpl::GetBlacklistedUrl(*fetcher, &retrieved_url));
-  EXPECT_EQ(blacklisted_url, retrieved_url.spec());
+  EXPECT_EQ(blacklisted_url, retrieved_url);
 }
 
 TEST_F(SuggestionsServiceTest, UpdateBlacklistDelay) {
-  base::TimeDelta initial_delay = suggestions_service_->blacklist_delay();
+  const base::TimeDelta initial_delay = suggestions_service_->blacklist_delay();
 
   // Delay unchanged on success.
   suggestions_service_->UpdateBlacklistDelay(true);
@@ -735,8 +697,8 @@
 }
 
 TEST_F(SuggestionsServiceTest, GetPageThumbnail) {
-  GURL test_url(kTestUrl);
-  GURL thumbnail_url("https://www.thumbnails.com/thumb.jpg");
+  const GURL test_url(kTestUrl);
+  const GURL thumbnail_url("https://www.thumbnails.com/thumb.jpg");
   base::Callback<void(const GURL&, const gfx::Image&)> dummy_callback;
 
   EXPECT_CALL(*mock_thumbnail_manager_, GetImageForURL(test_url, _));
diff --git a/content/browser/net/quota_policy_cookie_store.cc b/content/browser/net/quota_policy_cookie_store.cc
index dd3f359..438e1a06d 100644
--- a/content/browser/net/quota_policy_cookie_store.cc
+++ b/content/browser/net/quota_policy_cookie_store.cc
@@ -155,7 +155,8 @@
 
     if (!background_task_runner.get()) {
       background_task_runner = base::CreateSequencedTaskRunnerWithTraits(
-          {base::MayBlock(), base::TaskPriority::BACKGROUND});
+          {base::MayBlock(), base::TaskPriority::BACKGROUND,
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
     }
 
     scoped_refptr<net::SQLitePersistentCookieStore> sqlite_store(
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index 91b5d08..8f36295 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -70,3 +70,5 @@
 
     self.Flaky('Pixel_OffscreenCanvasTransferBeforeStyleResize',
               ['mac', 'linux'], bug=719186)
+    self.Flaky('Pixel_OffscreenCanvasTransferAfterStyleResize',
+              ['mac', 'win'], bug=719144)
diff --git a/extensions/renderer/worker_thread_dispatcher.cc b/extensions/renderer/worker_thread_dispatcher.cc
index 07f0a3a64..f3385752 100644
--- a/extensions/renderer/worker_thread_dispatcher.cc
+++ b/extensions/renderer/worker_thread_dispatcher.cc
@@ -5,6 +5,7 @@
 #include "extensions/renderer/worker_thread_dispatcher.h"
 
 #include "base/memory/ptr_util.h"
+#include "base/threading/platform_thread.h"
 #include "base/threading/thread_local.h"
 #include "base/values.h"
 #include "content/public/child/worker_thread.h"
@@ -25,20 +26,6 @@
 base::LazyInstance<base::ThreadLocalPointer<extensions::ServiceWorkerData>>::
     DestructorAtExit g_data_tls = LAZY_INSTANCE_INITIALIZER;
 
-void OnResponseOnWorkerThread(int request_id,
-                              bool succeeded,
-                              const std::unique_ptr<base::ListValue>& response,
-                              const std::string& error) {
-  // TODO(devlin): Using the RequestSender directly here won't work with
-  // NativeExtensionBindingsSystem (since there is no associated RequestSender
-  // in that case). We should instead be going
-  // ExtensionBindingsSystem::HandleResponse().
-  ServiceWorkerData* data = g_data_tls.Pointer()->Get();
-  WorkerThreadDispatcher::GetRequestSender()->HandleWorkerResponse(
-      request_id, data->service_worker_version_id(), succeeded, *response,
-      error);
-}
-
 ServiceWorkerData* GetServiceWorkerData() {
   ServiceWorkerData* data = g_data_tls.Pointer()->Get();
   DCHECK(data);
@@ -96,14 +83,51 @@
   return GetServiceWorkerData()->v8_schema_registry();
 }
 
+// static
+bool WorkerThreadDispatcher::HandlesMessageOnWorkerThread(
+    const IPC::Message& message) {
+  return message.type() == ExtensionMsg_ResponseWorker::ID;
+}
+
+// static
+void WorkerThreadDispatcher::ForwardIPC(int worker_thread_id,
+                                        const IPC::Message& message) {
+  WorkerThreadDispatcher::Get()->OnMessageReceivedOnWorkerThread(
+      worker_thread_id, message);
+}
+
 bool WorkerThreadDispatcher::OnControlMessageReceived(
     const IPC::Message& message) {
+  if (HandlesMessageOnWorkerThread(message)) {
+    int worker_thread_id = base::kInvalidThreadId;
+    bool found = base::PickleIterator(message).ReadInt(&worker_thread_id);
+    CHECK(found && worker_thread_id > 0);
+    base::TaskRunner* runner = GetTaskRunnerFor(worker_thread_id);
+    bool task_posted = runner->PostTask(
+        FROM_HERE, base::Bind(&WorkerThreadDispatcher::ForwardIPC,
+                              worker_thread_id, message));
+    DCHECK(task_posted) << "Could not PostTask IPC to worker thread.";
+    return true;
+  }
+  return false;
+}
+
+void WorkerThreadDispatcher::OnMessageReceivedOnWorkerThread(
+    int worker_thread_id,
+    const IPC::Message& message) {
+  CHECK_EQ(content::WorkerThread::GetCurrentId(), worker_thread_id);
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(WorkerThreadDispatcher, message)
     IPC_MESSAGE_HANDLER(ExtensionMsg_ResponseWorker, OnResponseWorker)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
-  return handled;
+  CHECK(handled);
+}
+
+base::TaskRunner* WorkerThreadDispatcher::GetTaskRunnerFor(
+    int worker_thread_id) {
+  base::AutoLock lock(task_runner_map_lock_);
+  return task_runner_map_[worker_thread_id];
 }
 
 bool WorkerThreadDispatcher::Send(IPC::Message* message) {
@@ -115,11 +139,14 @@
                                               bool succeeded,
                                               const base::ListValue& response,
                                               const std::string& error) {
-  content::WorkerThread::PostTask(
-      worker_thread_id,
-      base::Bind(&OnResponseOnWorkerThread, request_id, succeeded,
-                 // TODO(lazyboy): Can we avoid CreateDeepCopy()?
-                 base::Passed(response.CreateDeepCopy()), error));
+  // TODO(devlin): Using the RequestSender directly here won't work with
+  // NativeExtensionBindingsSystem (since there is no associated RequestSender
+  // in that case). We should instead be going
+  // ExtensionBindingsSystem::HandleResponse().
+  ServiceWorkerData* data = g_data_tls.Pointer()->Get();
+  WorkerThreadDispatcher::GetRequestSender()->HandleWorkerResponse(
+      request_id, data->service_worker_version_id(), succeeded, response,
+      error);
 }
 
 void WorkerThreadDispatcher::AddWorkerData(
@@ -140,6 +167,15 @@
         service_worker_version_id, std::move(bindings_system));
     g_data_tls.Pointer()->Set(new_data);
   }
+
+  int worker_thread_id = base::PlatformThread::CurrentId();
+  DCHECK_EQ(content::WorkerThread::GetCurrentId(), worker_thread_id);
+  {
+    base::AutoLock lock(task_runner_map_lock_);
+    auto* task_runner = base::ThreadTaskRunnerHandle::Get().get();
+    CHECK(task_runner);
+    task_runner_map_[worker_thread_id] = task_runner;
+  }
 }
 
 void WorkerThreadDispatcher::RemoveWorkerData(
@@ -150,6 +186,13 @@
     delete data;
     g_data_tls.Pointer()->Set(nullptr);
   }
+
+  int worker_thread_id = base::PlatformThread::CurrentId();
+  DCHECK_EQ(content::WorkerThread::GetCurrentId(), worker_thread_id);
+  {
+    base::AutoLock lock(task_runner_map_lock_);
+    task_runner_map_.erase(worker_thread_id);
+  }
 }
 
 }  // namespace extensions
diff --git a/extensions/renderer/worker_thread_dispatcher.h b/extensions/renderer/worker_thread_dispatcher.h
index 411fa34c..4a876b1 100644
--- a/extensions/renderer/worker_thread_dispatcher.h
+++ b/extensions/renderer/worker_thread_dispatcher.h
@@ -47,9 +47,17 @@
   void RemoveWorkerData(int64_t service_worker_version_id);
 
  private:
+  static bool HandlesMessageOnWorkerThread(const IPC::Message& message);
+  static void ForwardIPC(int worker_thread_id, const IPC::Message& message);
+
   // content::RenderThreadObserver:
   bool OnControlMessageReceived(const IPC::Message& message) override;
 
+  void OnMessageReceivedOnWorkerThread(int worker_thread_id,
+                                       const IPC::Message& message);
+
+  base::TaskRunner* GetTaskRunnerFor(int worker_thread_id);
+
   // IPC handlers.
   void OnResponseWorker(int worker_thread_id,
                         int request_id,
@@ -60,6 +68,10 @@
   // IPC sender. Belongs to the render thread, but thread safe.
   scoped_refptr<IPC::SyncMessageFilter> message_filter_;
 
+  using IDToTaskRunnerMap = std::map<base::PlatformThreadId, base::TaskRunner*>;
+  IDToTaskRunnerMap task_runner_map_;
+  base::Lock task_runner_map_lock_;
+
   DISALLOW_COPY_AND_ASSIGN(WorkerThreadDispatcher);
 };
 
diff --git a/services/service_manager/embedder/main.cc b/services/service_manager/embedder/main.cc
index f976be1..d20bb676 100644
--- a/services/service_manager/embedder/main.cc
+++ b/services/service_manager/embedder/main.cc
@@ -339,6 +339,19 @@
 #endif
   base::EnableTerminationOnOutOfMemory();
 
+#if defined(OS_LINUX)
+  // The various desktop environments set this environment variable that allows
+  // the dbus client library to connect directly to the bus. When this variable
+  // is not set (test environments like xvfb-run), the dbus client library will
+  // fall back to auto-launch mode. Auto-launch is dangerous as it can cause
+  // hangs (crbug.com/715658) . This one line disables the dbus auto-launch,
+  // by clobbering the DBUS_SESSION_BUS_ADDRESS env variable if not already set.
+  // The old auto-launch behavior, if needed, can be restored by setting
+  // DBUS_SESSION_BUS_ADDRESS="autolaunch:" before launching chrome.
+  const int kNoOverrideIfAlreadySet = 0;
+  setenv("DBUS_SESSION_BUS_ADDRESS", "disabled:", kNoOverrideIfAlreadySet);
+#endif
+
 #if defined(OS_WIN)
   base::win::RegisterInvalidParamHandler();
   ui::win::CreateATLModuleIfNeeded();
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 853e365..97b0a710 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -44,7 +44,7 @@
 
   defines = skia_for_chromium_defines
 
-  if (is_win) {
+  if (is_win || is_mac) {
     defines += [ "SK_FREETYPE_MINIMUM_RUNTIME_VERSION=(((FREETYPE_MAJOR) * 0x01000000) | ((FREETYPE_MINOR) * 0x00010000) | ((FREETYPE_PATCH) * 0x00000100))" ]
   }
 
@@ -323,6 +323,13 @@
   }
 
   # Select Skia ports.
+
+  # FreeType is needed everywhere, on Linux and Android as main font backend, on Windows and Mac as fallback backend for Variations.
+  sources += [
+    "//third_party/skia/src/ports/SkFontHost_FreeType.cpp",
+    "//third_party/skia/src/ports/SkFontHost_FreeType_common.cpp",
+  ]
+
   if (is_win) {
     sources += [
       "//third_party/skia/src/ports/SkFontHost_win.cpp",
@@ -368,19 +375,16 @@
     ]
   }
 
-  if (is_linux || is_android || is_win) {
+  if (is_win || is_mac) {
     sources += [
-      "//third_party/skia/src/ports/SkFontHost_FreeType.cpp",
-      "//third_party/skia/src/ports/SkFontHost_FreeType_common.cpp",
+      # Add the FreeType custom font manager as a fallback backend for variable fonts.
+      "//third_party/skia/src/ports/SkFontMgr_custom.cpp",
+      "//third_party/skia/src/ports/SkFontMgr_custom_empty.cpp",
     ]
   }
 
   if (is_win) {
     sources += [
-      # Add the custom FreeType font manager to instantiate variable fonts on Windows.
-      "//third_party/skia/src/ports/SkFontMgr_custom.cpp",
-      "//third_party/skia/src/ports/SkFontMgr_custom_empty.cpp",
-
       # Select the right BitmapPlatformDevice.
       "ext/raster_handle_allocator_win.cc",
     ]
@@ -405,6 +409,7 @@
     ":skia_opts",
     "//base",
     "//base/third_party/dynamic_annotations",
+    "//build/config/freetype",
   ]
 
   if (is_linux) {
@@ -412,7 +417,6 @@
       configs += [ "//build/config/linux/pangocairo" ]
     }
     deps += [
-      "//build/config/freetype",
       "//build/linux:fontconfig",
       "//third_party/expat",
       "//third_party/icu:icuuc",
@@ -426,10 +430,6 @@
     ]
   }
 
-  if (is_win || is_android) {
-    deps += [ "//build/config/freetype" ]
-  }
-
   if (skia_support_pdf) {
     deps += [
       "//third_party/sfntly",
diff --git a/third_party/WebKit/LayoutTests/NeverFixTests b/third_party/WebKit/LayoutTests/NeverFixTests
index a9ba3386..957fb04d 100644
--- a/third_party/WebKit/LayoutTests/NeverFixTests
+++ b/third_party/WebKit/LayoutTests/NeverFixTests
@@ -183,10 +183,6 @@
 
 # Variable system font only supported on Mac atm.
 crbug.com/670246 [ Linux Android Win Mac10.9 Mac10.10 ] fast/text/variable-fonts/variable-mac-system-font.html [ WontFix ]
-crbug.com/669453 [ Mac10.9 Mac10.10 ] fast/text/variable-fonts/variable-box-font.html [ WontFix ]
-crbug.com/669453 [ Mac10.9 Mac10.10 ] http/tests/webfont/variable-box-font-arraybuffer.html [ WontFix ]
-crbug.com/669453 [ Mac10.9 Mac10.10 ] virtual/mojo-loading/http/tests/webfont/variable-box-font-arraybuffer.html [ WontFix ]
-crbug.com/715900 [ Mac10.9 Mac10.10 ] fast/text/variable-fonts/variable-gsub.html [ WontFix ]
 
 # prefer_compositing_to_lcd_text causes things to get composited regardless of their opaqueness, causing the test to fail
 crbug.com/381840 virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-scroll-background-opaque-to-transparent.html [ WontFix ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 4650d1a2..e617d521 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -3351,7 +3351,6 @@
 crbug.com/716569 [ Android ] tables/mozilla_expected_failures/bugs/bug2479-5.html [ Failure ]
 
 # Sheriff failures 2017-05-02
-crbug.com/693065 [ Mac ] fast/text/variable-fonts/variable-gpos-m2b.html [ Failure ]
 crbug.com/717389 [ Linux Debug ] virtual/gpu/fast/canvas/canvas-clip-rule.html [ Crash ]
 crbug.com/717389 [ Linux Debug ] virtual/gpu/fast/canvas/canvas-path-context-clip.html [ Crash ]
 crbug.com/717389 [ Win7 Debug ] virtual/gpu/fast/canvas/canvas-clip-rule.html [ Timeout ]
diff --git a/third_party/WebKit/LayoutTests/W3CImportExpectations b/third_party/WebKit/LayoutTests/W3CImportExpectations
index 8bc977b3..5c34fb17 100644
--- a/third_party/WebKit/LayoutTests/W3CImportExpectations
+++ b/third_party/WebKit/LayoutTests/W3CImportExpectations
@@ -50,7 +50,8 @@
 ## Owners: mkwst@chromium.org
 # external/wpt/content-security-policy [ Pass ]
 external/wpt/cookies [ Skip ]
-external/wpt/cors [ Skip ]
+## Owners: blink-network-dev@chromium.org
+# external/wpt/cors [ Pass ]
 ## Owners: kojii@chromium.org,ksakamoto@chromium.org
 # external/wpt/css-font-loading [ Pass ]
 external/wpt/css/CSS1 [ Skip ]
@@ -257,7 +258,8 @@
 ## Owners: jrummell@chromium.org
 # external/wpt/encrypted-media [ Pass ]
 external/wpt/encrypted-media/Google [ Skip ]
-external/wpt/eventsource [ Skip ]
+## Owners: blink-network-dev@chromium.org
+# external/wpt/eventsource [ Pass ]
 external/wpt/ext-xhtml-pubid [ Skip ]
 ## Owners: tyoshino@chromium.org,yhirano@chromium.org,mkwst@chromium.org,japhet@chromium.org
 # external/wpt/fetch [ Pass ]
@@ -281,7 +283,8 @@
 external/wpt/html-longdesc [ Skip ]
 ## Owners: rijubrata.bhaumik@intel.com,mcasas@chromium.org
 # external/wpt/html-media-capture [ Pass ]
-external/wpt/http [ Skip ]
+## Owners: loading-dev@chromium.org
+# external/wpt/http [ Pass ]
 ## Owners: none; No tests in the directory.
 # external/wpt/images [ Pass ]
 external/wpt/imagebitmap-renderingcontext [ Skip ]
@@ -325,7 +328,8 @@
 ## Owners: chongz@chromium.org
 # external/wpt/pointerlock [ Pass ]
 external/wpt/presentation-api [ Skip ]
-external/wpt/progress-events [ Skip ]
+## Owners: blink-network-dev@chromium.org
+# external/wpt/progress-events [ Pass ]
 external/wpt/proximity [ Skip ]
 ## Owners: rob.buis@samsung.com
 # external/wpt/quirks-mode [ Pass ]
@@ -370,7 +374,8 @@
 # external/wpt/touch-events [ Pass ]
 ## Owners: dtapuska@chromium.org
 # external/wpt/uievents [ Pass ]
-external/wpt/url [ Skip ]
+## Owners: blink-network-dev@chromium.org
+# external/wpt/url [ Pass ]
 ## Owners: jsbell@chromium.org
 # external/wpt/user-timing [ Pass ]
 ## Owners: platform-capabilities@chromium.org
@@ -390,7 +395,8 @@
 # external/wpt/webmessaging [ Pass ]
 ## Owners: hta@chromium.org
 # external/wpt/webrtc [ Pass ]
-external/wpt/websockets [ Skip ]
+## Owners: blink-network-dev@chromium.org
+# external/wpt/websockets [ Pass ]
 ## Owners: michaeln@chromium.org,jsbell@chromium.org
 # external/wpt/webstorage [ Pass ]
 ## Owners: foolip@chromium.org,maksim.sisov@intel.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
new file mode 100644
index 0000000..d170ea0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest.show() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+promise_test(t => {
+  const request1 = new PaymentRequest([{
+    supportedMethods: ['basic-card'],
+  }], {
+    total: {
+      label: 'request1',
+      amount: {
+        currency: 'USD',
+        value: '1.00',
+      },
+    },
+  });
+  const request2 = new PaymentRequest([{
+    supportedMethods: ['basic-card'],
+  }], {
+    total: {
+      label: 'request2',
+      amount: {
+        currency: 'USD',
+        value: '1.00',
+      },
+    },
+  });
+  const result = promise_rejects(t, null, request1.show());
+  promise_rejects(t, 'AbortError', request2.show())
+    .then(t.step_func(() => request1.abort()));
+  return result;
+}, 'If the user agent\'s "payment request is showing" boolean is true, ' +
+   'then return a promise rejected with an "AbortError" DOMException.');
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/text/variable-fonts/variable-gpos-m2b.html b/third_party/WebKit/LayoutTests/fast/text/variable-fonts/variable-gpos-m2b.html
index cb49b8ac..ee87fbc 100644
--- a/third_party/WebKit/LayoutTests/fast/text/variable-fonts/variable-gpos-m2b.html
+++ b/third_party/WebKit/LayoutTests/fast/text/variable-fonts/variable-gpos-m2b.html
@@ -28,7 +28,7 @@
      with the combining box below. And it has a glyph for combining box below
      whose mark anchor can be shifted horizontally using the VM2B axis. The font
      also has N and O glyphs which have fixed shifted base anchor points at the
-     middle and at the right position. In this ref test we compare whether
+     middle and at the right position. In this ref test we check whether
      applying the VM2B axis works as expected and shifts the mark anchor point
      left so that the combining mark is placed correctly at the middle and at
      the right position. The VM2B rendering must be identical to the
diff --git a/third_party/WebKit/LayoutTests/http/tests/intersection-observer/cross-origin-iframe-with-nesting.html b/third_party/WebKit/LayoutTests/http/tests/intersection-observer/cross-origin-iframe-with-nesting.html
new file mode 100644
index 0000000..ef18c65
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/intersection-observer/cross-origin-iframe-with-nesting.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+
+<style>
+pre, #log {
+  position: absolute;
+  top: 0;
+  left: 200px;
+}
+iframe {
+  width: 200px;
+  height: 140px;
+  overflow-y: hidden;
+}
+.spacer {
+  height: 700px;
+}
+</style>
+
+<div class="spacer"></div>
+<iframe src="http://localhost:8080/intersection-observer/resources/nesting-cross-origin-subframe.html"></iframe>
+<div class="spacer"></div>
+
+<script>
+
+ // These helper functions are taken from
+ // intersection-observer/resources/intersection-observer-test-utils.js,
+ // which isn't available to http tests.
+function waitForNotification(f) {
+  requestAnimationFrame(function () {
+    setTimeout(function () { setTimeout(f); });
+  });
+}
+
+function checkRect(actual, expected, description) {
+  if (!expected.length)
+    return;
+  assert_equals(actual.left, expected[0], description + '.left');
+  assert_equals(actual.right, expected[1], description + '.right');
+  assert_equals(actual.top, expected[2], description + '.top');
+  assert_equals(actual.bottom, expected[3], description + '.bottom');
+}
+
+function checkJsonEntry(actual, expected) {
+  checkRect(
+      actual.boundingClientRect, expected.boundingClientRect,
+      'entry.boundingClientRect');
+  checkRect(
+      actual.intersectionRect, expected.intersectionRect,
+      'entry.intersectionRect');
+  if (actual.rootBounds == 'null')
+    assert_equals(expected.rootBounds, 'null', 'rootBounds is null');
+  else
+    checkRect(actual.rootBounds, expected.rootBounds, 'entry.rootBounds');
+  assert_equals(actual.target, expected.target);
+}
+
+function checkJsonEntries(actual, expected, description) {
+  test(function () {
+    assert_equals(actual.length, expected.length);
+    for (var i = 0; i < actual.length; i++)
+      checkJsonEntry(actual[i], expected[i]);
+  }, description);
+}
+
+async_test(function(t) {
+  var iframe = document.querySelector("iframe");
+
+  assert_equals(window.innerWidth, 800, "Window must be 800 pixels wide.");
+  assert_equals(window.innerHeight, 600, "Window must be 600 pixels high.");
+
+  function handleMessage(event) {
+    if (event.data.hasOwnProperty('ACK')) {
+      waitForNotification(function () { iframe.contentWindow.postMessage({ ACK: 1 }, "*"); },
+          "Message acknowledged");
+    } else if (event.data.hasOwnProperty('scrollTo')) {
+      document.scrollingElement.scrollTop = event.data.scrollTo;
+      waitForNotification(function() { iframe.contentWindow.postMessage("", "*"); },
+          "document.scrollingElement.scrollTop = " + event.data.scrollTo);
+    } else if (event.data.hasOwnProperty('actual')) {
+      checkJsonEntries(event.data.actual, event.data.expected, event.data.description);
+    } else if (event.data.hasOwnProperty('DONE')) {
+      document.scrollingElement.scrollTop = 0;
+      t.done();
+    } else {
+      var description = event.data.description;
+      waitForNotification(function() { iframe.contentWindow.postMessage("", "*"); }, description);
+    }
+  }
+
+  window.addEventListener("message", t.step_func(handleMessage));
+
+  iframe.onload = t.step_func(function() {
+    waitForNotification(function() { iframe.contentWindow.postMessage("", "*") }, "setup");
+  });
+}, "Intersection observer test with target in nested cross-origin subframes, potentially rendered in other processes.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/intersection-observer/resources/nested-cross-origin-subframe.html b/third_party/WebKit/LayoutTests/http/tests/intersection-observer/resources/nested-cross-origin-subframe.html
new file mode 100644
index 0000000..0bc615a4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/intersection-observer/resources/nested-cross-origin-subframe.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<div style="height: 200px; width: 100px;"></div>
+<div id="target" style="background-color: green; width:100px; height:100px"></div>
+<div style="height: 200px; width: 100px;"></div>
+
+<script>
+var port;
+var entries = [];
+var target = document.getElementById('target');
+var scroller = document.scrollingElement;
+var nextStep;
+var waiting_for_ack = false;
+
+function clientRectToJson(rect) {
+  if (!rect)
+    return "null";
+  return {
+    top: rect.top,
+    right: rect.right,
+    bottom: rect.bottom,
+    left: rect.left
+  };
+}
+
+function entryToJson(entry) {
+  return {
+    boundingClientRect: clientRectToJson(entry.boundingClientRect),
+    intersectionRect: clientRectToJson(entry.intersectionRect),
+    rootBounds: clientRectToJson(entry.rootBounds),
+    target: entry.target.id
+  };
+}
+
+function coordinatesToClientRectJson(top, right, bottom, left) {
+  return {
+    top: top,
+    right: right,
+    bottom: bottom,
+    left: left
+  };
+}
+
+// Use a rootMargin here, and verify it does NOT get applied for the cross-origin case.
+var observer = new IntersectionObserver(function(changes) {
+    entries = entries.concat(changes)
+}, { rootMargin: "7px" });
+observer.observe(target);
+
+function step0() {
+  entries = entries.concat(observer.takeRecords());
+  nextStep = step1;
+  var expected = [{
+    boundingClientRect: coordinatesToClientRectJson(8, 208, 108, 308),
+    intersectionRect: coordinatesToClientRectJson(0, 0, 0, 0),
+    rootBounds: "null",
+    target: target.id
+  }];
+  port.postMessage({
+    actual: entries.map(entryToJson),
+    expected: expected,
+    description: "First rAF"
+  }, "*");
+  entries = [];
+  port.postMessage({scrollTo: 200}, "*");
+}
+
+function step1() {
+  entries = entries.concat(observer.takeRecords());
+  port.postMessage({
+    actual: entries.map(entryToJson),
+    expected: [],
+    description: "topDocument.scrollingElement.scrollTop = 200"
+  }, "*");
+  entries = [];
+  scroller.scrollTop = 250;
+  nextStep = step2;
+  port.postMessage({}, "*");
+}
+
+function step2() {
+  entries = entries.concat(observer.takeRecords());
+  var expected = [{
+    boundingClientRect: coordinatesToClientRectJson(-42, 108, 58, 8),
+    intersectionRect: coordinatesToClientRectJson(0, 108, 58, 8),
+    rootBounds: "null",
+    target: target.id
+  }];
+  port.postMessage({
+    actual: entries.map(entryToJson),
+    expected: expected,
+    description: "iframeDocument.scrollingElement.scrollTop = 250"
+  }, "*");
+  entries = [];
+  nextStep = step3;
+  port.postMessage({scrollTo: 100}, "*");
+}
+
+function step3() {
+  entries = entries.concat(observer.takeRecords());
+  var expected = [{
+    boundingClientRect: coordinatesToClientRectJson(-42, 108, 58, 8),
+    intersectionRect: coordinatesToClientRectJson(0, 0, 0, 0),
+    rootBounds: "null",
+    target: target.id
+  }];
+  port.postMessage({
+    actual: entries.map(entryToJson),
+    expected: expected,
+    description: "topDocument.scrollingElement.scrollTop = 100"
+  }, "*");
+  port.postMessage({DONE: 1}, "*");
+}
+
+function handleMessage(event)
+{
+  port = event.source;
+  // The ACKs serve the purpose to create an extra full postMessage round trip
+  // delay before taking IntersectionObserver records. This accounts for a
+  // race condition that exists when this is an out-of-process iframe: the
+  // postMessage can trigger takeRecords before new intersections have been
+  // calculated. requestAnimationFrame() is not available here because of
+  // frame throttling in the same-process scenario.
+  if (event.data.hasOwnProperty('ACK')) {
+    nextStep();
+  } else {
+    port.postMessage({ ACK: 1 }, "*");
+  }
+}
+
+nextStep = step0;
+window.addEventListener("message", handleMessage);
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/intersection-observer/resources/nesting-cross-origin-subframe.html b/third_party/WebKit/LayoutTests/http/tests/intersection-observer/resources/nesting-cross-origin-subframe.html
new file mode 100644
index 0000000..cf40dd3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/intersection-observer/resources/nesting-cross-origin-subframe.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<style>
+iframe {
+  position:absolute;
+  top: 0px;
+  left: 0px;
+  width: 160px;
+  height: 100px;
+  overflow-y: scroll;
+}
+</style>
+<iframe src="http://127.0.0.1:8080/intersection-observer/resources/nested-cross-origin-subframe.html"></iframe>
+
+<script>
+var iframe = document.querySelector("iframe");
+var port;
+var iframe_loaded = false;
+var start_message_queued = false;
+// This frame forwards messages bidirectionally between its parent frame and
+// its child frame. The initiating message comes from the parent, but it
+// needs to be held if the child frame is not yet finished loading. The
+// booleans above account for the different possible orderings of events.
+function handleMessage(event) {
+  if (event.source == iframe.contentWindow) {
+    port.postMessage(event.data, "*");
+  } else if (iframe_loaded){
+    port = event.source;
+    iframe.contentWindow.postMessage(event.data, "*");
+  } else {
+    port = event.source;
+    start_message_queued = true;
+  }
+}
+window.addEventListener("message", handleMessage);
+iframe.onload = function () {
+    iframe_loaded = true;
+    if (start_message_queued) {
+      iframe.contentWindow.postMessage("", "*");
+      start_message_queued = false;
+    }
+}
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/webfont/variable-box-font-arraybuffer-expected.html b/third_party/WebKit/LayoutTests/http/tests/webfont/variable-box-font-arraybuffer-expected.html
index 9adfa624..753237c 100644
--- a/third_party/WebKit/LayoutTests/http/tests/webfont/variable-box-font-arraybuffer-expected.html
+++ b/third_party/WebKit/LayoutTests/http/tests/webfont/variable-box-font-arraybuffer-expected.html
@@ -10,5 +10,10 @@
     font-family: variabletest_box, sans-serif;
     font-size: 200px;
     }
+
+    .variations_codepath_trigger {
+    font-variation-settings: "none" 9999;
+    }
+
 </style>
-â–„ â–€
+â–„ <span class="variations_codepath_trigger">â–€</span>
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp
index cd578f3..883b2e6 100644
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -4926,8 +4926,11 @@
 
   for (Frame* child = frame_->Tree().FirstChild(); child;
        child = child->Tree().NextSibling()) {
-    if (!child->IsLocalFrame())
+    if (child->IsRemoteFrame()) {
+      if (RemoteFrameView* view = ToRemoteFrame(child)->View())
+        view->UpdateRemoteViewportIntersection();
       continue;
+    }
     if (FrameView* view = ToLocalFrame(child)->View())
       view->UpdateViewportIntersectionsForSubtree(target_state);
   }
diff --git a/third_party/WebKit/Source/core/frame/RemoteFrameView.cpp b/third_party/WebKit/Source/core/frame/RemoteFrameView.cpp
index d4dc05f..30f847e 100644
--- a/third_party/WebKit/Source/core/frame/RemoteFrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/RemoteFrameView.cpp
@@ -59,21 +59,24 @@
   // even if there are RemoteFrame ancestors in the frame tree.
   LayoutRect rect(0, 0, frame_rect_.Width(), frame_rect_.Height());
   rect.Move(remote_frame_->OwnerLayoutObject()->ContentBoxOffset());
-  if (!remote_frame_->OwnerLayoutObject()->MapToVisualRectInAncestorSpace(
-          nullptr, rect))
-    return;
-  IntRect root_visible_rect = local_root_view->VisibleContentRect();
-  IntRect viewport_intersection(rect);
-  viewport_intersection.Intersect(root_visible_rect);
-  viewport_intersection.Move(-local_root_view->ScrollOffsetInt());
+  IntRect viewport_intersection;
+  if (remote_frame_->OwnerLayoutObject()->MapToVisualRectInAncestorSpace(
+          nullptr, rect)) {
+    IntRect root_visible_rect = local_root_view->VisibleContentRect();
+    IntRect intersected_rect(rect);
+    intersected_rect.Intersect(root_visible_rect);
+    intersected_rect.Move(-local_root_view->ScrollOffsetInt());
 
-  // Translate the intersection rect from the root frame's coordinate space
-  // to the remote frame's coordinate space.
-  viewport_intersection = ConvertFromRootFrame(viewport_intersection);
+    // Translate the intersection rect from the root frame's coordinate space
+    // to the remote frame's coordinate space.
+    viewport_intersection = ConvertFromRootFrame(intersected_rect);
+  }
+
   if (viewport_intersection != last_viewport_intersection_) {
     remote_frame_->Client()->UpdateRemoteViewportIntersection(
         viewport_intersection);
   }
+
   last_viewport_intersection_ = viewport_intersection;
 }
 
@@ -112,8 +115,6 @@
   if (parent_)
     new_rect = parent_->ConvertToRootFrame(parent_->ContentsToFrame(new_rect));
   remote_frame_->Client()->FrameRectsChanged(new_rect);
-
-  UpdateRemoteViewportIntersection();
 }
 
 void RemoteFrameView::Hide() {
diff --git a/third_party/WebKit/Source/core/frame/RemoteFrameView.h b/third_party/WebKit/Source/core/frame/RemoteFrameView.h
index 90266252..bfb93c60 100644
--- a/third_party/WebKit/Source/core/frame/RemoteFrameView.h
+++ b/third_party/WebKit/Source/core/frame/RemoteFrameView.h
@@ -50,13 +50,13 @@
 
   IntRect ConvertFromContainingFrameViewBase(const IntRect&) const override;
 
+  void UpdateRemoteViewportIntersection();
+
   DECLARE_VIRTUAL_TRACE();
 
  private:
   explicit RemoteFrameView(RemoteFrame*);
 
-  void UpdateRemoteViewportIntersection();
-
   // The properties and handling of the cycle between RemoteFrame
   // and its RemoteFrameView corresponds to that between LocalFrame
   // and FrameView. Please see the FrameView::m_frame comment for
diff --git a/third_party/WebKit/Source/core/layout/LayoutText.cpp b/third_party/WebKit/Source/core/layout/LayoutText.cpp
index 6e007fd6..bedd7fc 100644
--- a/third_party/WebKit/Source/core/layout/LayoutText.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutText.cpp
@@ -1024,7 +1024,7 @@
     EWordBreak break_all_or_break_word) {
   DCHECK_GT(length, 0);
   LazyLineBreakIterator break_iterator(layout_text->GetText(),
-                                       LocaleForLineBreakIterator(style));
+                                       style.LocaleForLineBreakIterator());
   int next_breakable = -1;
   float min = std::numeric_limits<float>::max();
   int end = start + length;
@@ -1094,30 +1094,6 @@
   return max_fragment_width + layout_text->HyphenWidth(font, text_direction);
 }
 
-AtomicString LocaleForLineBreakIterator(const ComputedStyle& style) {
-  LineBreakIteratorMode mode = LineBreakIteratorMode::kDefault;
-  switch (style.GetLineBreak()) {
-    default:
-      NOTREACHED();
-    // Fall through.
-    case kLineBreakAuto:
-    case kLineBreakAfterWhiteSpace:
-      return style.Locale();
-    case kLineBreakNormal:
-      mode = LineBreakIteratorMode::kNormal;
-      break;
-    case kLineBreakStrict:
-      mode = LineBreakIteratorMode::kStrict;
-      break;
-    case kLineBreakLoose:
-      mode = LineBreakIteratorMode::kLoose;
-      break;
-  }
-  if (const LayoutLocale* locale = style.GetFontDescription().Locale())
-    return locale->LocaleWithBreakKeyword(mode);
-  return style.Locale();
-}
-
 void LayoutText::ComputePreferredLogicalWidths(
     float lead_width,
     HashSet<const SimpleFontData*>& fallback_fonts,
@@ -1147,7 +1123,7 @@
   float word_spacing = style_to_use.WordSpacing();
   int len = TextLength();
   LazyLineBreakIterator break_iterator(
-      text_, LocaleForLineBreakIterator(style_to_use));
+      text_, style_to_use.LocaleForLineBreakIterator());
   bool needs_word_spacing = false;
   bool ignoring_spaces = false;
   bool is_space = false;
diff --git a/third_party/WebKit/Source/core/layout/LayoutText.h b/third_party/WebKit/Source/core/layout/LayoutText.h
index 6462cadf..22c4160f 100644
--- a/third_party/WebKit/Source/core/layout/LayoutText.h
+++ b/third_party/WebKit/Source/core/layout/LayoutText.h
@@ -360,7 +360,6 @@
 }
 
 void ApplyTextTransform(const ComputedStyle*, String&, UChar);
-AtomicString LocaleForLineBreakIterator(const ComputedStyle&);
 
 }  // namespace blink
 
diff --git a/third_party/WebKit/Source/core/layout/line/BreakingContextInlineHeaders.h b/third_party/WebKit/Source/core/layout/line/BreakingContextInlineHeaders.h
index 59641fc6..84f7cac 100644
--- a/third_party/WebKit/Source/core/layout/line/BreakingContextInlineHeaders.h
+++ b/third_party/WebKit/Source/core/layout/line/BreakingContextInlineHeaders.h
@@ -744,7 +744,7 @@
                                                    int start,
                                                    int end) {
   LazyLineBreakIterator line_break_iterator(text.GetText(),
-                                            LocaleForLineBreakIterator(style));
+                                            style.LocaleForLineBreakIterator());
   int last_breakable_position = 0, next_breakable_position = -1;
   for (int i = start;; i = next_breakable_position + 1) {
     line_break_iterator.IsBreakable(i, next_breakable_position,
@@ -804,7 +804,7 @@
   int end = CanMidWordBreakBefore(text) ? start : start + 1;
   if (break_all) {
     LazyLineBreakIterator line_break_iterator(
-        text.GetText(), LocaleForLineBreakIterator(style));
+        text.GetText(), style.LocaleForLineBreakIterator());
     int next_breakable = -1;
     line_break_iterator.IsBreakable(end, next_breakable,
                                     LineBreakType::kBreakAll);
@@ -996,7 +996,7 @@
     layout_text_info_.text_ = layout_text;
     layout_text_info_.font_ = &font;
     layout_text_info_.line_break_iterator_.ResetStringAndReleaseIterator(
-        layout_text.GetText(), LocaleForLineBreakIterator(style));
+        layout_text.GetText(), style.LocaleForLineBreakIterator());
   } else if (layout_text_info_.font_ != &font) {
     layout_text_info_.font_ = &font;
   }
diff --git a/third_party/WebKit/Source/core/style/ComputedStyle.cpp b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
index 522a8a5..73ee14e 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyle.cpp
+++ b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
@@ -1583,6 +1583,27 @@
     it->value.ClearReset();
 }
 
+AtomicString ComputedStyle::LocaleForLineBreakIterator() const {
+  LineBreakIteratorMode mode = LineBreakIteratorMode::kDefault;
+  switch (GetLineBreak()) {
+    case kLineBreakAuto:
+    case kLineBreakAfterWhiteSpace:
+      return Locale();
+    case kLineBreakNormal:
+      mode = LineBreakIteratorMode::kNormal;
+      break;
+    case kLineBreakStrict:
+      mode = LineBreakIteratorMode::kStrict;
+      break;
+    case kLineBreakLoose:
+      mode = LineBreakIteratorMode::kLoose;
+      break;
+  }
+  if (const LayoutLocale* locale = GetFontDescription().Locale())
+    return locale->LocaleWithBreakKeyword(mode);
+  return Locale();
+}
+
 Hyphenation* ComputedStyle::GetHyphenation() const {
   return GetHyphens() == kHyphensAuto
              ? GetFontDescription().LocaleOrDefault().GetHyphenation()
diff --git a/third_party/WebKit/Source/core/style/ComputedStyle.h b/third_party/WebKit/Source/core/style/ComputedStyle.h
index d0b5313f..ab0afd03 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyle.h
+++ b/third_party/WebKit/Source/core/style/ComputedStyle.h
@@ -1995,6 +1995,7 @@
   const AtomicString& Locale() const {
     return LayoutLocale::LocaleString(GetFontDescription().Locale());
   }
+  AtomicString LocaleForLineBreakIterator() const;
 
   // FIXME: Remove letter-spacing/word-spacing and replace them with respective
   // FontBuilder calls.  letter-spacing
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn
index 1dc359e..8d3fd21 100644
--- a/third_party/WebKit/Source/platform/BUILD.gn
+++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -711,6 +711,8 @@
     "fonts/linux/FontPlatformDataLinux.cpp",
     "fonts/linux/FontRenderStyle.cpp",
     "fonts/linux/FontRenderStyle.h",
+    "fonts/mac/CoreTextVariationsSupport.cpp",
+    "fonts/mac/CoreTextVariationsSupport.h",
     "fonts/mac/FontCacheMac.mm",
     "fonts/mac/FontFamilyMatcherMac.h",
     "fonts/mac/FontFamilyMatcherMac.mm",
diff --git a/third_party/WebKit/Source/platform/fonts/FontCustomPlatformData.cpp b/third_party/WebKit/Source/platform/fonts/FontCustomPlatformData.cpp
index 8298ac2..2307530 100644
--- a/third_party/WebKit/Source/platform/fonts/FontCustomPlatformData.cpp
+++ b/third_party/WebKit/Source/platform/fonts/FontCustomPlatformData.cpp
@@ -37,10 +37,13 @@
 #include "platform/fonts/FontCache.h"
 #include "platform/fonts/FontPlatformData.h"
 #include "platform/fonts/WebFontDecoder.h"
+#if OS(MACOSX)
+#include "platform/fonts/mac/CoreTextVariationsSupport.h"
+#endif
 #include "platform/fonts/opentype/FontSettings.h"
 #include "third_party/skia/include/core/SkStream.h"
 #include "third_party/skia/include/core/SkTypeface.h"
-#if OS(WIN)
+#if OS(WIN) || OS(MACOSX)
 #include "third_party/skia/include/ports/SkFontMgr_empty.h"
 #endif
 #include "platform/wtf/PtrUtil.h"
@@ -74,6 +77,12 @@
   if (variation_settings && variation_settings->size() < UINT16_MAX) {
 #if OS(WIN)
     sk_sp<SkFontMgr> fm(SkFontMgr_New_Custom_Empty());
+#elif OS(MACOSX)
+    sk_sp<SkFontMgr> fm;
+    if (CoreTextVersionSupportsVariations())
+      fm = SkFontMgr::RefDefault();
+    else
+      fm = SkFontMgr_New_Custom_Empty();
 #else
     sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
 #endif
diff --git a/third_party/WebKit/Source/platform/fonts/FontPlatformData.cpp b/third_party/WebKit/Source/platform/fonts/FontPlatformData.cpp
index bbac096..a8001579 100644
--- a/third_party/WebKit/Source/platform/fonts/FontPlatformData.cpp
+++ b/third_party/WebKit/Source/platform/fonts/FontPlatformData.cpp
@@ -167,6 +167,8 @@
 };
 
 CGFontRef FontPlatformData::CgFont() const {
+  if (!CtFont())
+    return nullptr;
   return CTFontCopyGraphicsFont(CtFont(), 0);
 }
 #endif
diff --git a/third_party/WebKit/Source/platform/fonts/FontPlatformData.h b/third_party/WebKit/Source/platform/fonts/FontPlatformData.h
index 86b2108..d159487c 100644
--- a/third_party/WebKit/Source/platform/fonts/FontPlatformData.h
+++ b/third_party/WebKit/Source/platform/fonts/FontPlatformData.h
@@ -110,6 +110,10 @@
   ~FontPlatformData();
 
 #if OS(MACOSX)
+  // These methods return a nullptr for FreeType backed SkTypefaces, compare
+  // FontCustomPlatformData, which are used for variable fonts on Mac OS <
+  // 10.12. They should not return nullptr otherwise. So they allow
+  // distinguishing which backend the SkTypeface is using.
   CTFontRef CtFont() const;
   CGFontRef CgFont() const;
 #endif
diff --git a/third_party/WebKit/Source/platform/fonts/mac/CoreTextVariationsSupport.cpp b/third_party/WebKit/Source/platform/fonts/mac/CoreTextVariationsSupport.cpp
new file mode 100644
index 0000000..f41d251b
--- /dev/null
+++ b/third_party/WebKit/Source/platform/fonts/mac/CoreTextVariationsSupport.cpp
@@ -0,0 +1,20 @@
+// 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 "CoreTextVariationsSupport.h"
+
+#include <CoreText/CoreText.h>
+
+namespace blink {
+
+// Compare CoreText.h in an up to date SDK, redefining here since we don't seem
+// to have access to this value when building against the 10.10 SDK in our
+// standard Chrome build configuration.
+static const long kBlinkLocalCTVersionNumber10_12 = 0x00090000;
+
+bool CoreTextVersionSupportsVariations() {
+  return &CTGetCoreTextVersion &&
+         CTGetCoreTextVersion() >= kBlinkLocalCTVersionNumber10_12;
+}
+}
diff --git a/third_party/WebKit/Source/platform/fonts/mac/CoreTextVariationsSupport.h b/third_party/WebKit/Source/platform/fonts/mac/CoreTextVariationsSupport.h
new file mode 100644
index 0000000..0aee34c
--- /dev/null
+++ b/third_party/WebKit/Source/platform/fonts/mac/CoreTextVariationsSupport.h
@@ -0,0 +1,13 @@
+// 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 CoreTextVariationsSupport_h
+#define CoreTextVariationsSupport_h
+
+namespace blink {
+
+bool CoreTextVersionSupportsVariations();
+}
+
+#endif
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzFace.cpp b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzFace.cpp
index dc0d4426..faf1f1e6 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzFace.cpp
+++ b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzFace.cpp
@@ -307,7 +307,6 @@
   return harf_buzz_skia_font_funcs;
 }
 
-#if !OS(MACOSX)
 static hb_blob_t* HarfBuzzSkiaGetTable(hb_face_t* face,
                                        hb_tag_t tag,
                                        void* user_data) {
@@ -331,20 +330,25 @@
                         HB_MEMORY_MODE_WRITABLE, buffer,
                         WTF::Partitions::FastFree);
 }
-#endif
 
-#if !OS(MACOSX)
 static void DeleteTypefaceStream(void* stream_asset_ptr) {
   SkStreamAsset* stream_asset =
       reinterpret_cast<SkStreamAsset*>(stream_asset_ptr);
   delete stream_asset;
 }
-#endif
 
 hb_face_t* HarfBuzzFace::CreateFace() {
 #if OS(MACOSX)
-  hb_face_t* face = hb_coretext_face_create(platform_data_->CgFont());
-#else
+  // hb_face_t needs to be instantiated using the CoreText constructor for
+  // compatibility with AAT font, in which case HarfBuzz' CoreText backend is
+  // used. If we encounter a FreeType backed SkTypeface, for variable fonts on
+  // Mac OS < 10.12, follow the regular OpenType-only codepath below.
+  if (platform_data_->CgFont()) {
+    hb_face_t* face = hb_coretext_face_create(platform_data_->CgFont());
+    DCHECK(face);
+    return face;
+  }
+#endif
   hb_face_t* face = nullptr;
 
   DEFINE_STATIC_LOCAL(BooleanHistogram, zero_copy_success_histogram,
@@ -371,7 +375,7 @@
   } else {
     zero_copy_success_histogram.Count(true);
   }
-#endif
+
   DCHECK(face);
   return face;
 }
diff --git a/ui/arc/BUILD.gn b/ui/arc/BUILD.gn
index 56b3477..bcefece8 100644
--- a/ui/arc/BUILD.gn
+++ b/ui/arc/BUILD.gn
@@ -7,12 +7,13 @@
 
 static_library("arc") {
   sources = [
-    "notification/arc_custom_notification_item.cc",
-    "notification/arc_custom_notification_item.h",
     "notification/arc_custom_notification_view.cc",
     "notification/arc_custom_notification_view.h",
-    "notification/arc_notification_item.cc",
+    "notification/arc_notification_delegate.cc",
+    "notification/arc_notification_delegate.h",
     "notification/arc_notification_item.h",
+    "notification/arc_notification_item_impl.cc",
+    "notification/arc_notification_item_impl.h",
     "notification/arc_notification_manager.cc",
     "notification/arc_notification_manager.h",
     "notification/arc_notification_surface_manager.cc",
diff --git a/ui/arc/notification/arc_custom_notification_item.cc b/ui/arc/notification/arc_custom_notification_item.cc
deleted file mode 100644
index 521145d..0000000
--- a/ui/arc/notification/arc_custom_notification_item.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2016 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 "ui/arc/notification/arc_custom_notification_item.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "base/memory/ptr_util.h"
-#include "base/strings/string16.h"
-#include "base/strings/utf_string_conversions.h"
-#include "ui/arc/notification/arc_custom_notification_view.h"
-#include "ui/message_center/notification.h"
-#include "ui/message_center/notification_types.h"
-#include "ui/message_center/views/custom_notification_content_view_delegate.h"
-
-namespace arc {
-
-namespace {
-
-constexpr char kNotifierId[] = "ARC_NOTIFICATION";
-
-class ArcNotificationDelegate : public message_center::NotificationDelegate {
- public:
-  explicit ArcNotificationDelegate(ArcCustomNotificationItem* item)
-      : item_(item) {
-    DCHECK(item_);
-  }
-
-  std::unique_ptr<message_center::CustomContent> CreateCustomContent()
-      override {
-    auto view = base::MakeUnique<ArcCustomNotificationView>(item_);
-    auto content_view_delegate = view->CreateContentViewDelegate();
-    return base::MakeUnique<message_center::CustomContent>(
-        std::move(view), std::move(content_view_delegate));
-  }
-
-  void Close(bool by_user) override { item_->Close(by_user); }
-
-  void Click() override { item_->Click(); }
-
- private:
-  // The destructor is private since this class is ref-counted.
-  ~ArcNotificationDelegate() override {}
-
-  ArcCustomNotificationItem* const item_;
-
-  DISALLOW_COPY_AND_ASSIGN(ArcNotificationDelegate);
-};
-
-}  // namespace
-
-ArcCustomNotificationItem::ArcCustomNotificationItem(
-    ArcNotificationManager* manager,
-    message_center::MessageCenter* message_center,
-    const std::string& notification_key,
-    const AccountId& profile_id)
-    : ArcNotificationItem(manager,
-                          message_center,
-                          notification_key,
-                          profile_id) {
-}
-
-ArcCustomNotificationItem::~ArcCustomNotificationItem() {
-  for (auto& observer : observers_)
-    observer.OnItemDestroying();
-}
-
-void ArcCustomNotificationItem::UpdateWithArcNotificationData(
-    mojom::ArcNotificationDataPtr data) {
-  DCHECK(CalledOnValidThread());
-  DCHECK_EQ(notification_key(), data->key);
-
-  if (HasPendingNotification()) {
-    CacheArcNotificationData(std::move(data));
-    return;
-  }
-
-  message_center::RichNotificationData rich_data;
-  rich_data.pinned = (data->no_clear || data->ongoing_event);
-  rich_data.priority = ConvertAndroidPriority(data->priority);
-  if (data->small_icon)
-    rich_data.small_image = gfx::Image::CreateFrom1xBitmap(*data->small_icon);
-  if (data->accessible_name.has_value())
-    rich_data.accessible_name = base::UTF8ToUTF16(*data->accessible_name);
-
-  message_center::NotifierId notifier_id(
-      message_center::NotifierId::SYSTEM_COMPONENT, kNotifierId);
-  notifier_id.profile_id = profile_id().GetUserEmail();
-
-  auto notification = base::MakeUnique<message_center::Notification>(
-      message_center::NOTIFICATION_TYPE_CUSTOM, notification_id(),
-      base::UTF8ToUTF16(data->title), base::UTF8ToUTF16(data->message),
-      gfx::Image(),
-      base::UTF8ToUTF16("arc"),  // display source
-      GURL(),                    // empty origin url, for system component
-      notifier_id, rich_data, new ArcNotificationDelegate(this));
-  notification->set_timestamp(base::Time::FromJavaTime(data->time));
-  SetNotification(std::move(notification));
-
-  pinned_ = rich_data.pinned;
-  expand_state_ = data->expand_state;
-  shown_contents_ = data->shown_contents;
-
-  if (!data->snapshot_image || data->snapshot_image->isNull()) {
-    snapshot_ = gfx::ImageSkia();
-  } else {
-    snapshot_ = gfx::ImageSkia(gfx::ImageSkiaRep(
-        *data->snapshot_image, data->snapshot_image_scale));
-  }
-
-  for (auto& observer : observers_)
-    observer.OnItemUpdated();
-
-  AddToMessageCenter();
-}
-
-void ArcCustomNotificationItem::AddObserver(Observer* observer) {
-  observers_.AddObserver(observer);
-}
-
-void ArcCustomNotificationItem::RemoveObserver(Observer* observer) {
-  observers_.RemoveObserver(observer);
-}
-
-void ArcCustomNotificationItem::IncrementWindowRefCount() {
-  ++window_ref_count_;
-  if (window_ref_count_ == 1)
-    manager()->CreateNotificationWindow(notification_key());
-}
-
-void ArcCustomNotificationItem::DecrementWindowRefCount() {
-  DCHECK_GT(window_ref_count_, 0);
-  --window_ref_count_;
-  if (window_ref_count_ == 0)
-    manager()->CloseNotificationWindow(notification_key());
-}
-
-}  // namespace arc
diff --git a/ui/arc/notification/arc_custom_notification_item.h b/ui/arc/notification/arc_custom_notification_item.h
deleted file mode 100644
index f4ed4567..0000000
--- a/ui/arc/notification/arc_custom_notification_item.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_ARC_NOTIFICATION_ARC_CUSTOM_NOTIFICATION_ITEM_H_
-#define UI_ARC_NOTIFICATION_ARC_CUSTOM_NOTIFICATION_ITEM_H_
-
-#include "base/macros.h"
-#include "base/observer_list.h"
-#include "ui/arc/notification/arc_notification_item.h"
-#include "ui/gfx/image/image_skia.h"
-
-namespace arc {
-
-class ArcCustomNotificationItem : public ArcNotificationItem {
- public:
-  class Observer {
-   public:
-    // Invoked when the notification data for this item has changed.
-    virtual void OnItemDestroying() = 0;
-
-    // Invoked when the notification data for the item is updated.
-    virtual void OnItemUpdated() = 0;
-
-   protected:
-    virtual ~Observer() = default;
-  };
-
-  ArcCustomNotificationItem(ArcNotificationManager* manager,
-                            message_center::MessageCenter* message_center,
-                            const std::string& notification_key,
-                            const AccountId& profile_id);
-  ~ArcCustomNotificationItem() override;
-
-  void UpdateWithArcNotificationData(
-      mojom::ArcNotificationDataPtr data) override;
-
-  void CloseFromCloseButton();
-
-  void AddObserver(Observer* observer);
-  void RemoveObserver(Observer* observer);
-
-  // Increment |window_ref_count_| and a CreateNotificationWindow request
-  // is sent when |window_ref_count_| goes from zero to one.
-  void IncrementWindowRefCount();
-
-  // Decrement |window_ref_count_| and a CloseNotificationWindow request
-  // is sent when |window_ref_count_| goes from one to zero.
-  void DecrementWindowRefCount();
-
-  bool pinned() const { return pinned_; }
-  const gfx::ImageSkia& snapshot() const { return snapshot_; }
-  mojom::ArcNotificationExpandState expand_state() const {
-    return expand_state_;
-  }
-  mojom::ArcNotificationShownContents shown_contents() const {
-    return shown_contents_;
-  }
-
- private:
-  bool pinned_ = false;
-  mojom::ArcNotificationExpandState expand_state_ =
-      mojom::ArcNotificationExpandState::FIXED_SIZE;
-  mojom::ArcNotificationShownContents shown_contents_ =
-      mojom::ArcNotificationShownContents::CONTENTS_SHOWN;
-  gfx::ImageSkia snapshot_;
-  int window_ref_count_ = 0;
-
-  base::ObserverList<Observer> observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(ArcCustomNotificationItem);
-};
-
-}  // namespace arc
-
-#endif  // UI_ARC_NOTIFICATION_ARC_CUSTOM_NOTIFICATION_ITEM_H_
diff --git a/ui/arc/notification/arc_custom_notification_view.cc b/ui/arc/notification/arc_custom_notification_view.cc
index d28a6dd7..77a98df0 100644
--- a/ui/arc/notification/arc_custom_notification_view.cc
+++ b/ui/arc/notification/arc_custom_notification_view.cc
@@ -212,7 +212,7 @@
     : message_center::PaddedButton(owner), owner_(owner) {
   if (owner_->item_) {
     set_background(views::Background::CreateSolidBackground(
-        GetControlButtonBackgroundColor(owner_->item_->shown_contents())));
+        GetControlButtonBackgroundColor(owner_->item_->GetShownContents())));
   } else {
     set_background(views::Background::CreateSolidBackground(
         message_center::kControlButtonBackgroundColor));
@@ -229,21 +229,23 @@
   owner_->UpdateControlButtonsVisibility();
 }
 
-ArcCustomNotificationView::ArcCustomNotificationView(
-    ArcCustomNotificationItem* item)
+ArcCustomNotificationView::ArcCustomNotificationView(ArcNotificationItem* item)
     : item_(item),
-      notification_key_(item->notification_key()),
+      notification_key_(item->GetNotificationKey()),
       event_forwarder_(new EventForwarder(this)) {
   SetFocusBehavior(FocusBehavior::ALWAYS);
 
   item_->IncrementWindowRefCount();
   item_->AddObserver(this);
 
-  ArcNotificationSurfaceManager::Get()->AddObserver(this);
-  exo::NotificationSurface* surface =
-      ArcNotificationSurfaceManager::Get()->GetSurface(notification_key_);
-  if (surface)
-    OnNotificationSurfaceAdded(surface);
+  auto* surface_manager = ArcNotificationSurfaceManager::Get();
+  if (surface_manager) {
+    surface_manager->AddObserver(this);
+    exo::NotificationSurface* surface =
+        surface_manager->GetSurface(notification_key_);
+    if (surface)
+      OnNotificationSurfaceAdded(surface);
+  }
 
   // Create a layer as an anchor to insert surface copy during a slide.
   SetPaintToLayer();
@@ -252,13 +254,14 @@
 
 ArcCustomNotificationView::~ArcCustomNotificationView() {
   SetSurface(nullptr);
-  if (item_) {
-    item_->DecrementWindowRefCount();
-    item_->RemoveObserver(this);
-  }
 
-  if (ArcNotificationSurfaceManager::Get())
-    ArcNotificationSurfaceManager::Get()->RemoveObserver(this);
+  auto* surface_manager = ArcNotificationSurfaceManager::Get();
+  if (surface_manager)
+    surface_manager->RemoveObserver(this);
+  if (item_) {
+    item_->RemoveObserver(this);
+    item_->DecrementWindowRefCount();
+  }
 }
 
 std::unique_ptr<message_center::CustomNotificationContentViewDelegate>
@@ -311,7 +314,7 @@
 
   if (item_->IsOpeningSettingsSupported())
     CreateSettingsButton();
-  if (!item_->pinned())
+  if (!item_->GetPinned())
     CreateCloseButton();
 
   views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
@@ -360,9 +363,12 @@
 }
 
 void ArcCustomNotificationView::UpdatePreferredSize() {
-  gfx::Size preferred_size =
-      surface_ ? surface_->GetSize() : item_ ? item_->snapshot().size()
-                                             : gfx::Size();
+  gfx::Size preferred_size;
+  if (surface_)
+    preferred_size = surface_->GetSize();
+  else if (item_)
+    preferred_size = item_->GetSnapshot().size();
+
   if (preferred_size.IsEmpty())
     return;
 
@@ -409,11 +415,11 @@
   if (!item_)
     return;
 
-  if (item_->pinned() && close_button_) {
+  if (item_->GetPinned() && close_button_) {
     control_buttons_view_->RemoveChildView(close_button_.get());
     close_button_.reset();
     Layout();
-  } else if (!item_->pinned() && !close_button_) {
+  } else if (!item_->GetPinned() && !close_button_) {
     CreateCloseButton();
     Layout();
   }
@@ -467,11 +473,11 @@
 
   if (settings_button_ &&
       settings_button_->background()->get_color() !=
-          GetControlButtonBackgroundColor(item_->shown_contents()))
+          GetControlButtonBackgroundColor(item_->GetShownContents()))
     return true;
   if (close_button_ &&
       close_button_->background()->get_color() !=
-          GetControlButtonBackgroundColor(item_->shown_contents()))
+          GetControlButtonBackgroundColor(item_->GetShownContents()))
     return true;
   return false;
 }
@@ -556,11 +562,11 @@
   views::NativeViewHost::OnPaint(canvas);
 
   // Bail if there is a |surface_| or no item or no snapshot image.
-  if (surface_ || !item_ || item_->snapshot().isNull())
+  if (surface_ || !item_ || item_->GetSnapshot().isNull())
     return;
   const gfx::Rect contents_bounds = GetContentsBounds();
-  canvas->DrawImageInt(item_->snapshot(), 0, 0, item_->snapshot().width(),
-                       item_->snapshot().height(), contents_bounds.x(),
+  canvas->DrawImageInt(item_->GetSnapshot(), 0, 0, item_->GetSnapshot().width(),
+                       item_->GetSnapshot().height(), contents_bounds.x(),
                        contents_bounds.y(), contents_bounds.width(),
                        contents_bounds.height(), false);
 }
@@ -636,7 +642,7 @@
 
 void ArcCustomNotificationView::ButtonPressed(views::Button* sender,
                                               const ui::Event& event) {
-  if (item_ && !item_->pinned() && sender == close_button_.get()) {
+  if (item_ && !item_->GetPinned() && sender == close_button_.get()) {
     CHECK_EQ(message_center::CustomNotificationView::kViewClassName,
              parent()->GetClassName());
     static_cast<message_center::CustomNotificationView*>(parent())
@@ -706,7 +712,7 @@
 
   if (item_) {
     const SkColor target =
-        GetControlButtonBackgroundColor(item_->shown_contents());
+        GetControlButtonBackgroundColor(item_->GetShownContents());
     const SkColor start =
         target == message_center::kControlButtonBackgroundColor
             ? SK_ColorTRANSPARENT
diff --git a/ui/arc/notification/arc_custom_notification_view.h b/ui/arc/notification/arc_custom_notification_view.h
index d88c689..e958a936 100644
--- a/ui/arc/notification/arc_custom_notification_view.h
+++ b/ui/arc/notification/arc_custom_notification_view.h
@@ -9,7 +9,7 @@
 #include <string>
 
 #include "base/macros.h"
-#include "ui/arc/notification/arc_custom_notification_item.h"
+#include "ui/arc/notification/arc_notification_item.h"
 #include "ui/arc/notification/arc_notification_surface_manager.h"
 #include "ui/aura/window_observer.h"
 #include "ui/gfx/animation/animation_delegate.h"
@@ -41,11 +41,11 @@
     : public views::NativeViewHost,
       public views::ButtonListener,
       public aura::WindowObserver,
-      public ArcCustomNotificationItem::Observer,
+      public ArcNotificationItem::Observer,
       public ArcNotificationSurfaceManager::Observer,
       public gfx::AnimationDelegate {
  public:
-  explicit ArcCustomNotificationView(ArcCustomNotificationItem* item);
+  explicit ArcCustomNotificationView(ArcNotificationItem* item);
   ~ArcCustomNotificationView() override;
 
   std::unique_ptr<message_center::CustomNotificationContentViewDelegate>
@@ -108,7 +108,7 @@
                              const gfx::Rect& new_bounds) override;
   void OnWindowDestroying(aura::Window* window) override;
 
-  // ArcCustomNotificationItem::Observer
+  // ArcNotificationItem::Observer
   void OnItemDestroying() override;
   void OnItemUpdated() override;
 
@@ -122,7 +122,7 @@
 
   // If |item_| is null, we may be about to be destroyed. In this case,
   // we have to be careful about what we do.
-  ArcCustomNotificationItem* item_ = nullptr;
+  ArcNotificationItem* item_ = nullptr;
   exo::NotificationSurface* surface_ = nullptr;
 
   const std::string notification_key_;
diff --git a/ui/arc/notification/arc_notification_delegate.cc b/ui/arc/notification/arc_notification_delegate.cc
new file mode 100644
index 0000000..f71db1ef
--- /dev/null
+++ b/ui/arc/notification/arc_notification_delegate.cc
@@ -0,0 +1,39 @@
+// 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 "ui/arc/notification/arc_notification_delegate.h"
+
+#include "ui/arc/notification/arc_custom_notification_view.h"
+#include "ui/arc/notification/arc_notification_item.h"
+
+namespace arc {
+
+ArcNotificationDelegate::ArcNotificationDelegate(
+    base::WeakPtr<ArcNotificationItem> item)
+    : item_(item) {
+  DCHECK(item_);
+}
+
+ArcNotificationDelegate::~ArcNotificationDelegate() {}
+
+std::unique_ptr<message_center::CustomContent>
+ArcNotificationDelegate::CreateCustomContent() {
+  DCHECK(item_);
+  auto view = base::MakeUnique<ArcCustomNotificationView>(item_.get());
+  auto content_view_delegate = view->CreateContentViewDelegate();
+  return base::MakeUnique<message_center::CustomContent>(
+      std::move(view), std::move(content_view_delegate));
+}
+
+void ArcNotificationDelegate::Close(bool by_user) {
+  DCHECK(item_);
+  item_->Close(by_user);
+}
+
+void ArcNotificationDelegate::Click() {
+  DCHECK(item_);
+  item_->Click();
+}
+
+}  // namespace arc
diff --git a/ui/arc/notification/arc_notification_delegate.h b/ui/arc/notification/arc_notification_delegate.h
new file mode 100644
index 0000000..787010b2
--- /dev/null
+++ b/ui/arc/notification/arc_notification_delegate.h
@@ -0,0 +1,42 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ARC_NOTIFICATION_ARC_CUSTOM_DELEGATE_H_
+#define UI_ARC_NOTIFICATION_ARC_CUSTOM_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/message_center/notification_delegate.h"
+
+namespace arc {
+
+class ArcNotificationItem;
+
+/*
+ * Implementation of NotificationDelegate for ARC notifications.
+ */
+class ArcNotificationDelegate : public message_center::NotificationDelegate {
+ public:
+  explicit ArcNotificationDelegate(base::WeakPtr<ArcNotificationItem> item);
+
+  // message_center::NotificationDelegate overrides:
+  std::unique_ptr<message_center::CustomContent> CreateCustomContent() override;
+  void Close(bool by_user) override;
+  void Click() override;
+
+ private:
+  // The destructor is private since this class is ref-counted.
+  ~ArcNotificationDelegate() override;
+
+  // We use weak ptr to detect potential use-after-free. The lives of objects
+  // around ARC notification is somewhat complex so we want to use it until
+  // it gets stable.
+  base::WeakPtr<ArcNotificationItem> item_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArcNotificationDelegate);
+};
+
+}  // namespace arc
+
+#endif  // UI_ARC_NOTIFICATION_ARC_CUSTOM_DELEGATE_H_
diff --git a/ui/arc/notification/arc_notification_item.cc b/ui/arc/notification/arc_notification_item.cc
deleted file mode 100644
index 2c4cac44..0000000
--- a/ui/arc/notification/arc_notification_item.cc
+++ /dev/null
@@ -1,349 +0,0 @@
-// Copyright 2016 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 "ui/arc/notification/arc_notification_item.h"
-
-#include <algorithm>
-#include <utility>
-#include <vector>
-
-#include "base/memory/ptr_util.h"
-#include "base/strings/string16.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/task_runner.h"
-#include "base/task_scheduler/post_task.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkPaint.h"
-#include "ui/gfx/codec/png_codec.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/image/image.h"
-#include "ui/gfx/text_elider.h"
-#include "ui/message_center/message_center_style.h"
-#include "ui/message_center/notification.h"
-#include "ui/message_center/notification_types.h"
-#include "ui/message_center/notifier_settings.h"
-
-namespace arc {
-
-namespace {
-
-constexpr char kNotifierId[] = "ARC_NOTIFICATION";
-constexpr char kNotificationIdPrefix[] = "ARC_NOTIFICATION_";
-
-SkBitmap DecodeImage(const std::vector<uint8_t>& data) {
-  DCHECK(!data.empty());  // empty string should be handled in caller.
-
-  // We may decode an image in the browser process since it has been generated
-  // in NotificationListerService in Android and should be safe.
-  SkBitmap bitmap;
-  gfx::PNGCodec::Decode(&data[0], data.size(), &bitmap);
-  return bitmap;
-}
-
-// Crops the image to proper size for Chrome Notification. It accepts only
-// specified aspect ratio. Otherwise, it might be letterboxed.
-SkBitmap CropImage(const SkBitmap& original_bitmap) {
-  DCHECK_NE(0, original_bitmap.width());
-  DCHECK_NE(0, original_bitmap.height());
-
-  const SkSize container_size = SkSize::Make(
-      message_center::kNotificationPreferredImageWidth,
-      message_center::kNotificationPreferredImageHeight);
-  const float container_aspect_ratio =
-      static_cast<float>(message_center::kNotificationPreferredImageWidth) /
-      message_center::kNotificationPreferredImageHeight;
-  const float image_aspect_ratio =
-      static_cast<float>(original_bitmap.width()) / original_bitmap.height();
-
-  SkRect source_rect;
-  if (image_aspect_ratio > container_aspect_ratio) {
-    float width = original_bitmap.height() * container_aspect_ratio;
-    source_rect = SkRect::MakeXYWH((original_bitmap.width() - width) / 2,
-                                   0,
-                                   width,
-                                   original_bitmap.height());
-  } else {
-    float height = original_bitmap.width() / container_aspect_ratio;
-    source_rect = SkRect::MakeXYWH(0,
-                                   (original_bitmap.height() - height) / 2,
-                                   original_bitmap.width(),
-                                   height);
-  }
-
-  SkBitmap container_bitmap;
-  container_bitmap.allocN32Pixels(container_size.width(),
-                                  container_size.height());
-  SkPaint paint;
-  paint.setFilterQuality(kHigh_SkFilterQuality);
-  SkCanvas container_image(container_bitmap);
-  container_image.drawColor(message_center::kImageBackgroundColor);
-  container_image.drawBitmapRect(
-      original_bitmap, source_rect, SkRect::MakeSize(container_size), &paint);
-
-  return container_bitmap;
-}
-
-class ArcNotificationDelegate : public message_center::NotificationDelegate {
- public:
-  explicit ArcNotificationDelegate(base::WeakPtr<ArcNotificationItem> item)
-      : item_(item) {}
-
-  void Close(bool by_user) override {
-    if (item_)
-      item_->Close(by_user);
-  }
-
-  // Indicates all notifications have a click handler. This changes the mouse
-  // cursor on hover.
-  // TODO(yoshiki): Return the correct value according to the content intent
-  // and the flags.
-  bool HasClickedListener() override { return true; }
-
-  void Click() override {
-    if (item_)
-      item_->Click();
-  }
-
-  void ButtonClick(int button_index) override {
-    if (item_)
-      item_->ButtonClick(button_index);
-  }
-
- private:
-  // The destructor is private since this class is ref-counted.
-  ~ArcNotificationDelegate() override {}
-
-  base::WeakPtr<ArcNotificationItem> item_;
-
-  DISALLOW_COPY_AND_ASSIGN(ArcNotificationDelegate);
-};
-
-}  // anonymous namespace
-
-ArcNotificationItem::ArcNotificationItem(
-    ArcNotificationManager* manager,
-    message_center::MessageCenter* message_center,
-    const std::string& notification_key,
-    const AccountId& profile_id)
-    : manager_(manager),
-      message_center_(message_center),
-      profile_id_(profile_id),
-      notification_key_(notification_key),
-      notification_id_(kNotificationIdPrefix + notification_key_),
-      weak_ptr_factory_(this) {}
-
-void ArcNotificationItem::UpdateWithArcNotificationData(
-    mojom::ArcNotificationDataPtr data) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(notification_key_ == data->key);
-
-  // Check if a decode task is on-going or not. If |notification_| is non-null,
-  // a decode task is on-going asynchronously. Otherwise, there is no task and
-  // cache the latest data to the |newer_data_| property.
-  // TODO(yoshiki): Refactor and remove this check by omitting image decoding
-  // from here.
-  if (HasPendingNotification()) {
-    CacheArcNotificationData(std::move(data));
-    return;
-  }
-
-  message_center::RichNotificationData rich_data;
-  message_center::NotificationType type;
-
-  switch (data->type) {
-    case mojom::ArcNotificationType::BASIC:
-      type = message_center::NOTIFICATION_TYPE_BASE_FORMAT;
-      break;
-    case mojom::ArcNotificationType::LIST:
-      type = message_center::NOTIFICATION_TYPE_MULTIPLE;
-
-      if (!data->texts.has_value())
-        break;
-
-      for (size_t i = 0;
-           i < std::min(data->texts->size(),
-                        message_center::kNotificationMaximumItems - 1);
-           i++) {
-        rich_data.items.emplace_back(base::string16(),
-                                     base::UTF8ToUTF16(data->texts->at(i)));
-      }
-
-      if (data->texts->size() > message_center::kNotificationMaximumItems) {
-        // Show an elipsis as the 5th item if there are more than 5 items.
-        rich_data.items.emplace_back(base::string16(), gfx::kEllipsisUTF16);
-      } else if (data->texts->size() ==
-                 message_center::kNotificationMaximumItems) {
-        // Show the 5th item if there are exact 5 items.
-        rich_data.items.emplace_back(
-            base::string16(),
-            base::UTF8ToUTF16(data->texts->at(data->texts->size() - 1)));
-      }
-      break;
-    case mojom::ArcNotificationType::IMAGE:
-      type = message_center::NOTIFICATION_TYPE_IMAGE;
-
-      if (data->big_picture && !data->big_picture->isNull()) {
-        rich_data.image = gfx::Image::CreateFrom1xBitmap(
-            CropImage(*data->big_picture));
-      }
-      break;
-    case mojom::ArcNotificationType::PROGRESS:
-      type = message_center::NOTIFICATION_TYPE_PROGRESS;
-      rich_data.timestamp = base::Time::UnixEpoch() +
-                            base::TimeDelta::FromMilliseconds(data->time);
-      rich_data.progress = std::max(
-          0, std::min(100, static_cast<int>(std::round(
-                               static_cast<float>(data->progress_current) /
-                               data->progress_max * 100))));
-      break;
-  }
-  DCHECK(IsKnownEnumValue(data->type)) << "Unsupported notification type: "
-                                      << data->type;
-
-  for (size_t i = 0; i < data->buttons.size(); i++) {
-    rich_data.buttons.emplace_back(
-        base::UTF8ToUTF16(data->buttons.at(i)->label));
-  }
-
-  // If the client is old (version < 1), both |no_clear| and |ongoing_event|
-  // are false.
-  rich_data.pinned = (data->no_clear || data->ongoing_event);
-
-  rich_data.priority = ConvertAndroidPriority(data->priority);
-  if (data->small_icon)
-    rich_data.small_image = gfx::Image::CreateFrom1xBitmap(*data->small_icon);
-
-  // The identifier of the notifier, which is used to distinguish the notifiers
-  // in the message center.
-  message_center::NotifierId notifier_id(
-      message_center::NotifierId::SYSTEM_COMPONENT, kNotifierId);
-  notifier_id.profile_id = profile_id_.GetUserEmail();
-
-  SetNotification(base::MakeUnique<message_center::Notification>(
-      type, notification_id_, base::UTF8ToUTF16(data->title),
-      base::UTF8ToUTF16(data->message),
-      gfx::Image(),              // icon image: Will be overriden later.
-      base::UTF8ToUTF16("arc"),  // display source
-      GURL(),                    // empty origin url, for system component
-      notifier_id, rich_data,
-      new ArcNotificationDelegate(weak_ptr_factory_.GetWeakPtr())));
-
-  if (data->icon_data.size() == 0) {
-    OnImageDecoded(SkBitmap());  // Pass an empty bitmap.
-    return;
-  }
-
-  // TODO(yoshiki): Remove decoding by passing a bitmap directly from Android.
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
-      base::Bind(&DecodeImage, data->icon_data),
-      base::Bind(&ArcNotificationItem::OnImageDecoded,
-                 weak_ptr_factory_.GetWeakPtr()));
-}
-
-ArcNotificationItem::~ArcNotificationItem() {}
-
-void ArcNotificationItem::OnClosedFromAndroid() {
-  being_removed_by_manager_ = true;  // Closing is initiated by the manager.
-  message_center_->RemoveNotification(notification_id_, false /* by_user */);
-}
-
-void ArcNotificationItem::Close(bool by_user) {
-  if (being_removed_by_manager_) {
-    // Closing is caused by the manager, so we don't need to nofify a close
-    // event to the manager.
-    return;
-  }
-
-  // Do not touch its any members afterwards, because this instance will be
-  // destroyed in the following call
-  manager_->SendNotificationRemovedFromChrome(notification_key_);
-}
-
-void ArcNotificationItem::Click() {
-  manager_->SendNotificationClickedOnChrome(notification_key_);
-}
-
-void ArcNotificationItem::ButtonClick(int button_index) {
-  manager_->SendNotificationButtonClickedOnChrome(
-      notification_key_, button_index);
-}
-
-void ArcNotificationItem::OpenSettings() {
-  manager_->OpenNotificationSettings(notification_key_);
-}
-
-bool ArcNotificationItem::IsOpeningSettingsSupported() const {
-  return manager_->IsOpeningSettingsSupported();
-}
-
-void ArcNotificationItem::ToggleExpansion() {
-  manager_->SendNotificationToggleExpansionOnChrome(notification_key_);
-}
-
-// Converts from Android notification priority to Chrome notification priority.
-// On Android, PRIORITY_DEFAULT does not pop up, so this maps PRIORITY_DEFAULT
-// to Chrome's -1 to adapt that behavior. Also, this maps PRIORITY_LOW and _HIGH
-// to -2 and 0 respectively to adjust the value with keeping the order among
-// _LOW, _DEFAULT and _HIGH.
-// static
-// TODO(yoshiki): rewrite this conversion as typemap
-int ArcNotificationItem::ConvertAndroidPriority(
-    mojom::ArcNotificationPriority android_priority) {
-  switch (android_priority) {
-    case mojom::ArcNotificationPriority::MIN:
-    case mojom::ArcNotificationPriority::LOW:
-      return message_center::MIN_PRIORITY;
-    case mojom::ArcNotificationPriority::DEFAULT:
-      return message_center::LOW_PRIORITY;
-    case mojom::ArcNotificationPriority::HIGH:
-      return message_center::DEFAULT_PRIORITY;
-    case mojom::ArcNotificationPriority::MAX:
-      return message_center::MAX_PRIORITY;
-
-    // fall-through
-    default:
-      NOTREACHED() << "Invalid Priority: " << android_priority;
-      return message_center::DEFAULT_PRIORITY;
-  }
-}
-
-bool ArcNotificationItem::HasPendingNotification() {
-  return (notification_ != nullptr);
-}
-
-void ArcNotificationItem::CacheArcNotificationData(
-    mojom::ArcNotificationDataPtr data) {
-  // If old |newer_data_| has been stored, discard the old one.
-  newer_data_ = std::move(data);
-}
-
-void ArcNotificationItem::SetNotification(
-    std::unique_ptr<message_center::Notification> notification) {
-  notification_ = std::move(notification);
-}
-
-void ArcNotificationItem::AddToMessageCenter() {
-  DCHECK(notification_);
-  message_center_->AddNotification(std::move(notification_));
-
-  if (newer_data_) {
-    // There is the newer data, so updates again.
-    UpdateWithArcNotificationData(std::move(newer_data_));
-  }
-}
-
-bool ArcNotificationItem::CalledOnValidThread() const {
-  return thread_checker_.CalledOnValidThread();
-}
-
-void ArcNotificationItem::OnImageDecoded(const SkBitmap& bitmap) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap);
-  notification_->set_icon(image);
-  AddToMessageCenter();
-}
-
-}  // namespace arc
diff --git a/ui/arc/notification/arc_notification_item.h b/ui/arc/notification/arc_notification_item.h
index 555225c..b0d363df 100644
--- a/ui/arc/notification/arc_notification_item.h
+++ b/ui/arc/notification/arc_notification_item.h
@@ -5,104 +5,76 @@
 #ifndef UI_ARC_NOTIFICATION_ARC_NOTIFICATION_ITEM_H_
 #define UI_ARC_NOTIFICATION_ARC_NOTIFICATION_ITEM_H_
 
-#include <memory>
-#include <string>
-
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/threading/thread_checker.h"
 #include "components/arc/common/notifications.mojom.h"
-#include "components/signin/core/account_id/account_id.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/arc/notification/arc_notification_manager.h"
-#include "ui/message_center/message_center.h"
+#include "ui/gfx/image/image_skia.h"
 
 namespace arc {
 
-// The class represents each ARC notification. One instance of this class
-// corresponds to one ARC notification.
 class ArcNotificationItem {
  public:
-  ArcNotificationItem(ArcNotificationManager* manager,
-                      message_center::MessageCenter* message_center,
-                      const std::string& notification_key,
-                      const AccountId& profile_id);
-  virtual ~ArcNotificationItem();
+  class Observer {
+   public:
+    // Invoked when the notification data for this item has changed.
+    virtual void OnItemDestroying() = 0;
 
-  virtual void UpdateWithArcNotificationData(
-      mojom::ArcNotificationDataPtr data);
+    // Invoked when the notification data for the item is updated.
+    virtual void OnItemUpdated() = 0;
 
-  // Methods called from ArcNotificationManager:
-  void OnClosedFromAndroid();
+   protected:
+    virtual ~Observer() = default;
+  };
 
-  // Methods called from ArcNotificationItemDelegate:
-  void Close(bool by_user);
-  void Click();
-  void ButtonClick(int button_index);
-  void OpenSettings();
-  bool IsOpeningSettingsSupported() const;
-  void ToggleExpansion();
+  virtual ~ArcNotificationItem() = default;
 
-  const std::string& notification_key() const { return notification_key_; }
+  // Called when the notification is closed on Android-side. This is called from
+  // ArcNotificationManager.
+  virtual void OnClosedFromAndroid() = 0;
+  // Called when the notification is updated on Android-side. This is called
+  // from ArcNotificationManager.
+  virtual void OnUpdatedFromAndroid(mojom::ArcNotificationDataPtr data) = 0;
 
- protected:
-  static int ConvertAndroidPriority(
-      mojom::ArcNotificationPriority android_priority);
+  // Called when the notification is closed on Chrome-side. This is called from
+  // ArcNotificationDelegate.
+  virtual void Close(bool by_user) = 0;
+  // Called when the notification is clicked by user. This is called from
+  // ArcNotificationDelegate.
+  virtual void Click() = 0;
 
-  // Checks whether there is on-going |notification_|.
-  bool HasPendingNotification();
-  // Cache the |data| in |newer_data_|.
-  void CacheArcNotificationData(mojom::ArcNotificationDataPtr data);
+  // Called when the user wants to open an intrinsic setting of notification.
+  // This is called from ArcCustomNotificationView.
+  virtual void OpenSettings() = 0;
+  // Called when the user wants to toggle expansio of notification. This is
+  // called from ArcCustomNotificationView.
+  virtual void ToggleExpansion() = 0;
+  // Returns true if this notification has an intrinsic setting which shown
+  // inside the notification content area. This is called from
+  // ArcCustomNotificationView.
+  virtual bool IsOpeningSettingsSupported() const = 0;
 
-  // Sets the pending |notification_|.
-  void SetNotification(
-      std::unique_ptr<message_center::Notification> notification);
+  // Adds an observer.
+  virtual void AddObserver(Observer* observer) = 0;
+  // Removes the observer.
+  virtual void RemoveObserver(Observer* observer) = 0;
 
-  // Add |notification_| to message center and update again if there is
-  // |newer_data_|.
-  void AddToMessageCenter();
+  // Increments |window_ref_count_| and a CreateNotificationWindow request
+  // is sent when |window_ref_count_| goes from zero to one.
+  virtual void IncrementWindowRefCount() = 0;
 
-  bool CalledOnValidThread() const;
+  // Decrements |window_ref_count_| and a CloseNotificationWindow request
+  // is sent when |window_ref_count_| goes from one to zero.
+  virtual void DecrementWindowRefCount() = 0;
 
-  const AccountId& profile_id() const { return profile_id_; }
-  const std::string& notification_id() const { return notification_id_; }
-  message_center::MessageCenter* message_center() { return message_center_; }
-  ArcNotificationManager* manager() { return manager_; }
-
-  message_center::Notification* pending_notification() {
-    return notification_.get();
-  }
-
- private:
-  void OnImageDecoded(const SkBitmap& bitmap);
-
-  ArcNotificationManager* const manager_;
-  message_center::MessageCenter* const message_center_;
-  const AccountId profile_id_;
-
-  const std::string notification_key_;
-  const std::string notification_id_;
-
-  // Stores on-going notification data during the image decoding.
-  // This field will be removed after removing async task of image decoding.
-  std::unique_ptr<message_center::Notification> notification_;
-
-  // The flag to indicate that the removing is initiated by the manager and we
-  // don't need to notify a remove event to the manager.
-  // This is true only when:
-  //   (1) the notification is being removed
-  //   (2) the removing is initiated by manager
-  bool being_removed_by_manager_ = false;
-
-  // Stores the latest notification data which is newer than the on-going data.
-  // If the on-going data is either none or the latest, this is null.
-  // This field will be removed after removing async task of image decoding.
-  mojom::ArcNotificationDataPtr newer_data_;
-
-  base::ThreadChecker thread_checker_;
-  base::WeakPtrFactory<ArcNotificationItem> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ArcNotificationItem);
+  // Returns the current pinned state.
+  virtual bool GetPinned() const = 0;
+  // Returns the current snapshot.
+  virtual const gfx::ImageSkia& GetSnapshot() const = 0;
+  // Returns the current expand state.
+  virtual mojom::ArcNotificationExpandState GetExpandState() const = 0;
+  // Returns the current type of shown contents.
+  virtual mojom::ArcNotificationShownContents GetShownContents() const = 0;
+  // Returns the notification key passed from Android-side.
+  virtual const std::string& GetNotificationKey() const = 0;
 };
 
 }  // namespace arc
diff --git a/ui/arc/notification/arc_notification_item_impl.cc b/ui/arc/notification/arc_notification_item_impl.cc
new file mode 100644
index 0000000..d4ed1b0b
--- /dev/null
+++ b/ui/arc/notification/arc_notification_item_impl.cc
@@ -0,0 +1,194 @@
+// 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 "ui/arc/notification/arc_notification_item_impl.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/arc/notification/arc_custom_notification_view.h"
+#include "ui/arc/notification/arc_notification_delegate.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/image/image.h"
+#include "ui/message_center/message_center_style.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_types.h"
+#include "ui/message_center/notifier_settings.h"
+
+namespace arc {
+
+namespace {
+
+constexpr char kNotifierId[] = "ARC_NOTIFICATION";
+constexpr char kNotificationIdPrefix[] = "ARC_NOTIFICATION_";
+
+// Converts from Android notification priority to Chrome notification priority.
+// On Android, PRIORITY_DEFAULT does not pop up, so this maps PRIORITY_DEFAULT
+// to Chrome's -1 to adapt that behavior. Also, this maps PRIORITY_LOW and
+// _HIGH to -2 and 0 respectively to adjust the value with keeping the order
+// among _LOW, _DEFAULT and _HIGH. static
+// TODO(yoshiki): rewrite this conversion as typemap
+int ConvertAndroidPriority(mojom::ArcNotificationPriority android_priority) {
+  switch (android_priority) {
+    case mojom::ArcNotificationPriority::MIN:
+    case mojom::ArcNotificationPriority::LOW:
+      return message_center::MIN_PRIORITY;
+    case mojom::ArcNotificationPriority::DEFAULT:
+      return message_center::LOW_PRIORITY;
+    case mojom::ArcNotificationPriority::HIGH:
+      return message_center::DEFAULT_PRIORITY;
+    case mojom::ArcNotificationPriority::MAX:
+      return message_center::MAX_PRIORITY;
+  }
+
+  NOTREACHED() << "Invalid Priority: " << android_priority;
+  return message_center::DEFAULT_PRIORITY;
+}
+
+}  // anonymous namespace
+
+ArcNotificationItemImpl::ArcNotificationItemImpl(
+    ArcNotificationManager* manager,
+    message_center::MessageCenter* message_center,
+    const std::string& notification_key,
+    const AccountId& profile_id)
+    : manager_(manager),
+      message_center_(message_center),
+      profile_id_(profile_id),
+      notification_key_(notification_key),
+      notification_id_(kNotificationIdPrefix + notification_key_),
+      weak_ptr_factory_(this) {}
+
+ArcNotificationItemImpl::~ArcNotificationItemImpl() {
+  for (auto& observer : observers_)
+    observer.OnItemDestroying();
+}
+
+void ArcNotificationItemImpl::OnUpdatedFromAndroid(
+    mojom::ArcNotificationDataPtr data) {
+  DCHECK(CalledOnValidThread());
+  DCHECK_EQ(notification_key_, data->key);
+
+  message_center::RichNotificationData rich_data;
+  rich_data.pinned = (data->no_clear || data->ongoing_event);
+  rich_data.priority = ConvertAndroidPriority(data->priority);
+  if (data->small_icon)
+    rich_data.small_image = gfx::Image::CreateFrom1xBitmap(*data->small_icon);
+  if (data->accessible_name.has_value())
+    rich_data.accessible_name = base::UTF8ToUTF16(*data->accessible_name);
+
+  message_center::NotifierId notifier_id(
+      message_center::NotifierId::SYSTEM_COMPONENT, kNotifierId);
+  notifier_id.profile_id = profile_id_.GetUserEmail();
+
+  auto notification = base::MakeUnique<message_center::Notification>(
+      message_center::NOTIFICATION_TYPE_CUSTOM, notification_id_,
+      base::UTF8ToUTF16(data->title), base::UTF8ToUTF16(data->message),
+      gfx::Image(),
+      base::UTF8ToUTF16("arc"),  // display source
+      GURL(),                    // empty origin url, for system component
+      notifier_id, rich_data,
+      new ArcNotificationDelegate(weak_ptr_factory_.GetWeakPtr()));
+  notification->set_timestamp(base::Time::FromJavaTime(data->time));
+
+  pinned_ = rich_data.pinned;
+  expand_state_ = data->expand_state;
+  shown_contents_ = data->shown_contents;
+
+  if (!data->snapshot_image || data->snapshot_image->isNull()) {
+    snapshot_ = gfx::ImageSkia();
+  } else {
+    snapshot_ = gfx::ImageSkia(
+        gfx::ImageSkiaRep(*data->snapshot_image, data->snapshot_image_scale));
+  }
+
+  for (auto& observer : observers_)
+    observer.OnItemUpdated();
+
+  message_center_->AddNotification(std::move(notification));
+}
+
+void ArcNotificationItemImpl::OnClosedFromAndroid() {
+  being_removed_by_manager_ = true;  // Closing is initiated by the manager.
+  message_center_->RemoveNotification(notification_id_, false /* by_user */);
+}
+
+void ArcNotificationItemImpl::Close(bool by_user) {
+  if (being_removed_by_manager_) {
+    // Closing is caused by the manager, so we don't need to nofify a close
+    // event to the manager.
+    return;
+  }
+
+  // Do not touch its any members afterwards, because this instance will be
+  // destroyed in the following call
+  manager_->SendNotificationRemovedFromChrome(notification_key_);
+}
+
+void ArcNotificationItemImpl::Click() {
+  manager_->SendNotificationClickedOnChrome(notification_key_);
+}
+
+void ArcNotificationItemImpl::OpenSettings() {
+  manager_->OpenNotificationSettings(notification_key_);
+}
+
+bool ArcNotificationItemImpl::IsOpeningSettingsSupported() const {
+  return manager_->IsOpeningSettingsSupported();
+}
+
+void ArcNotificationItemImpl::ToggleExpansion() {
+  manager_->SendNotificationToggleExpansionOnChrome(notification_key_);
+}
+
+bool ArcNotificationItemImpl::CalledOnValidThread() const {
+  return thread_checker_.CalledOnValidThread();
+}
+
+void ArcNotificationItemImpl::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void ArcNotificationItemImpl::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void ArcNotificationItemImpl::IncrementWindowRefCount() {
+  ++window_ref_count_;
+  if (window_ref_count_ == 1)
+    manager_->CreateNotificationWindow(notification_key_);
+}
+
+void ArcNotificationItemImpl::DecrementWindowRefCount() {
+  DCHECK_GT(window_ref_count_, 0);
+  --window_ref_count_;
+  if (window_ref_count_ == 0)
+    manager_->CloseNotificationWindow(notification_key_);
+}
+
+bool ArcNotificationItemImpl::GetPinned() const {
+  return pinned_;
+}
+
+const gfx::ImageSkia& ArcNotificationItemImpl::GetSnapshot() const {
+  return snapshot_;
+}
+
+mojom::ArcNotificationExpandState ArcNotificationItemImpl::GetExpandState()
+    const {
+  return expand_state_;
+}
+
+mojom::ArcNotificationShownContents ArcNotificationItemImpl::GetShownContents()
+    const {
+  return shown_contents_;
+}
+
+const std::string& ArcNotificationItemImpl::GetNotificationKey() const {
+  return notification_key_;
+}
+
+}  // namespace arc
diff --git a/ui/arc/notification/arc_notification_item_impl.h b/ui/arc/notification/arc_notification_item_impl.h
new file mode 100644
index 0000000..01bee75
--- /dev/null
+++ b/ui/arc/notification/arc_notification_item_impl.h
@@ -0,0 +1,92 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ARC_NOTIFICATION_ARC_NOTIFICATION_ITEM_IMPL_H_
+#define UI_ARC_NOTIFICATION_ARC_NOTIFICATION_ITEM_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/signin/core/account_id/account_id.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/arc/notification/arc_notification_item.h"
+#include "ui/arc/notification/arc_notification_manager.h"
+#include "ui/message_center/message_center.h"
+
+namespace arc {
+
+// The class represents each ARC notification. One instance of this class
+// corresponds to one ARC notification.
+class ArcNotificationItemImpl : public ArcNotificationItem {
+ public:
+  ArcNotificationItemImpl(ArcNotificationManager* manager,
+                          message_center::MessageCenter* message_center,
+                          const std::string& notification_key,
+                          const AccountId& profile_id);
+  ~ArcNotificationItemImpl() override;
+
+  // ArcNotificationItem overrides:
+  void OnClosedFromAndroid() override;
+  void OnUpdatedFromAndroid(mojom::ArcNotificationDataPtr data) override;
+  void Close(bool by_user) override;
+  void Click() override;
+  void OpenSettings() override;
+  bool IsOpeningSettingsSupported() const override;
+  void ToggleExpansion() override;
+  void AddObserver(Observer* observer) override;
+  void RemoveObserver(Observer* observer) override;
+  void IncrementWindowRefCount() override;
+  void DecrementWindowRefCount() override;
+  bool GetPinned() const override;
+  const gfx::ImageSkia& GetSnapshot() const override;
+  mojom::ArcNotificationExpandState GetExpandState() const override;
+  mojom::ArcNotificationShownContents GetShownContents() const override;
+  const std::string& GetNotificationKey() const override;
+
+ private:
+  // Return true if it's on the thread this instance is created on.
+  bool CalledOnValidThread() const;
+
+  ArcNotificationManager* const manager_;
+  message_center::MessageCenter* const message_center_;
+
+  // The pinned state of the latest notification.
+  bool pinned_ = false;
+  // The snapshot of the latest notification.
+  gfx::ImageSkia snapshot_;
+  // The expand state of the latest notification.
+  mojom::ArcNotificationExpandState expand_state_ =
+      mojom::ArcNotificationExpandState::FIXED_SIZE;
+  // The type of shown content of the latest notification.
+  mojom::ArcNotificationShownContents shown_contents_ =
+      mojom::ArcNotificationShownContents::CONTENTS_SHOWN;
+  // The reference counter of the window.
+  int window_ref_count_ = 0;
+
+  base::ObserverList<Observer> observers_;
+
+  const AccountId profile_id_;
+  const std::string notification_key_;
+  const std::string notification_id_;
+
+  // The flag to indicate that the removing is initiated by the manager and we
+  // don't need to notify a remove event to the manager.
+  // This is true only when:
+  //   (1) the notification is being removed
+  //   (2) the removing is initiated by manager
+  bool being_removed_by_manager_ = false;
+
+  base::ThreadChecker thread_checker_;
+  base::WeakPtrFactory<ArcNotificationItemImpl> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArcNotificationItemImpl);
+};
+
+}  // namespace arc
+
+#endif  // UI_ARC_NOTIFICATION_ARC_NOTIFICATION_ITEM_IMPL_H_
diff --git a/ui/arc/notification/arc_notification_manager.cc b/ui/arc/notification/arc_notification_manager.cc
index 7f63ba2a..91a2b4f8 100644
--- a/ui/arc/notification/arc_notification_manager.cc
+++ b/ui/arc/notification/arc_notification_manager.cc
@@ -13,8 +13,7 @@
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/arc/arc_bridge_service.h"
-#include "ui/arc/notification/arc_custom_notification_item.h"
-#include "ui/arc/notification/arc_notification_item.h"
+#include "ui/arc/notification/arc_notification_item_impl.h"
 
 namespace arc {
 
@@ -66,23 +65,16 @@
   const std::string& key = data->key;
   auto it = items_.find(key);
   if (it == items_.end()) {
-    // Old client with version < 5 would have use_custom_notification default,
-    // which is false.
-    const bool use_custom_notification = data->use_custom_notification;
     // Show a notification on the primary logged-in user's desktop.
     // TODO(yoshiki): Reconsider when ARC supports multi-user.
-    ArcNotificationItem* item =
-        use_custom_notification
-            ? new ArcCustomNotificationItem(this, message_center_, key,
-                                            main_profile_id_)
-            : new ArcNotificationItem(this, message_center_, key,
-                                      main_profile_id_);
+    auto item = base::MakeUnique<ArcNotificationItemImpl>(
+        this, message_center_, key, main_profile_id_);
     // TODO(yoshiki): Use emplacement for performance when it's available.
-    auto result = items_.insert(std::make_pair(key, base::WrapUnique(item)));
+    auto result = items_.insert(std::make_pair(key, std::move(item)));
     DCHECK(result.second);
     it = result.first;
   }
-  it->second->UpdateWithArcNotificationData(std::move(data));
+  it->second->OnUpdatedFromAndroid(std::move(data));
 }
 
 void ArcNotificationManager::OnNotificationRemoved(const std::string& key) {