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