Skip to content

Commit

Permalink
Desktop PWA: Adding prototype for Run PWA on user OS login
Browse files Browse the repository at this point in the history
This is part 1 of 3 of the prototype to configure PWAS to run on
OS login.

Updates for the prototype include:
- Added feature flag "Desktop PWAs run on OS login", disabled by default.
- Added Startup folder to Windows base paths and shell util.
- Added "in_startup" entry to Web App Shortcut locations.
- If feature flag is enabled, show a checkbox in the PWA install dialog.
- If checked, PWA install task creates an additional shortcut in
  Windows Startup folder by setting "in_startup" location to true.

Additional parts for the feature to come in next CLs.

Explainer: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/RunOnLogin/Explainer.md
I2P: https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/T6d2zqF_jpw/76TP7Bc2DwAJ
Bug: 897302
Change-Id: I0870e5a26e94e8e60001b78c95e53da134388f0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2026256
Commit-Queue: Carlos Frias <carlos.frias@microsoft.com>
Reviewed-by: Alexey Baskakov <loyso@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Reviewed-by: Alan Cutter <alancutter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772460}
  • Loading branch information
Carlos Frias authored and Commit Bot committed May 27, 2020
1 parent 84cdb3e commit 5ddded6
Show file tree
Hide file tree
Showing 32 changed files with 574 additions and 10 deletions.
12 changes: 12 additions & 0 deletions base/base_paths_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ bool PathProviderWin(int key, FilePath* result) {
return false;
cur = FilePath(system_buffer);
break;
case base::DIR_COMMON_STARTUP:
if (FAILED(SHGetFolderPath(nullptr, CSIDL_COMMON_STARTUP, nullptr,
SHGFP_TYPE_CURRENT, system_buffer)))
return false;
cur = FilePath(system_buffer);
break;
case base::DIR_USER_STARTUP:
if (FAILED(SHGetFolderPath(nullptr, CSIDL_STARTUP, nullptr,
SHGFP_TYPE_CURRENT, system_buffer)))
return false;
cur = FilePath(system_buffer);
break;
case base::DIR_APP_DATA:
if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT,
system_buffer)))
Expand Down
4 changes: 4 additions & 0 deletions base/base_paths_win.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ enum {
// Start Menu\Programs"
DIR_START_MENU, // Usually "C:\Users\<user>\AppData\Roaming\
// Microsoft\Windows\Start Menu\Programs"
DIR_COMMON_STARTUP, // Usually "C:\ProgramData\Microsoft\Windows\
// Start Menu\Programs\Startup"
DIR_USER_STARTUP, // Usually "C:\Users\<user>\AppData\Roaming\
// Microsoft\Windows\Start Menu\Programs\Startup"
DIR_APP_DATA, // Application Data directory under the user
// profile.
DIR_LOCAL_APP_DATA, // "Local Settings\Application Data" directory
Expand Down
6 changes: 6 additions & 0 deletions chrome/app/generated_resources.grd
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,12 @@ are declared in tools/grit/grit_rule.gni.
<message name="IDS_INSTALL_PWA_BUTTON_LABEL" desc="Text for button that installs a web app to the operating system.">
Install
</message>
<message name="IDS_INSTALL_PWA_RUN_ON_OS_LOGIN_LABEL" desc="Text for checkbox label for auto starting the web app with user login to the operating system session.">
{0, select,
tablet {Start app when you sign in to your tablet}
computer {Start app when you sign in to your computer}
other {Start app when you sign in to your device}}
</message>

<message name="IDS_BOOKMARK_APP_AX_BUBBLE_NAME_LABEL" desc="Text preceding the name of a bookmark app, read by spoken feedback.">
Shortcut name
Expand Down
4 changes: 4 additions & 0 deletions chrome/browser/about_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,10 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kDesktopPWAsWithoutExtensionsName,
flag_descriptions::kDesktopPWAsWithoutExtensionsDescription, kOsDesktop,
FEATURE_VALUE_TYPE(features::kDesktopPWAsWithoutExtensions)},
{"enable-desktop-pwas-run-on-os-login",
flag_descriptions::kDesktopPWAsRunOnOsLoginName,
flag_descriptions::kDesktopPWAsRunOnOsLoginDescription, kOsDesktop,
FEATURE_VALUE_TYPE(features::kDesktopPWAsRunOnOsLogin)},
{"enable-system-webapps", flag_descriptions::kEnableSystemWebAppsName,
flag_descriptions::kEnableSystemWebAppsDescription, kOsDesktop,
FEATURE_VALUE_TYPE(features::kSystemWebApps)},
Expand Down
5 changes: 5 additions & 0 deletions chrome/browser/flag-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,11 @@
"owners": [ "desktop-pwas-team@google.com" ],
"expiry_milestone": 88
},
{
"name": "enable-desktop-pwas-run-on-os-login",
"owners": [ "cafrias@microsoft.com", "desktop-pwas-team@google.com" ],
"expiry_milestone": 85
},
{
"name": "enable-desktop-pwas-tab-strip",
"owners": [ "desktop-pwas-team@google.com" ],
Expand Down
5 changes: 5 additions & 0 deletions chrome/browser/flag_descriptions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,11 @@ const char kDesktopPWAsWithoutExtensionsDescription[] =
"is incomplete and may corrupt your synced Chrome profile. Test accounts "
"only are advised.";

const char kDesktopPWAsRunOnOsLoginName[] = "Desktop PWAs run on OS login";
const char kDesktopPWAsRunOnOsLoginDescription[] =
"Enable installed PWAs to be configured to automatically start when the OS "
"user logs in.";

const char kEnableSystemWebAppsName[] = "System Web Apps";
const char kEnableSystemWebAppsDescription[] =
"Experimental system for using the Desktop PWA framework for running System"
Expand Down
3 changes: 3 additions & 0 deletions chrome/browser/flag_descriptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,9 @@ extern const char kDesktopPWAsTabStripLinkCapturingDescription[];
extern const char kDesktopPWAsWithoutExtensionsName[];
extern const char kDesktopPWAsWithoutExtensionsDescription[];

extern const char kDesktopPWAsRunOnOsLoginName[];
extern const char kDesktopPWAsRunOnOsLoginDescription[];

extern const char kEnableSystemWebAppsName[];
extern const char kEnableSystemWebAppsDescription[];

Expand Down
42 changes: 41 additions & 1 deletion chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

#include "chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h"

#include <utility>

#include "base/i18n/message_formatter.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
Expand All @@ -29,6 +33,12 @@

namespace {

#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
constexpr char kDeviceTypeForCheckbox[] = "computer";
#else
constexpr char kDeviceTypeForCheckbox[] = "other";
#endif

PWAConfirmationBubbleView* g_bubble_ = nullptr;

bool g_auto_accept_pwa_for_testing = false;
Expand Down Expand Up @@ -79,14 +89,20 @@ bool PWAConfirmationBubbleView::IsShowing() {
return g_bubble_;
}

// static
PWAConfirmationBubbleView* PWAConfirmationBubbleView::GetBubbleForTesting() {
return g_bubble_;
}

PWAConfirmationBubbleView::PWAConfirmationBubbleView(
views::View* anchor_view,
views::Button* highlight_button,
std::unique_ptr<WebApplicationInfo> web_app_info,
chrome::AppInstallationAcceptanceCallback callback)
: LocationBarBubbleDelegateView(anchor_view, nullptr),
web_app_info_(std::move(web_app_info)),
callback_(std::move(callback)) {
callback_(std::move(callback)),
run_on_os_login_(nullptr) {
DCHECK(web_app_info_);

WidgetDelegate::SetShowCloseButton(true);
Expand Down Expand Up @@ -136,6 +152,17 @@ PWAConfirmationBubbleView::PWAConfirmationBubbleView(
web_app_info_->enable_experimental_tabbed_window);
}

// TODO(crbug.com/897302): This is an experimental UI added to prototype
// The PWA Run on OS Login feature, final design is yet to be decided.
if (base::FeatureList::IsEnabled(features::kDesktopPWAsRunOnOsLogin)) {
// TODO(crbug.com/897302): Detect the type of device and supply the proper
// constant for the string.
run_on_os_login_ = labels->AddChildView(std::make_unique<views::Checkbox>(
base::i18n::MessageFormatter::FormatWithNumberedArgs(
l10n_util::GetStringUTF16(IDS_INSTALL_PWA_RUN_ON_OS_LOGIN_LABEL),
kDeviceTypeForCheckbox)));
}

chrome::RecordDialogCreation(chrome::DialogIdentifier::PWA_CONFIRMATION);

SetHighlightedButton(highlight_button);
Expand All @@ -162,10 +189,23 @@ bool PWAConfirmationBubbleView::Accept() {
web_app_info_->enable_experimental_tabbed_window =
tabbed_window_checkbox_->GetChecked();
}

// User opt-in in checkbox is passed via the web_app_info structure to the
// underlying PWA install code.
// The definition of run_on_os_login_ is dependent on
// features::kDesktopPWAsRunOnOsLogin being enabled.
if (run_on_os_login_)
web_app_info_->run_on_os_login = run_on_os_login_->GetChecked();

std::move(callback_).Run(true, std::move(web_app_info_));
return true;
}

views::Checkbox*
PWAConfirmationBubbleView::GetRunOnOsLoginCheckboxForTesting() {
return run_on_os_login_;
}

namespace chrome {

void ShowPWAInstallBubble(content::WebContents* web_contents,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_UI_VIEWS_WEB_APPS_PWA_CONFIRMATION_BUBBLE_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_PWA_CONFIRMATION_BUBBLE_VIEW_H_

#include <memory>

#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
#include "chrome/common/web_application_info.h"
Expand All @@ -20,6 +22,7 @@ class Checkbox;
class PWAConfirmationBubbleView : public LocationBarBubbleDelegateView {
public:
static bool IsShowing();
static PWAConfirmationBubbleView* GetBubbleForTesting();

PWAConfirmationBubbleView(views::View* anchor_view,
views::Button* highlight_button,
Expand All @@ -31,10 +34,12 @@ class PWAConfirmationBubbleView : public LocationBarBubbleDelegateView {
views::View* GetInitiallyFocusedView() override;
void WindowClosing() override;
bool Accept() override;
views::Checkbox* GetRunOnOsLoginCheckboxForTesting();

private:
std::unique_ptr<WebApplicationInfo> web_app_info_;
chrome::AppInstallationAcceptanceCallback callback_;
views::Checkbox* run_on_os_login_ = nullptr;

// Checkbox to launch window with tab strip.
views::Checkbox* tabbed_window_checkbox_ = nullptr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,78 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <utility>

#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/components/web_app_id.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/web_application_info.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "ui/views/controls/button/checkbox.h"

class PWAConfirmationBubbleViewBrowserTest : public InProcessBrowserTest {
public:
PWAConfirmationBubbleViewBrowserTest() {
// Tests will crash if kDesktopPWAsRunOnOsLogin feature flag is not enabled.
// AcceptBubbleInPWAWindowRunOnOsLoginChecked and
// AcceptBubbleInPWAWindowRunOnOsLoginUnchecked tests interact with the
// checkbox which is only added if feature flag is enabled.
scoped_feature_list_.InitAndEnableFeature(
features::kDesktopPWAsRunOnOsLogin);
}
~PWAConfirmationBubbleViewBrowserTest() override = default;

std::unique_ptr<WebApplicationInfo> GetAppInfo() {
auto app_info = std::make_unique<WebApplicationInfo>();
app_info->title = base::UTF8ToUTF16("Test app 2");
app_info->app_url = GURL("https://example2.com");
app_info->open_as_window = true;
return app_info;
}

std::unique_ptr<WebApplicationInfo> GetCallbackAppInfoFromDialog(
bool run_on_os_login_checked) {
std::unique_ptr<WebApplicationInfo> resulting_app_info = nullptr;
auto app_info = GetAppInfo();

base::RunLoop loop;
// Show the PWA install dialog.
chrome::ShowPWAInstallBubble(
browser()->tab_strip_model()->GetActiveWebContents(),
std::move(app_info),
base::BindLambdaForTesting(
[&](bool accepted,
std::unique_ptr<WebApplicationInfo> app_info_callback) {
resulting_app_info = std::move(app_info_callback);
loop.Quit();
}));

// Get bubble dialog, set checkbox and accept.
PWAConfirmationBubbleView* bubble_dialog =
PWAConfirmationBubbleView::GetBubbleForTesting();
bubble_dialog->GetRunOnOsLoginCheckboxForTesting()->SetChecked(
run_on_os_login_checked);
bubble_dialog->Accept();

loop.Run();

return resulting_app_info;
}

using PWAConfirmationBubbleViewBrowserTest = InProcessBrowserTest;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(PWAConfirmationBubbleViewBrowserTest,
ShowBubbleInPWAWindow) {
Expand All @@ -24,10 +84,7 @@ IN_PROC_BROWSER_TEST_F(PWAConfirmationBubbleViewBrowserTest,
web_app::AppId app_id = web_app::InstallWebApp(profile, std::move(app_info));
Browser* browser = web_app::LaunchWebAppBrowser(profile, app_id);

app_info = std::make_unique<WebApplicationInfo>();
app_info->title = base::UTF8ToUTF16("Test app 2");
app_info->app_url = GURL("https://example2.com");
app_info->open_as_window = true;
app_info = GetAppInfo();
// Tests that we don't crash when showing the install prompt in a PWA window.
chrome::ShowPWAInstallBubble(
browser->tab_strip_model()->GetActiveWebContents(), std::move(app_info),
Expand All @@ -43,3 +100,17 @@ IN_PROC_BROWSER_TEST_F(PWAConfirmationBubbleViewBrowserTest,
browser->tab_strip_model()->GetActiveWebContents(), std::move(app_info),
base::DoNothing());
}

IN_PROC_BROWSER_TEST_F(PWAConfirmationBubbleViewBrowserTest,
AcceptBubbleInPWAWindowRunOnOsLoginChecked) {
auto resulting_app_info =
GetCallbackAppInfoFromDialog(/*run_on_os_login_checked=*/true);
EXPECT_TRUE(resulting_app_info->run_on_os_login);
}

IN_PROC_BROWSER_TEST_F(PWAConfirmationBubbleViewBrowserTest,
AcceptBubbleInPWAWindowRunOnOsLoginUnchecked) {
auto resulting_app_info =
GetCallbackAppInfoFromDialog(/*run_on_os_login_checked=*/false);
EXPECT_FALSE(resulting_app_info->run_on_os_login);
}
3 changes: 3 additions & 0 deletions chrome/browser/ui/web_applications/web_app_menu_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ void WebAppMenuModel::ExecuteCommand(int command_id, int event_flags) {
}

void WebAppMenuModel::Build() {
// TODO(crbug.com/897302): Expose UI for user opt out and reenable for the Run
// on OS Login feature.

if (CreateActionToolbarOverflowMenu())
AddSeparator(ui::UPPER_SEPARATOR);
AddItemWithStringId(IDC_WEB_APP_MENU_APP_INFO,
Expand Down
25 changes: 25 additions & 0 deletions chrome/browser/web_applications/components/app_shortcut_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ void AppShortcutManager::OnWebAppUninstalled(const AppId& app_id) {
base::FilePath shortcut_data_dir =
internals::GetShortcutDataDir(*shortcut_info);

if (base::FeatureList::IsEnabled(features::kDesktopPWAsRunOnOsLogin)) {
internals::GetShortcutIOTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&internals::UnregisterRunOnOsLogin,
shortcut_info->profile_path, shortcut_info->title));
}

internals::PostShortcutIOTask(
base::BindOnce(&internals::DeletePlatformShortcuts, shortcut_data_dir),
std::move(shortcut_info));
Expand Down Expand Up @@ -183,4 +190,22 @@ void AppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts(
std::move(info), std::move(callback));
}

void AppShortcutManager::RegisterRunOnOsLogin(
const AppId& app_id,
RegisterRunOnOsLoginCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

GetShortcutInfoForApp(
app_id,
base::BindOnce(
&AppShortcutManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void AppShortcutManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin(
RegisterRunOnOsLoginCallback callback,
std::unique_ptr<ShortcutInfo> info) {
internals::ScheduleRegisterRunOnOsLogin(std::move(info), std::move(callback));
}

} // namespace web_app
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class AppShortcutManager : public AppRegistrarObserver {
virtual void CreateShortcuts(const AppId& app_id,
bool add_to_desktop,
CreateShortcutsCallback callback);
virtual void RegisterRunOnOsLogin(const AppId& app_id,
RegisterRunOnOsLoginCallback callback);

// Registers a shortcuts menu for the web app's icon with the OS.
void RegisterShortcutsMenuWithOs(
Expand Down Expand Up @@ -95,6 +97,10 @@ class AppShortcutManager : public AppRegistrarObserver {
CreateShortcutsCallback callback,
std::unique_ptr<ShortcutInfo> info);

void OnShortcutInfoRetrievedRegisterRunOnOsLogin(
RegisterRunOnOsLoginCallback callback,
std::unique_ptr<ShortcutInfo> info);

ScopedObserver<AppRegistrar, AppRegistrarObserver> app_registrar_observer_{
this};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ InstallManager::InstallParams ConvertExternalInstallOptionsToParams(
params.add_to_applications_menu = install_options.add_to_applications_menu;
params.add_to_desktop = install_options.add_to_desktop;
params.add_to_quick_launch_bar = install_options.add_to_quick_launch_bar;
params.run_on_os_login = install_options.run_on_os_login;
params.add_to_search = install_options.add_to_search;
params.add_to_management = install_options.add_to_management;
params.is_disabled = install_options.is_disabled;
Expand Down
Loading

0 comments on commit 5ddded6

Please sign in to comment.