1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
6 
7 #include <string>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/check_op.h"
12 #include "base/debug/dump_without_crashing.h"
13 #include "base/files/file_path.h"
14 #include "base/metrics/histogram_functions.h"
15 #include "base/optional.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/browser/apps/app_service/app_launch_params.h"
18 #include "chrome/browser/apps/app_service/app_service_metrics.h"
19 #include "chrome/browser/apps/app_service/launch_utils.h"
20 #include "chrome/browser/chromeos/printing/print_management/print_management_uma.h"
21 #include "chrome/browser/chromeos/profiles/profile_helper.h"
22 #include "chrome/browser/installable/installable_params.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser_list.h"
25 #include "chrome/browser/ui/browser_navigator.h"
26 #include "chrome/browser/ui/browser_navigator_params.h"
27 #include "chrome/browser/ui/browser_window.h"
28 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
29 #include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
30 #include "chrome/browser/web_applications/components/app_registrar.h"
31 #include "chrome/browser/web_applications/components/os_integration_manager.h"
32 #include "chrome/browser/web_applications/components/web_app_helpers.h"
33 #include "chrome/browser/web_applications/system_web_app_manager.h"
34 #include "chrome/browser/web_applications/web_app_provider.h"
35 #include "chrome/browser/web_launch/web_launch_files_helper.h"
36 #include "ui/base/window_open_disposition.h"
37 #include "ui/display/types/display_constants.h"
38 
39 namespace {
40 // Returns the profile where we should launch System Web Apps into. It returns
41 // the most appropriate profile for launching, if the provided |profile| is
42 // unsuitable. It returns nullptr if the we can't find a suitable profile.
GetProfileForSystemWebAppLaunch(Profile * profile)43 Profile* GetProfileForSystemWebAppLaunch(Profile* profile) {
44   DCHECK(profile);
45 
46   // We can't launch into certain profiles, and we can't find a suitable
47   // alternative.
48   if (profile->IsSystemProfile())
49     return nullptr;
50 #if defined(OS_CHROMEOS)
51   if (chromeos::ProfileHelper::IsSigninProfile(profile))
52     return nullptr;
53 #endif
54 
55   // For a guest sessions, launch into the primary off-the-record profile, which
56   // is used for browsing in guest sessions. We do this because the "original"
57   // profile of the guest session can't create windows.
58   if (profile->IsGuestSession())
59     return profile->GetPrimaryOTRProfile();
60 
61   // We don't support launching SWA in incognito profiles, use the original
62   // profile if an incognito profile is provided (with the exception of guest
63   // session, which is implemented with an incognito profile, thus it is handled
64   // above).
65   if (profile->IsIncognitoProfile())
66     return profile->GetOriginalProfile();
67 
68   // Use the profile provided in other scenarios.
69   return profile;
70 }
71 }  // namespace
72 
73 namespace web_app {
74 namespace {
75 
LogPrintManagementEntryPoints(apps::mojom::AppLaunchSource source)76 void LogPrintManagementEntryPoints(apps::mojom::AppLaunchSource source) {
77   if (source == apps::mojom::AppLaunchSource::kSourceAppLauncher) {
78     base::UmaHistogramEnumeration("Printing.CUPS.PrintManagementAppEntryPoint",
79                                   PrintManagementAppEntryPoint::kLauncher);
80   } else if (source == apps::mojom::AppLaunchSource::kSourceIntentUrl) {
81     base::UmaHistogramEnumeration("Printing.CUPS.PrintManagementAppEntryPoint",
82                                   PrintManagementAppEntryPoint::kBrowser);
83   }
84 }
85 
86 }  // namespace
87 
GetSystemWebAppTypeForAppId(Profile * profile,AppId app_id)88 base::Optional<SystemAppType> GetSystemWebAppTypeForAppId(Profile* profile,
89                                                           AppId app_id) {
90   auto* provider = WebAppProvider::Get(profile);
91   return provider ? provider->system_web_app_manager().GetSystemAppTypeForAppId(
92                         app_id)
93                   : base::Optional<SystemAppType>();
94 }
95 
GetAppIdForSystemWebApp(Profile * profile,SystemAppType app_type)96 base::Optional<AppId> GetAppIdForSystemWebApp(Profile* profile,
97                                               SystemAppType app_type) {
98   auto* provider = WebAppProvider::Get(profile);
99   return provider
100              ? provider->system_web_app_manager().GetAppIdForSystemApp(app_type)
101              : base::Optional<AppId>();
102 }
103 
CreateSystemWebAppLaunchParams(Profile * profile,SystemAppType app_type,int64_t display_id)104 base::Optional<apps::AppLaunchParams> CreateSystemWebAppLaunchParams(
105     Profile* profile,
106     SystemAppType app_type,
107     int64_t display_id) {
108   base::Optional<AppId> app_id = GetAppIdForSystemWebApp(profile, app_type);
109   // TODO(calamity): Decide whether to report app launch failure or CHECK fail.
110   if (!app_id)
111     return base::nullopt;
112 
113   auto* provider = WebAppProvider::Get(profile);
114   DCHECK(provider);
115 
116   DisplayMode display_mode =
117       provider->registrar().GetAppEffectiveDisplayMode(app_id.value());
118 
119   // TODO(crbug/1113502): Plumb through better launch sources from callsites.
120   apps::AppLaunchParams params = apps::CreateAppIdLaunchParamsWithEventFlags(
121       app_id.value(), /*event_flags=*/0,
122       apps::mojom::AppLaunchSource::kSourceChromeInternal, display_id,
123       /*fallback_container=*/
124       ConvertDisplayModeToAppLaunchContainer(display_mode));
125   params.launch_source = apps::mojom::LaunchSource::kFromChromeInternal;
126 
127   return params;
128 }
129 
130 namespace {
GetLaunchDirectory(const std::vector<base::FilePath> & launch_files)131 base::FilePath GetLaunchDirectory(
132     const std::vector<base::FilePath>& launch_files) {
133   // |launch_dir| is the directory that contains all |launch_files|. If
134   // there are no launch files, launch_dir is empty.
135   base::FilePath launch_dir =
136       launch_files.size() ? launch_files[0].DirName() : base::FilePath();
137 
138 #if DCHECK_IS_ON()
139   // Check |launch_files| all come from the same directory.
140   if (!launch_dir.empty()) {
141     for (auto path : launch_files) {
142       DCHECK_EQ(launch_dir, path.DirName());
143     }
144   }
145 #endif
146 
147   return launch_dir;
148 }
149 }  // namespace
150 
LaunchSystemWebApp(Profile * profile,SystemAppType app_type,const GURL & url,base::Optional<apps::AppLaunchParams> params,bool * did_create)151 Browser* LaunchSystemWebApp(Profile* profile,
152                             SystemAppType app_type,
153                             const GURL& url,
154                             base::Optional<apps::AppLaunchParams> params,
155                             bool* did_create) {
156   Profile* profile_for_launch = GetProfileForSystemWebAppLaunch(profile);
157   if (profile_for_launch == nullptr || profile_for_launch != profile) {
158     // The provided profile can't launch system web apps. Complain about this so
159     // we can catch the call site, and ask them to pick the right profile.
160     base::debug::DumpWithoutCrashing();
161 
162     DVLOG(1) << "LaunchSystemWebApp is called on a profile that can't launch "
163                 "system  web apps. Please check the profile you are using is "
164                 "correct."
165              << (profile_for_launch
166                      ? "Instead, launch the app into a suitable profile "
167                        "based on your intention."
168                      : "Can't find a suitable profile based on the provided "
169                        "argument. Thus ignore the launch request.");
170 
171     NOTREACHED();
172 
173     if (profile_for_launch == nullptr)
174       return nullptr;
175   }
176 
177   auto* provider = WebAppProvider::Get(profile_for_launch);
178 
179   if (!provider)
180     return nullptr;
181 
182   if (!params) {
183     params = CreateSystemWebAppLaunchParams(profile_for_launch, app_type,
184                                             display::kInvalidDisplayId);
185   }
186   if (!params)
187     return nullptr;
188   params->override_url = url;
189 
190   DCHECK_EQ(params->app_id,
191             *GetAppIdForSystemWebApp(profile_for_launch, app_type));
192 
193   // TODO(crbug/1117655): The file manager records metrics directly when opening
194   // a file registered to an app, but can't tell if an SWA will ultimately be
195   // used to open it. Remove this when the file manager code is moved into
196   // the app service.
197   if (params->launch_source != apps::mojom::LaunchSource::kFromFileManager) {
198     apps::RecordAppLaunch(params->app_id, params->launch_source);
199   }
200   // Log enumerated entry point for Print Management App. Only log here if the
201   // app was launched from the browser (omnibox) or from the system launcher.
202   if (app_type == SystemAppType::PRINT_MANAGEMENT) {
203     LogPrintManagementEntryPoints(params->source);
204   }
205 
206   // Make sure we have a browser for app.  Always reuse an existing browser for
207   // popups, otherwise check app type whether we should use a single window.
208   // TODO(crbug.com/1060423): Allow apps to control whether popups are single.
209   Browser* browser = nullptr;
210   Browser::Type browser_type = Browser::TYPE_APP;
211   if (params->disposition == WindowOpenDisposition::NEW_POPUP)
212     browser_type = Browser::TYPE_APP_POPUP;
213   if (browser_type == Browser::TYPE_APP_POPUP ||
214       provider->system_web_app_manager().IsSingleWindow(app_type)) {
215     browser =
216         FindSystemWebAppBrowser(profile_for_launch, app_type, browser_type);
217   }
218 
219   // We create the app window if no existing browser found.
220   if (did_create)
221     *did_create = !browser;
222 
223   content::WebContents* web_contents = nullptr;
224 
225   if (!browser) {
226     // TODO(crbug.com/1129340): Remove these lines and make CCA resizeable after
227     // CCA supports responsive UI.
228     bool can_resize = app_type != SystemAppType::CAMERA;
229     browser = CreateWebApplicationWindow(profile_for_launch, params->app_id,
230                                          params->disposition, can_resize);
231   }
232 
233   // Navigate application window to application's |url| if necessary.
234   // Help app always navigates because its url might not match the url inside
235   // the iframe, and the iframe's url is the one that matters.
236   web_contents = browser->tab_strip_model()->GetWebContentsAt(0);
237   if (!web_contents || web_contents->GetURL() != url ||
238       app_type == SystemAppType::HELP) {
239     web_contents = NavigateWebApplicationWindow(
240         browser, params->app_id, url, WindowOpenDisposition::CURRENT_TAB);
241   }
242 
243   // Send launch files.
244   if (provider->os_integration_manager().IsFileHandlingAPIAvailable(
245           params->app_id)) {
246     if (provider->system_web_app_manager().AppShouldReceiveLaunchDirectory(
247             app_type)) {
248       web_launch::WebLaunchFilesHelper::SetLaunchDirectoryAndLaunchPaths(
249           web_contents, web_contents->GetURL(),
250           GetLaunchDirectory(params->launch_files), params->launch_files);
251     } else {
252       web_launch::WebLaunchFilesHelper::SetLaunchPaths(
253           web_contents, web_contents->GetURL(), params->launch_files);
254     }
255   }
256 
257   // TODO(crbug.com/1114939): Need to make sure the browser is shown on the
258   // correct desktop, when used in multi-profile mode.
259   browser->window()->Show();
260   return browser;
261 }
262 
FindSystemWebAppBrowser(Profile * profile,SystemAppType app_type,Browser::Type browser_type)263 Browser* FindSystemWebAppBrowser(Profile* profile,
264                                  SystemAppType app_type,
265                                  Browser::Type browser_type) {
266   // TODO(calamity): Determine whether, during startup, we need to wait for
267   // app install and then provide a valid answer here.
268   base::Optional<AppId> app_id = GetAppIdForSystemWebApp(profile, app_type);
269   if (!app_id)
270     return nullptr;
271 
272   auto* provider = WebAppProvider::Get(profile);
273   DCHECK(provider);
274 
275   if (!provider->registrar().IsInstalled(app_id.value()))
276     return nullptr;
277 
278   for (auto* browser : *BrowserList::GetInstance()) {
279     if (browser->profile() != profile || browser->type() != browser_type)
280       continue;
281 
282     if (GetAppIdFromApplicationName(browser->app_name()) == app_id.value())
283       return browser;
284   }
285 
286   return nullptr;
287 }
288 
IsSystemWebApp(Browser * browser)289 bool IsSystemWebApp(Browser* browser) {
290   DCHECK(browser);
291   return browser->app_controller() &&
292          browser->app_controller()->is_for_system_web_app();
293 }
294 
IsBrowserForSystemWebApp(Browser * browser,SystemAppType type)295 bool IsBrowserForSystemWebApp(Browser* browser, SystemAppType type) {
296   DCHECK(browser);
297   return browser->app_controller() &&
298          browser->app_controller()->system_app_type() == type;
299 }
300 
GetCapturingSystemAppForURL(Profile * profile,const GURL & url)301 base::Optional<SystemAppType> GetCapturingSystemAppForURL(Profile* profile,
302                                                           const GURL& url) {
303   auto* provider = WebAppProvider::Get(profile);
304 
305   if (!provider)
306     return base::nullopt;
307 
308   return provider->system_web_app_manager().GetCapturingSystemAppForURL(url);
309 }
310 
GetSystemWebAppMinimumWindowSize(Browser * browser)311 gfx::Size GetSystemWebAppMinimumWindowSize(Browser* browser) {
312   DCHECK(browser);
313   if (!browser->app_controller())
314     return gfx::Size();  // Not an app.
315 
316   auto* app_controller = browser->app_controller();
317   if (!app_controller->HasAppId())
318     return gfx::Size();
319 
320   auto* provider = WebAppProvider::Get(browser->profile());
321   if (!provider)
322     return gfx::Size();
323 
324   return provider->system_web_app_manager().GetMinimumWindowSize(
325       app_controller->GetAppId());
326 }
327 
328 }  // namespace web_app
329