[email protected] | c333e79 | 2012-01-06 16:57:39 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 5 | #include "chrome/browser/extensions/navigation_observer.h" |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 6 | |
Sebastien Marchand | f1349f5 | 2019-01-25 03:16:41 | [diff] [blame] | 7 | #include "base/bind.h" |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 8 | #include "chrome/browser/extensions/extension_service.h" |
| 9 | #include "chrome/browser/profiles/profile.h" |
[email protected] | cdcb1dee | 2012-01-04 00:46:20 | [diff] [blame] | 10 | #include "content/public/browser/navigation_controller.h" |
[email protected] | ad23a09 | 2011-12-28 07:02:04 | [diff] [blame] | 11 | #include "content/public/browser/navigation_entry.h" |
[email protected] | ad50def5 | 2011-10-19 23:17:07 | [diff] [blame] | 12 | #include "content/public/browser/notification_service.h" |
[email protected] | c333e79 | 2012-01-06 16:57:39 | [diff] [blame] | 13 | #include "content/public/browser/notification_types.h" |
[email protected] | dccba4f8 | 2014-05-29 00:52:56 | [diff] [blame] | 14 | #include "extensions/browser/extension_prefs.h" |
[email protected] | 59b0e60 | 2014-01-30 00:41:24 | [diff] [blame] | 15 | #include "extensions/browser/extension_system.h" |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 16 | |
[email protected] | c5eed49 | 2012-01-04 17:07:50 | [diff] [blame] | 17 | using content::NavigationController; |
[email protected] | 10f417c5 | 2011-12-28 21:04:23 | [diff] [blame] | 18 | using content::NavigationEntry; |
| 19 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 20 | namespace extensions { |
| 21 | |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 22 | namespace { |
| 23 | // Whether to repeatedly prompt for the same extension id. |
| 24 | bool g_repeat_prompting = false; |
| 25 | } |
| 26 | |
Evan Stade | 75872a6 | 2019-09-06 21:17:38 | [diff] [blame] | 27 | NavigationObserver::NavigationObserver(Profile* profile) : profile_(profile) { |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 28 | RegisterForNotifications(); |
lazyboy | 56a9b918 | 2016-04-19 22:13:44 | [diff] [blame] | 29 | extension_registry_observer_.Add(ExtensionRegistry::Get(profile)); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 30 | } |
| 31 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 32 | NavigationObserver::~NavigationObserver() {} |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 33 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 34 | void NavigationObserver::Observe(int type, |
| 35 | const content::NotificationSource& source, |
| 36 | const content::NotificationDetails& details) { |
thestig | f0ed196 | 2016-06-13 21:14:25 | [diff] [blame] | 37 | DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 38 | |
[email protected] | c5eed49 | 2012-01-04 17:07:50 | [diff] [blame] | 39 | NavigationController* controller = |
| 40 | content::Source<NavigationController>(source).ptr(); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 41 | if (!profile_->IsSameProfile( |
[email protected] | a2602382 | 2011-12-29 00:23:55 | [diff] [blame] | 42 | Profile::FromBrowserContext(controller->GetBrowserContext()))) |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 43 | return; |
| 44 | |
| 45 | PromptToEnableExtensionIfNecessary(controller); |
| 46 | } |
| 47 | |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 48 | // static |
| 49 | void NavigationObserver::SetAllowedRepeatedPromptingForTesting(bool allowed) { |
| 50 | g_repeat_prompting = allowed; |
| 51 | } |
| 52 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 53 | void NavigationObserver::RegisterForNotifications() { |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 54 | registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, |
[email protected] | ad50def5 | 2011-10-19 23:17:07 | [diff] [blame] | 55 | content::NotificationService::AllSources()); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 56 | } |
| 57 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 58 | void NavigationObserver::PromptToEnableExtensionIfNecessary( |
[email protected] | c5eed49 | 2012-01-04 17:07:50 | [diff] [blame] | 59 | NavigationController* nav_controller) { |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 60 | // Bail out if we're already running a prompt. |
| 61 | if (!in_progress_prompt_extension_id_.empty()) |
| 62 | return; |
| 63 | |
[email protected] | afe9aba | 2013-08-16 20:31:34 | [diff] [blame] | 64 | NavigationEntry* nav_entry = nav_controller->GetVisibleEntry(); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 65 | if (!nav_entry) |
| 66 | return; |
| 67 | |
Charles Reis | 56a84aa67 | 2017-10-26 20:10:42 | [diff] [blame] | 68 | const GURL& url = nav_entry->GetURL(); |
nasko | 5ff4eea | 2017-05-25 02:51:00 | [diff] [blame] | 69 | |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 70 | // NOTE: We only consider chrome-extension:// urls, and deliberately don't |
| 71 | // consider hosted app urls. This is because it's really annoying to visit the |
| 72 | // site associated with a hosted app (like calendar.google.com or |
| 73 | // drive.google.com) and have it repeatedly prompt you to re-enable an item. |
| 74 | // Visiting a chrome-extension:// url is a much stronger signal, and, without |
| 75 | // the item enabled, we won't show anything. |
| 76 | // TODO(devlin): While true, I still wonder how useful this is. We should get |
| 77 | // metrics. |
| 78 | if (!url.SchemeIs(kExtensionScheme)) |
| 79 | return; |
| 80 | |
| 81 | const Extension* extension = ExtensionRegistry::Get(profile_) |
| 82 | ->disabled_extensions() |
| 83 | .GetExtensionOrAppByURL(url); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 84 | if (!extension) |
| 85 | return; |
| 86 | |
| 87 | // Try not to repeatedly prompt the user about the same extension. |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 88 | if (!prompted_extensions_.insert(extension->id()).second && |
| 89 | !g_repeat_prompting) { |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 90 | return; |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 91 | } |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 92 | |
[email protected] | 7c82539c | 2014-02-19 06:09:17 | [diff] [blame] | 93 | ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 94 | // TODO(devlin): Why do we only consider extensions that escalate permissions? |
| 95 | // Maybe because it's the only one we have a good prompt for? |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 96 | if (extension_prefs->DidExtensionEscalatePermissions(extension->id())) { |
| 97 | // Keep track of the extension id and nav controller we're prompting for. |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 98 | // These must be reset in OnInstallPromptDone. |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 99 | in_progress_prompt_extension_id_ = extension->id(); |
| 100 | in_progress_prompt_navigation_controller_ = nav_controller; |
| 101 | |
[email protected] | 619f8618 | 2012-07-03 21:30:18 | [diff] [blame] | 102 | extension_install_prompt_.reset( |
[email protected] | 91e51d61 | 2012-10-21 23:03:05 | [diff] [blame] | 103 | new ExtensionInstallPrompt(nav_controller->GetWebContents())); |
rdevlin.cronin | f84cab7 | 2015-12-12 03:45:23 | [diff] [blame] | 104 | ExtensionInstallPrompt::PromptType type = |
| 105 | ExtensionInstallPrompt::GetReEnablePromptTypeForExtension(profile_, |
| 106 | extension); |
| 107 | extension_install_prompt_->ShowDialog( |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 108 | base::Bind(&NavigationObserver::OnInstallPromptDone, |
| 109 | weak_factory_.GetWeakPtr()), |
| 110 | extension, nullptr, |
Jinho Bang | b5216cec | 2018-01-17 19:43:11 | [diff] [blame] | 111 | std::make_unique<ExtensionInstallPrompt::Prompt>(type), |
rdevlin.cronin | f84cab7 | 2015-12-12 03:45:23 | [diff] [blame] | 112 | ExtensionInstallPrompt::GetDefaultShowDialogCallback()); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 113 | } |
| 114 | } |
| 115 | |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 116 | void NavigationObserver::OnInstallPromptDone( |
| 117 | ExtensionInstallPrompt::Result result) { |
lazyboy | 56a9b918 | 2016-04-19 22:13:44 | [diff] [blame] | 118 | // The extension was already uninstalled. |
| 119 | if (in_progress_prompt_extension_id_.empty()) |
| 120 | return; |
| 121 | |
David Bertoni | 58c113a | 2019-08-02 19:53:26 | [diff] [blame] | 122 | ExtensionRegistry* extension_registry = ExtensionRegistry::Get(profile_); |
| 123 | const Extension* extension = extension_registry->GetExtensionById( |
| 124 | in_progress_prompt_extension_id_, ExtensionRegistry::COMPATIBILITY); |
lazyboy | 56a9b918 | 2016-04-19 22:13:44 | [diff] [blame] | 125 | CHECK(extension); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 126 | |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 127 | if (result == ExtensionInstallPrompt::Result::ACCEPTED) { |
| 128 | NavigationController* nav_controller = |
| 129 | in_progress_prompt_navigation_controller_; |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 130 | CHECK(nav_controller); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 131 | |
David Bertoni | 58c113a | 2019-08-02 19:53:26 | [diff] [blame] | 132 | ExtensionService* extension_service = |
| 133 | ExtensionSystem::Get(profile_)->extension_service(); |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 134 | // Grant permissions, re-enable the extension, and then reload the tab. |
| 135 | extension_service->GrantPermissionsAndEnableExtension(extension); |
toyoshim | 6142d96f | 2016-12-19 09:07:25 | [diff] [blame] | 136 | nav_controller->Reload(content::ReloadType::NORMAL, true); |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 137 | } else { |
rdevlin.cronin | 4b184ca | 2017-01-13 01:35:44 | [diff] [blame] | 138 | // TODO(devlin): These metrics aren't very useful, since they're lumped in |
| 139 | // with the same for re-enabling/canceling when the extension first gets |
| 140 | // disabled, which is likely significantly more common (though impossible to |
| 141 | // tell). We need to separate these. |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 142 | std::string histogram_name = |
lazyboy | 56a9b918 | 2016-04-19 22:13:44 | [diff] [blame] | 143 | result == ExtensionInstallPrompt::Result::USER_CANCELED |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 144 | ? "ReEnableCancel" |
| 145 | : "ReEnableAbort"; |
| 146 | ExtensionService::RecordPermissionMessagesHistogram(extension, |
| 147 | histogram_name.c_str()); |
| 148 | } |
| 149 | |
David Bertoni | 58c113a | 2019-08-02 19:53:26 | [diff] [blame] | 150 | in_progress_prompt_extension_id_.clear(); |
rdevlin.cronin | 4159305 | 2016-01-08 01:40:12 | [diff] [blame] | 151 | in_progress_prompt_navigation_controller_ = nullptr; |
| 152 | extension_install_prompt_.reset(); |
[email protected] | f9c292b3 | 2011-09-13 16:14:05 | [diff] [blame] | 153 | } |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 154 | |
lazyboy | 56a9b918 | 2016-04-19 22:13:44 | [diff] [blame] | 155 | void NavigationObserver::OnExtensionUninstalled( |
| 156 | content::BrowserContext* browser_context, |
| 157 | const Extension* extension, |
| 158 | UninstallReason reason) { |
| 159 | if (in_progress_prompt_extension_id_.empty() || |
| 160 | in_progress_prompt_extension_id_ != extension->id()) { |
| 161 | return; |
| 162 | } |
| 163 | |
| 164 | in_progress_prompt_extension_id_ = std::string(); |
| 165 | in_progress_prompt_navigation_controller_ = nullptr; |
| 166 | extension_install_prompt_.reset(); |
| 167 | } |
| 168 | |
[email protected] | d9ede58 | 2012-08-14 19:21:38 | [diff] [blame] | 169 | } // namespace extensions |