Skip to content

Commit

Permalink
AppHome: implements the LaunchApp API
Browse files Browse the repository at this point in the history
`LaunchApp` is an backend API exposed to frontend of `chrome://apps`.
It's used for launching an app for specific `app-id`

For background & design document, see following document:
 https://docs.google.com/document/d/1I_Ot2Wuyyztt1YIE61rpkeXewaMnPzfMNF-6qCbdtVw/edit#heading=h.q3ma6p1wdg55

Bug: 1350406
Change-Id: Ifbedb2a1885ecac1f3b3c4f216cb06bdcf3b87ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3977035
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: Camille Lamy <clamy@chromium.org>
Commit-Queue: Fangzhen Song <songfangzhen@bytedance.com>
Reviewed-by: Phillis Tang <phillis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1076663}
  • Loading branch information
keeling-cr authored and Chromium LUCI CQ committed Nov 29, 2022
1 parent 72fb30e commit 27c1116
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 19 deletions.
4 changes: 4 additions & 0 deletions chrome/browser/resources/app_home/browser_proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export class BrowserProxy {
registerAppRemoveEvent(callback: Function) {
this.callbackRouter.removeApp.addListener(callback);
}

registerAppEnableEvent(callback: Function) {
this.callbackRouter.enableExtensionApp.addListener(callback);
}
}

let instance: BrowserProxy|null = null;
16 changes: 16 additions & 0 deletions chrome/browser/ui/webui/app_home/app_home.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ struct AppInfo {
url.mojom.Url icon_url;
};

struct ClickEvent {
// TODO(crbug.com/1350406): Use enum class to represent button event.
double button;
bool alt_key;
bool ctrl_key;
bool meta_key;
bool shift_key;
};

interface PageHandlerFactory {
CreatePageHandler(pending_remote<Page> page,
pending_receiver<PageHandler> handler);
Expand All @@ -31,6 +40,11 @@ interface PageHandler {
ShowAppSettings(string app_id);
// Create shortcut link for app.
CreateAppShortcut(string app_id) => ();
// Launch app for specific `app_id`.
// `source` is the launch source used for metrics reporting,
// see `AppLaunchBucket` in extension_constants.h for more detail.
// `click_event` is used for determining launch container for apps.
LaunchApp(string app_id, int32 source, ClickEvent? click_event);
};

// The `Page` interface is used for sending mojom action messsage
Expand All @@ -41,4 +55,6 @@ interface Page {
AddApp(AppInfo app_info);
// Inform frontend that an app is uninstalled.
RemoveApp(AppInfo app_info);
// Inform frontend that an extension app is enabled.
EnableExtensionApp(string extension_app_id);
};
172 changes: 158 additions & 14 deletions chrome/browser/ui/webui/app_home/app_home_page_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,36 @@
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_source.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/apps/app_info_dialog.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/extensions/extension_enable_flow.h"
#include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
#include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h"
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/extensions/extension_metrics.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "content/public/browser/web_ui.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "net/base/url_util.h"
#include "ui/base/window_open_disposition_utils.h"
#include "url/gurl.h"

using content::WebUI;
Expand Down Expand Up @@ -184,11 +194,11 @@ void AppHomePageHandler::FillExtensionInfoList(
void AppHomePageHandler::OnExtensionUninstallDialogClosed(
bool did_start_uninstall,
const std::u16string& error) {
CleanupAfterUninstall();
ResetExtensionDialogState();
}

void AppHomePageHandler::CleanupAfterUninstall() {
uninstall_dialog_prompting_ = false;
void AppHomePageHandler::ResetExtensionDialogState() {
extension_dialog_prompting_ = false;
}

void AppHomePageHandler::UninstallWebApp(const std::string& web_app_id) {
Expand All @@ -200,13 +210,13 @@ void AppHomePageHandler::UninstallWebApp(const std::string& web_app_id) {
return;
}

uninstall_dialog_prompting_ = true;
extension_dialog_prompting_ = true;

auto uninstall_success_callback = base::BindOnce(
[](base::WeakPtr<AppHomePageHandler> app_home_page_handler,
webapps::UninstallResultCode code) {
if (app_home_page_handler) {
app_home_page_handler->CleanupAfterUninstall();
app_home_page_handler->ResetExtensionDialogState();
}
},
weak_ptr_factory_.GetWeakPtr());
Expand Down Expand Up @@ -239,7 +249,7 @@ void AppHomePageHandler::UninstallExtensionApp(const Extension* extension) {
return;
}

uninstall_dialog_prompting_ = true;
extension_dialog_prompting_ = true;

Browser* browser = GetCurrentBrowser();
extension_uninstall_dialog_ = extensions::ExtensionUninstallDialog::Create(
Expand All @@ -251,13 +261,6 @@ void AppHomePageHandler::UninstallExtensionApp(const Extension* extension) {
extensions::UNINSTALL_SOURCE_CHROME_APPS_PAGE);
}

void AppHomePageHandler::GetApps(GetAppsCallback callback) {
std::vector<app_home::mojom::AppInfoPtr> result;
FillWebAppInfoList(&result);
FillExtensionInfoList(&result);
std::move(callback).Run(std::move(result));
}

void AppHomePageHandler::OnWebAppWillBeUninstalled(
const web_app::AppId& app_id) {
auto app_info = app_home::mojom::AppInfo::New();
Expand Down Expand Up @@ -288,8 +291,42 @@ void AppHomePageHandler::OnExtensionUninstalled(
page_->RemoveApp(std::move(app_info));
}

void AppHomePageHandler::PromptToEnableExtensionApp(
const std::string& extension_app_id) {
if (extension_dialog_prompting_)
return; // Only one prompt at a time.

extension_dialog_prompting_ = true;
extension_enable_flow_ =
std::make_unique<ExtensionEnableFlow>(profile_, extension_app_id, this);
extension_enable_flow_->StartForWebContents(web_ui_->GetWebContents());
}

void AppHomePageHandler::ExtensionEnableFlowFinished() {
// We bounce this off the NTP so the browser can update the apps icon.
// If we don't launch the app asynchronously, then the app's disabled
// icon disappears but isn't replaced by the enabled icon, making a poor
// visual experience.
page_->EnableExtensionApp(extension_enable_flow_->extension_id());

extension_enable_flow_.reset();
ResetExtensionDialogState();
}

void AppHomePageHandler::ExtensionEnableFlowAborted(bool user_initiated) {
extension_enable_flow_.reset();
ResetExtensionDialogState();
}

void AppHomePageHandler::GetApps(GetAppsCallback callback) {
std::vector<app_home::mojom::AppInfoPtr> result;
FillWebAppInfoList(&result);
FillExtensionInfoList(&result);
std::move(callback).Run(std::move(result));
}

void AppHomePageHandler::UninstallApp(const std::string& app_id) {
if (uninstall_dialog_prompting_)
if (extension_dialog_prompting_)
return;

if (web_app_provider_->registrar().IsInstalled(app_id) &&
Expand Down Expand Up @@ -342,4 +379,111 @@ void AppHomePageHandler::CreateAppShortcut(const std::string& app_id,
CreateExtensionAppShortcut(extension, std::move(callback));
}

void AppHomePageHandler::LaunchApp(const std::string& app_id,
int source,
app_home::mojom::ClickEventPtr click_event) {
extension_misc::AppLaunchBucket launch_bucket =
static_cast<extension_misc::AppLaunchBucket>(source);

extensions::Manifest::Type type;
GURL full_launch_url;
apps::LaunchContainer launch_container;

web_app::WebAppRegistrar& registrar = web_app_provider_->registrar();
if (registrar.IsInstalled(app_id) && !IsYoutubeExtension(app_id)) {
type = extensions::Manifest::Type::TYPE_HOSTED_APP;
full_launch_url = registrar.GetAppStartUrl(app_id);
launch_container = web_app::ConvertDisplayModeToAppLaunchContainer(
registrar.GetAppEffectiveDisplayMode(app_id));
} else {
const Extension* extension = extensions::ExtensionRegistry::Get(profile_)
->enabled_extensions()
.GetByID(app_id);

// Prompt the user to re-enable the application if disabled.
if (!extension) {
PromptToEnableExtensionApp(app_id);
return;
}
type = extension->GetType();
full_launch_url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
launch_container = extensions::GetLaunchContainer(
extensions::ExtensionPrefs::Get(profile_), extension);
}

WindowOpenDisposition disposition =
click_event ? ui::DispositionFromClick(
click_event->button == 1.0, click_event->alt_key,
click_event->ctrl_key, click_event->meta_key,
click_event->shift_key)
: WindowOpenDisposition::CURRENT_TAB;
GURL override_url;

if (app_id != extensions::kWebStoreAppId) {
CHECK_NE(launch_bucket, extension_misc::APP_LAUNCH_BUCKET_INVALID);
extensions::RecordAppLaunchType(launch_bucket, type);
} else {
extensions::RecordWebStoreLaunch();
override_url = net::AppendQueryParameter(
full_launch_url, extension_urls::kWebstoreSourceField,
"chrome-ntp-icon");
}

if (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB ||
disposition == WindowOpenDisposition::NEW_WINDOW) {
// TODO(jamescook): Proper support for background tabs.
apps::AppLaunchParams params(
app_id,
disposition == WindowOpenDisposition::NEW_WINDOW
? apps::LaunchContainer::kLaunchContainerWindow
: apps::LaunchContainer::kLaunchContainerTab,
disposition, apps::LaunchSource::kFromNewTabPage);
params.override_url = override_url;
apps::AppServiceProxyFactory::GetForProfile(profile_)
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(params), base::DoNothing());
} else {
// To give a more "launchy" experience when using the NTP launcher, we close
// it automatically. However, if the chrome://apps page is the LAST page in
// the browser window, then we don't close it.
Browser* browser = GetCurrentBrowser();
base::WeakPtr<Browser> browser_ptr;
content::WebContents* old_contents = nullptr;
base::WeakPtr<content::WebContents> old_contents_ptr;
if (browser) {
browser_ptr = browser->AsWeakPtr();
old_contents = browser->tab_strip_model()->GetActiveWebContents();
old_contents_ptr = old_contents->GetWeakPtr();
}

apps::AppLaunchParams params(
app_id, launch_container,
old_contents ? WindowOpenDisposition::CURRENT_TAB
: WindowOpenDisposition::NEW_FOREGROUND_TAB,
apps::LaunchSource::kFromNewTabPage);
params.override_url = override_url;
apps::AppServiceProxyFactory::GetForProfile(profile_)
->BrowserAppLauncher()
->LaunchAppWithParams(
std::move(params),
base::BindOnce(
[](base::WeakPtr<Browser> apps_page_browser,
base::WeakPtr<content::WebContents> old_contents,
content::WebContents* new_web_contents) {
if (!apps_page_browser || !old_contents)
return;
if (new_web_contents != old_contents.get() &&
apps_page_browser->tab_strip_model()->count() > 1) {
// This will also destroy the handler, so do not perform
// any actions after.
chrome::CloseWebContents(apps_page_browser.get(),
old_contents.get(),
/*add_to_history=*/true);
}
},
browser_ptr, old_contents_ptr));
}
}

} // namespace webapps
25 changes: 21 additions & 4 deletions chrome/browser/ui/webui/app_home/app_home_page_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
#include "chrome/browser/ui/webui/app_home/app_home.mojom.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_install_manager.h"
Expand All @@ -17,6 +18,8 @@
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"

class ExtensionEnableFlow;

namespace content {
class WebUI;
} // namespace content
Expand All @@ -37,7 +40,8 @@ class AppHomePageHandler
: public app_home::mojom::PageHandler,
public web_app::WebAppInstallManagerObserver,
public extensions::ExtensionRegistryObserver,
public extensions::ExtensionUninstallDialog::Delegate {
public extensions::ExtensionUninstallDialog::Delegate,
public ExtensionEnableFlowDelegate {
public:
AppHomePageHandler(
content::WebUI*,
Expand Down Expand Up @@ -68,6 +72,9 @@ class AppHomePageHandler
void ShowAppSettings(const std::string& app_id) override;
void CreateAppShortcut(const std::string& app_id,
CreateAppShortcutCallback callback) override;
void LaunchApp(const std::string& app_id,
int source,
app_home::mojom::ClickEventPtr click_event) override;

private:
Browser* GetCurrentBrowser();
Expand All @@ -76,8 +83,11 @@ class AppHomePageHandler
// needed.
extensions::ExtensionUninstallDialog* CreateExtensionUninstallDialog();

// Reset some instance flags we use to track the currently uninstalling app.
void CleanupAfterUninstall();
// Prompts the user to re-enable the extension app for |extension_app_id|.
void PromptToEnableExtensionApp(const std::string& extension_app_id);

// Reset some instance flags we use to track the currently prompting app.
void ResetExtensionDialogState();

// ExtensionUninstallDialog::Delegate:
void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
Expand All @@ -88,6 +98,10 @@ class AppHomePageHandler
void CreateWebAppShortcut(const std::string& app_id, base::OnceClosure done);
void CreateExtensionAppShortcut(const extensions::Extension* extension,
base::OnceClosure done);
// ExtensionEnableFlowDelegate:
void ExtensionEnableFlowFinished() override;
void ExtensionEnableFlowAborted(bool user_initiated) override;

void UninstallWebApp(const std::string& web_app_id);
void UninstallExtensionApp(const extensions::Extension* extension);
void FillWebAppInfoList(std::vector<app_home::mojom::AppInfoPtr>* result);
Expand Down Expand Up @@ -120,7 +134,10 @@ class AppHomePageHandler
std::unique_ptr<extensions::ExtensionUninstallDialog>
extension_uninstall_dialog_;

bool uninstall_dialog_prompting_ = false;
bool extension_dialog_prompting_ = false;

// Used to show confirmation UI for enabling extensions.
std::unique_ptr<ExtensionEnableFlow> extension_enable_flow_;

// Used for passing callbacks.
base::WeakPtrFactory<AppHomePageHandler> weak_ptr_factory_{this};
Expand Down
1 change: 1 addition & 0 deletions chrome/browser/ui/webui/app_home/mock_app_home_page.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class MockAppHomePage : public app_home::mojom::Page {

MOCK_METHOD1(AddApp, void(app_home::mojom::AppInfoPtr));
MOCK_METHOD1(RemoveApp, void(app_home::mojom::AppInfoPtr));
MOCK_METHOD1(EnableExtensionApp, void(const std::string&));
mojo::Receiver<app_home::mojom::Page> receiver_{this};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {AppInfo, PageCallbackRouter, PageHandlerInterface} from 'chrome://apps/app_home.mojom-webui.js';
import {AppInfo, ClickEvent, PageCallbackRouter, PageHandlerInterface} from 'chrome://apps/app_home.mojom-webui.js';
import {BrowserProxy} from 'chrome://apps/browser_proxy.js';

interface AppList {
Expand All @@ -27,6 +27,8 @@ export class FakePageHandler implements PageHandlerInterface {
createAppShortcut(_appId: string) {
return Promise.resolve();
}

launchApp(_appId: string, _source: number, _clickEvent: ClickEvent) {}
}

export class TestAppHomeBrowserProxy implements BrowserProxy {
Expand All @@ -41,4 +43,5 @@ export class TestAppHomeBrowserProxy implements BrowserProxy {
}

registerAppRemoveEvent(_callback: Function) {}
registerAppEnableEvent(_callback: Function) {}
}

0 comments on commit 27c1116

Please sign in to comment.