| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/navigation_observer.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_system.h" |
| |
| using content::NavigationController; |
| using content::NavigationEntry; |
| |
| namespace extensions { |
| |
| namespace { |
| // Whether to repeatedly prompt for the same extension id. |
| bool g_repeat_prompting = false; |
| } |
| |
| NavigationObserver::NavigationObserver(Profile* profile) : profile_(profile) { |
| RegisterForNotifications(); |
| extension_registry_observation_.Observe(ExtensionRegistry::Get(profile)); |
| } |
| |
| NavigationObserver::~NavigationObserver() {} |
| |
| void NavigationObserver::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type); |
| |
| NavigationController* controller = |
| content::Source<NavigationController>(source).ptr(); |
| if (!profile_->IsSameOrParent( |
| Profile::FromBrowserContext(controller->GetBrowserContext()))) |
| return; |
| |
| PromptToEnableExtensionIfNecessary(controller); |
| } |
| |
| // static |
| void NavigationObserver::SetAllowedRepeatedPromptingForTesting(bool allowed) { |
| g_repeat_prompting = allowed; |
| } |
| |
| void NavigationObserver::RegisterForNotifications() { |
| registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, |
| content::NotificationService::AllSources()); |
| } |
| |
| void NavigationObserver::PromptToEnableExtensionIfNecessary( |
| NavigationController* nav_controller) { |
| // Bail out if we're already running a prompt. |
| if (!in_progress_prompt_extension_id_.empty()) |
| return; |
| |
| NavigationEntry* nav_entry = nav_controller->GetVisibleEntry(); |
| if (!nav_entry) |
| return; |
| |
| const GURL& url = nav_entry->GetURL(); |
| |
| // NOTE: We only consider chrome-extension:// urls, and deliberately don't |
| // consider hosted app urls. This is because it's really annoying to visit the |
| // site associated with a hosted app (like calendar.google.com or |
| // drive.google.com) and have it repeatedly prompt you to re-enable an item. |
| // Visiting a chrome-extension:// url is a much stronger signal, and, without |
| // the item enabled, we won't show anything. |
| // TODO(devlin): While true, I still wonder how useful this is. We should get |
| // metrics. |
| if (!url.SchemeIs(kExtensionScheme)) |
| return; |
| |
| const Extension* extension = ExtensionRegistry::Get(profile_) |
| ->disabled_extensions() |
| .GetExtensionOrAppByURL(url); |
| if (!extension) |
| return; |
| |
| // Try not to repeatedly prompt the user about the same extension. |
| if (!prompted_extensions_.insert(extension->id()).second && |
| !g_repeat_prompting) { |
| return; |
| } |
| |
| ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); |
| // TODO(devlin): Why do we only consider extensions that escalate permissions? |
| // Maybe because it's the only one we have a good prompt for? |
| if (extension_prefs->DidExtensionEscalatePermissions(extension->id())) { |
| // Keep track of the extension id and nav controller we're prompting for. |
| // These must be reset in OnInstallPromptDone. |
| in_progress_prompt_extension_id_ = extension->id(); |
| in_progress_prompt_navigation_controller_ = nav_controller; |
| |
| extension_install_prompt_ = std::make_unique<ExtensionInstallPrompt>( |
| nav_controller->GetWebContents()); |
| ExtensionInstallPrompt::PromptType type = |
| ExtensionInstallPrompt::GetReEnablePromptTypeForExtension(profile_, |
| extension); |
| extension_install_prompt_->ShowDialog( |
| base::BindRepeating(&NavigationObserver::OnInstallPromptDone, |
| weak_factory_.GetWeakPtr()), |
| extension, nullptr, |
| std::make_unique<ExtensionInstallPrompt::Prompt>(type), |
| ExtensionInstallPrompt::GetDefaultShowDialogCallback()); |
| } |
| } |
| |
| void NavigationObserver::OnInstallPromptDone( |
| ExtensionInstallPrompt::Result result) { |
| // The extension was already uninstalled. |
| if (in_progress_prompt_extension_id_.empty()) |
| return; |
| |
| ExtensionRegistry* extension_registry = ExtensionRegistry::Get(profile_); |
| const Extension* extension = extension_registry->GetExtensionById( |
| in_progress_prompt_extension_id_, ExtensionRegistry::EVERYTHING); |
| CHECK(extension); |
| |
| if (result == ExtensionInstallPrompt::Result::ACCEPTED) { |
| NavigationController* nav_controller = |
| in_progress_prompt_navigation_controller_; |
| CHECK(nav_controller); |
| |
| ExtensionService* extension_service = |
| ExtensionSystem::Get(profile_)->extension_service(); |
| // Grant permissions, re-enable the extension, and then reload the tab. |
| extension_service->GrantPermissionsAndEnableExtension(extension); |
| nav_controller->Reload(content::ReloadType::NORMAL, true); |
| } |
| |
| in_progress_prompt_extension_id_.clear(); |
| in_progress_prompt_navigation_controller_ = nullptr; |
| extension_install_prompt_.reset(); |
| } |
| |
| void NavigationObserver::OnExtensionUninstalled( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| UninstallReason reason) { |
| if (in_progress_prompt_extension_id_.empty() || |
| in_progress_prompt_extension_id_ != extension->id()) { |
| return; |
| } |
| |
| in_progress_prompt_extension_id_ = std::string(); |
| in_progress_prompt_navigation_controller_ = nullptr; |
| extension_install_prompt_.reset(); |
| } |
| |
| } // namespace extensions |