| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/web_applications/web_app_tab_helper.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/unguessable_token.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/task_manager/web_contents_tags.h" |
| #include "chrome/browser/web_applications/manifest_update_manager.h" |
| #include "chrome/browser/web_applications/os_integration/os_integration_manager.h" |
| #include "chrome/browser/web_applications/policy/web_app_policy_manager.h" |
| #include "chrome/browser/web_applications/web_app_audio_focus_id_map.h" |
| #include "chrome/browser/web_applications/web_app_launch_queue.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_ui_manager.h" |
| #include "content/public/browser/media_session.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/site_instance.h" |
| |
| namespace web_app { |
| |
| void WebAppTabHelper::CreateForWebContents(content::WebContents* contents) { |
| DCHECK(contents); |
| if (!FromWebContents(contents)) { |
| contents->SetUserData(UserDataKey(), |
| std::make_unique<WebAppTabHelper>(contents)); |
| } |
| } |
| |
| const AppId* WebAppTabHelper::GetAppId(content::WebContents* web_contents) { |
| auto* tab_helper = WebAppTabHelper::FromWebContents(web_contents); |
| if (!tab_helper) |
| return nullptr; |
| return tab_helper->app_id_.has_value() ? &tab_helper->app_id_.value() |
| : nullptr; |
| } |
| |
| WebAppTabHelper::WebAppTabHelper(content::WebContents* web_contents) |
| : content::WebContentsUserData<WebAppTabHelper>(*web_contents), |
| content::WebContentsObserver(web_contents), |
| provider_(WebAppProvider::GetForLocalAppsUnchecked( |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()))) { |
| DCHECK(provider_); |
| observation_.Observe(&provider_->install_manager()); |
| SetAppId( |
| FindAppWithUrlInScope(web_contents->GetSiteInstance()->GetSiteURL())); |
| } |
| |
| WebAppTabHelper::~WebAppTabHelper() = default; |
| |
| const base::UnguessableToken& WebAppTabHelper::GetAudioFocusGroupIdForTesting() |
| const { |
| return audio_focus_group_id_; |
| } |
| |
| WebAppLaunchQueue& WebAppTabHelper::EnsureLaunchQueue() { |
| if (!launch_queue_) { |
| launch_queue_ = std::make_unique<WebAppLaunchQueue>( |
| web_contents(), provider_->registrar_unsafe()); |
| } |
| return *launch_queue_; |
| } |
| |
| void WebAppTabHelper::SetAppId(absl::optional<AppId> app_id) { |
| // Empty string should not be used to indicate "no app ID". |
| DCHECK(!app_id || !app_id->empty()); |
| DCHECK(!app_id || provider_->registrar_unsafe().IsInstalled(*app_id) || |
| provider_->registrar_unsafe().IsUninstalling(*app_id)); |
| if (app_id_ == app_id) { |
| return; |
| } |
| |
| absl::optional<AppId> previous_app_id = std::move(app_id_); |
| app_id_ = std::move(app_id); |
| |
| OnAssociatedAppChanged(previous_app_id, app_id_); |
| } |
| |
| void WebAppTabHelper::ReadyToCommitNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (navigation_handle->IsInPrimaryMainFrame()) { |
| const GURL& url = navigation_handle->GetURL(); |
| SetAppId(FindAppWithUrlInScope(url)); |
| } |
| |
| // If navigating to a Web App (including navigation in sub frames), let |
| // `WebAppUiManager` observers perform tab-secific setup for navigations in |
| // Web Apps. |
| if (app_id_.has_value()) { |
| provider_->ui_manager().NotifyReadyToCommitNavigation(app_id_.value(), |
| navigation_handle); |
| } |
| } |
| |
| void WebAppTabHelper::PrimaryPageChanged(content::Page& page) { |
| // This method is invoked whenever primary page of a WebContents |
| // (WebContents::GetPrimaryPage()) changes to `page`. This happens in one of |
| // the following cases: |
| // 1) when the current RenderFrameHost in the primary main frame changes after |
| // a navigation. |
| // 2) when the current RenderFrameHost in the primary main frame is |
| // reinitialized after a crash. |
| // 3) when a cross-document navigation commits in the current RenderFrameHost |
| // of the primary main frame. |
| // |
| // The new primary page might either be a brand new one (if the committed |
| // navigation created a new document in the primary main frame) or an existing |
| // one (back-forward cache restore or prerendering activation). |
| // |
| // This notification is not dispatched for changes of pages in the non-primary |
| // frame trees (prerendering, fenced frames) and when the primary page is |
| // destroyed (e.g., when closing a tab). |
| // |
| // See the declaration of WebContentsObserver::PrimaryPageChanged for more |
| // information. |
| provider_->manifest_update_manager().MaybeUpdate( |
| page.GetMainDocument().GetLastCommittedURL(), app_id_, web_contents()); |
| |
| ReinstallPlaceholderAppIfNecessary( |
| page.GetMainDocument().GetLastCommittedURL()); |
| } |
| |
| void WebAppTabHelper::DidCloneToNewWebContents( |
| content::WebContents* old_web_contents, |
| content::WebContents* new_web_contents) { |
| // When the WebContents that this is attached to is cloned, give the new clone |
| // a WebAppTabHelper. |
| CreateForWebContents(new_web_contents); |
| auto* new_tab_helper = FromWebContents(new_web_contents); |
| |
| // Clone common state: |
| new_tab_helper->SetAppId(app_id_); |
| } |
| |
| bool WebAppTabHelper::IsInAppWindow() const { |
| return provider_->ui_manager().IsInAppWindow(web_contents()); |
| } |
| |
| void WebAppTabHelper::OnWebAppInstalled(const AppId& installed_app_id) { |
| // Check if current web_contents url is in scope for the newly installed app. |
| absl::optional<AppId> app_id = |
| FindAppWithUrlInScope(web_contents()->GetURL()); |
| if (app_id == installed_app_id) |
| SetAppId(app_id); |
| } |
| |
| void WebAppTabHelper::OnWebAppWillBeUninstalled( |
| const AppId& uninstalled_app_id) { |
| if (app_id_ == uninstalled_app_id) |
| SetAppId(absl::nullopt); |
| } |
| |
| void WebAppTabHelper::OnWebAppInstallManagerDestroyed() { |
| observation_.Reset(); |
| SetAppId(absl::nullopt); |
| } |
| |
| void WebAppTabHelper::OnAssociatedAppChanged( |
| const absl::optional<AppId>& previous_app_id, |
| const absl::optional<AppId>& new_app_id) { |
| provider_->ui_manager().NotifyOnAssociatedAppChanged( |
| web_contents(), previous_app_id, new_app_id); |
| |
| // Tag WebContents for Task Manager. |
| // cases to consider: |
| // 1. non-app -> app (association added) |
| // 2. non-app -> non-app |
| // 3. app -> app (association changed) |
| // 4. app -> non-app (association removed) |
| |
| if (new_app_id.has_value() && !new_app_id->empty()) { |
| // case 1 & 3: |
| // WebContents could already be tagged with TabContentsTag or WebAppTag, |
| // therefore we want to clear it. |
| task_manager::WebContentsTags::ClearTag(web_contents()); |
| task_manager::WebContentsTags::CreateForWebApp( |
| web_contents(), new_app_id.value(), |
| provider_->registrar_unsafe().IsIsolated(new_app_id.value())); |
| } else { |
| // case 4: |
| if (previous_app_id.has_value() && !previous_app_id->empty()) { |
| // remove WebAppTag, add TabContentsTag. |
| task_manager::WebContentsTags::ClearTag(web_contents()); |
| task_manager::WebContentsTags::CreateForTabContents(web_contents()); |
| } |
| // case 2: do nothing |
| } |
| |
| UpdateAudioFocusGroupId(); |
| } |
| |
| void WebAppTabHelper::UpdateAudioFocusGroupId() { |
| if (app_id_.has_value() && IsInAppWindow()) { |
| audio_focus_group_id_ = |
| provider_->audio_focus_id_map().CreateOrGetIdForApp(app_id_.value()); |
| } else { |
| audio_focus_group_id_ = base::UnguessableToken::Null(); |
| } |
| |
| content::MediaSession::Get(web_contents()) |
| ->SetAudioFocusGroupId(audio_focus_group_id_); |
| } |
| |
| void WebAppTabHelper::ReinstallPlaceholderAppIfNecessary(const GURL& url) { |
| provider_->policy_manager().ReinstallPlaceholderAppIfNecessary( |
| url, base::DoNothing()); |
| } |
| |
| absl::optional<AppId> WebAppTabHelper::FindAppWithUrlInScope( |
| const GURL& url) const { |
| return provider_->registrar_unsafe().FindAppWithUrlInScope(url); |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(WebAppTabHelper); |
| |
| } // namespace web_app |