Add a custom tab bar to use for desktop PWAs.

Bug: 853593
Change-Id: I4618ca91c53a18ed7a56cb8dce0416d25318ab41
Reviewed-on: https://chromium-review.googlesource.com/c/1328084
Commit-Queue: Jay Harris <harrisjay@chromium.org>
Reviewed-by: Peter Kasting <pkasting@chromium.org>
Reviewed-by: Matt Giuca <mgiuca@chromium.org>
Cr-Commit-Position: refs/heads/master@{#613322}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e427c04..902f935b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2536,6 +2536,8 @@
       "views/load_complete_listener.h",
       "views/location_bar/content_setting_image_view.cc",
       "views/location_bar/content_setting_image_view.h",
+      "views/location_bar/custom_tab_bar_view.cc",
+      "views/location_bar/custom_tab_bar_view.h",
       "views/location_bar/find_bar_icon.cc",
       "views/location_bar/find_bar_icon.h",
       "views/location_bar/icon_label_bubble_view.cc",
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 6e0ce319..5033fa2 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -669,9 +669,9 @@
   // |contents| can be NULL because GetWindowTitleForCurrentTab is called by the
   // window during the window's creation (before tabs have been added).
   if (contents) {
-    title = hosted_app_controller_ ? hosted_app_controller_->GetTitle()
-                                   : contents->GetTitle();
-    FormatTitleForDisplay(&title);
+    title = FormatTitleForDisplay(hosted_app_controller_
+                                      ? hosted_app_controller_->GetTitle()
+                                      : contents->GetTitle());
   }
 
   // If there is no title, leave it empty for apps.
@@ -700,14 +700,16 @@
 }
 
 // static
-void Browser::FormatTitleForDisplay(base::string16* title) {
+base::string16 Browser::FormatTitleForDisplay(base::string16 title) {
   size_t current_index = 0;
   size_t match_index;
-  while ((match_index = title->find(L'\n', current_index)) !=
+  while ((match_index = title.find(L'\n', current_index)) !=
          base::string16::npos) {
-    title->replace(match_index, 1, base::string16());
+    title.replace(match_index, 1, base::string16());
     current_index = match_index;
   }
+
+  return title;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index c5dc61d..9769a792 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -336,7 +336,7 @@
       content::WebContents* contents) const;
 
   // Prepares a title string for display (removes embedded newlines, etc).
-  static void FormatTitleForDisplay(base::string16* title);
+  static base::string16 FormatTitleForDisplay(base::string16 title);
 
   // OnBeforeUnload handling //////////////////////////////////////////////////
 
diff --git a/chrome/browser/ui/bubble_anchor_util.h b/chrome/browser/ui/bubble_anchor_util.h
index 9a21594..ac2162a 100644
--- a/chrome/browser/ui/bubble_anchor_util.h
+++ b/chrome/browser/ui/bubble_anchor_util.h
@@ -21,6 +21,7 @@
 enum Anchor {
   kLocationBar,
   kAppMenuButton,
+  kCustomTabBar,
 };
 
 // Offset from the window edge to show bubbles when there is no location bar.
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index a86076b..8a73739 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -316,7 +316,7 @@
 // kDesktopPWAWindowing flag.
 class HostedAppTest
     : public extensions::ExtensionBrowserTest,
-      public ::testing::WithParamInterface<std::tuple<AppType, bool>> {
+      public ::testing::WithParamInterface<std::tuple<AppType, bool, bool>> {
  public:
   HostedAppTest()
       : app_browser_(nullptr),
@@ -328,8 +328,9 @@
     https_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
 
     bool desktop_pwa_flag;
+    bool use_custom_tab_flag;
 
-    std::tie(app_type_, desktop_pwa_flag) = GetParam();
+    std::tie(app_type_, desktop_pwa_flag, use_custom_tab_flag) = GetParam();
     std::vector<base::Feature> enabled_features;
     std::vector<base::Feature> disabled_features = {
         predictors::kSpeculativePreconnectFeature};
@@ -342,6 +343,9 @@
 #endif
     }
 
+    auto& features = use_custom_tab_flag ? enabled_features : disabled_features;
+    features.push_back(features::kDesktopPWAsCustomTabUI);
+
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
     extensions::ExtensionBrowserTest::SetUp();
   }
@@ -918,9 +922,12 @@
             app_browser->GetWindowTitleForCurrentTab(false));
 }
 
+using HostedAppCustomTabBarOnlyTest = HostedAppTest;
+
 // Ensure that hosted app windows display the app title instead of the page
 // title when off scope.
-IN_PROC_BROWSER_TEST_P(HostedAppTest, OffScopeUrlsDisplayAppTitle) {
+IN_PROC_BROWSER_TEST_P(HostedAppCustomTabBarOnlyTest,
+                       OffScopeUrlsDisplayAppTitle) {
   ASSERT_TRUE(https_server()->Start());
   GURL url = GetSecureAppURL();
 
@@ -949,7 +956,8 @@
 
 // Ensure that hosted app windows display the app title instead of the page
 // title when using http.
-IN_PROC_BROWSER_TEST_P(HostedAppTest, InScopeHttpUrlsDisplayAppTitle) {
+IN_PROC_BROWSER_TEST_P(HostedAppCustomTabBarOnlyTest,
+                       InScopeHttpUrlsDisplayAppTitle) {
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url = embedded_test_server()->GetURL("app.site.com", "/simple.html");
   WebApplicationInfo web_app_info;
@@ -2712,30 +2720,44 @@
 
 INSTANTIATE_TEST_CASE_P(/* no prefix */,
                         HostedAppTest,
-                        ::testing::Combine(kAppTypeValues, ::testing::Bool()));
+                        ::testing::Combine(kAppTypeValues,
+                                           ::testing::Bool(),
+                                           ::testing::Bool()));
+
 INSTANTIATE_TEST_CASE_P(/* no prefix */,
-                        HostedAppPWAOnlyTest,
-                        ::testing::Values(std::tuple<AppType, bool>{
-                            AppType::BOOKMARK_APP, true}));
+                        HostedAppCustomTabBarOnlyTest,
+                        ::testing::Combine(kAppTypeValues,
+                                           ::testing::Bool(),
+                                           ::testing::Values(true)));
+INSTANTIATE_TEST_CASE_P(
+    /* no prefix */,
+    HostedAppPWAOnlyTest,
+    ::testing::Combine(::testing::Values(AppType::BOOKMARK_APP),
+                       ::testing::Values(true),
+                       ::testing::Bool()));
 INSTANTIATE_TEST_CASE_P(
     /* no prefix */,
     BookmarkAppOnlyTest,
     ::testing::Combine(::testing::Values(AppType::BOOKMARK_APP),
+                       ::testing::Bool(),
                        ::testing::Bool()));
 
 INSTANTIATE_TEST_CASE_P(
     /* no prefix */,
     HostedAppProcessModelTest,
     ::testing::Combine(::testing::Values(AppType::HOSTED_APP),
+                       ::testing::Bool(),
                        ::testing::Bool()));
 INSTANTIATE_TEST_CASE_P(
     /* no prefix */,
     HostedAppIsolatedOriginTest,
     ::testing::Combine(::testing::Values(AppType::HOSTED_APP),
+                       ::testing::Bool(),
                        ::testing::Bool()));
 
 INSTANTIATE_TEST_CASE_P(
     /* no prefix */,
     HostedAppSitePerProcessTest,
     ::testing::Combine(::testing::Values(AppType::HOSTED_APP),
+                       ::testing::Bool(),
                        ::testing::Bool()));
diff --git a/chrome/browser/ui/omnibox/omnibox_theme.cc b/chrome/browser/ui/omnibox/omnibox_theme.cc
index eb042c7..ce80ecc1 100644
--- a/chrome/browser/ui/omnibox/omnibox_theme.cc
+++ b/chrome/browser/ui/omnibox/omnibox_theme.cc
@@ -129,6 +129,24 @@
   return gfx::kPlaceholderColor;
 }
 
+SkColor GetOmniboxSecurityChipColor(
+    OmniboxTint tint,
+    security_state::SecurityLevel security_level) {
+  if (security_level == security_state::SECURE_WITH_POLICY_INSTALLED_CERT) {
+    return GetOmniboxColor(OmniboxPart::LOCATION_BAR_TEXT_DIMMED, tint);
+  }
+
+  OmniboxPartState state = OmniboxPartState::CHIP_DEFAULT;
+  if (security_level == security_state::EV_SECURE ||
+      security_level == security_state::SECURE) {
+    state = OmniboxPartState::CHIP_SECURE;
+  } else if (security_level == security_state::DANGEROUS) {
+    state = OmniboxPartState::CHIP_DANGEROUS;
+  }
+
+  return GetOmniboxColor(OmniboxPart::LOCATION_BAR_SECURITY_CHIP, tint, state);
+}
+
 float GetOmniboxStateAlpha(OmniboxPartState state) {
   switch (state) {
     case OmniboxPartState::NORMAL:
diff --git a/chrome/browser/ui/omnibox/omnibox_theme.h b/chrome/browser/ui/omnibox/omnibox_theme.h
index 001bd45e..5c8625b 100644
--- a/chrome/browser/ui/omnibox/omnibox_theme.h
+++ b/chrome/browser/ui/omnibox/omnibox_theme.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_THEME_H_
 #define CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_THEME_H_
 
+#include "components/security_state/core/security_state.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 // A part of the omnibox (location bar, location bar decoration, or dropdown).
@@ -53,6 +54,11 @@
                         OmniboxTint tint,
                         OmniboxPartState state = OmniboxPartState::NORMAL);
 
+// Returns the color of the security chip given |tint| and |security_level|.
+SkColor GetOmniboxSecurityChipColor(
+    OmniboxTint tint,
+    security_state::SecurityLevel security_level);
+
 float GetOmniboxStateAlpha(OmniboxPartState state);
 
 #endif  // CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_THEME_H_
diff --git a/chrome/browser/ui/views/bubble_anchor_util_views.cc b/chrome/browser/ui/views/bubble_anchor_util_views.cc
index 192f311a..edd18e5 100644
--- a/chrome/browser/ui/views/bubble_anchor_util_views.cc
+++ b/chrome/browser/ui/views/bubble_anchor_util_views.cc
@@ -24,8 +24,13 @@
     return {browser_view->GetLocationBarView(),
             browser_view->GetLocationBarView()->location_icon_view(),
             views::BubbleBorder::TOP_LEFT};
-  // Fall back to menu button if no location bar present.
 
+  if (anchor == kCustomTabBar && browser_view->toolbar()->custom_tab_bar())
+    return {browser_view->toolbar()->custom_tab_bar(),
+            browser_view->toolbar()->custom_tab_bar()->location_icon_view(),
+            views::BubbleBorder::TOP_LEFT};
+
+  // Fall back to menu button.
   views::Button* app_menu_button =
       browser_view->toolbar_button_provider()->GetAppMenuButton();
   if (app_menu_button && app_menu_button->IsDrawn())
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
new file mode 100644
index 0000000..d5461da
--- /dev/null
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
@@ -0,0 +1,157 @@
+// Copyright 2018 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/location_bar/custom_tab_bar_view.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/omnibox/omnibox_theme.h"
+#include "chrome/browser/ui/page_info/page_info_dialog.h"
+#include "chrome/browser/ui/views/chrome_typography.h"
+#include "components/url_formatter/url_formatter.h"
+#include "content/public/browser/navigation_entry.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/style/typography.h"
+#include "ui/views/style/typography_provider.h"
+
+// Container view for laying out and rendering the title/origin of the current
+// page.
+class CustomTabBarTitleOriginView : public views::View {
+ public:
+  CustomTabBarTitleOriginView() {
+    title_label_ = new views::Label(
+        base::string16(), views::style::TextContext::CONTEXT_DIALOG_TITLE);
+    location_label_ = new views::Label(base::string16());
+
+    constexpr SkColor text_color = gfx::kGoogleGrey900;
+    title_label_->SetEnabledColor(text_color);
+    location_label_->SetEnabledColor(text_color);
+
+    AddChildView(title_label_);
+    AddChildView(location_label_);
+
+    auto layout = std::make_unique<views::BoxLayout>(
+        views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0);
+    layout->set_cross_axis_alignment(
+        views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_START);
+    SetLayoutManager(std::move(layout));
+  }
+
+  void Update(base::string16 title, base::string16 location) {
+    title_label_->SetText(title);
+    location_label_->SetText(location);
+  }
+
+ private:
+  views::Label* title_label_;
+  views::Label* location_label_;
+};
+
+// static
+const char CustomTabBarView::kViewClassName[] = "CustomTabBarView";
+
+CustomTabBarView::CustomTabBarView(Browser* browser,
+                                   LocationBarView::Delegate* delegate)
+    : TabStripModelObserver(), delegate_(delegate) {
+  constexpr SkColor background_color = SK_ColorWHITE;
+  SetBackground(views::CreateSolidBackground(background_color));
+  browser->tab_strip_model()->AddObserver(this);
+
+  const gfx::FontList& font_list = views::style::GetFont(
+      CONTEXT_OMNIBOX_PRIMARY, views::style::STYLE_PRIMARY);
+  location_icon_view_ = new LocationIconView(font_list, this);
+  AddChildView(location_icon_view_);
+
+  title_origin_view_ = new CustomTabBarTitleOriginView();
+  AddChildView(title_origin_view_);
+
+  int padding = GetLayoutConstant(LayoutConstant::LOCATION_BAR_ELEMENT_PADDING);
+  // The location icon already has some padding, so we subtract it from the
+  // padding we're going to apply.
+  int location_icon_padding =
+      GetLayoutInsets(LayoutInset::LOCATION_BAR_ICON_INTERIOR_PADDING).left();
+  gfx::Insets insets(padding, padding - location_icon_padding, padding,
+                     padding);
+
+  auto layout = std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal, insets, 0);
+  layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_CENTER);
+
+  SetLayoutManager(std::move(layout));
+
+  constexpr SkColor border_color = gfx::kGoogleGrey400;
+  // Create a bottom border.
+  SetBorder(views::CreateSolidSidedBorder(0, 0, 1, 0, border_color));
+}
+
+CustomTabBarView::~CustomTabBarView() {}
+
+void CustomTabBarView::TabChangedAt(content::WebContents* contents,
+                                    int index,
+                                    TabChangeType change_type) {
+  if (!contents)
+    return;
+
+  content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
+  base::string16 title, location;
+  if (entry) {
+    title = Browser::FormatTitleForDisplay(entry->GetTitleForDisplay());
+    location = url_formatter::FormatUrl(
+        entry->GetVirtualURL(), url_formatter::kFormatUrlOmitDefaults,
+        net::UnescapeRule::NORMAL, nullptr, nullptr, nullptr);
+  }
+
+  title_origin_view_->Update(title, location);
+  location_icon_view_->Update(/*suppress animations = */ false);
+
+  last_title_ = title;
+  last_location_ = location;
+
+  Layout();
+}
+
+content::WebContents* CustomTabBarView::GetWebContents() {
+  return delegate_->GetWebContents();
+}
+
+bool CustomTabBarView::IsEditingOrEmpty() {
+  return false;
+}
+
+void CustomTabBarView::OnLocationIconPressed(const ui::MouseEvent& event) {}
+
+void CustomTabBarView::OnLocationIconDragged(const ui::MouseEvent& event) {}
+
+bool CustomTabBarView::ShowPageInfoDialog() {
+  return ::ShowPageInfoDialog(GetWebContents(),
+                              bubble_anchor_util::Anchor::kCustomTabBar);
+}
+
+SkColor CustomTabBarView::GetSecurityChipColor(
+    security_state::SecurityLevel security_level) const {
+  // TODO(harrisjay): Use app theme color to determine OmniboxTint.
+  return GetOmniboxSecurityChipColor(OmniboxTint::LIGHT, security_level);
+}
+
+gfx::ImageSkia CustomTabBarView::GetLocationIcon(
+    LocationIconView::Delegate::IconFetchedCallback on_icon_fetched) const {
+  return gfx::CreateVectorIcon(
+      delegate_->GetLocationBarModel()->GetVectorIcon(),
+      GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
+      GetSecurityChipColor(GetLocationBarModel()->GetSecurityLevel(false)));
+}
+
+SkColor CustomTabBarView::GetLocationIconInkDropColor() const {
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextfieldDefaultColor);
+}
+
+const LocationBarModel* CustomTabBarView::GetLocationBarModel() const {
+  return delegate_->GetLocationBarModel();
+}
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
new file mode 100644
index 0000000..29f58297
--- /dev/null
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.h
@@ -0,0 +1,63 @@
+// Copyright 2018 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_VIEWS_LOCATION_BAR_CUSTOM_TAB_BAR_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_LOCATION_BAR_CUSTOM_TAB_BAR_VIEW_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
+#include "chrome/browser/ui/views/location_bar/location_icon_view.h"
+
+class CustomTabBarTitleOriginView;
+
+// A CustomTabBarView displays a read only title and origin for the current page
+// and a security status icon. This is visible if the hosted app window is
+// displaying a page over HTTP or if the current page is outside of the app
+// scope.
+class CustomTabBarView : public views::View,
+                         public TabStripModelObserver,
+                         public LocationIconView::Delegate {
+ public:
+  static const char kViewClassName[];
+
+  CustomTabBarView(Browser* browser, LocationBarView::Delegate* delegate);
+  ~CustomTabBarView() override;
+
+  LocationIconView* location_icon_view() { return location_icon_view_; }
+
+  // TabstripModelObserver:
+  void TabChangedAt(content::WebContents* contents,
+                    int index,
+                    TabChangeType change_type) override;
+
+  // LocationIconView::Delegate:
+  content::WebContents* GetWebContents() override;
+  bool IsEditingOrEmpty() override;
+  void OnLocationIconPressed(const ui::MouseEvent& event) override;
+  void OnLocationIconDragged(const ui::MouseEvent& event) override;
+  SkColor GetSecurityChipColor(
+      security_state::SecurityLevel security_level) const override;
+  bool ShowPageInfoDialog() override;
+  const LocationBarModel* GetLocationBarModel() const override;
+  gfx::ImageSkia GetLocationIcon(LocationIconView::Delegate::IconFetchedCallback
+                                     on_icon_fetched) const override;
+  SkColor GetLocationIconInkDropColor() const override;
+
+  // Methods for testing.
+  base::string16 title_for_testing() const { return last_title_; }
+  base::string16 location_for_testing() const { return last_location_; }
+
+ private:
+  base::string16 last_title_;
+  base::string16 last_location_;
+
+  LocationBarView::Delegate* delegate_ = nullptr;
+  LocationIconView* location_icon_view_ = nullptr;
+  CustomTabBarTitleOriginView* title_origin_view_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(CustomTabBarView);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_LOCATION_BAR_CUSTOM_TAB_BAR_VIEW_H_
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
new file mode 100644
index 0000000..51e61fe
--- /dev/null
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
@@ -0,0 +1,349 @@
+// Copyright 2018 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 "base/scoped_observer.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/ssl/cert_verifier_browser_test.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/web_application_info.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+
+const base::FilePath::CharType kDocRoot[] =
+    FILE_PATH_LITERAL("chrome/test/data");
+
+namespace {
+
+// Waits until the title of any tab in the browser for |contents| has the title
+// |target_title|.
+class TestTitleObserver : public TabStripModelObserver {
+ public:
+  // Create a new TitleObserver for the browser of |contents|, waiting for
+  // |target_title|.
+  TestTitleObserver(content::WebContents* contents, base::string16 target_title)
+      : contents_(contents),
+        target_title_(target_title),
+        tab_strip_model_observer_(this) {
+    browser_ = chrome::FindBrowserWithWebContents(contents_);
+    tab_strip_model_observer_.Add(browser_->tab_strip_model());
+  }
+
+  // Run a loop, blocking until a tab has the title |target_title|.
+  void Wait() {
+    if (seen_target_title_)
+      return;
+
+    awaiter_.Run();
+  }
+
+  // TabstripModelObserver:
+  void TabChangedAt(content::WebContents* contents,
+                    int index,
+                    TabChangeType change_type) override {
+    content::NavigationEntry* entry =
+        contents->GetController().GetVisibleEntry();
+    base::string16 title = entry ? entry->GetTitle() : base::string16();
+
+    if (title != target_title_)
+      return;
+
+    seen_target_title_ = true;
+    awaiter_.Quit();
+  }
+
+ private:
+  bool seen_target_title_ = false;
+
+  content::WebContents* contents_;
+  Browser* browser_;
+  base::string16 target_title_;
+  base::RunLoop awaiter_;
+  ScopedObserver<TabStripModel, TestTitleObserver> tab_strip_model_observer_;
+};
+
+// Opens a new popup window from |web_contents| on |target_url| and returns
+// the Browser it opened in.
+Browser* OpenPopup(content::WebContents* web_contents, const GURL& target_url) {
+  content::TestNavigationObserver nav_observer(target_url);
+  nav_observer.StartWatchingNewWebContents();
+
+  std::string script = "window.open('" + target_url.spec() +
+                       "', 'popup', 'width=400 height=400');";
+  EXPECT_TRUE(content::ExecuteScript(web_contents, script));
+  nav_observer.Wait();
+
+  return chrome::FindLastActive();
+}
+
+// Navigates to |target_url| and waits for navigation to complete.
+void NavigateAndWait(content::WebContents* web_contents,
+                     const GURL& target_url) {
+  content::TestNavigationObserver nav_observer(web_contents);
+
+  std::string script = "window.location = '" + target_url.spec() + "';";
+  EXPECT_TRUE(content::ExecuteScript(web_contents, script));
+  nav_observer.Wait();
+}
+
+// Navigates |web_contents| to |location|, waits for navigation to complete
+// and then sets document.title to be |title| and waits for the change
+// to propogate.
+void SetTitleAndLocation(content::WebContents* web_contents,
+                         const base::string16 title,
+                         const GURL& location) {
+  NavigateAndWait(web_contents, location);
+
+  TestTitleObserver title_observer(web_contents, title);
+
+  std::string script = "document.title = '" + base::UTF16ToASCII(title) + "';";
+  EXPECT_TRUE(content::ExecuteScript(web_contents, script));
+
+  title_observer.Wait();
+}
+
+}  // namespace
+
+class CustomTabBarViewBrowserTest : public extensions::ExtensionBrowserTest {
+ public:
+  CustomTabBarViewBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+  ~CustomTabBarViewBrowserTest() override {}
+
+ protected:
+  void SetUpInProcessBrowserTestFixture() override {
+    extensions::ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
+    cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    extensions::ExtensionBrowserTest::TearDownInProcessBrowserTestFixture();
+    cert_verifier_.TearDownInProcessBrowserTestFixture();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
+    // Browser will both run and display insecure content.
+    command_line->AppendSwitch(switches::kAllowRunningInsecureContent);
+    cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpOnMainThread() override {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kDesktopPWAsStayInWindow, features::kDesktopPWAWindowing,
+         features::kDesktopPWAsCustomTabUI},
+        {});
+    https_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
+
+    // Everything should be redirected to the http server.
+    host_resolver()->AddRule("*", "127.0.0.1");
+    // All SSL cert checks should be valid.
+    cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+
+    browser_view_ = BrowserView::GetBrowserViewForBrowser(browser());
+
+    location_bar_ = browser_view_->GetLocationBarView();
+    custom_tab_bar_ = browser_view_->toolbar()->custom_tab_bar();
+  }
+
+  void InstallPWA(const GURL& app_url) {
+    WebApplicationInfo web_app_info;
+    web_app_info.app_url = app_url;
+    web_app_info.scope = app_url.GetWithoutFilename();
+    web_app_info.open_as_window = true;
+
+    auto* app = InstallBookmarkApp(web_app_info);
+
+    ui_test_utils::UrlLoadObserver url_observer(
+        app_url, content::NotificationService::AllSources());
+    app_browser_ = LaunchAppBrowser(app);
+    url_observer.Wait();
+
+    DCHECK(app_browser_);
+    DCHECK(app_browser_ != browser());
+  }
+
+  Browser* app_browser_;
+  BrowserView* browser_view_;
+  LocationBarView* location_bar_;
+  CustomTabBarView* custom_tab_bar_;
+
+  net::EmbeddedTestServer* https_server() { return &https_server_; }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  net::EmbeddedTestServer https_server_;
+  // Similar to net::MockCertVerifier, but also updates the CertVerifier
+  // used by the NetworkService. This is needed for when tests run with
+  // the NetworkService enabled.
+  ChromeMockCertVerifier cert_verifier_;
+
+  DISALLOW_COPY_AND_ASSIGN(CustomTabBarViewBrowserTest);
+};
+
+// Check the custom tab bar is not instantiated for a tabbed browser window.
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest,
+                       IsNotCreatedInTabbedBrowser) {
+  EXPECT_TRUE(browser_view_->IsBrowserTypeNormal());
+  EXPECT_FALSE(custom_tab_bar_);
+}
+
+// Check the custom tab bar is not instantiated for a popup window.
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest, IsNotCreatedInPopup) {
+  Browser* popup = OpenPopup(browser_view_->GetActiveWebContents(),
+                             GURL("http://example.com"));
+  EXPECT_TRUE(popup);
+
+  BrowserView* popup_view = BrowserView::GetBrowserViewForBrowser(popup);
+
+  // The popup should be in a new window.
+  EXPECT_NE(browser_view_, popup_view);
+
+  // Popups are not the normal browser view.
+  EXPECT_FALSE(popup_view->IsBrowserTypeNormal());
+  // Popups should not have a custom tab bar view.
+  EXPECT_FALSE(popup_view->toolbar()->custom_tab_bar());
+}
+
+// Check the custom tab will be used for a Desktop PWA.
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest, IsUsedForDesktopPWA) {
+  ASSERT_TRUE(https_server()->Start());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  const GURL& url = https_server()->GetURL("app.com", "/ssl/google.html");
+  InstallPWA(url);
+
+  EXPECT_TRUE(app_browser_);
+
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
+  EXPECT_NE(app_view, browser_view_);
+
+  EXPECT_FALSE(app_view->IsBrowserTypeNormal());
+
+  // Custom tab bar should be created.
+  EXPECT_TRUE(app_view->toolbar()->custom_tab_bar());
+}
+
+// The custom tab bar should update with the title and location of the current
+// page.
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest, TitleAndLocationUpdate) {
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL& app_url = https_server()->GetURL("app.com", "/ssl/google.html");
+  const GURL& navigate_to =
+      https_server()->GetURL("app.com", "/ssl/blank_page.html");
+
+  InstallPWA(app_url);
+
+  EXPECT_TRUE(app_browser_);
+
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
+  EXPECT_NE(app_view, browser_view_);
+
+  SetTitleAndLocation(app_view->GetActiveWebContents(),
+                      base::ASCIIToUTF16("FooBar"), navigate_to);
+
+  EXPECT_EQ(base::ASCIIToUTF16(navigate_to.spec()),
+            app_view->toolbar()->custom_tab_bar()->location_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16("FooBar"),
+            app_view->toolbar()->custom_tab_bar()->title_for_testing());
+}
+
+// If the page doesn't specify a title, we should use the origin.
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest,
+                       UsesLocationInsteadOfEmptyTitles) {
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL& app_url = https_server()->GetURL("app.com", "/ssl/google.html");
+  InstallPWA(app_url);
+
+  EXPECT_TRUE(app_browser_);
+
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
+  EXPECT_NE(app_view, browser_view_);
+
+  // Empty title should use location.
+  SetTitleAndLocation(app_view->GetActiveWebContents(), base::string16(),
+                      GURL("http://example.test/"));
+  EXPECT_EQ(base::ASCIIToUTF16("example.test"),
+            app_view->toolbar()->custom_tab_bar()->location_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16("example.test"),
+            app_view->toolbar()->custom_tab_bar()->title_for_testing());
+}
+
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest, URLsWithEmojiArePunyCoded) {
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL& app_url = https_server()->GetURL("app.com", "/ssl/google.html");
+  const GURL& navigate_to = GURL("https://🔒.example/ssl/blank_page.html");
+
+  InstallPWA(app_url);
+
+  EXPECT_TRUE(app_browser_);
+
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
+  EXPECT_NE(app_view, browser_view_);
+
+  SetTitleAndLocation(app_view->GetActiveWebContents(),
+                      base::ASCIIToUTF16("FooBar"), navigate_to);
+
+  EXPECT_EQ(base::UTF8ToUTF16("https://xn--lv8h.example/ssl/blank_page.html"),
+            app_view->toolbar()->custom_tab_bar()->location_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16("FooBar"),
+            app_view->toolbar()->custom_tab_bar()->title_for_testing());
+}
+
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest,
+                       URLsWithNonASCIICharactersDisplayNormally) {
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL& app_url = https_server()->GetURL("app.com", "/ssl/google.html");
+  const GURL& navigate_to = GURL("https://ΐ.example/ssl/blank_page.html");
+
+  InstallPWA(app_url);
+
+  EXPECT_TRUE(app_browser_);
+
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
+  EXPECT_NE(app_view, browser_view_);
+
+  SetTitleAndLocation(app_view->GetActiveWebContents(),
+                      base::ASCIIToUTF16("FooBar"), navigate_to);
+
+  EXPECT_EQ(base::UTF8ToUTF16("https://ΐ.example/ssl/blank_page.html"),
+            app_view->toolbar()->custom_tab_bar()->location_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16("FooBar"),
+            app_view->toolbar()->custom_tab_bar()->title_for_testing());
+}
+
+IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest,
+                       BannedCharactersAreURLEncoded) {
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL& app_url = https_server()->GetURL("app.com", "/ssl/google.html");
+  const GURL& navigate_to = GURL("https://ΐ.example/🔒/blank_page.html");
+
+  InstallPWA(app_url);
+
+  EXPECT_TRUE(app_browser_);
+
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
+  EXPECT_NE(app_view, browser_view_);
+
+  SetTitleAndLocation(app_view->GetActiveWebContents(),
+                      base::ASCIIToUTF16("FooBar"), navigate_to);
+
+  EXPECT_EQ(base::UTF8ToUTF16("https://ΐ.example/%F0%9F%94%92/blank_page.html"),
+            app_view->toolbar()->custom_tab_bar()->location_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16("FooBar"),
+            app_view->toolbar()->custom_tab_bar()->title_for_testing());
+}
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 34e4b11..b930b27 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -1186,20 +1186,7 @@
 
 SkColor LocationBarView::GetSecurityChipColor(
     security_state::SecurityLevel security_level) const {
-  // Only used in ChromeOS.
-  if (security_level == security_state::SECURE_WITH_POLICY_INSTALLED_CERT)
-    return GetColor(OmniboxPart::LOCATION_BAR_TEXT_DIMMED);
-
-  OmniboxPartState state = OmniboxPartState::CHIP_DEFAULT;
-  if (security_level == security_state::EV_SECURE ||
-      security_level == security_state::SECURE) {
-    state = OmniboxPartState::CHIP_SECURE;
-  } else if (security_level == security_state::DANGEROUS) {
-    state = OmniboxPartState::CHIP_DANGEROUS;
-  }
-
-  return GetOmniboxColor(OmniboxPart::LOCATION_BAR_SECURITY_CHIP, tint(),
-                         state);
+  return GetOmniboxSecurityChipColor(tint(), security_level);
 }
 
 bool LocationBarView::ShowPageInfoDialog() {
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 73e7ebe..37bc104 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -709,7 +709,7 @@
                 ? l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE)
                 : CoreTabHelper::GetDefaultTitle();
   } else {
-    Browser::FormatTitleForDisplay(&title);
+    title = Browser::FormatTitleForDisplay(title);
   }
   title_->SetText(title);
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 999a6eb1..ac59fa1 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -46,6 +46,7 @@
 #include "chrome/browser/ui/views/translate/translate_bubble_view.h"
 #include "chrome/browser/ui/views/translate/translate_icon_view.h"
 #include "chrome/browser/upgrade_detector/upgrade_detector.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
@@ -102,6 +103,18 @@
   return ui::MaterialDesignController::touch_ui() ? 0 : 8;
 }
 
+// Gets the display mode for a given browser.
+ToolbarView::DisplayMode GetDisplayMode(Browser* browser) {
+  if (browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP))
+    return ToolbarView::DisplayMode::NORMAL;
+
+  if (browser->hosted_app_controller() &&
+      base::FeatureList::IsEnabled(features::kDesktopPWAsCustomTabUI))
+    return ToolbarView::DisplayMode::CUSTOM_TAB;
+
+  return ToolbarView::DisplayMode::LOCATION;
+}
+
 }  // namespace
 
 // static
@@ -114,9 +127,7 @@
     : browser_(browser),
       browser_view_(browser_view),
       app_menu_icon_controller_(browser->profile(), this),
-      display_mode_(browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP)
-                        ? DISPLAYMODE_NORMAL
-                        : DISPLAYMODE_LOCATION) {
+      display_mode_(GetDisplayMode(browser)) {
   set_id(VIEW_ID_TOOLBAR);
 
   chrome::AddCommandObserver(browser_, IDC_BACK, this);
@@ -144,13 +155,19 @@
 void ToolbarView::Init() {
   location_bar_ = new LocationBarView(browser_, browser_->profile(),
                                       browser_->command_controller(), this,
-                                      !is_display_mode_normal());
+                                      display_mode_ != DisplayMode::NORMAL);
   // Make sure the toolbar shows by default.
   size_animation_.Reset(1);
 
-  if (!is_display_mode_normal()) {
+  if (display_mode_ != DisplayMode::NORMAL) {
     AddChildView(location_bar_);
     location_bar_->Init();
+
+    if (display_mode_ == DisplayMode::CUSTOM_TAB) {
+      custom_tab_bar_ = new CustomTabBarView(browser_, this);
+      AddChildView(custom_tab_bar_);
+    }
+
     initialized_ = true;
     return;
   }
@@ -290,7 +307,11 @@
 
 void ToolbarView::SetToolbarVisibility(bool visible) {
   SetVisible(visible);
-  location_bar_->SetVisible(visible);
+  views::View* bar = display_mode_ == DisplayMode::CUSTOM_TAB
+                         ? static_cast<views::View*>(custom_tab_bar_)
+                         : static_cast<views::View*>(location_bar_);
+
+  bar->SetVisible(visible);
 }
 
 void ToolbarView::UpdateToolbarVisibility(bool visible, bool animate) {
@@ -498,7 +519,14 @@
   if (!initialized_)
     return;
 
-  if (!is_display_mode_normal()) {
+  if (display_mode_ == DisplayMode::CUSTOM_TAB) {
+    custom_tab_bar_->SetBounds(0, 0, width(),
+                               custom_tab_bar_->GetPreferredSize().height());
+    location_bar_->SetVisible(false);
+    return;
+  }
+
+  if (display_mode_ == DisplayMode::LOCATION) {
     location_bar_->SetBounds(0, 0, width(),
                              location_bar_->GetPreferredSize().height());
     return;
@@ -626,7 +654,7 @@
 }
 
 void ToolbarView::OnPaintBackground(gfx::Canvas* canvas) {
-  if (!is_display_mode_normal())
+  if (display_mode_ != DisplayMode::NORMAL)
     return;
 
   const ui::ThemeProvider* tp = GetThemeProvider();
@@ -655,7 +683,7 @@
 }
 
 void ToolbarView::OnThemeChanged() {
-  if (is_display_mode_normal())
+  if (display_mode_ == DisplayMode::NORMAL)
     LoadImages();
 }
 
@@ -695,7 +723,7 @@
 
 // ui::MaterialDesignControllerObserver:
 void ToolbarView::OnTouchUiChanged() {
-  if (is_display_mode_normal()) {
+  if (display_mode_ == DisplayMode::NORMAL) {
     LoadImages();
     PreferredSizeChanged();
   }
@@ -770,8 +798,12 @@
 
 gfx::Size ToolbarView::GetSizeInternal(
     gfx::Size (View::*get_size)() const) const {
-  gfx::Size size((location_bar_->*get_size)());
-  if (is_display_mode_normal()) {
+  View* view = display_mode_ == DisplayMode::CUSTOM_TAB
+                   ? static_cast<View*>(custom_tab_bar_)
+                   : static_cast<View*>(location_bar_);
+
+  gfx::Size size = (view->*get_size)();
+  if (display_mode_ == DisplayMode::NORMAL) {
     const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
     const int browser_actions_width =
         (browser_actions_->*get_size)().width();
@@ -793,7 +825,7 @@
 }
 
 gfx::Size ToolbarView::SizeForContentSize(gfx::Size size) const {
-  if (is_display_mode_normal()) {
+  if (display_mode_ == DisplayMode::NORMAL) {
     // The size of the toolbar is computed using the size of the location bar
     // and constant padding values.
     int content_height = std::max(back_->GetPreferredSize().height(),
@@ -810,7 +842,7 @@
 }
 
 void ToolbarView::LoadImages() {
-  DCHECK(is_display_mode_normal());
+  DCHECK_EQ(display_mode_, DisplayMode::NORMAL);
 
   const ui::ThemeProvider* tp = GetThemeProvider();
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index 23b8a08..2e2f7aa 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/ui/toolbar/back_forward_menu_model.h"
 #include "chrome/browser/ui/views/frame/browser_root_view.h"
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
+#include "chrome/browser/ui/views/location_bar/custom_tab_bar_view.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
@@ -68,6 +69,15 @@
                     public BrowserRootView::DropTarget,
                     public ui::MaterialDesignControllerObserver {
  public:
+  // Types of display mode this toolbar can have.
+  enum class DisplayMode {
+    NORMAL,     // Normal toolbar with buttons, etc.
+    LOCATION,   // Slimline toolbar showing only compact location
+                // bar, used for popups.
+    CUSTOM_TAB  // Custom tab bar, used in PWAs when a location
+                // needs to be displayed.
+  };
+
   // The view class name.
   static const char kViewClassName[];
 
@@ -122,6 +132,7 @@
   ToolbarButton* back_button() const { return back_; }
   ReloadButton* reload_button() const { return reload_; }
   LocationBarView* location_bar() const { return location_bar_; }
+  CustomTabBarView* custom_tab_bar() { return custom_tab_bar_; }
   media_router::CastToolbarButton* cast_button() const { return cast_; }
   AvatarToolbarButton* avatar_button() const { return avatar_; }
   BrowserAppMenuButton* app_menu_button() const { return app_menu_button_; }
@@ -175,10 +186,6 @@
   bool AcceleratorPressed(const ui::Accelerator& acc) override;
   void ChildPreferredSizeChanged(views::View* child) override;
 
-  bool is_display_mode_normal() const {
-    return display_mode_ == DISPLAYMODE_NORMAL;
-  }
-
  protected:
   // AccessiblePaneView:
   bool SetPaneFocusAndFocusDefault() override;
@@ -186,19 +193,12 @@
   // ui::MaterialDesignControllerObserver:
   void OnTouchUiChanged() override;
 
-  // This controls Toolbar and LocationBar visibility.
-  // If we don't both, tab navigation from the app menu breaks
+  // This controls Toolbar, LocationBar and CustomTabBar visibility.
+  // If we don't set all three, tab navigation from the app menu breaks
   // on Chrome OS.
   void SetToolbarVisibility(bool visible);
 
  private:
-  // Types of display mode this toolbar can have.
-  enum DisplayMode {
-    DISPLAYMODE_NORMAL,   // Normal toolbar with buttons, etc.
-    DISPLAYMODE_LOCATION  // Slimline toolbar showing only compact location
-                          // bar, used for popups.
-  };
-
   // AnimationDelegate:
   void AnimationEnded(const gfx::Animation* animation) override;
   void AnimationProgressed(const gfx::Animation* animation) override;
@@ -251,6 +251,7 @@
   ToolbarButton* forward_ = nullptr;
   ReloadButton* reload_ = nullptr;
   HomeButton* home_ = nullptr;
+  CustomTabBarView* custom_tab_bar_ = nullptr;
   LocationBarView* location_bar_ = nullptr;
   BrowserActionsContainer* browser_actions_ = nullptr;
   media_router::CastToolbarButton* cast_ = nullptr;
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 48dc4bd..d1584b3 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -212,7 +212,7 @@
 // Determines whether out of scope pages in the hosted app will use the
 // custom tab UI.
 const base::Feature kDesktopPWAsCustomTabUI{"DesktopPWAsCustomTabUI",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Determines whether in scope requests are always opened in the same window.
 const base::Feature kDesktopPWAsStayInWindow{"DesktopPWAsStayInWindow",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ab57261..f638d7a 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1533,6 +1533,7 @@
         "../browser/ui/views/hung_renderer_view_browsertest.cc",
         "../browser/ui/views/importer/import_lock_dialog_view_browsertest.cc",
         "../browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc",
+        "../browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc",
         "../browser/ui/views/location_bar/location_bar_view_browsertest.cc",
         "../browser/ui/views/location_bar/location_icon_view_browsertest.cc",
         "../browser/ui/views/location_bar/zoom_bubble_view_browsertest.cc",