Implement the new Sync Confirmation dialog on Linux and Windows.

This CL mostly implements the sync confirmation dialog that is shown at
the end of the tab modal signin flow described in the associated bug. It
allows the user to make an explicit decision about starting sync before
it's started and before sign in is completed.

TEST=
1. Enable the --enable-password-separated-signin-flow flag in
   chrome://flags and restart Chrome
2. In a non-signed in Chrome Profile, select "Sign in to Chrome" from
   the User Menu.
3. Enter valid credentials to sign in to Chrome, the sign in window
   should close and the sync confirmation window should be shown
4. From that window, clicking "Got it" should close the window and leave
   the user signed in with sync started. Clicking "Undo" should close
   the window and leave the user signed out with sync not started.

BUG=533004

Review URL: https://codereview.chromium.org/1487283005

Cr-Commit-Position: refs/heads/master@{#371907}
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 22fb547..f7ce981 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -10913,6 +10913,20 @@
         </message>
       </if>
 
+      <!--  Sync Confirmation section of the tab modal signin flow -->
+      <message name="IDS_SYNC_CONFIRMATION_TITLE" desc="Title of the sync confirmation dialog in the tab modal signin flow">
+        You're now signed in to Chrome
+      </message>
+      <message name="IDS_SYNC_CONFIRMATION_BODY" desc="Body of the sync confirmation dialog in the tab modal signin flow">
+        Your bookmarks, history, passwords and other settings will be synced to your Google Account so you can use them wherever you use Chrome. You can change this in <ph name="BEGIN_LINK">&lt;a id="settingsLink" href="#"&gt;Settings&lt;/a&gt;.</ph>
+      </message>
+      <message name="IDS_SYNC_CONFIRMATION_CONFIRM_BUTTON_LABEL" desc="Label of the confirmation button in the sync confirmation dialog of the tab modal signin flow">
+        Ok, got it
+      </message>
+      <message name="IDS_SYNC_CONFIRMATION_UNDO_BUTTON_LABEL" desc="Label of the undo button in the sync confirmation dialog of the tab modal signin flow">
+        Undo
+      </message>
+
       <message name="IDS_PLUGIN_OUTDATED_PROMPT" desc="Info Bar message when an outdated plugin was disabled">
         <ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph> was blocked because it is out of date.
       </message>
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 491be54..bbfa598f 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -325,6 +325,9 @@
       <include name="IDR_SITE_ENGAGEMENT_ENGAGEMENT_TABLE_CSS" file="resources\engagement\engagement_table.css" type="BINDATA" />
       <include name="IDR_SITE_ENGAGEMENT_ENGAGEMENT_TABLE_HTML" file="resources\engagement\engagement_table.html" type="BINDATA" />
       <include name="IDR_SITE_ENGAGEMENT_ENGAGEMENT_TABLE_JS" file="resources\engagement\engagement_table.js" type="BINDATA" />
+      <include name="IDR_SYNC_CONFIRMATION_CSS" file="resources\sync_confirmation\sync_confirmation.css" type="BINDATA" />
+      <include name="IDR_SYNC_CONFIRMATION_HTML" file="resources\sync_confirmation\sync_confirmation.html" allowexternalscript="true" type="BINDATA" />
+      <include name="IDR_SYNC_CONFIRMATION_JS" file="resources\sync_confirmation\sync_confirmation.js" type="BINDATA" />
       <include name="IDR_UBER_HTML" file="resources\uber\uber.html" flattenhtml="true" type="BINDATA" />
       <include name="IDR_UBER_JS" file="resources\uber\uber.js" type="BINDATA" />
       <include name="IDR_UBER_FRAME_HTML" file="resources\uber\uber_frame.html" flattenhtml="true" type="BINDATA" />
diff --git a/chrome/browser/profiles/profile_avatar_icon_util.cc b/chrome/browser/profiles/profile_avatar_icon_util.cc
index ccdcb59e..6f50842 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util.cc
+++ b/chrome/browser/profiles/profile_avatar_icon_util.cc
@@ -10,6 +10,7 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_info_cache.h"
@@ -28,10 +29,19 @@
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/skia_util.h"
+#include "url/gurl.h"
+#include "url/url_canon.h"
 
 // Helper methods for transforming and drawing avatar icons.
 namespace {
 
+// Path format for specifying thumbnail's size.
+const char kThumbnailSizeFormat[] = "s%d-c";
+// Default thumbnail size.
+const int kDefaultThumbnailSize = 64;
+// Separator of URL path components.
+const char kURLPathSeparator = '/';
+
 // Determine what the scaled height of the avatar icon should be for a
 // specified width, to preserve the aspect ratio.
 int GetScaledAvatarHeightForWidth(int width, const gfx::ImageSkia& avatar) {
@@ -388,4 +398,49 @@
   return false;
 }
 
+bool GetImageURLWithThumbnailSize(
+    const GURL& old_url, int size, GURL* new_url) {
+  DCHECK(new_url);
+  std::vector<std::string> components = base::SplitString(
+      old_url.path(), std::string(1, kURLPathSeparator),
+      base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (components.empty())
+    return false;
+
+  const std::string& old_path = old_url.path();
+  std::string default_size_component(
+      base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize));
+  std::string new_size_component(
+      base::StringPrintf(kThumbnailSizeFormat, size));
+
+  size_t pos = old_path.find(default_size_component);
+  size_t end = std::string::npos;
+  if (pos != std::string::npos) {
+    // The default size is already specified in the URL so it needs to be
+    // replaced with the new size.
+    end = pos + default_size_component.size();
+  } else {
+    // The default size is not in the URL so try to insert it before the last
+    // component.
+    const std::string& file_name = old_url.ExtractFileName();
+    if (!file_name.empty()) {
+      pos = old_path.find(file_name);
+      end = pos - 1;
+    }
+  }
+
+  if (pos != std::string::npos) {
+    std::string new_path = old_path.substr(0, pos) + new_size_component +
+                           old_path.substr(end);
+    GURL::Replacements replacement;
+    replacement.SetPathStr(new_path.c_str());
+    *new_url = old_url.ReplaceComponents(replacement);
+    return new_url->is_valid();
+  }
+
+  // We can't set the image size, just use the default size.
+  *new_url = old_url;
+  return true;
+}
+
 }  // namespace profiles
diff --git a/chrome/browser/profiles/profile_avatar_icon_util.h b/chrome/browser/profiles/profile_avatar_icon_util.h
index f93cf56..f746118 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util.h
+++ b/chrome/browser/profiles/profile_avatar_icon_util.h
@@ -19,6 +19,7 @@
 class Image;
 }
 
+class GURL;
 class SkBitmap;
 
 namespace profiles {
@@ -93,6 +94,16 @@
 // is, returns true and its index through |icon_index|. If not, returns false.
 bool IsDefaultAvatarIconUrl(const std::string& icon_url, size_t *icon_index);
 
+// Given an image URL this function builds a new URL set to |thumbnail_size|.
+// For example, if |thumbnail_size| was set to 256 and |old_url| was either:
+//   https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg
+//   or
+//   https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg
+// then return value in |new_url| would be:
+//   https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg
+bool GetImageURLWithThumbnailSize(
+    const GURL& old_url, int thumbnail_size, GURL* new_url);
+
 }  // namespace profiles
 
 #endif  // CHROME_BROWSER_PROFILES_PROFILE_AVATAR_ICON_UTIL_H_
diff --git a/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc b/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc
index 39b0b5d..ea7b625 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc
+++ b/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc
@@ -11,6 +11,7 @@
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_rep.h"
 #include "ui/gfx/image/image_unittest_util.h"
+#include "url/gurl.h"
 
 namespace {
 
@@ -106,4 +107,75 @@
   VerifyScaling(result2, size);
 }
 
+TEST(ProfileInfoUtilTest, GetImageURLWithThumbnailSizeNoInitialSize) {
+  GURL initial_url(
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg");
+  const std::string expected_url =
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s128-c/photo.jpg";
+
+  GURL transformed_url;
+  EXPECT_TRUE(profiles::GetImageURLWithThumbnailSize(
+      initial_url, 128, &transformed_url));
+
+  EXPECT_EQ(transformed_url, GURL(expected_url));
+}
+
+TEST(ProfileInfoUtilTest, GetImageURLWithThumbnailSizeSizeAlreadySpecified) {
+  // If there's already a size specified in the URL, it should be changed to the
+  // specified size in the resulting URL.
+  GURL initial_url(
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg");
+  const std::string expected_url =
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s128-c/photo.jpg";
+
+  GURL transformed_url;
+  EXPECT_TRUE(profiles::GetImageURLWithThumbnailSize(
+      initial_url, 128, &transformed_url));
+
+  EXPECT_EQ(transformed_url, GURL(expected_url));
+}
+
+TEST(ProfileInfoUtilTest, GetImageURLWithThumbnailSizeSameSize) {
+  // If there's already a size specified in the URL, and it's already the
+  // requested size, true should be returned and the URL should remain
+  // unchanged.
+  GURL initial_url(
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg");
+  const std::string expected_url =
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg";
+
+  GURL transformed_url;
+  EXPECT_TRUE(profiles::GetImageURLWithThumbnailSize(
+      initial_url, 64, &transformed_url));
+
+  EXPECT_EQ(transformed_url, GURL(expected_url));
+}
+
+TEST(ProfileInfoUtilTest, GetImageURLWithThumbnailSizeNoFileNameInPath) {
+  GURL initial_url(
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/");
+  const std::string expected_url =
+      "https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/";
+
+  // If there is no file path component in the URL path, we should fail the
+  // modification, but return true since the URL is still potentially valid and
+  // not modify the input URL.
+  GURL new_url;
+  EXPECT_TRUE(profiles::GetImageURLWithThumbnailSize(
+      initial_url, 64, &new_url));
+
+  EXPECT_EQ(new_url, GURL(expected_url));
+}
+
+TEST(ProfileInfoUtilTest, GetImageURLWithThumbnailInvalidURL) {
+  GURL initial_url;
+
+  GURL new_url("http://example.com");
+  EXPECT_FALSE(profiles::GetImageURLWithThumbnailSize(
+      initial_url, 128, &new_url));
+
+  // The new URL should be unchanged since the transformation failed.
+  EXPECT_EQ(new_url, GURL("http://example.com"));
+}
+
 }  // namespace
diff --git a/chrome/browser/profiles/profile_downloader.cc b/chrome/browser/profiles/profile_downloader.cc
index 8f0b246..01e19295 100644
--- a/chrome/browser/profiles/profile_downloader.cc
+++ b/chrome/browser/profiles/profile_downloader.cc
@@ -18,6 +18,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_avatar_icon_util.h"
 #include "chrome/browser/profiles/profile_downloader_delegate.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/account_fetcher_service_factory.h"
@@ -47,11 +48,6 @@
 const char kAuthorizationHeader[] =
     "Authorization: Bearer %s";
 
-// Path format for specifying thumbnail's size.
-const char kThumbnailSizeFormat[] = "s%d-c";
-// Default thumbnail size.
-const int kDefaultThumbnailSize = 64;
-
 // Separator of URL path components.
 const char kURLPathSeparator = '/';
 
@@ -70,55 +66,6 @@
 // Index of path component with photo version.
 const int kPhotoVersionPathComponentIndex = 3;
 
-// Given an image URL this function builds a new URL set to |size|.
-// For example, if |size| was set to 256 and |old_url| was either:
-//   https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg
-//   or
-//   https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg
-// then return value in |new_url| would be:
-//   https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg
-bool GetImageURLWithSize(const GURL& old_url, int size, GURL* new_url) {
-  DCHECK(new_url);
-  std::vector<std::string> components = base::SplitString(
-      old_url.path(), std::string(1, kURLPathSeparator),
-      base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-  if (components.size() == 0)
-    return false;
-
-  const std::string& old_spec = old_url.spec();
-  std::string default_size_component(
-      base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize));
-  std::string new_size_component(
-      base::StringPrintf(kThumbnailSizeFormat, size));
-
-  size_t pos = old_spec.find(default_size_component);
-  size_t end = std::string::npos;
-  if (pos != std::string::npos) {
-    // The default size is already specified in the URL so it needs to be
-    // replaced with the new size.
-    end = pos + default_size_component.size();
-  } else {
-    // The default size is not in the URL so try to insert it before the last
-    // component.
-    const std::string& file_name = old_url.ExtractFileName();
-    if (!file_name.empty()) {
-      pos = old_spec.find(file_name);
-      end = pos - 1;
-    }
-  }
-
-  if (pos != std::string::npos) {
-    std::string new_spec = old_spec.substr(0, pos) + new_size_component +
-                           old_spec.substr(end);
-    *new_url = GURL(new_spec);
-    return new_url->is_valid();
-  }
-
-  // We can't set the image size, just use the default size.
-  *new_url = old_url;
-  return true;
-}
-
 }  // namespace
 
 // static
@@ -214,9 +161,10 @@
 
 std::string ProfileDownloader::GetProfilePictureURL() const {
   GURL url;
-  if (GetImageURLWithSize(GURL(account_info_.picture_url),
-                          delegate_->GetDesiredImageSideLength(),
-                          &url)) {
+  if (profiles::GetImageURLWithThumbnailSize(
+          GURL(account_info_.picture_url),
+          delegate_->GetDesiredImageSideLength(),
+          &url)) {
     return url.spec();
   }
   return account_info_.picture_url;
diff --git a/chrome/browser/resources/sync_confirmation/sync_confirmation.css b/chrome/browser/resources/sync_confirmation/sync_confirmation.css
new file mode 100644
index 0000000..c57ae25a
--- /dev/null
+++ b/chrome/browser/resources/sync_confirmation/sync_confirmation.css
@@ -0,0 +1,102 @@
+/* Copyright 2015 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. */
+
+body {
+  font-size: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+a {
+  color: rgb(85, 149, 254);
+  text-decoration: none;
+}
+
+.picture img {
+  border-radius: 50%;
+  max-height: 100%;
+  max-width: 100%;
+}
+
+.container {
+  background-color: white;
+  height: 351px;
+  overflow: hidden;
+  width: 448px;
+}
+
+.top-title-bar {
+  -webkit-padding-start: 24px;
+  -webkit-padding-start: 24px;
+  align-items: center;
+  border-bottom: 1px solid lightgray;
+  color: #333;
+  display: flex;
+  font-size: 1em;
+  height: 56px;
+}
+
+.details {
+  height: 250px;
+  padding: 20px 24px;
+}
+
+.sync-message {
+  color: #595959;
+  font-size: 0.8125em;
+  line-height: 150%;
+  padding-bottom: 32px;
+}
+
+.picture-container {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  padding-bottom: 32px;
+}
+
+.picture {
+  height: 96px;
+  position: relative;
+  width: 96px;
+}
+
+#profile-picture,
+.checkmark-bubble {
+  position: absolute;
+}
+
+.checkmark-bubble {
+  background-color: white;
+  background-image: url(//resources/images/check_circle.svg);
+  background-size: 100%;
+  border: 1px solid white;
+  border-radius: 50%;
+  display: inline-block;
+  height: 30px;
+  left: 64px;
+  top: 66px;
+  width: 30px;
+}
+
+.action-container {
+  align-items: baseline;
+  display: flex;
+  justify-content: flex-end;
+}
+
+paper-button {
+  font-size:  0.8125em;
+  padding-left: 12px;
+  padding-right: 12px;
+}
+
+#confirmButton {
+  background-color: rgb(66, 133, 244);
+  color: white;
+}
+
+#undoButton {
+  color: #5A5A5A;
+}
diff --git a/chrome/browser/resources/sync_confirmation/sync_confirmation.html b/chrome/browser/resources/sync_confirmation/sync_confirmation.html
new file mode 100644
index 0000000..b6673da
--- /dev/null
+++ b/chrome/browser/resources/sync_confirmation/sync_confirmation.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+  <head>
+    <meta charset="utf-8">
+    <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html">
+    <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
+    <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+    <link rel="stylesheet" href="sync_confirmation.css"></link>
+  </head>
+  <body>
+    <div class="container">
+      <div class="top-title-bar" i18n-content="syncConfirmationTitle"></div>
+      <div class="details">
+        <div class="picture-container">
+          <div class="picture">
+            <img id="profile-picture"></img>
+            <div class="checkmark-bubble"></div>
+          </div>
+        </div>
+        <div class="sync-message"
+            i18n-values=".innerHTML:syncConfirmationBody"></div>
+        <div class="action-container">
+          <paper-button id="confirmButton"
+              i18n-content="syncConfirmationConfirmLabel"></paper-button>
+          <paper-button id="undoButton"
+              i18n-content="syncConfirmationUndoLabel"></paper-button>
+        </div>
+      </div>
+    </div>
+  </body>
+  <script src="chrome://resources/js/cr.js"></script>
+  <script src="chrome://resources/js/load_time_data.js"></script>
+  <script src="chrome://resources/js/util.js"></script>
+  <script src="sync_confirmation.js"></script>
+  <script src="chrome://sync-confirmation/strings.js"></script>
+  <script src="chrome://resources/js/i18n_template.js"></script>
+</html>
diff --git a/chrome/browser/resources/sync_confirmation/sync_confirmation.js b/chrome/browser/resources/sync_confirmation/sync_confirmation.js
new file mode 100644
index 0000000..aeb8b40
--- /dev/null
+++ b/chrome/browser/resources/sync_confirmation/sync_confirmation.js
@@ -0,0 +1,37 @@
+/* Copyright 2015 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. */
+
+cr.define('sync.confirmation', function() {
+  'use strict';
+
+  function onConfirm(e) {
+    chrome.send('confirm');
+  }
+
+  function onUndo(e) {
+    chrome.send('undo');
+  }
+
+  function onGoToSettings(e) {
+    chrome.send('goToSettings');
+  }
+
+  function initialize() {
+    $('confirmButton').addEventListener('click', onConfirm);
+    $('undoButton').addEventListener('click', onUndo);
+    $('settingsLink').addEventListener('click', onGoToSettings);
+    chrome.send('initialized');
+  }
+
+  function setUserImageURL(url) {
+    $('profile-picture').src = url;
+  }
+
+  return {
+    initialize: initialize,
+    setUserImageURL: setUserImageURL
+  };
+});
+
+document.addEventListener('DOMContentLoaded', sync.confirmation.initialize);
diff --git a/chrome/browser/sync/test/integration/sync_test.cc b/chrome/browser/sync/test/integration/sync_test.cc
index 0624bb44..3085c4f 100644
--- a/chrome/browser/sync/test/integration/sync_test.cc
+++ b/chrome/browser/sync/test/integration/sync_test.cc
@@ -618,7 +618,7 @@
     // be removed. http://crbug.com/484388
     for (int i = 0; i < num_clients_; ++i) {
       LoginUIServiceFactory::GetForProfile(GetProfile(i))->
-          SyncConfirmationUIClosed(false /* configure_sync_first */);
+          SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
     }
   }
 
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index 8190853..ee1337a4 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -391,6 +391,11 @@
   // open. Does nothing otherwise.
   virtual void CloseModalSigninWindow() = 0;
 
+  // Shows the tab modal sync confirmation dialog that informs the user about
+  // sync and gives them a chance to abort signin under the tab modal signin
+  // flow.
+  virtual void ShowModalSyncConfirmationWindow() = 0;
+
   // Returns the height inset for RenderView when detached bookmark bar is
   // shown.  Invoked when a new RenderHostView is created for a non-NTP
   // navigation entry and the bookmark bar is detached.
diff --git a/chrome/browser/ui/cocoa/browser_window_cocoa.h b/chrome/browser/ui/cocoa/browser_window_cocoa.h
index 4d9d9d2..ea77c31 100644
--- a/chrome/browser/ui/cocoa/browser_window_cocoa.h
+++ b/chrome/browser/ui/cocoa/browser_window_cocoa.h
@@ -165,6 +165,7 @@
   void ShowModalSigninWindow(AvatarBubbleMode mode,
                              signin_metrics::AccessPoint access_point) override;
   void CloseModalSigninWindow() override;
+  void ShowModalSyncConfirmationWindow() override;
   int GetRenderViewHeightInsetWithDetachedBookmarkBar() override;
   void ExecuteExtensionCommand(const extensions::Extension* extension,
                                const extensions::Command& command) override;
diff --git a/chrome/browser/ui/cocoa/browser_window_cocoa.mm b/chrome/browser/ui/cocoa/browser_window_cocoa.mm
index 64b2829f..931b5bbb 100644
--- a/chrome/browser/ui/cocoa/browser_window_cocoa.mm
+++ b/chrome/browser/ui/cocoa/browser_window_cocoa.mm
@@ -862,10 +862,15 @@
     signin_metrics::AccessPoint access_point) {
   NOTREACHED();
 }
+
 void BrowserWindowCocoa::CloseModalSigninWindow() {
   NOTREACHED();
 }
 
+void BrowserWindowCocoa::ShowModalSyncConfirmationWindow() {
+  NOTREACHED();
+}
+
 int
 BrowserWindowCocoa::GetRenderViewHeightInsetWithDetachedBookmarkBar() {
   if (browser_->bookmark_bar_state() != BookmarkBar::DETACHED)
diff --git a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm
index 0fe1b98b..8d9ecfd 100644
--- a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm
+++ b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm
@@ -1127,7 +1127,7 @@
 - (IBAction)configureSyncSettings:(id)sender {
   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
   LoginUIServiceFactory::GetForProfile(browser_->profile())->
-      SyncConfirmationUIClosed(true);
+      SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
   ProfileMetrics::LogProfileNewAvatarMenuSignin(
       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
 }
@@ -1135,7 +1135,7 @@
 - (IBAction)syncSettingsConfirmed:(id)sender {
   tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
   LoginUIServiceFactory::GetForProfile(browser_->profile())->
-      SyncConfirmationUIClosed(false);
+      SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   ProfileMetrics::LogProfileNewAvatarMenuSignin(
       ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
@@ -1174,7 +1174,7 @@
 - (void)windowWillClose:(NSNotification*)notification {
   if (tutorialMode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
     LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(false);
+        SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   }
 
   [super windowWillClose:notification];
diff --git a/chrome/browser/ui/sync/one_click_signin_sync_starter.cc b/chrome/browser/ui/sync/one_click_signin_sync_starter.cc
index 1a00187..2b11a5f5b 100644
--- a/chrome/browser/ui/sync/one_click_signin_sync_starter.cc
+++ b/chrome/browser/ui/sync/one_click_signin_sync_starter.cc
@@ -41,6 +41,7 @@
 #include "components/browser_sync/browser/profile_sync_service.h"
 #include "components/signin/core/browser/signin_manager.h"
 #include "components/signin/core/browser/signin_metrics.h"
+#include "components/signin/core/common/profile_management_switches.h"
 #include "components/sync_driver/sync_prefs.h"
 #include "content/public/browser/user_metrics.h"
 #include "net/base/url_util.h"
@@ -411,20 +412,25 @@
 }
 
 void OneClickSigninSyncStarter::OnSyncConfirmationUIClosed(
-    bool configure_sync_first) {
-  if (configure_sync_first) {
-    content::RecordAction(
-        base::UserMetricsAction("Signin_Signin_WithAdvancedSyncSettings"));
-    chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage);
-  } else {
-    content::RecordAction(
-        base::UserMetricsAction("Signin_Signin_WithDefaultSyncSettings"));
-    ProfileSyncService* profile_sync_service = GetProfileSyncService();
-    if (profile_sync_service) {
-      profile_sync_service->SetFirstSetupComplete();
-      profile_sync_service->RequestStart();
+    LoginUIService::SyncConfirmationUIClosedResults results) {
+  switch (results) {
+    case LoginUIService::CONFIGURE_SYNC_FIRST:
+      content::RecordAction(
+          base::UserMetricsAction("Signin_Signin_WithAdvancedSyncSettings"));
+      chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage);
+      break;
+    case LoginUIService::SYNC_WITH_DEFAULT_SETTINGS: {
+      content::RecordAction(
+          base::UserMetricsAction("Signin_Signin_WithDefaultSyncSettings"));
+      ProfileSyncService* profile_sync_service = GetProfileSyncService();
+      if (profile_sync_service)
+        profile_sync_service->SetFirstSetupComplete();
+      FinishProfileSyncServiceSetup();
+      break;
     }
-    FinishProfileSyncServiceSetup();
+    case LoginUIService::ABORT_SIGNIN:
+      FinishProfileSyncServiceSetup();
+      break;
   }
 
   delete this;
@@ -491,8 +497,12 @@
       break;
     }
     case CONFIRM_SYNC_SETTINGS_FIRST:
-      // Blocks sync until the sync settings confirmation UI is closed.
-      DisplayFinalConfirmationBubble(base::string16());
+      if (switches::UsePasswordSeparatedSigninFlow()) {
+        DisplayModalSyncConfirmationWindow();
+      } else {
+        // Blocks sync until the sync settings confirmation UI is closed.
+        DisplayFinalConfirmationBubble(base::string16());
+      }
       return;
     case CONFIGURE_SYNC_FIRST:
       ShowSettingsPage(true);  // Show sync config UI.
@@ -521,6 +531,11 @@
       DisplayLoginResult(browser_, custom_message);
 }
 
+void OneClickSigninSyncStarter::DisplayModalSyncConfirmationWindow() {
+  browser_ = EnsureBrowser(browser_, profile_, desktop_type_);
+  browser_->window()->ShowModalSyncConfirmationWindow();
+}
+
 // static
 Browser* OneClickSigninSyncStarter::EnsureBrowser(
     Browser* browser,
diff --git a/chrome/browser/ui/sync/one_click_signin_sync_starter.h b/chrome/browser/ui/sync/one_click_signin_sync_starter.h
index e562242..349a539 100644
--- a/chrome/browser/ui/sync/one_click_signin_sync_starter.h
+++ b/chrome/browser/ui/sync/one_click_signin_sync_starter.h
@@ -134,7 +134,8 @@
   void AccountAddedToCookie(const GoogleServiceAuthError& error) override;
 
   // LoginUIService::Observer override.
-  void OnSyncConfirmationUIClosed(bool configure_sync_first) override;
+  void OnSyncConfirmationUIClosed(
+      LoginUIService::SyncConfirmationUIClosedResults results) override;
 
 #if defined(ENABLE_CONFIGURATION_POLICY)
   // User input handler for the signin confirmation dialog.
@@ -218,6 +219,8 @@
   // the default "You are signed in" message is displayed.
   void DisplayFinalConfirmationBubble(const base::string16& custom_message);
 
+  void DisplayModalSyncConfirmationWindow();
+
   // Loads the |continue_url_| in the current tab.
   void LoadContinueUrl();
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index adad05a..2d7a8e0 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2555,7 +2555,6 @@
   profiles::TutorialMode tutorial_mode;
   profiles::BubbleViewModeFromAvatarBubbleMode(mode, &bubble_view_mode,
                                                &tutorial_mode);
-
   if (SigninViewController::ShouldShowModalSigninForMode(bubble_view_mode)) {
     ShowModalSigninWindow(mode, access_point);
   } else {
@@ -2570,6 +2569,10 @@
 #endif
 }
 
+void BrowserView::CloseModalSigninWindow() {
+  signin_view_controller_.CloseModalSignin();
+}
+
 void BrowserView::ShowModalSigninWindow(
     AvatarBubbleMode mode,
     signin_metrics::AccessPoint access_point) {
@@ -2581,8 +2584,8 @@
                                           access_point);
 }
 
-void BrowserView::CloseModalSigninWindow() {
-  signin_view_controller_.CloseModalSignin();
+void BrowserView::ShowModalSyncConfirmationWindow() {
+  signin_view_controller_.ShowModalSyncConfirmationDialog(browser());
 }
 
 int BrowserView::GetRenderViewHeightInsetWithDetachedBookmarkBar() {
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index ff19911..7995bca 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -372,6 +372,7 @@
   void ShowModalSigninWindow(AvatarBubbleMode mode,
                              signin_metrics::AccessPoint access_point) override;
   void CloseModalSigninWindow() override;
+  void ShowModalSyncConfirmationWindow() override;
   int GetRenderViewHeightInsetWithDetachedBookmarkBar() override;
   void ExecuteExtensionCommand(const extensions::Extension* extension,
                                const extensions::Command& command) override;
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.cc b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
index da130fc9..99219617 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
@@ -866,7 +866,7 @@
 
   if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
     LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(false /* configure_sync_first */);
+        SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   }
 }
 
@@ -914,7 +914,7 @@
     ShowViewFromMode(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH);
   } else if (sender == tutorial_sync_settings_ok_button_) {
     LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(false /* configure_sync_first */);
+        SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
     DismissTutorial();
     ProfileMetrics::LogProfileNewAvatarMenuSignin(
         ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
@@ -1015,7 +1015,7 @@
     PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT);
   } else if (sender == tutorial_sync_settings_link_) {
     LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(true /* configure_sync_first */);
+        SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
     tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
     ProfileMetrics::LogProfileNewAvatarMenuSignin(
         ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
diff --git a/chrome/browser/ui/views/profiles/signin_view_controller.cc b/chrome/browser/ui/views/profiles/signin_view_controller.cc
index f394d784..48727552 100644
--- a/chrome/browser/ui/views/profiles/signin_view_controller.cc
+++ b/chrome/browser/ui/views/profiles/signin_view_controller.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/webui/signin/inline_login_ui.h"
+#include "chrome/common/url_constants.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/signin/core/browser/signin_error_controller.h"
 #include "components/signin/core/common/profile_management_switches.h"
@@ -34,6 +35,8 @@
 const int kFixedGaiaViewWidth = 448;
 const int kNavigationButtonSize = 16;
 const int kNavigationButtonOffset = 16;
+const int kSyncConfirmationDialogWidth = 448;
+const int kSyncConfirmationDialogHeight = 351;
 
 // View that contains the signin web contents and the back/close overlay button.
 class HostView : public views::View {
@@ -222,6 +225,16 @@
   return web_view;
 }
 
+views::WebView* SigninViewController::CreateSyncConfirmationWebView(
+    Profile* profile) {
+  views::WebView* web_view = new views::WebView(profile);
+  web_view->LoadInitialURL(GURL(chrome::kChromeUISyncConfirmationURL));
+  web_view->SetPreferredSize(
+      gfx::Size(kSyncConfirmationDialogWidth, kSyncConfirmationDialogHeight));
+
+  return web_view;
+}
+
 void SigninViewController::ShowModalSignin(
     profiles::BubbleViewMode mode,
     Browser* browser,
@@ -244,6 +257,12 @@
   modal_signin_delegate_ = nullptr;
 }
 
+void SigninViewController::ShowModalSyncConfirmationDialog(Browser* browser) {
+  CloseModalSignin();
+  modal_signin_delegate_ = new ModalSigninDelegate(
+      this, CreateSyncConfirmationWebView(browser->profile()), browser);
+}
+
 // static
 bool SigninViewController::ShouldShowModalSigninForMode(
     profiles::BubbleViewMode mode) {
diff --git a/chrome/browser/ui/views/profiles/signin_view_controller.h b/chrome/browser/ui/views/profiles/signin_view_controller.h
index fdc3b923..0372acc 100644
--- a/chrome/browser/ui/views/profiles/signin_view_controller.h
+++ b/chrome/browser/ui/views/profiles/signin_view_controller.h
@@ -42,6 +42,8 @@
       Profile* profile,
       signin_metrics::AccessPoint access_point);
 
+  static views::WebView* CreateSyncConfirmationWebView(Profile* profile);
+
   // Shows the signin flow as a tab modal dialog attached to |browser|'s active
   // web contents.
   // |access_point| indicates the access point used to open the Gaia sign in
@@ -50,6 +52,8 @@
                        Browser* browser,
                        signin_metrics::AccessPoint access_point);
 
+  void ShowModalSyncConfirmationDialog(Browser* browser);
+
   // Closes the tab-modal signin flow previously shown using this
   // SigninViewController, if one exists. Does nothing otherwise.
   void CloseModalSignin();
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index b60ade0..3a460f3f 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -53,6 +53,7 @@
 #include "chrome/browser/ui/webui/settings/md_settings_ui.h"
 #include "chrome/browser/ui/webui/signin/inline_login_ui.h"
 #include "chrome/browser/ui/webui/signin/profile_signin_confirmation_ui.h"
+#include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h"
 #include "chrome/browser/ui/webui/signin/user_manager_ui.h"
 #include "chrome/browser/ui/webui/signin_internals_ui.h"
 #include "chrome/browser/ui/webui/supervised_user_internals_ui.h"
@@ -510,6 +511,8 @@
 #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) && !defined(OS_IOS)
   if (url.host() == chrome::kChromeUIUserManagerHost)
     return &NewWebUI<UserManagerUI>;
+  if (url.host() == chrome::kChromeUISyncConfirmationHost)
+    return &NewWebUI<SyncConfirmationUI>;
 #endif
 
   /****************************************************************************
diff --git a/chrome/browser/ui/webui/signin/login_ui_service.cc b/chrome/browser/ui/webui/signin/login_ui_service.cc
index d6a2c6e..879362e 100644
--- a/chrome/browser/ui/webui/signin/login_ui_service.cc
+++ b/chrome/browser/ui/webui/signin/login_ui_service.cc
@@ -45,11 +45,12 @@
   FOR_EACH_OBSERVER(Observer, observer_list_, OnLoginUIClosed(ui));
 }
 
-void LoginUIService::SyncConfirmationUIClosed(bool configure_sync_first) {
+void LoginUIService::SyncConfirmationUIClosed(
+    SyncConfirmationUIClosedResults results) {
   FOR_EACH_OBSERVER(
       Observer,
       observer_list_,
-      OnSyncConfirmationUIClosed(configure_sync_first));
+      OnSyncConfirmationUIClosed(results));
 }
 
 void LoginUIService::UntrustedLoginUIShown() {
diff --git a/chrome/browser/ui/webui/signin/login_ui_service.h b/chrome/browser/ui/webui/signin/login_ui_service.h
index 23a9d11..ce3ffead 100644
--- a/chrome/browser/ui/webui/signin/login_ui_service.h
+++ b/chrome/browser/ui/webui/signin/login_ui_service.h
@@ -31,6 +31,17 @@
     virtual ~LoginUI() {}
   };
 
+  // Used when the sync confirmation UI is closed to signify which option was
+  // selected by the user.
+  enum SyncConfirmationUIClosedResults {
+    // Start sync immediately.
+    SYNC_WITH_DEFAULT_SETTINGS,
+    // Show the user the sync settings before starting sync.
+    CONFIGURE_SYNC_FIRST,
+    // The signing process was aborted, don't start sync or show settings.
+    ABORT_SIGNIN,
+  };
+
   // Interface for obervers of LoginUIService.
   class Observer {
    public:
@@ -42,10 +53,10 @@
     // |ui| The login UI that was just closed; will never be null.
     virtual void OnLoginUIClosed(LoginUI* ui) {}
 
-    // Called when the sync confirmation UI is closed. |configure_sync_first|
-    // is true if the user has requested to configure the sync settings before
-    // sync starts.
-    virtual void OnSyncConfirmationUIClosed(bool configure_sync_first) {}
+    // Called when the sync confirmation UI is closed. |results| indicates the
+    // option chosen by the user in the confirmation UI.
+    virtual void OnSyncConfirmationUIClosed(
+        SyncConfirmationUIClosedResults results) {}
 
     // Called when a confirmation UI for untrusted signin is shown.
     virtual void OnUntrustedLoginUIShown() {}
@@ -75,7 +86,7 @@
   void LoginUIClosed(LoginUI* ui);
 
   // Called when the sync settings confirmation UI is closed.
-  void SyncConfirmationUIClosed(bool configure_sync_first);
+  void SyncConfirmationUIClosed(SyncConfirmationUIClosedResults results);
 
   // Called when a confirmation UI for untrusted signin is shown.
   void UntrustedLoginUIShown();
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc b/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc
new file mode 100644
index 0000000..4c07b75
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc
@@ -0,0 +1,113 @@
+// Copyright 2015 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/webui/signin/sync_confirmation_handler.h"
+
+#include "base/bind.h"
+#include "chrome/browser/profiles/profile_avatar_icon_util.h"
+#include "chrome/browser/signin/account_tracker_service_factory.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
+#include "components/signin/core/browser/account_tracker_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "url/gurl.h"
+
+const int kProfileImageSize = 128;
+
+SyncConfirmationHandler::SyncConfirmationHandler() {}
+
+SyncConfirmationHandler::~SyncConfirmationHandler() {
+  Profile* profile = Profile::FromWebUI(web_ui());
+  AccountTrackerServiceFactory::GetForProfile(profile)->RemoveObserver(this);
+}
+
+void SyncConfirmationHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback("confirm",
+      base::Bind(&SyncConfirmationHandler::HandleConfirm,
+                 base::Unretained(this)));
+  web_ui()->RegisterMessageCallback("undo",
+      base::Bind(&SyncConfirmationHandler::HandleUndo, base::Unretained(this)));
+  web_ui()->RegisterMessageCallback("initialized",
+      base::Bind(&SyncConfirmationHandler::HandleInitialized,
+                 base::Unretained(this)));
+  web_ui()->RegisterMessageCallback("goToSettings",
+      base::Bind(&SyncConfirmationHandler::HandleGoToSettings,
+                 base::Unretained(this)));
+}
+
+void SyncConfirmationHandler::HandleConfirm(const base::ListValue* args) {
+  CloseModalSigninWindow(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
+}
+
+void SyncConfirmationHandler::HandleGoToSettings(const base::ListValue* args) {
+  CloseModalSigninWindow(LoginUIService::CONFIGURE_SYNC_FIRST);
+}
+
+void SyncConfirmationHandler::HandleUndo(const base::ListValue* args) {
+  Browser* browser = GetDesktopBrowser();
+  LoginUIServiceFactory::GetForProfile(browser->profile())->
+      SyncConfirmationUIClosed(LoginUIService::ABORT_SIGNIN);
+  SigninManagerFactory::GetForProfile(Profile::FromWebUI(web_ui()))->SignOut(
+      signin_metrics::ABORT_SIGNIN,
+      signin_metrics::SignoutDelete::IGNORE_METRIC);
+  browser->window()->CloseModalSigninWindow();
+}
+
+void SyncConfirmationHandler::HandleInitialized(const base::ListValue* args) {
+  Browser* browser = GetDesktopBrowser();
+  Profile* profile = browser->profile();
+  std::vector<AccountInfo> accounts =
+      AccountTrackerServiceFactory::GetForProfile(profile)->GetAccounts();
+
+  if (accounts.empty())
+    return;
+
+  AccountInfo primary_account_info = accounts[0];
+
+  if (!primary_account_info.IsValid())
+    AccountTrackerServiceFactory::GetForProfile(profile)->AddObserver(this);
+  else
+    SetUserImageURL(primary_account_info.picture_url);
+}
+
+void SyncConfirmationHandler::SetUserImageURL(const std::string& picture_url) {
+  GURL url;
+  if (profiles::GetImageURLWithThumbnailSize(GURL(picture_url),
+                                             kProfileImageSize,
+                                             &url)) {
+    base::StringValue picture_url_value(url.spec());
+    web_ui()->CallJavascriptFunction(
+        "sync.confirmation.setUserImageURL", picture_url_value);
+  }
+}
+
+void SyncConfirmationHandler::OnAccountUpdated(const AccountInfo& info) {
+  DCHECK(info.IsValid());
+  Profile* profile = Profile::FromWebUI(web_ui());
+  AccountTrackerServiceFactory::GetForProfile(profile)->RemoveObserver(this);
+
+  SetUserImageURL(info.picture_url);
+}
+
+Browser* SyncConfirmationHandler::GetDesktopBrowser() {
+  Browser* browser = chrome::FindBrowserWithWebContents(
+      web_ui()->GetWebContents());
+  if (!browser) {
+    browser = chrome::FindLastActiveWithProfile(
+        Profile::FromWebUI(web_ui()), chrome::GetActiveDesktop());
+  }
+  DCHECK(browser);
+  return browser;
+}
+
+void SyncConfirmationHandler::CloseModalSigninWindow(
+    LoginUIService::SyncConfirmationUIClosedResults results) {
+  Browser* browser = GetDesktopBrowser();
+  LoginUIServiceFactory::GetForProfile(browser->profile())->
+      SyncConfirmationUIClosed(results);
+  browser->window()->CloseModalSigninWindow();
+}
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_handler.h b/chrome/browser/ui/webui/signin/sync_confirmation_handler.h
new file mode 100644
index 0000000..cb3506a
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_handler.h
@@ -0,0 +1,65 @@
+// Copyright 2015 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_WEBUI_SIGNIN_SYNC_CONFIRMATION_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SIGNIN_SYNC_CONFIRMATION_HANDLER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/webui/signin/login_ui_service.h"
+#include "components/signin/core/browser/account_tracker_service.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+class Browser;
+
+namespace base {
+class ListValue;
+}
+
+class SyncConfirmationHandler : public content::WebUIMessageHandler,
+                                public AccountTrackerService::Observer {
+ public:
+  SyncConfirmationHandler();
+  ~SyncConfirmationHandler() override;
+
+  // content::WebUIMessageHandler:
+  void RegisterMessages() override;
+
+  // AccountTrackerService::Observer:
+  void OnAccountUpdated(const AccountInfo& info) override;
+
+ protected:
+  // Handles "confirm" message from the page. No arguments.
+  // This message is sent when the user confirms that they want complete sign in
+  // with default sync settings.
+  virtual void HandleConfirm(const base::ListValue* args);
+
+  // Handles "undo" message from the page. No arguments.
+  // This message is sent when the user clicks "undo" on the sync confirmation
+  // dialog, which aborts signin and prevents sync from starting.
+  virtual void HandleUndo(const base::ListValue* args);
+
+  // Handles "initialized" message from the page. No arguments.
+  // This message is sent when the sync confirmation dialog is finished being
+  // initialized.
+  virtual void HandleInitialized(const base::ListValue* args);
+
+  // Handles "goToSettings" message from the page. No arguments.
+  // This message is sent when the user clicks on the "Settings" link in the
+  // sync confirmation dialog, which completes sign in but takes the user to the
+  // sync settings page for configuration before starting sync.
+  virtual void HandleGoToSettings(const base::ListValue* args);
+
+  // Sets the profile picture shown in the dialog to the image at |url|.
+  virtual void SetUserImageURL(const std::string& url);
+
+  Browser* GetDesktopBrowser();
+  void CloseModalSigninWindow(
+      LoginUIService::SyncConfirmationUIClosedResults results);
+
+  DISALLOW_COPY_AND_ASSIGN(SyncConfirmationHandler);
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SIGNIN_SYNC_CONFIRMATION_HANDLER_H_
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc b/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc
new file mode 100644
index 0000000..c5bd966
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc
@@ -0,0 +1,209 @@
+// 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 "chrome/browser/ui/webui/signin/sync_confirmation_handler.h"
+
+#include "base/values.h"
+#include "chrome/browser/profiles/profile_avatar_icon_util.h"
+#include "chrome/browser/signin/account_fetcher_service_factory.h"
+#include "chrome/browser/signin/account_tracker_service_factory.h"
+#include "chrome/browser/signin/fake_account_fetcher_service_builder.h"
+#include "chrome/browser/signin/fake_signin_manager_builder.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/dialog_test_browser_window.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/browser_sync/browser/profile_sync_service.h"
+#include "components/signin/core/browser/account_fetcher_service.h"
+#include "components/signin/core/browser/fake_account_fetcher_service.h"
+#include "components/signin/core/browser/fake_signin_manager.h"
+#include "content/public/test/test_web_ui.h"
+
+const int kExpectedProfileImageSize = 128;
+
+class TestingSyncConfirmationHandler : public SyncConfirmationHandler {
+ public:
+  explicit TestingSyncConfirmationHandler(content::WebUI* web_ui) {
+    set_web_ui(web_ui);
+  }
+
+  using SyncConfirmationHandler::HandleConfirm;
+  using SyncConfirmationHandler::HandleUndo;
+  using SyncConfirmationHandler::HandleInitialized;
+  using SyncConfirmationHandler::HandleGoToSettings;
+  using SyncConfirmationHandler::SetUserImageURL;
+};
+
+class SyncConfirmationHandlerTest : public BrowserWithTestWindowTest {
+ public:
+  SyncConfirmationHandlerTest() : web_ui_(new content::TestWebUI) {}
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+    chrome::NewTab(browser());
+    web_ui()->set_web_contents(
+        browser()->tab_strip_model()->GetActiveWebContents());
+
+    // WebUI owns the handlers.
+    handler_ = new TestingSyncConfirmationHandler(web_ui());
+    sync_confirmation_ui_.reset(
+        new SyncConfirmationUI(web_ui(), handler_));
+
+    // This dialog assumes the signin flow was completed, which kicks off the
+    // SigninManager.
+    new OneClickSigninSyncStarter(
+        profile(),
+        browser(),
+        "gaia",
+        "[email protected]",
+        "password",
+        "refresh_token",
+        OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS,
+        nullptr,
+        OneClickSigninSyncStarter::NO_CONFIRMATION,
+        GURL(),
+        GURL(),
+        OneClickSigninSyncStarter::Callback());
+  }
+
+  void TearDown() override {
+    sync_confirmation_ui_.reset();
+    web_ui_.reset();
+    BrowserWithTestWindowTest::TearDown();
+  }
+
+  TestingSyncConfirmationHandler* handler() {
+    return handler_;
+  }
+
+  content::TestWebUI* web_ui() {
+    return web_ui_.get();
+  }
+
+  FakeAccountFetcherService* account_fetcher_service() {
+    return static_cast<FakeAccountFetcherService*>(
+        AccountFetcherServiceFactory::GetForProfile(profile()));
+  }
+
+  FakeSigninManager* signin_manager() {
+    return static_cast<FakeSigninManager*>(
+        SigninManagerFactory::GetForProfile(profile()));
+  }
+
+  ProfileSyncService* sync() {
+    return ProfileSyncServiceFactory::GetForProfile(profile());
+  }
+
+  // BrowserWithTestWindowTest
+  BrowserWindow* CreateBrowserWindow() override {
+    return new DialogTestBrowserWindow;
+  }
+
+  TestingProfile* CreateProfile() override {
+    TestingProfile::Builder builder;
+    builder.AddTestingFactory(AccountFetcherServiceFactory::GetInstance(),
+                              FakeAccountFetcherServiceBuilder::BuildForTests);
+    builder.AddTestingFactory(
+        SigninManagerFactory::GetInstance(), BuildFakeSigninManagerBase);
+    return builder.Build().release();
+  }
+
+private:
+  scoped_ptr<content::TestWebUI> web_ui_;
+  scoped_ptr<SyncConfirmationUI> sync_confirmation_ui_;
+  TestingSyncConfirmationHandler* handler_;  // Not owned.
+};
+
+TEST_F(SyncConfirmationHandlerTest, TestSetImageIfPrimaryAccountReady) {
+  account_fetcher_service()->FakeUserInfoFetchSuccess(
+      "gaia",
+      "[email protected]",
+      "gaia",
+      "",
+      "full_name",
+      "given_name",
+      "locale",
+      "http://picture.example.com/picture.jpg");
+
+  handler()->HandleInitialized(nullptr);
+  EXPECT_EQ(1U, web_ui()->call_data().size());
+  EXPECT_EQ("sync.confirmation.setUserImageURL",
+            web_ui()->call_data()[0]->function_name());
+  EXPECT_TRUE(
+      web_ui()->call_data()[0]->arg1()->IsType(base::Value::TYPE_STRING));
+  std::string passed_picture_url;
+  EXPECT_TRUE(
+      web_ui()->call_data()[0]->arg1()->GetAsString(&passed_picture_url));
+
+  std::string original_picture_url =
+      AccountTrackerServiceFactory::GetForProfile(profile())->
+          GetAccountInfo("gaia").picture_url;
+  GURL picture_url_with_size;
+  EXPECT_TRUE(profiles::GetImageURLWithThumbnailSize(GURL(original_picture_url),
+                                                     kExpectedProfileImageSize,
+                                                     &picture_url_with_size));
+  EXPECT_EQ(picture_url_with_size.spec(), passed_picture_url);
+  handler()->HandleUndo(nullptr);
+}
+
+TEST_F(SyncConfirmationHandlerTest, TestSetImageIfPrimaryAccountReadyLater) {
+  handler()->HandleInitialized(nullptr);
+  EXPECT_EQ(0U, web_ui()->call_data().size());
+
+  account_fetcher_service()->FakeUserInfoFetchSuccess(
+      "gaia",
+      "[email protected]",
+      "gaia",
+      "",
+      "full_name",
+      "given_name",
+      "locale",
+      "http://picture.example.com/picture.jpg");
+
+  EXPECT_EQ(1U, web_ui()->call_data().size());
+  EXPECT_EQ("sync.confirmation.setUserImageURL",
+            web_ui()->call_data()[0]->function_name());
+  EXPECT_TRUE(
+      web_ui()->call_data()[0]->arg1()->IsType(base::Value::TYPE_STRING));
+  std::string passed_picture_url;
+  EXPECT_TRUE(
+      web_ui()->call_data()[0]->arg1()->GetAsString(&passed_picture_url));
+
+  std::string original_picture_url =
+      AccountTrackerServiceFactory::GetForProfile(profile())->
+          GetAccountInfo("gaia").picture_url;
+  GURL picture_url_with_size;
+  EXPECT_TRUE(profiles::GetImageURLWithThumbnailSize(GURL(original_picture_url),
+                                                     kExpectedProfileImageSize,
+                                                     &picture_url_with_size));
+  EXPECT_EQ(picture_url_with_size.spec(), passed_picture_url);
+  handler()->HandleUndo(nullptr);
+}
+
+TEST_F(SyncConfirmationHandlerTest, TestHandleUndo) {
+  EXPECT_FALSE(sync()->IsFirstSetupComplete());
+  EXPECT_TRUE(sync()->IsFirstSetupInProgress());
+
+  handler()->HandleUndo(nullptr);
+
+  EXPECT_FALSE(sync()->IsFirstSetupInProgress());
+  EXPECT_FALSE(sync()->IsFirstSetupComplete());
+  EXPECT_FALSE(
+      SigninManagerFactory::GetForProfile(profile())->IsAuthenticated());
+}
+
+TEST_F(SyncConfirmationHandlerTest, TestHandleConfirm) {
+  EXPECT_FALSE(sync()->IsFirstSetupComplete());
+  EXPECT_TRUE(sync()->IsFirstSetupInProgress());
+
+  handler()->HandleConfirm(nullptr);
+
+  EXPECT_FALSE(sync()->IsFirstSetupInProgress());
+  EXPECT_TRUE(sync()->IsFirstSetupComplete());
+  EXPECT_TRUE(
+      SigninManagerFactory::GetForProfile(profile())->IsAuthenticated());
+}
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc b/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
new file mode 100644
index 0000000..f1ede0a
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 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/webui/signin/sync_confirmation_ui.h"
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/signin/sync_confirmation_handler.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "grit/browser_resources.h"
+#include "ui/base/webui/web_ui_util.h"
+
+SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui)
+    : SyncConfirmationUI(web_ui, new SyncConfirmationHandler) {}
+
+SyncConfirmationUI::SyncConfirmationUI(
+    content::WebUI* web_ui, SyncConfirmationHandler* handler)
+    : WebDialogUI(web_ui){
+  Profile* profile = Profile::FromWebUI(web_ui);
+  content::WebUIDataSource* source =
+      content::WebUIDataSource::Create(chrome::kChromeUISyncConfirmationHost);
+  source->SetJsonPath("strings.js");
+  source->SetDefaultResource(IDR_SYNC_CONFIRMATION_HTML);
+  source->AddResourcePath("sync_confirmation.css", IDR_SYNC_CONFIRMATION_CSS);
+  source->AddResourcePath("sync_confirmation.js", IDR_SYNC_CONFIRMATION_JS);
+
+  source->AddLocalizedString("syncConfirmationTitle",
+      IDS_SYNC_CONFIRMATION_TITLE);
+  source->AddLocalizedString("syncConfirmationBody",
+      IDS_SYNC_CONFIRMATION_BODY);
+  source->AddLocalizedString("syncConfirmationConfirmLabel",
+      IDS_SYNC_CONFIRMATION_CONFIRM_BUTTON_LABEL);
+  source->AddLocalizedString("syncConfirmationUndoLabel",
+      IDS_SYNC_CONFIRMATION_UNDO_BUTTON_LABEL);
+
+  base::DictionaryValue strings;
+  webui::SetLoadTimeDataDefaults(
+      g_browser_process->GetApplicationLocale(), &strings);
+  source->AddLocalizedStrings(strings);
+
+  content::WebUIDataSource::Add(profile, source);
+  web_ui->AddMessageHandler(handler);
+}
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_ui.h b/chrome/browser/ui/webui/signin/sync_confirmation_ui.h
new file mode 100644
index 0000000..f42e629
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_ui.h
@@ -0,0 +1,27 @@
+// Copyright 2015 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_WEBUI_SIGNIN_SYNC_CONFIRMATION_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_SIGNIN_SYNC_CONFIRMATION_UI_H_
+
+#include "base/macros.h"
+#include "ui/web_dialogs/web_dialog_ui.h"
+
+class SyncConfirmationHandler;
+
+namespace ui {
+class WebUI;
+}
+
+class SyncConfirmationUI : public ui::WebDialogUI {
+ public:
+  explicit SyncConfirmationUI(content::WebUI* web_ui);
+   // Used to inject a SyncConfirmationHandler in tests.
+   SyncConfirmationUI(content::WebUI* web_ui, SyncConfirmationHandler* handler);
+  ~SyncConfirmationUI() override {}
+
+  DISALLOW_COPY_AND_ASSIGN(SyncConfirmationUI);
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SIGNIN_SYNC_CONFIRMATION_UI_H_
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index b151447a..c991338 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -1323,6 +1323,10 @@
       'browser/ui/webui/app_launcher_page_ui.h',
       'browser/ui/webui/signin/inline_login_handler_impl.cc',
       'browser/ui/webui/signin/inline_login_handler_impl.h',
+      'browser/ui/webui/signin/sync_confirmation_handler.cc',
+      'browser/ui/webui/signin/sync_confirmation_handler.h',
+      'browser/ui/webui/signin/sync_confirmation_ui.cc',
+      'browser/ui/webui/signin/sync_confirmation_ui.h',
       'browser/ui/webui/signin/user_manager_screen_handler.cc',
       'browser/ui/webui/signin/user_manager_screen_handler.h',
       'browser/ui/webui/signin/user_manager_ui.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 2c1504b..4a866c2 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1617,6 +1617,7 @@
       'browser/signin/signin_global_error_unittest.cc',
       'browser/sync/sync_global_error_unittest.cc',
       'browser/upgrade_detector_impl_unittest.cc',
+      'browser/ui/webui/signin/sync_confirmation_handler_unittest.cc'
     ],
     'chrome_unit_tests_app_list_sources': [
       'browser/ui/app_list/app_list_positioner_unittest.cc',
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 5e2954c..785bbfb 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -81,6 +81,7 @@
 const char kChromeUISuggestionsURL[] = "chrome://suggestions/";
 const char kChromeUISupervisedUserPassphrasePageURL[] =
     "chrome://managed-user-passphrase/";
+const char kChromeUISyncConfirmationURL[] = "chrome://sync-confirmation/";
 const char kChromeUITermsURL[] = "chrome://terms/";
 const char kChromeUIThemeURL[] = "chrome://theme/";
 const char kChromeUIThumbnailURL[] = "chrome://thumb/";
@@ -230,6 +231,7 @@
 const char kChromeUISupervisedUserInternalsHost[] = "supervised-user-internals";
 const char kChromeUISupervisedUserPassphrasePageHost[] =
     "managed-user-passphrase";
+const char kChromeUISyncConfirmationHost[] = "sync-confirmation";
 const char kChromeUISyncHost[] = "sync";
 const char kChromeUISyncFileSystemInternalsHost[] = "syncfs-internals";
 const char kChromeUISyncInternalsHost[] = "sync-internals";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 62d3045..4ef21ce 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -78,6 +78,7 @@
 extern const char kChromeUISiteEngagementHost[];
 extern const char kChromeUISuggestionsURL[];
 extern const char kChromeUISupervisedUserPassphrasePageURL[];
+extern const char kChromeUISyncConfirmationURL[];
 extern const char kChromeUITermsURL[];
 extern const char kChromeUIThemeURL[];
 extern const char kChromeUIThumbnailURL[];
@@ -217,6 +218,7 @@
 extern const char kChromeUISuggestionsHost[];
 extern const char kChromeUISupervisedUserInternalsHost[];
 extern const char kChromeUISupervisedUserPassphrasePageHost[];
+extern const char kChromeUISyncConfirmationHost[];
 extern const char kChromeUISyncHost[];
 extern const char kChromeUISyncFileSystemInternalsHost[];
 extern const char kChromeUISyncInternalsHost[];
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h
index c707b4bc..f317123 100644
--- a/chrome/test/base/test_browser_window.h
+++ b/chrome/test/base/test_browser_window.h
@@ -147,6 +147,7 @@
       AvatarBubbleMode mode,
       signin_metrics::AccessPoint access_point) override {}
   void CloseModalSigninWindow() override {}
+  void ShowModalSyncConfirmationWindow() override {}
   int GetRenderViewHeightInsetWithDetachedBookmarkBar() override;
   void ExecuteExtensionCommand(const extensions::Extension* extension,
                                const extensions::Command& command) override;