blob: 6e5ddcb283b6934115d290833e0cfd7b9fc777cf [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string_view>
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_icon_waiter.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/webapps/browser/test/service_worker_registration_waiter.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ui_base_switches.h"
#include "ui/native_theme/native_theme.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/windows_version.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/public/cpp/style/dark_light_mode_controller.h"
#endif
using ::testing::ElementsAre;
namespace {
constexpr char kHistogramClosingReason[] =
"WebApp.DefaultOffline.ClosingReason";
constexpr char kHistogramDurationShown[] =
"WebApp.DefaultOffline.DurationShown";
} // namespace
namespace web_app {
enum class PageFlagParam {
kWithDefaultPageFlag = 0,
kWithoutDefaultPageFlag = 1,
kMaxValue = kWithoutDefaultPageFlag
};
class WebAppOfflineTest : public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
base::ScopedAllowBlockingForTesting allow_blocking;
override_registration_ =
OsIntegrationTestOverrideImpl::OverrideForTesting();
}
void TearDownOnMainThread() override {
test::UninstallAllWebApps(browser()->profile());
{
base::ScopedAllowBlockingForTesting allow_blocking;
override_registration_.reset();
}
}
// Start a web app without a service worker and disconnect.
webapps::AppId StartWebAppAndDisconnect(content::WebContents* web_contents,
std::string_view relative_url) {
GURL target_url(embedded_test_server()->GetURL(relative_url));
web_app::NavigateViaLinkClickToURLAndWait(browser(), target_url);
webapps::AppId app_id = web_app::test::InstallPwaForCurrentUrl(browser());
WebAppIconWaiter(browser()->profile(), app_id).Wait();
std::unique_ptr<content::URLLoaderInterceptor> interceptor =
content::URLLoaderInterceptor::SetupRequestFailForURL(
target_url, net::ERR_INTERNET_DISCONNECTED);
content::TestNavigationObserver observer(web_contents, 1);
web_contents->GetController().Reload(content::ReloadType::NORMAL, false);
observer.Wait();
return app_id;
}
// Start a PWA with a service worker and disconnect.
void StartPwaAndDisconnect(content::WebContents* web_contents,
std::string_view relative_url) {
GURL target_url(embedded_test_server()->GetURL(relative_url));
web_app::ServiceWorkerRegistrationWaiter registration_waiter(
browser()->profile(), target_url);
web_app::NavigateViaLinkClickToURLAndWait(browser(), target_url);
registration_waiter.AwaitRegistration();
webapps::AppId app_id = web_app::test::InstallPwaForCurrentUrl(browser());
WebAppIconWaiter(browser()->profile(), app_id).Wait();
std::unique_ptr<content::URLLoaderInterceptor> interceptor =
content::URLLoaderInterceptor::SetupRequestFailForURL(
target_url, net::ERR_INTERNET_DISCONNECTED);
content::TestNavigationObserver observer(web_contents, 1);
web_contents->GetController().Reload(content::ReloadType::NORMAL, false);
observer.Wait();
}
void ReloadWebContents(content::WebContents* web_contents) {
content::TestNavigationObserver observer(web_contents, 1);
web_contents->GetController().Reload(content::ReloadType::NORMAL, false);
observer.Wait();
}
void CloseBrowser(content::WebContents* web_contents) {
Browser* app_browser = chrome::FindBrowserWithTab(web_contents);
app_browser->window()->Close();
ui_test_utils::WaitForBrowserToClose(app_browser);
}
private:
std::unique_ptr<OsIntegrationTestOverrideImpl::BlockingRegistration>
override_registration_;
};
class WebAppOfflinePageTest : public WebAppOfflineTest {
public:
void SyncHistograms() {
content::FetchHistogramsFromChildProcesses();
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
}
// Expect that the histogram has been updated.
void ExpectUniqueSample(net::Error error, int samples) {
SyncHistograms();
histogram_tester_.ExpectUniqueSample(
"Net.ErrorPageCounts.WebAppAlternativeErrorPage", -error, samples);
}
base::HistogramTester* histogram() { return &histogram_tester_; }
private:
base::HistogramTester histogram_tester_;
};
// When a web app with a manifest and no service worker is offline it should
// display the default offline page rather than the dino.
// When the exact same conditions are applied with the feature flag disabled
// expect that the default offline page is not shown.
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest, WebAppOfflinePageIsDisplayed) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
StartWebAppAndDisconnect(web_contents, "/banners/no-sw-with-colors.html");
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 1);
// Expect that the default offline page is showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') !== null")
.ExtractBool());
}
// When a web app with a manifest and service worker that doesn't handle being
// offline it should display the default offline page rather than the dino.
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest,
WebAppOfflineWithEmptyServiceWorker) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
StartPwaAndDisconnect(web_contents, "/banners/background-color.html");
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 1);
// Expect that the default offline page is showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') !== null")
.ExtractBool());
}
// When a web app with a manifest and service worker that handles being offline
// it should not display the default offline page.
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest, WebAppOfflineWithServiceWorker) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
StartPwaAndDisconnect(web_contents, "/banners/theme-color.html");
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
// Expect that the default offline page is not showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') === null")
.ExtractBool());
}
// Default offline page icon test.
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest, WebAppOfflinePageIconShowing) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
StartWebAppAndDisconnect(web_contents,
"/banners/no_sw_fetch_handler_test_page.html");
WaitForLoadStop(web_contents);
constexpr char kExpectedIconUrl[] =
"data:image/"
"png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAIAAAAErfB6AAAAAXNSR0IArs4c6QAADvBJREFU"
"eJztnHuMXPV1x8/3/"
"O68dndmd2dtHDAmBoxxzMsOjwA2GFo1UVJC0xa1pUqDoihRU0IKIY2B8AhPE6ukRG2hDZXaC"
"oHSoqhq2jQhqQBhU2PzNGAgEMCQEBt7H7Oved37O6d//"
"GaXXXvXeLGdvfvj99FobO3M3Lkzn/n97vmdc+7F/"
"Lu2U8BfeLZ3IHBoCYI9Jwj2nCDYc4JgzwmCPScI9pwg2HOCYM8Jgj0nCPacINhzgmDPCYI9J"
"wj2nCDYc4JgzwmCPScI9pwg2HOCYM8Jgj0nCPacINhzgmDPCYI9Jwj2nCDYc4JgzwmCPScI9"
"pwg2HOCYM8Jgj0nCPacINhzgmDPCYI9Jwj2nCDYc4JgzwmCPScI9pwg2HOCYM8Jgj0nCPacI"
"NhzgmDPCYI9Jwj2nCDYc4JgzwmCPScI9pxotnfg/aOqDERT/"
"URFKREFMAu7lTLmqmAlBZCI7qoKqRKBSInI/ScToZw3VigonpOCVRVALLqwnf/"
"ut3s6s0bJOSZRZeCpdxq3Pj5QLhirs72vs83cE6xEAJi0EcvXT+u+eFlx7+"
"e0Z3BDIowgeA4KJlXDGGzoHx/ffvGyYiLqRq17MBGNGEMNIUA/"
"8HbnpGADVGNZXDJXntaVNbCiEYMBN28TUcQwDCLC+"
"HH5A8wcWyapqlVl4IqPlk6cn7OqZrLdwB7MJcHOYj3RTyzOX3JiKRE1gBLtHEkAaJiRp2LOC"
"HaxlRWdX+Bbzi5nDTMpESp1+61N/"
"S5+nu19TCNzRjCpGqKG1evO7F5SzoqoEgF051ODW3Y0iUg1TNFTMDcEq2rEqDTtp48u/"
"MmyDlG1qob56Z21u7cOMRMRBb9TMgcEKykDtUSOKkbXnd1dyLBVBVBp2Js3V/"
"oamjNwGawZbFMVRAwyE26M1kPun328eGwbCpq0hT03kgLmwDIJCoKK0hWndi7vycVWAYoY92"
"4bfuitensGMz36qhKAptVqMumFDGrP7EdM3groAFDDam3yRgyorbWRVCRK0y5YVQ1oqCkXHF"
"P4/AnFxCqRRoytu2p3Pj0YMWJRndnoJQMaiuXipe3XfKxLxpLYIOyqJn/2P7v6G5IxvC/"
"HqgAYNNSQC47J33ZOj1ViIlGKGDtGkr98uO/"
"VSlyIOA2jOO2CATRFFxWj6z5WzkWcWDGMeqy3bxncUZWuHNfrghl+"
"kULUHuGHr41etLTtk8d0jP99SXfm2+eWv/DT3SBtxe3T7RVpPdbju6P15/"
"Ys7sxOfOiuZyovDzQ7Mqmwm/ZjsKoSaWz18pWlkw7LWRHDYNB9L4/"
"8xy+qxQwnVkGY4QBuTdEJ4SsP9W3bXSciK2JFrOgfHNdx6YrScFMMjx1rJ87/"
"2hIvSrkIt59TXtyZTUSsSGxFVf/hmcrdzw23Z4xQWhKl6RXsYqtqrB9fnP/iKSVVBYgIrw/"
"Et2+ptGdY3Cw68+McQKSaYeyuydoN/"
"YN1S4TxguOVp3WtWZgfrGvE0D0OpICqGsZIUy5fWfrE0e2iagAQMoaf2tlY98RgIWKogjQNB"
"+D0ClYlEBLR+QW+eVXZwM14APTax/p3jloDOqDcJKCq+QiP/LKx/"
"okBwyBSBpS0pxCtX9NzVJFHY+HJCTK3Whuo2c8c23blaV2iSqruaD1Qs1c80tffkIghwAyD+"
"kNISgUDykSjsaw9vXN5T1ZUE1EG/"
"vWF4QffrOUMH3iECoAIhQzufm7k314aMsyJKECxlZPm5649qzvHsJPtGqZqrCfOy9y8upwda"
"yVRIkCv3tD39K5mW8SSsoRLGgWrqgGGmvb3l7R9dnlRlEQ1a/"
"gXA43vPjOYCEV8cBaa7o1U6VuPV57b1cgYVoUBWZHPLi99bnlHPVE3iN2qKBHKR3Tj2V1Lur"
"OxFVK1Sob5H7cO/fur1baMWxqlym8qBTNQi3Vhh/"
"nG6V0dWU6sqzHIHU8OvtAbt40lOg78jQBY1VyE7UP22o19Iw3rtgoiVb1pVfeqw3MjTWvgas"
"taj+XylaULji0mIhFDiDKGt+yorX+iItKa4Q/C5z+opE6wqooqQb+yorRyQT4RjQwiph+/"
"Mfr9n4+W82wPajedK2B0ZvnBt+q3bh5gEAGuNtWeNd85v+eIDtMUcb+"
"5Tx1duPzULivKgAuvdo0max/t2zEqhfGgL2WkS7ALWBpWz1mY/"
"+LJnbG4hCLeGUlu2lQRghIOenjqxnFnznzv+"
"eEHfj7i5DnxJ8zL3XRWtyg1RI8uRevX9BQyDFK4J6jesnlg8864K8+"
"pbeJMkWB9dzzhttXltgwbct+arttSeWUgKRgcohDGuRHFDZsq23obhtmqgjSx+"
"kfLil86qVhvynfO617cmRVRAEJg4P6XRv5520gxy4mkIy05FSkSTKpMVLdy9RldJ83PudCGg"
"R+/"
"Vr33pdG2DA7WoXeaN9ecwfbBZO2jfaOxNQARMdQwvvrRzh9cuOC8RW2i6kIuw3j2ndq1G/"
"szgLrc5SHarQMmLYJVKWIMNu3HP1y45ISia80hQm8tufHxgdhNgIf0awSsUinLD/"
"2yccumgUTIEtyOHVnMfOrY9sgwA6JEQG81ufzhvkpTIwNNs97UCFaGNqwe3hZdf2Z3e9aIKg"
"iGsX5LZVtfnI+gh3h9CSJARbUjg+89P/"
"LD10YzjLGAmsRlTVWVIEo3P155cldciOZAL1gqBKuCCE0rl60onnJYLhYVJcP42fbRe18cyb"
"ZC29/AjkABAFbpmg39L/c22GUrSRkgVasaMe5/cehfXhwuRFBCyu2mQrASMWi4Kb+1KP/"
"nK0uqBNWM4d5qcseTg9WE2rIwIAPsUVqfeNuDvevw2L8iPIhEtRDhreHk+"
"k0DLpxWVbd4M8DmHbXrN1UiwLgsZkpKCtMz++"
"VCEDWtHNFhrjurXIiMKwg2Elm3eeDBV0fzbWbH6LSFOxAlTemvy1jpqVWKb1i1VdsboTlW7y"
"1EaIv4PaNdHXOcj3DR0ra9i/"
"+GyGBCOiP1IzglgnV+wZz+oXxsxTAADNSTwaZ+bkXne36BjUQ/XIpc/"
"mv8flEx+"
"sxJpXKBrZIoMejVgfiF3jgfvVftuHXahL1sRemi44qunOCy1gyyqqcdXrhtdfnLP+"
"tNQK1BnG7HmH/X9tneB6rGcmJPZtOfLkyUMnxIIpe7nh289Ke9R3RGsUz7nFZrX8OuOjz3/"
"QsWlAtGWyfFgKg1V1ulCHTVht6/"
"3zpSMGh17uyrOWCWScUIJmolCDFWfVPVfZiYiBIxUaa1XGndi2osrS03rWYNhhpCvK8ivHNZ"
"jWVhu1l3bnleW2RFGCBA1dUN3d6qVVx1Rnnr7uSxt+"
"v5zD6be1JAKoIsIiLVRDQRtUrunrFfNzPN8thMftp+KICoGsY3Tu9yBwsQJaIgumfr0E3/"
"1+8UMwikXXlz5/"
"k9C9o5tpLykypSMYJdbihiRPz+"
"h4IbRhg7Eo9vyW2zLeJWwmkqF274jjT14mVtXzqlZMVFUsgYPL+"
"78d1nht4cilfMz154XNFVGqzI8eXsHWt6LvnJbiIF0jtHz75gJYoYQ0350evViSeC7g8gSlR"
"LWT5vUcG91t3vHE02/bqej9hdyyFiPNfbzEaQKe0SuXLkiT2Zdef0EAEkLkCuxva6x/"
"q3D9n2jPn6o/"
"1Ly9llPTkRYSJRvfDY9q+d2rh9y2ApZ8bS0akTPftBlvtKEtGBmp1wJYb9JpYlh+Ve/"
"cJRsZWMYXf/g1dGL7r/V6YUWWnNEYUMl3K810UdXFsdCVEG9MCnF6w+sqBjnXUMuuGx/"
"tu3DHbnjRDVEzl3Yf6+3z2sI8Nu2iHCcNN+/"
"ie7H3yz1pkz6Swozf4Ixlj4ekRxxjsDomosH2o3e0zRhQjZUnR48d2Y2SrtXUhWAikZpuG6v"
"XV19+ojC4kokyqRYf7v14bv2jrcmWOrRKT5CA//qr5+y8Ct58xz8ZdV7cpHf72m57X/"
"3PnGkG1LZUl49oOscSuxvJ9bUyjZK95Woubkp8mUzTSqEVOlbv/"
"wuLbLVnaKvmv3jUrz2o0D7veBVtUSbdGkBi4GJVaO7c7esrrckYVr6Rp7/"
"7SQCsGzQmtdlOgJPdnrzyq7sxmIiIBGItds6H+lYvPRu/"
"F3q4GL6IZNlWfHG7gYiciFSzouPaXUtMpjqbRZ/"
"mwT+"
"OAKdi0ZWaYbzupaWs66BY8lQOlvn6n81xu1Ypat0HiuqtXAZfDWsP3mxr6hhgVIVZlIVa86o"
"/uTi/ODDRulbNX0ARXs6gTVWP/ilOKFSzpcmch1w298u/"
"Y3Tw3lDBEpoADGKwqu4aSU5f99q37zpv6JDVyRwR3nzftIORpNxCBFZxd+"
"EAW7ybmW6PmLcmvP6LaqLoPGwK7R5K8e7RtsaoZZdCzPNuHg7cZxV8780wsj920bdqGWE7+"
"olPn2ufPaM0iU0pP9mNuCx0/UdVmw8ZsVne4UX1VioGGlnOd1q8r5CFbUKimBQN/"
"c2Pd8X1LM7qszF4ASEXDj5srT79Qj5qZVq1pL5LyjCl9dURpuCqUmnJ7bgidmwfIRj9+"
"XcjxdoxRAopo3uOd3ek5ekGcgazhj2DDu2Tr0wCvVjghjK9pph6Br4Hp72K7d0DfStLmIs4Y"
"LEWcYV59Z/vLJxYbVlMTSs78OPhD2zoKNX8owG/"
"GUeSsmGkl01RH5huBHr1d17OTgoaZd98QgTer8mnYIArBCxRw/"
"9uvm1x7p+70lHVbVgFwjykd6suU8DzZ1ygul/oaZ/"
"UzWgTBVFuw9LkaqSgyqJTrcsETjFpWAngLP5JJb6mb74VhqTZnwa1AwynmeUc710DG3R/"
"B0WbB9XE7YTdGFCMXsnp89FppJ7Q9uU51Z7s5NGqpKlMxsU4eQuS14YhZsyr9P9xKd6iX7ft"
"V0m7JKU17yNA1253yQFXhPgmDPCYI9Jwj2nCDYc4JgzwmCPScI9pwg2HOCYM8Jgj0nCPacIN"
"hzgmDPCYI9Jwj2nCDYc4JgzwmCPScI9pwg2HOCYM8Jgj0nCPacINhzgmDPCYI9Jwj2nCDYc4"
"JgzwmCPScI9pwg2HOCYM8Jgj0nCPacINhzgmDPCYI9Jwj2nCDYc4JgzwmCPScI9pwg2HOCYM"
"8Jgj0nCPacINhzgmDPCYI9Jwj2nCDYc4JgzwmCPef/Afw/wBt50raAAAAAAElFTkSuQmCC";
// Ensure that we don't proceed until the icon loading is finished.
ASSERT_EQ(
true,
EvalJs(web_contents,
"var promiseResolve;"
"var imageLoadedPromise = new Promise(resolve => {"
" promiseResolve = resolve;"
"});"
"function mutatedCallback(mutations) {"
" let mutation = mutations[0];"
" if (mutation.attributeName == 'src' &&"
" mutation.target.src.startsWith('data:image/png')) {"
" console.log('Change in src observed, resolving promise');"
" promiseResolve();"
" }"
"}"
"let observer = new MutationObserver(mutatedCallback);"
"observer.observe(document.getElementById('icon'),"
" {attributes: true});"
"if (document.getElementById('icon').src.startsWith("
" 'data:image/png')) {"
" console.log('Inline src already set, resolving promise');"
" promiseResolve();"
"}"
"imageLoadedPromise.then(function(e) {"
" return true;"
"});")
.ExtractBool());
// Expect that the icon on the default offline page is showing.
EXPECT_EQ("You're offline",
EvalJs(web_contents,
"document.getElementById('default-web-app-msg').textContent")
.ExtractString());
EXPECT_EQ("Manifest test app",
EvalJs(web_contents, "document.title").ExtractString());
EXPECT_EQ(kExpectedIconUrl,
EvalJs(web_contents, "document.getElementById('icon').src")
.ExtractString());
EXPECT_EQ("inline",
EvalJs(web_contents,
"document.getElementById('offlineIcon').style.display")
.ExtractString());
}
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest, WebAppOfflineMetricsNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
StartWebAppAndDisconnect(web_contents, "/banners/no-sw-with-colors.html");
SyncHistograms();
histogram()->ExpectTotalCount(kHistogramDurationShown, 0);
histogram()->ExpectTotalCount(kHistogramClosingReason, 0);
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 1);
// Expect that the default offline page is showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') !== null")
.ExtractBool());
// Navigate somewhere else (anywhere else but the current page will do).
EXPECT_TRUE(NavigateToURL(web_contents, GURL("about:blank")));
SyncHistograms();
histogram()->ExpectTotalCount(kHistogramDurationShown, 1);
histogram()->ExpectTotalCount(kHistogramClosingReason, 1);
EXPECT_THAT(histogram()->GetAllSamples(kHistogramClosingReason),
ElementsAre(base::Bucket(/* min= */ 1, /* count= */ 1)));
}
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest, WebAppOfflineMetricsBackOnline) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
StartWebAppAndDisconnect(web_contents, "/banners/no-sw-with-colors.html");
SyncHistograms();
histogram()->ExpectTotalCount(kHistogramDurationShown, 0);
histogram()->ExpectTotalCount(kHistogramClosingReason, 0);
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 1);
// Expect that the default offline page is showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') !== null")
.ExtractBool());
// The URL interceptor only blocks the first navigation. This one should
// go through.
ReloadWebContents(web_contents);
// Expect that the default offline page is not showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') === null")
.ExtractBool());
SyncHistograms();
histogram()->ExpectTotalCount(kHistogramDurationShown, 1);
histogram()->ExpectTotalCount(kHistogramClosingReason, 1);
EXPECT_THAT(histogram()->GetAllSamples(kHistogramClosingReason),
ElementsAre(base::Bucket(/* min= */ 0, /* count= */ 1)));
}
IN_PROC_BROWSER_TEST_F(WebAppOfflinePageTest, WebAppOfflineMetricsPwaClosing) {
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 0);
webapps::AppId app_id =
StartWebAppAndDisconnect(web_contents, "/banners/no-sw-with-colors.html");
SyncHistograms();
histogram()->ExpectTotalCount(kHistogramDurationShown, 0);
histogram()->ExpectTotalCount(kHistogramClosingReason, 0);
ExpectUniqueSample(net::ERR_INTERNET_DISCONNECTED, 1);
// Expect that the default offline page is showing.
EXPECT_TRUE(EvalJs(web_contents,
"document.getElementById('default-web-app-msg') !== null")
.ExtractBool());
CloseBrowser(web_contents);
SyncHistograms();
histogram()->ExpectTotalCount(kHistogramDurationShown, 1);
histogram()->ExpectTotalCount(kHistogramClosingReason, 1);
EXPECT_THAT(histogram()->GetAllSamples(kHistogramClosingReason),
ElementsAre(base::Bucket(/* min= */ 2, /* count= */ 1)));
}
class WebAppOfflineDarkModeTest
: public WebAppOfflineTest,
public testing::WithParamInterface<blink::mojom::PreferredColorScheme> {
public:
void SetUp() override {
#if BUILDFLAG(IS_WIN)
InProcessBrowserTest::SetUp();
#elif BUILDFLAG(IS_MAC)
// TODO(crbug.com/40215627): Get this test suite working.
GTEST_SKIP();
#else
InProcessBrowserTest::SetUp();
#endif // BUILDFLAG(IS_MAC)
}
void SetUpOnMainThread() override {
WebAppOfflineTest::SetUpOnMainThread();
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Explicitly set dark mode in ChromeOS or we can't get light mode after
// sunset (due to dark mode auto-scheduling).
ash::DarkLightModeController::Get()->SetDarkModeEnabledForTest(
GetParam() == blink::mojom::PreferredColorScheme::kDark);
#endif
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
// ShellContentBrowserClient::OverrideWebkitPrefs() overrides the
// prefers-color-scheme according to switches::kForceDarkMode
// command line.
if (GetParam() == blink::mojom::PreferredColorScheme::kDark)
command_line->AppendSwitch(switches::kForceDarkMode);
}
};
// Testing offline page in dark mode for a web app with a manifest and no
// service worker.
// TODO(crbug.com/40871921): tests are flaky on Lacros and Linux.
#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_LINUX)
#define MAYBE_WebAppOfflineDarkModeNoServiceWorker \
DISABLED_WebAppOfflineDarkModeNoServiceWorker
#else
#define MAYBE_WebAppOfflineDarkModeNoServiceWorker \
WebAppOfflineDarkModeNoServiceWorker
#endif
IN_PROC_BROWSER_TEST_P(WebAppOfflineDarkModeTest,
MAYBE_WebAppOfflineDarkModeNoServiceWorker) {
#if BUILDFLAG(IS_WIN)
if (GetParam() == blink::mojom::PreferredColorScheme::kLight &&
ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()) {
GTEST_SKIP() << "Host is in dark mode; skipping test";
}
#endif // BUILDFLAG(IS_WIN)
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
StartWebAppAndDisconnect(web_contents, "/banners/no-sw-with-colors.html");
if (GetParam() == blink::mojom::PreferredColorScheme::kDark) {
// Expect that the default offline page is showing with dark mode colors.
EXPECT_TRUE(
EvalJs(web_contents,
"window.matchMedia('(prefers-color-scheme: dark)').matches")
.ExtractBool());
EXPECT_EQ(
EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('div')).color")
.ExtractString(),
"rgb(227, 227, 227)");
EXPECT_EQ(EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('body'))."
"backgroundColor")
.ExtractString(),
"rgb(31, 31, 31)");
} else {
// Expect that the default offline page is showing with light mode colors.
EXPECT_TRUE(
EvalJs(web_contents,
"window.matchMedia('(prefers-color-scheme: light)').matches")
.ExtractBool());
EXPECT_EQ(
EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('div')).color")
.ExtractString(),
"rgb(31, 31, 31)");
EXPECT_EQ(EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('body'))."
"backgroundColor")
.ExtractString(),
"rgb(255, 255, 255)");
}
}
// Testing offline page in dark mode for a web app with a manifest and service
// worker that does not handle offline error.
// TODO(crbug.com/40871921): tests are flaky on Lacros and Linux.
#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_LINUX)
#define MAYBE_WebAppOfflineDarkModeEmptyServiceWorker \
DISABLED_WebAppOfflineDarkModeEmptyServiceWorker
#else
#define MAYBE_WebAppOfflineDarkModeEmptyServiceWorker \
WebAppOfflineDarkModeEmptyServiceWorker
#endif
IN_PROC_BROWSER_TEST_P(WebAppOfflineDarkModeTest,
MAYBE_WebAppOfflineDarkModeEmptyServiceWorker) {
#if BUILDFLAG(IS_WIN)
if (GetParam() == blink::mojom::PreferredColorScheme::kLight &&
ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()) {
GTEST_SKIP() << "Host is in dark mode; skipping test";
}
#endif // BUILDFLAG(IS_WIN)
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
StartPwaAndDisconnect(web_contents,
"/banners/manifest_test_page_empty_fetch_handler.html");
if (GetParam() == blink::mojom::PreferredColorScheme::kDark) {
// Expect that the default offline page is showing with dark mode colors.
EXPECT_TRUE(
EvalJs(web_contents,
"window.matchMedia('(prefers-color-scheme: dark)').matches")
.ExtractBool());
EXPECT_EQ(
EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('div')).color")
.ExtractString(),
"rgb(227, 227, 227)");
EXPECT_EQ(EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('body'))."
"backgroundColor")
.ExtractString(),
"rgb(31, 31, 31)");
} else {
// Expect that the default offline page is showing with light mode colors.
EXPECT_TRUE(
EvalJs(web_contents,
"window.matchMedia('(prefers-color-scheme: light)').matches")
.ExtractBool());
EXPECT_EQ(
EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('div')).color")
.ExtractString(),
"rgb(31, 31, 31)");
EXPECT_EQ(EvalJs(web_contents,
"window.getComputedStyle(document.querySelector('body'))."
"backgroundColor")
.ExtractString(),
"rgb(255, 255, 255)");
}
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
WebAppOfflineDarkModeTest,
::testing::Values(blink::mojom::PreferredColorScheme::kDark,
blink::mojom::PreferredColorScheme::kLight));
} // namespace web_app