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/ash/launcher/app_service/app_service_instance_registry_helper.h"
6 
7 #include <set>
8 #include <string>
9 #include <vector>
10 
11 #include "base/stl_util.h"
12 #include "base/time/time.h"
13 #include "chrome/browser/apps/app_service/app_service_proxy.h"
14 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
15 #include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
16 #include "chrome/browser/chromeos/crostini/crostini_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.h"
20 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/browser/web_applications/components/web_app_id.h"
25 #include "components/services/app_service/public/cpp/instance_update.h"
26 #include "components/services/app_service/public/mojom/types.mojom.h"
27 #include "content/public/browser/web_contents.h"
28 #include "extensions/common/constants.h"
29 #include "ui/wm/core/window_util.h"
30 #include "ui/wm/public/activation_client.h"
31 
AppServiceInstanceRegistryHelper(AppServiceAppWindowLauncherController * controller)32 AppServiceInstanceRegistryHelper::AppServiceInstanceRegistryHelper(
33     AppServiceAppWindowLauncherController* controller)
34     : controller_(controller),
35       proxy_(apps::AppServiceProxyFactory::GetForProfile(
36           controller->owner()->profile())),
37       launcher_controller_helper_(std::make_unique<LauncherControllerHelper>(
38           controller->owner()->profile())) {
39   DCHECK(controller_);
40 }
41 
42 AppServiceInstanceRegistryHelper::~AppServiceInstanceRegistryHelper() = default;
43 
ActiveUserChanged()44 void AppServiceInstanceRegistryHelper::ActiveUserChanged() {
45   proxy_ = apps::AppServiceProxyFactory::GetForProfile(
46       ProfileManager::GetActiveUserProfile());
47 }
48 
AdditionalUserAddedToSession(Profile * profile)49 void AppServiceInstanceRegistryHelper::AdditionalUserAddedToSession(
50     Profile* profile) {
51   proxy_ = apps::AppServiceProxyFactory::GetForProfile(profile);
52 }
53 
OnActiveTabChanged(content::WebContents * old_contents,content::WebContents * new_contents)54 void AppServiceInstanceRegistryHelper::OnActiveTabChanged(
55     content::WebContents* old_contents,
56     content::WebContents* new_contents) {
57   if (old_contents) {
58     auto* window = old_contents->GetNativeView();
59 
60     // Get the app_id from the existed instance first. If there is no record for
61     // the window, get the app_id from contents. Because when Chrome app open
62     // method is changed from windows to tabs, the app_id could be changed based
63     // on the URL, e.g. google photos, which might cause instance app_id
64     // inconsistent DCHECK error.
65     std::string app_id = GetAppId(window);
66     if (app_id.empty())
67       app_id = launcher_controller_helper_->GetAppID(old_contents);
68 
69     // If app_id is empty, we should not set it as inactive because this is
70     // Chrome's tab.
71     if (!app_id.empty()) {
72       apps::InstanceState state = GetState(window);
73       // If the app has been inactive, we don't need to update the instance.
74       if ((state & apps::InstanceState::kActive) !=
75           apps::InstanceState::kUnknown) {
76         state = static_cast<apps::InstanceState>(state &
77                                                  ~apps::InstanceState::kActive);
78         OnInstances(app_id, GetWindow(old_contents), std::string(), state);
79       }
80     }
81   }
82 
83   if (new_contents) {
84     auto* window = GetWindow(new_contents);
85 
86     // Get the app_id from the existed instance first. If there is no record for
87     // the window, get the app_id from contents. Because when Chrome app open
88     // method is changed from windows to tabs, the app_id could be changed based
89     // on the URL, e.g. google photos, which might cause instance app_id
90     // inconsistent DCHECK error.
91     std::string app_id = GetAppId(window);
92     if (app_id.empty())
93       app_id = GetAppId(new_contents);
94 
95     // When the user drags a tab to a new browser, or to an other browser, the
96     // top window could be changed, so the relation for the tap window and the
97     // browser window.
98     UpdateTabWindow(app_id, window);
99 
100     // If the app is active, it should be started, running, and visible.
101     apps::InstanceState state = static_cast<apps::InstanceState>(
102         apps::InstanceState::kStarted | apps::InstanceState::kRunning |
103         apps::InstanceState::kActive | apps::InstanceState::kVisible);
104     OnInstances(app_id, window, std::string(), state);
105   }
106 }
107 
OnTabReplaced(content::WebContents * old_contents,content::WebContents * new_contents)108 void AppServiceInstanceRegistryHelper::OnTabReplaced(
109     content::WebContents* old_contents,
110     content::WebContents* new_contents) {
111   OnTabClosing(old_contents);
112   OnTabInserted(new_contents);
113 }
114 
OnTabInserted(content::WebContents * contents)115 void AppServiceInstanceRegistryHelper::OnTabInserted(
116     content::WebContents* contents) {
117   std::string app_id = GetAppId(contents);
118   aura::Window* window = GetWindow(contents);
119 
120   // When the user drags a tab to a new browser, or to an other browser, it
121   // could generate a temp instance for this window with the Chrome application
122   // app_id. For this case, this temp instance can be deleted, otherwise, DCHECK
123   // error for inconsistent app_id.
124   const std::string old_app_id = GetAppId(window);
125   if (!old_app_id.empty() && app_id != old_app_id) {
126     RemoveTabWindow(old_app_id, window);
127     OnInstances(old_app_id, window, std::string(),
128                 apps::InstanceState::kDestroyed);
129   }
130 
131   // The tab window could be dragged to a new browser, and the top window could
132   // be changed, so clear the old top window first, then add the new top window.
133   UpdateTabWindow(app_id, window);
134   apps::InstanceState state = static_cast<apps::InstanceState>(
135       apps::InstanceState::kStarted | apps::InstanceState::kRunning);
136 
137   // Observe the tab, because when the system is shutdown or some other cases,
138   // the window could be destroyed without calling OnTabClosing. So observe the
139   // tab to get the notify when the window is destroyed.
140   controller_->ObserveWindow(window);
141   OnInstances(app_id, window, std::string(), state);
142 }
143 
OnTabClosing(content::WebContents * contents)144 void AppServiceInstanceRegistryHelper::OnTabClosing(
145     content::WebContents* contents) {
146   aura::Window* window = GetWindow(contents);
147 
148   // When the tab is closed, if the window does not exists in the AppService
149   // InstanceRegistry, we don't need to update the status.
150   const std::string app_id = GetAppId(window);
151   if (app_id.empty())
152     return;
153 
154   RemoveTabWindow(app_id, window);
155   OnInstances(app_id, window, std::string(), apps::InstanceState::kDestroyed);
156 }
157 
OnBrowserRemoved()158 void AppServiceInstanceRegistryHelper::OnBrowserRemoved() {
159   auto windows = GetWindows(extension_misc::kChromeAppId);
160   for (auto* window : windows) {
161     if (!chrome::FindBrowserWithWindow(window)) {
162       // Remove windows from |browser_window_to_tab_window_| and
163       // |tab_window_to_browser_window_|, because OnTabClosing could be not
164       // called for tabs in the browser, when the browser is removed.
165       if (base::Contains(browser_window_to_tab_window_, window)) {
166         for (auto* w : browser_window_to_tab_window_[window]) {
167           tab_window_to_browser_window_.erase(w);
168           OnInstances(GetAppId(w), w, std::string(),
169                       apps::InstanceState::kDestroyed);
170         }
171         browser_window_to_tab_window_.erase(window);
172       }
173 
174       // The browser is removed if the window can't be found, so update the
175       // Chrome window instance as destroyed.
176       OnInstances(extension_misc::kChromeAppId, window, std::string(),
177                   apps::InstanceState::kDestroyed);
178     }
179   }
180 }
181 
OnInstances(const std::string & app_id,aura::Window * window,const std::string & launch_id,apps::InstanceState state)182 void AppServiceInstanceRegistryHelper::OnInstances(const std::string& app_id,
183                                                    aura::Window* window,
184                                                    const std::string& launch_id,
185                                                    apps::InstanceState state) {
186   if (app_id.empty() || !window)
187     return;
188 
189   // If the window is not observed, this means the window is being destroyed. In
190   // this case, don't add the instance because we might keep the record for the
191   // destroyed window, which could cause crash.
192   if (state != apps::InstanceState::kDestroyed &&
193       !controller_->IsObservingWindow(window)) {
194     state = apps::InstanceState::kDestroyed;
195   }
196 
197   std::unique_ptr<apps::Instance> instance =
198       std::make_unique<apps::Instance>(app_id, window);
199   instance->SetLaunchId(launch_id);
200   instance->UpdateState(state, base::Time::Now());
201 
202   std::vector<std::unique_ptr<apps::Instance>> deltas;
203   deltas.push_back(std::move(instance));
204 
205   // The window could be teleported from the inactive user's profile to the
206   // current active user, so search all proxies. If the instance is found from a
207   // proxy, still save to that proxy, otherwise, save to the current active user
208   // profile's proxy.
209   apps::AppServiceProxy* proxy = proxy_;
210   for (auto* profile : controller_->GetProfileList()) {
211     auto* proxy_for_profile =
212         apps::AppServiceProxyFactory::GetForProfile(profile);
213     if (proxy_for_profile->InstanceRegistry().Exists(window)) {
214       proxy = proxy_for_profile;
215       break;
216     }
217   }
218   proxy->InstanceRegistry().OnInstances(std::move(deltas));
219 }
220 
OnSetShelfIDForBrowserWindowContents(content::WebContents * contents)221 void AppServiceInstanceRegistryHelper::OnSetShelfIDForBrowserWindowContents(
222     content::WebContents* contents) {
223   aura::Window* window = GetWindow(contents);
224   if (!window || !window->GetToplevelWindow())
225     return;
226 
227   // If the app id is changed, call OnTabInserted to remove the old app id in
228   // AppService InstanceRegistry, and insert the new app id.
229   std::string app_id = GetAppId(contents);
230   const std::string old_app_id = GetAppId(window);
231   if (app_id != old_app_id)
232     OnTabInserted(contents);
233 
234   // When system startup, session restore creates windows before
235   // ChromeLauncherController is created, so windows restored can’t get the
236   // visible and activated status from OnWindowVisibilityChanged and
237   // OnWindowActivated. Also web apps are ready at the very late phase which
238   // delays the shelf id setting for windows. So check the top window's visible
239   // and activated status when we have the shelf id.
240   window = window->GetToplevelWindow();
241   const std::string top_app_id = GetAppId(window);
242   if (!top_app_id.empty())
243     app_id = top_app_id;
244   OnWindowVisibilityChanged(ash::ShelfID(app_id), window, window->IsVisible());
245   auto* client = wm::GetActivationClient(window->GetRootWindow());
246   if (client) {
247     SetWindowActivated(ash::ShelfID(app_id), window,
248                        /*active*/ window == client->GetActiveWindow());
249   }
250 }
251 
OnWindowVisibilityChanged(const ash::ShelfID & shelf_id,aura::Window * window,bool visible)252 void AppServiceInstanceRegistryHelper::OnWindowVisibilityChanged(
253     const ash::ShelfID& shelf_id,
254     aura::Window* window,
255     bool visible) {
256   if (shelf_id.app_id != extension_misc::kChromeAppId) {
257     // For Web apps opened in an app window, we need to find the top level
258     // window to compare with the parameter |window|, because we save the tab
259     // window in AppService InstanceRegistry for Web apps, and we should set the
260     // state for the tab window to keep one instance for the Web app.
261     auto windows = GetWindows(shelf_id.app_id);
262     for (auto* it : windows) {
263       auto tab_it = tab_window_to_browser_window_.find(it);
264       if (tab_it == tab_window_to_browser_window_.end() ||
265           tab_it->second != window) {
266         continue;
267       }
268 
269       // When the user drags a tab to a new browser, or to an other browser, the
270       // top window could be changed, so update the relation for the tap window
271       // and the browser window.
272       UpdateTabWindow(shelf_id.app_id, it);
273 
274       apps::InstanceState state = CalculateVisibilityState(it, visible);
275       OnInstances(shelf_id.app_id, it, shelf_id.launch_id, state);
276       return;
277     }
278     return;
279   }
280 
281   apps::InstanceState state = CalculateVisibilityState(window, visible);
282   OnInstances(extension_misc::kChromeAppId, window, std::string(), state);
283 
284   if (!base::Contains(browser_window_to_tab_window_, window))
285     return;
286 
287   // For Chrome browser app windows, sets the state for each tab window instance
288   // in this browser.
289   for (auto* it : browser_window_to_tab_window_[window]) {
290     const std::string app_id = GetAppId(it);
291     if (app_id.empty())
292       continue;
293     apps::InstanceState state = CalculateVisibilityState(it, visible);
294     OnInstances(app_id, it, std::string(), state);
295   }
296 }
297 
SetWindowActivated(const ash::ShelfID & shelf_id,aura::Window * window,bool active)298 void AppServiceInstanceRegistryHelper::SetWindowActivated(
299     const ash::ShelfID& shelf_id,
300     aura::Window* window,
301     bool active) {
302   if (shelf_id.app_id != extension_misc::kChromeAppId) {
303     // For Web apps opened in an app window, we need to find the top level
304     // window to compare with |window|, because we save the tab
305     // window in AppService InstanceRegistry for Web apps, and we should set the
306     // state for the tab window to keep one instance for the Web app.
307     auto windows = GetWindows(shelf_id.app_id);
308     for (auto* it : windows) {
309       if (it->GetToplevelWindow() != window)
310         continue;
311 
312       // When the user drags a tab to a new browser, or to an other browser, the
313       // top window could be changed, so the relation for the tap window and the
314       // browser window.
315       UpdateTabWindow(shelf_id.app_id, it);
316 
317       apps::InstanceState state = CalculateActivatedState(it, active);
318       OnInstances(shelf_id.app_id, it, shelf_id.launch_id, state);
319       return;
320     }
321     return;
322   }
323 
324   apps::InstanceState state = CalculateActivatedState(window, active);
325   OnInstances(extension_misc::kChromeAppId, window, std::string(), state);
326 
327   if (!base::Contains(browser_window_to_tab_window_, window))
328     return;
329 
330   // For the Chrome browser, when the window is activated, the active tab is set
331   // as started, running, visible and active state.
332   if (active) {
333     Browser* browser = chrome::FindBrowserWithWindow(window);
334     if (!browser)
335       return;
336 
337     content::WebContents* contents =
338         browser->tab_strip_model()->GetActiveWebContents();
339     if (!contents)
340       return;
341 
342     apps::InstanceState state = static_cast<apps::InstanceState>(
343         apps::InstanceState::kStarted | apps::InstanceState::kRunning |
344         apps::InstanceState::kActive | apps::InstanceState::kVisible);
345     auto* contents_window = GetWindow(contents);
346 
347     // Get the app_id from the existed instance first. The app_id for PWAs could
348     // be changed based on the URL, e.g. google photos, which might cause
349     // instance app_id inconsistent DCHECK error.
350     std::string app_id = GetAppId(contents_window);
351     app_id = app_id.empty() ? GetAppId(contents) : app_id;
352 
353     // When the user drags a tab to a new browser, or to an other browser, the
354     // top window could be changed, so the relation for the tap window and the
355     // browser window.
356     UpdateTabWindow(app_id, contents_window);
357 
358     OnInstances(app_id, contents_window, std::string(), state);
359     return;
360   }
361 
362   // For Chrome browser app windows, sets the state for each tab window instance
363   // in this browser.
364   for (auto* it : browser_window_to_tab_window_[window]) {
365     const std::string app_id = GetAppId(it);
366     if (app_id.empty())
367       continue;
368     apps::InstanceState state = CalculateActivatedState(it, active);
369     OnInstances(app_id, it, std::string(), state);
370   }
371 }
372 
CalculateVisibilityState(aura::Window * window,bool visible) const373 apps::InstanceState AppServiceInstanceRegistryHelper::CalculateVisibilityState(
374     aura::Window* window,
375     bool visible) const {
376   apps::InstanceState state = GetState(window);
377   state = static_cast<apps::InstanceState>(
378       state | apps::InstanceState::kStarted | apps::InstanceState::kRunning);
379   state = (visible) ? static_cast<apps::InstanceState>(
380                           state | apps::InstanceState::kVisible)
381                     : static_cast<apps::InstanceState>(
382                           state & ~(apps::InstanceState::kVisible));
383   return state;
384 }
385 
CalculateActivatedState(aura::Window * window,bool active) const386 apps::InstanceState AppServiceInstanceRegistryHelper::CalculateActivatedState(
387     aura::Window* window,
388     bool active) const {
389   // If the app is active, it should be started, running, and visible.
390   if (active) {
391     return static_cast<apps::InstanceState>(
392         apps::InstanceState::kStarted | apps::InstanceState::kRunning |
393         apps::InstanceState::kActive | apps::InstanceState::kVisible);
394   }
395 
396   apps::InstanceState state = GetState(window);
397   state = static_cast<apps::InstanceState>(
398       state | apps::InstanceState::kStarted | apps::InstanceState::kRunning);
399   state =
400       static_cast<apps::InstanceState>(state & ~apps::InstanceState::kActive);
401   return state;
402 }
403 
IsOpenedInBrowser(const std::string & app_id,aura::Window * window) const404 bool AppServiceInstanceRegistryHelper::IsOpenedInBrowser(
405     const std::string& app_id,
406     aura::Window* window) const {
407   // Crostini Terminal App with the app_id kCrostiniTerminalSystemAppId is a
408   // System Web App.
409   if (app_id == crostini::kCrostiniTerminalSystemAppId)
410     return true;
411 
412   if (crostini::IsUnmatchedCrostiniShelfAppId(app_id))
413     return false;
414 
415   for (auto* profile : controller_->GetProfileList()) {
416     auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
417     apps::mojom::AppType app_type =
418         proxy->AppRegistryCache().GetAppType(app_id);
419     if (app_type == apps::mojom::AppType::kUnknown)
420       continue;
421 
422     if (app_type != apps::mojom::AppType::kExtension &&
423         app_type != apps::mojom::AppType::kWeb) {
424       return false;
425     }
426   }
427 
428   // For Extension apps, and Web apps, AppServiceAppWindowLauncherController
429   // should only handle Chrome apps, managed by extensions::AppWindow, which
430   // should set |browser_context| in AppService InstanceRegistry. So if
431   // |browser_context| is not null, the app is a Chrome app,
432   // AppServiceAppWindowLauncherController should handle it, otherwise, it is
433   // opened in a browser, and AppServiceAppWindowLauncherController should skip
434   // them.
435   //
436   // The window could be teleported from the inactive user's profile to the
437   // current active user, so search all proxies.
438   for (auto* profile : controller_->GetProfileList()) {
439     content::BrowserContext* browser_context = nullptr;
440     auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
441     bool found = false;
442     proxy->InstanceRegistry().ForOneInstance(
443         window, [&browser_context, &found](const apps::InstanceUpdate& update) {
444           browser_context = update.BrowserContext();
445           found = true;
446         });
447     if (!found)
448       continue;
449     return (browser_context) ? false : true;
450   }
451   return true;
452 }
453 
GetAppId(aura::Window * window) const454 std::string AppServiceInstanceRegistryHelper::GetAppId(
455     aura::Window* window) const {
456   for (auto* profile : controller_->GetProfileList()) {
457     auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
458     std::string app_id = proxy->InstanceRegistry().GetShelfId(window).app_id;
459     if (!app_id.empty())
460       return app_id;
461   }
462   return std::string();
463 }
464 
GetAppId(content::WebContents * contents) const465 std::string AppServiceInstanceRegistryHelper::GetAppId(
466     content::WebContents* contents) const {
467   std::string app_id = launcher_controller_helper_->GetAppID(contents);
468   if (!app_id.empty())
469     return app_id;
470   return extension_misc::kChromeAppId;
471 }
472 
GetWindow(content::WebContents * contents)473 aura::Window* AppServiceInstanceRegistryHelper::GetWindow(
474     content::WebContents* contents) {
475   std::string app_id = launcher_controller_helper_->GetAppID(contents);
476   aura::Window* window = contents->GetNativeView();
477 
478   // If |app_id| is empty, it is a browser tab. Returns the toplevel window in
479   // this case.
480   if (app_id.empty())
481     window = window->GetToplevelWindow();
482   return window;
483 }
484 
GetWindows(const std::string & app_id)485 std::set<aura::Window*> AppServiceInstanceRegistryHelper::GetWindows(
486     const std::string& app_id) {
487   std::set<aura::Window*> windows;
488   for (auto* profile : controller_->GetProfileList()) {
489     auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
490     auto w = proxy->InstanceRegistry().GetWindows(app_id);
491     windows = base::STLSetUnion<std::set<aura::Window*>>(windows, w);
492   }
493   return windows;
494 }
495 
GetState(aura::Window * window) const496 apps::InstanceState AppServiceInstanceRegistryHelper::GetState(
497     aura::Window* window) const {
498   for (auto* profile : controller_->GetProfileList()) {
499     auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
500     auto state = proxy->InstanceRegistry().GetState(window);
501     if (state != apps::InstanceState::kUnknown)
502       return state;
503   }
504   return apps::InstanceState::kUnknown;
505 }
506 
AddTabWindow(const std::string & app_id,aura::Window * window)507 void AppServiceInstanceRegistryHelper::AddTabWindow(const std::string& app_id,
508                                                     aura::Window* window) {
509   if (app_id == extension_misc::kChromeAppId)
510     return;
511 
512   aura::Window* top_level_window = window->GetToplevelWindow();
513   browser_window_to_tab_window_[top_level_window].insert(window);
514   tab_window_to_browser_window_[window] = top_level_window;
515 }
516 
RemoveTabWindow(const std::string & app_id,aura::Window * window)517 void AppServiceInstanceRegistryHelper::RemoveTabWindow(
518     const std::string& app_id,
519     aura::Window* window) {
520   if (app_id == extension_misc::kChromeAppId)
521     return;
522 
523   auto it = tab_window_to_browser_window_.find(window);
524   if (it == tab_window_to_browser_window_.end())
525     return;
526 
527   aura::Window* top_level_window = it->second;
528 
529   auto browser_it = browser_window_to_tab_window_.find(top_level_window);
530   DCHECK(browser_it != browser_window_to_tab_window_.end());
531   browser_it->second.erase(window);
532   if (browser_it->second.empty())
533     browser_window_to_tab_window_.erase(browser_it);
534   tab_window_to_browser_window_.erase(it);
535 }
536 
UpdateTabWindow(const std::string & app_id,aura::Window * window)537 void AppServiceInstanceRegistryHelper::UpdateTabWindow(
538     const std::string& app_id,
539     aura::Window* window) {
540   RemoveTabWindow(app_id, window);
541   AddTabWindow(app_id, window);
542 }
543