Commit 1440d756 authored by Eric Willigers's avatar Eric Willigers Committed by Commit Bot

shelf: AppShortcutLauncherItemController supports web apps

A new AppMatcher class is introduced, and used by
AppShortcutLauncherItemController to match browser web
contents against app id.

No extensions APIs are used for bookmark apps.

Bug: 1048055
Change-Id: I16197a28d89140bd4c863fb4891878720ba378f6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2087323
Commit-Queue: Nancy Wang <nancylingwang@chromium.org>
Reviewed-by: default avatarNancy Wang <nancylingwang@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Auto-Submit: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748066}
parent 86877b8c
...@@ -340,12 +340,21 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowBrowserTest, ...@@ -340,12 +340,21 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowBrowserTest,
EXPECT_EQ(0u, windows.size()); EXPECT_EQ(0u, windows.size());
} }
// TODO(crbug.com/2078377): Parameterize this test, retire
// AppServiceWebAndBookmarkAppBrowserTest.
class AppServiceAppWindowWebAppBrowserTest class AppServiceAppWindowWebAppBrowserTest
: public AppServiceAppWindowBrowserTest { : public AppServiceAppWindowBrowserTest,
public ::testing::WithParamInterface<web_app::ProviderType> {
protected: protected:
AppServiceAppWindowWebAppBrowserTest() {
if (GetParam() == web_app::ProviderType::kWebApps) {
scoped_feature_list_.InitAndEnableFeature(
features::kDesktopPWAsWithoutExtensions);
} else {
scoped_feature_list_.InitAndDisableFeature(
features::kDesktopPWAsWithoutExtensions);
}
}
~AppServiceAppWindowWebAppBrowserTest() override = default;
// AppServiceAppWindowBrowserTest: // AppServiceAppWindowBrowserTest:
void SetUpOnMainThread() override { void SetUpOnMainThread() override {
AppServiceAppWindowBrowserTest::SetUpOnMainThread(); AppServiceAppWindowBrowserTest::SetUpOnMainThread();
...@@ -376,12 +385,15 @@ class AppServiceAppWindowWebAppBrowserTest ...@@ -376,12 +385,15 @@ class AppServiceAppWindowWebAppBrowserTest
return https_server_.GetURL("app.com", "/ssl/google.html"); return https_server_.GetURL("app.com", "/ssl/google.html");
} }
private:
// For mocking a secure site. // For mocking a secure site.
net::EmbeddedTestServer https_server_; net::EmbeddedTestServer https_server_;
base::test::ScopedFeatureList scoped_feature_list_;
}; };
// Test that we have the correct instance for Web apps. // Test that we have the correct instance for Web apps.
IN_PROC_BROWSER_TEST_F(AppServiceAppWindowWebAppBrowserTest, WebAppsWindow) { IN_PROC_BROWSER_TEST_P(AppServiceAppWindowWebAppBrowserTest, WebAppsWindow) {
std::string app_id = CreateWebApp(); std::string app_id = CreateWebApp();
auto windows = app_service_proxy_->InstanceRegistry().GetWindows(app_id); auto windows = app_service_proxy_->InstanceRegistry().GetWindows(app_id);
...@@ -432,32 +444,6 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowWebAppBrowserTest, WebAppsWindow) { ...@@ -432,32 +444,6 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowWebAppBrowserTest, WebAppsWindow) {
EXPECT_EQ(0u, windows.size()); EXPECT_EQ(0u, windows.size());
} }
class AppServiceWebAndBookmarkAppBrowserTest
: public AppServiceAppWindowWebAppBrowserTest,
public ::testing::WithParamInterface<web_app::ProviderType> {
protected:
AppServiceWebAndBookmarkAppBrowserTest() {
if (GetParam() == web_app::ProviderType::kWebApps) {
scoped_feature_list_.InitAndEnableFeature(
features::kDesktopPWAsWithoutExtensions);
} else if (GetParam() == web_app::ProviderType::kBookmarkApps) {
scoped_feature_list_.InitAndDisableFeature(
features::kDesktopPWAsWithoutExtensions);
}
}
~AppServiceWebAndBookmarkAppBrowserTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(AppServiceWebAndBookmarkAppBrowserTest, GetWindows) {
const std::string app_id = CreateWebApp();
auto windows = app_service_proxy_->InstanceRegistry().GetWindows(app_id);
EXPECT_EQ(1u, windows.size());
}
class AppServiceAppWindowArcAppBrowserTest class AppServiceAppWindowArcAppBrowserTest
: public AppServiceAppWindowBrowserTest { : public AppServiceAppWindowBrowserTest {
protected: protected:
...@@ -643,7 +629,7 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowArcAppBrowserTest, ArcAppsWindow) { ...@@ -643,7 +629,7 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowArcAppBrowserTest, ArcAppsWindow) {
} }
INSTANTIATE_TEST_SUITE_P(All, INSTANTIATE_TEST_SUITE_P(All,
AppServiceWebAndBookmarkAppBrowserTest, AppServiceAppWindowWebAppBrowserTest,
::testing::Values(web_app::ProviderType::kBookmarkApps, ::testing::Values(web_app::ProviderType::kBookmarkApps,
web_app::ProviderType::kWebApps), web_app::ProviderType::kWebApps),
web_app::ProviderTypeParamToString); web_app::ProviderTypeParamToString);
...@@ -26,7 +26,10 @@ ...@@ -26,7 +26,10 @@
#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h" #include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
...@@ -44,46 +47,134 @@ namespace { ...@@ -44,46 +47,134 @@ namespace {
// The time delta between clicks in which clicks to launch V2 apps are ignored. // The time delta between clicks in which clicks to launch V2 apps are ignored.
const int kClickSuppressionInMS = 1000; const int kClickSuppressionInMS = 1000;
// Returns true if this app matches the given |web_contents|. To accelerate // AppMatcher is used to determine if various WebContents instances are
// the matching, the app managing |extension| as well as the parsed // associated with a specific app. Clients should call CanMatchWebContents()
// |refocus_pattern| get passed. If |deprecated_is_app| is true, the application // before iterating through WebContents instances and calling
// gets first checked against its original URL since a windowed app might have // WebContentMatchesApp().
// navigated away from its app domain. class AppMatcher {
bool WebContentMatchesApp(const std::string& app_id, public:
const extensions::Extension* extension, AppMatcher(Profile* profile,
const URLPattern& refocus_pattern, const std::string& app_id,
content::WebContents* web_contents, const URLPattern& refocus_pattern)
Browser* browser) { : app_id_(app_id), refocus_pattern_(refocus_pattern) {
// If the browser is an app window, and the app name matches the extension, DCHECK(profile);
// then the contents match the app. if (web_app::WebAppProviderBase* provider =
if (browser->deprecated_is_app()) { web_app::WebAppProviderBase::GetProviderBase(profile)) {
const extensions::Extension* browser_extension = if (provider->registrar().IsLocallyInstalled(app_id)) {
ExtensionRegistry::Get(browser->profile()) registrar_ = &provider->registrar();
->GetExtensionById( }
web_app::GetAppIdFromApplicationName(browser->app_name()), }
ExtensionRegistry::EVERYTHING); if (!registrar_)
return browser_extension == extension; extension_ = GetExtensionForAppID(app_id, profile);
} }
// Apps set to launch in app windows should not match contents running in AppMatcher(const AppMatcher&) = delete;
// tabs. AppMatcher& operator=(const AppMatcher&) = delete;
if (extensions::LaunchesInWindow(browser->profile(), extension))
return false; bool CanMatchWebContents() const { return registrar_ || extension_; }
// There are three ways to identify the association of a URL with this // Returns true if this app matches the given |web_contents|. If
// extension: // |deprecated_is_app| is true, the application gets first checked against its
// - The refocus pattern is matched (needed for apps like drive). // original URL since a windowed app might have navigated away from its app
// - The extension's origin + extent gets matched. // domain.
// - The launcher controller knows that the tab got created for this app. // May only be called if CanMatchWebContents() return true.
const GURL tab_url = web_contents->GetURL(); bool WebContentMatchesApp(content::WebContents* web_contents,
return ( Browser* browser) const {
(!refocus_pattern.match_all_urls() && DCHECK(CanMatchWebContents());
refocus_pattern.MatchesURL(tab_url)) || return extension_ ? WebContentMatchesHostedApp(web_contents, browser)
(extension->OverlapsWithOrigin(tab_url) && : WebContentMatchesWebApp(web_contents, browser);
extension->web_extent().MatchesURL(tab_url)) || }
ChromeLauncherController::instance()->IsWebContentHandledByApplication(
web_contents, app_id)); private:
} bool WebContentMatchesHostedApp(content::WebContents* web_contents,
Browser* browser) const {
DCHECK(extension_);
DCHECK(!registrar_);
// If the browser is an app window, and the app name matches the extension,
// then the contents match the app.
if (browser->deprecated_is_app()) {
const Extension* browser_extension =
ExtensionRegistry::Get(browser->profile())
->GetExtensionById(
web_app::GetAppIdFromApplicationName(browser->app_name()),
ExtensionRegistry::EVERYTHING);
return browser_extension == extension_;
}
// Apps set to launch in app windows should not match contents running in
// tabs.
if (extensions::LaunchesInWindow(browser->profile(), extension_))
return false;
// There are three ways to identify the association of a URL with this
// extension:
// - The refocus pattern is matched (needed for apps like drive).
// - The extension's origin + extent gets matched.
// - The launcher controller knows that the tab got created for this app.
const GURL tab_url = web_contents->GetURL();
return (
(!refocus_pattern_.match_all_urls() &&
refocus_pattern_.MatchesURL(tab_url)) ||
(extension_->OverlapsWithOrigin(tab_url) &&
extension_->web_extent().MatchesURL(tab_url)) ||
ChromeLauncherController::instance()->IsWebContentHandledByApplication(
web_contents, app_id_));
}
// Returns true if this web app matches the given |web_contents|. If
// |deprecated_is_app| is true, the application gets first checked against its
// original URL since a windowed app might have navigated away from its app
// domain.
bool WebContentMatchesWebApp(content::WebContents* web_contents,
Browser* browser) const {
DCHECK(registrar_);
DCHECK(!extension_);
// If the browser is a web app window, and the window app id matches,
// then the contents match the app.
if (browser->app_controller() && browser->app_controller()->HasAppId())
return browser->app_controller()->GetAppId() == app_id_;
// Bookmark apps set to launch in app windows should not match contents
// running in tabs.
if (registrar_->GetAppUserDisplayMode(app_id_) ==
web_app::DisplayMode::kStandalone &&
!base::FeatureList::IsEnabled(
features::kDesktopPWAsWithoutExtensions)) {
return false;
}
// There are three ways to identify the association of a URL with this
// web app:
// - The refocus pattern is matched (needed for apps like drive).
// - The web app's scope gets matched.
// - The launcher controller knows that the tab got created for this web
// app.
const GURL tab_url = web_contents->GetURL();
base::Optional<GURL> app_scope = registrar_->GetAppScope(app_id_);
DCHECK(app_scope.has_value());
return (
(!refocus_pattern_.match_all_urls() &&
refocus_pattern_.MatchesURL(tab_url)) ||
(base::StartsWith(tab_url.spec(), app_scope->spec(),
base::CompareCase::SENSITIVE)) ||
ChromeLauncherController::instance()->IsWebContentHandledByApplication(
web_contents, app_id_));
}
const std::string app_id_;
const URLPattern refocus_pattern_;
// AppMatcher is stack allocated. Pointer members below are not owned.
// registrar_ is set when app_id_ is a web app.
const web_app::AppRegistrar* registrar_ = nullptr;
// extension_ is set when app_id_ is a hosted app.
const Extension* extension_ = nullptr;
};
} // namespace } // namespace
...@@ -101,8 +192,6 @@ std::vector<content::WebContents*> ...@@ -101,8 +192,6 @@ std::vector<content::WebContents*>
AppShortcutLauncherItemController::GetRunningApplications( AppShortcutLauncherItemController::GetRunningApplications(
const std::string& app_id, const std::string& app_id,
const GURL& refocus_url) { const GURL& refocus_url) {
std::vector<content::WebContents*> items;
URLPattern refocus_pattern(URLPattern::SCHEME_ALL); URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
refocus_pattern.SetMatchAllURLs(true); refocus_pattern.SetMatchAllURLs(true);
...@@ -111,11 +200,12 @@ AppShortcutLauncherItemController::GetRunningApplications( ...@@ -111,11 +200,12 @@ AppShortcutLauncherItemController::GetRunningApplications(
refocus_pattern.Parse(refocus_url.spec()); refocus_pattern.Parse(refocus_url.spec());
} }
const Extension* extension = GetExtensionForAppID( Profile* const profile = ChromeLauncherController::instance()->profile();
app_id, ChromeLauncherController::instance()->profile()); AppMatcher matcher(profile, app_id, refocus_pattern);
// It is possible to come here While an extension gets loaded. std::vector<content::WebContents*> items;
if (!extension) // It is possible to come here while an app gets loaded.
if (!matcher.CanMatchWebContents())
return items; return items;
for (auto* browser : *BrowserList::GetInstance()) { for (auto* browser : *BrowserList::GetInstance()) {
...@@ -124,8 +214,7 @@ AppShortcutLauncherItemController::GetRunningApplications( ...@@ -124,8 +214,7 @@ AppShortcutLauncherItemController::GetRunningApplications(
TabStripModel* tab_strip = browser->tab_strip_model(); TabStripModel* tab_strip = browser->tab_strip_model();
for (int index = 0; index < tab_strip->count(); index++) { for (int index = 0; index < tab_strip->count(); index++) {
content::WebContents* web_contents = tab_strip->GetWebContentsAt(index); content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
if (WebContentMatchesApp(app_id, extension, refocus_pattern, web_contents, if (matcher.WebContentMatchesApp(web_contents, browser))
browser))
items.push_back(web_contents); items.push_back(web_contents);
} }
} }
...@@ -279,13 +368,12 @@ content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() { ...@@ -279,13 +368,12 @@ content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
refocus_pattern.Parse(refocus_url_.spec()); refocus_pattern.Parse(refocus_url_.spec());
} }
ChromeLauncherController* controller = ChromeLauncherController::instance(); Profile* const profile = ChromeLauncherController::instance()->profile();
const Extension* extension = AppMatcher matcher(profile, app_id(), refocus_pattern);
GetExtensionForAppID(app_id(), controller->profile());
// We may get here while the extension is loading (and NULL). // It is possible to come here while an app gets loaded.
if (!extension) if (!matcher.CanMatchWebContents())
return NULL; return nullptr;
const BrowserList* browser_list = BrowserList::GetInstance(); const BrowserList* browser_list = BrowserList::GetInstance();
for (BrowserList::const_reverse_iterator it = for (BrowserList::const_reverse_iterator it =
...@@ -300,8 +388,7 @@ content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() { ...@@ -300,8 +388,7 @@ content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
for (int index = 0; index < tab_strip->count(); index++) { for (int index = 0; index < tab_strip->count(); index++) {
content::WebContents* web_contents = tab_strip->GetWebContentsAt( content::WebContents* web_contents = tab_strip->GetWebContentsAt(
(index + active_index) % tab_strip->count()); (index + active_index) % tab_strip->count());
if (WebContentMatchesApp(app_id(), extension, refocus_pattern, if (matcher.WebContentMatchesApp(web_contents, browser))
web_contents, browser))
return web_contents; return web_contents;
} }
} }
...@@ -316,12 +403,11 @@ content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() { ...@@ -316,12 +403,11 @@ content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
TabStripModel* tab_strip = browser->tab_strip_model(); TabStripModel* tab_strip = browser->tab_strip_model();
for (int index = 0; index < tab_strip->count(); index++) { for (int index = 0; index < tab_strip->count(); index++) {
content::WebContents* web_contents = tab_strip->GetWebContentsAt(index); content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
if (WebContentMatchesApp(app_id(), extension, refocus_pattern, if (matcher.WebContentMatchesApp(web_contents, browser))
web_contents, browser))
return web_contents; return web_contents;
} }
} }
return NULL; return nullptr;
} }
ash::ShelfAction AppShortcutLauncherItemController::ActivateContent( ash::ShelfAction AppShortcutLauncherItemController::ActivateContent(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment