1 // Copyright (c) 2012 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/extensions/extension_keybinding_registry.h"
6
7 #include "base/values.h"
8 #include "chrome/browser/extensions/active_tab_permission_granter.h"
9 #include "chrome/browser/extensions/extension_service.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/common/extensions/command.h"
12 #include "content/public/browser/browser_context.h"
13 #include "extensions/browser/event_router.h"
14 #include "extensions/browser/extension_registry.h"
15 #include "extensions/browser/extension_system.h"
16 #include "extensions/browser/notification_types.h"
17 #include "extensions/common/extension_set.h"
18 #include "extensions/common/manifest_constants.h"
19
20 namespace {
21 const char kOnCommandEventName[] = "commands.onCommand";
22 } // namespace
23
24 namespace extensions {
25
ExtensionKeybindingRegistry(content::BrowserContext * context,ExtensionFilter extension_filter,Delegate * delegate)26 ExtensionKeybindingRegistry::ExtensionKeybindingRegistry(
27 content::BrowserContext* context,
28 ExtensionFilter extension_filter,
29 Delegate* delegate)
30 : browser_context_(context),
31 extension_filter_(extension_filter),
32 delegate_(delegate),
33 extension_registry_observer_(this) {
34 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
35
36 Profile* profile = Profile::FromBrowserContext(browser_context_);
37 registrar_.Add(this,
38 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
39 content::Source<Profile>(profile->GetOriginalProfile()));
40 registrar_.Add(this,
41 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
42 content::Source<Profile>(profile->GetOriginalProfile()));
43 }
44
~ExtensionKeybindingRegistry()45 ExtensionKeybindingRegistry::~ExtensionKeybindingRegistry() {
46 }
47
RemoveExtensionKeybinding(const Extension * extension,const std::string & command_name)48 void ExtensionKeybindingRegistry::RemoveExtensionKeybinding(
49 const Extension* extension,
50 const std::string& command_name) {
51 EventTargets::iterator it = event_targets_.begin();
52 while (it != event_targets_.end()) {
53 TargetList& target_list = it->second;
54 TargetList::iterator target = target_list.begin();
55 while (target != target_list.end()) {
56 if (target->first == extension->id() &&
57 (command_name.empty() || command_name == target->second))
58 target = target_list.erase(target);
59 else
60 target++;
61 }
62
63 EventTargets::iterator old = it++;
64 if (target_list.empty()) {
65 // Let each platform-specific implementation get a chance to clean up.
66 RemoveExtensionKeybindingImpl(old->first, command_name);
67 event_targets_.erase(old);
68
69 // If a specific command_name was requested, it has now been deleted so no
70 // further work is required.
71 if (!command_name.empty())
72 break;
73 }
74 }
75 }
76
Init()77 void ExtensionKeybindingRegistry::Init() {
78 ExtensionService* service =
79 ExtensionSystem::Get(browser_context_)->extension_service();
80 if (!service)
81 return; // ExtensionService can be null during testing.
82
83 const ExtensionSet* extensions = service->extensions();
84 ExtensionSet::const_iterator iter = extensions->begin();
85 for (; iter != extensions->end(); ++iter)
86 if (ExtensionMatchesFilter(iter->get()))
87 AddExtensionKeybinding(iter->get(), std::string());
88 }
89
ShouldIgnoreCommand(const std::string & command) const90 bool ExtensionKeybindingRegistry::ShouldIgnoreCommand(
91 const std::string& command) const {
92 return command == manifest_values::kPageActionCommandEvent ||
93 command == manifest_values::kBrowserActionCommandEvent;
94 }
95
NotifyEventTargets(const ui::Accelerator & accelerator)96 bool ExtensionKeybindingRegistry::NotifyEventTargets(
97 const ui::Accelerator& accelerator) {
98 return ExecuteCommands(accelerator, std::string());
99 }
100
CommandExecuted(const std::string & extension_id,const std::string & command)101 void ExtensionKeybindingRegistry::CommandExecuted(
102 const std::string& extension_id, const std::string& command) {
103 ExtensionService* service =
104 ExtensionSystem::Get(browser_context_)->extension_service();
105
106 const Extension* extension = service->extensions()->GetByID(extension_id);
107 if (!extension)
108 return;
109
110 // Grant before sending the event so that the permission is granted before
111 // the extension acts on the command. NOTE: The Global Commands handler does
112 // not set the delegate as it deals only with named commands (not page/browser
113 // actions that are associated with the current page directly).
114 ActiveTabPermissionGranter* granter =
115 delegate_ ? delegate_->GetActiveTabPermissionGranter() : NULL;
116 if (granter)
117 granter->GrantIfRequested(extension);
118
119 scoped_ptr<base::ListValue> args(new base::ListValue());
120 args->Append(new base::StringValue(command));
121
122 scoped_ptr<Event> event(new Event(kOnCommandEventName, args.Pass()));
123 event->restrict_to_browser_context = browser_context_;
124 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
125 EventRouter::Get(browser_context_)
126 ->DispatchEventToExtension(extension_id, event.Pass());
127 }
128
IsAcceleratorRegistered(const ui::Accelerator & accelerator) const129 bool ExtensionKeybindingRegistry::IsAcceleratorRegistered(
130 const ui::Accelerator& accelerator) const {
131 return event_targets_.find(accelerator) != event_targets_.end();
132 }
133
AddEventTarget(const ui::Accelerator & accelerator,const std::string & extension_id,const std::string & command_name)134 void ExtensionKeybindingRegistry::AddEventTarget(
135 const ui::Accelerator& accelerator,
136 const std::string& extension_id,
137 const std::string& command_name) {
138 event_targets_[accelerator].push_back(
139 std::make_pair(extension_id, command_name));
140 // Shortcuts except media keys have only one target in the list. See comment
141 // about |event_targets_|.
142 if (!extensions::Command::IsMediaKey(accelerator))
143 DCHECK_EQ(1u, event_targets_[accelerator].size());
144 }
145
GetFirstTarget(const ui::Accelerator & accelerator,std::string * extension_id,std::string * command_name) const146 bool ExtensionKeybindingRegistry::GetFirstTarget(
147 const ui::Accelerator& accelerator,
148 std::string* extension_id,
149 std::string* command_name) const {
150 EventTargets::const_iterator targets = event_targets_.find(accelerator);
151 if (targets == event_targets_.end())
152 return false;
153
154 DCHECK(!targets->second.empty());
155 TargetList::const_iterator first_target = targets->second.begin();
156 *extension_id = first_target->first;
157 *command_name = first_target->second;
158 return true;
159 }
160
IsEventTargetsEmpty() const161 bool ExtensionKeybindingRegistry::IsEventTargetsEmpty() const {
162 return event_targets_.empty();
163 }
164
ExecuteCommand(const std::string & extension_id,const ui::Accelerator & accelerator)165 void ExtensionKeybindingRegistry::ExecuteCommand(
166 const std::string& extension_id,
167 const ui::Accelerator& accelerator) {
168 ExecuteCommands(accelerator, extension_id);
169 }
170
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)171 void ExtensionKeybindingRegistry::OnExtensionLoaded(
172 content::BrowserContext* browser_context,
173 const Extension* extension) {
174 if (ExtensionMatchesFilter(extension))
175 AddExtensionKeybinding(extension, std::string());
176 }
177
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)178 void ExtensionKeybindingRegistry::OnExtensionUnloaded(
179 content::BrowserContext* browser_context,
180 const Extension* extension,
181 UnloadedExtensionInfo::Reason reason) {
182 if (ExtensionMatchesFilter(extension))
183 RemoveExtensionKeybinding(extension, std::string());
184 }
185
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)186 void ExtensionKeybindingRegistry::Observe(
187 int type,
188 const content::NotificationSource& source,
189 const content::NotificationDetails& details) {
190 switch (type) {
191 case extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED:
192 case extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED: {
193 std::pair<const std::string, const std::string>* payload =
194 content::Details<std::pair<const std::string, const std::string> >(
195 details).ptr();
196
197 const Extension* extension = ExtensionSystem::Get(browser_context_)
198 ->extension_service()
199 ->extensions()
200 ->GetByID(payload->first);
201 // During install and uninstall the extension won't be found. We'll catch
202 // those events above, with the LOADED/UNLOADED, so we ignore this event.
203 if (!extension)
204 return;
205
206 if (ExtensionMatchesFilter(extension)) {
207 if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED)
208 AddExtensionKeybinding(extension, payload->second);
209 else
210 RemoveExtensionKeybinding(extension, payload->second);
211 }
212 break;
213 }
214 default:
215 NOTREACHED();
216 break;
217 }
218 }
219
ExtensionMatchesFilter(const extensions::Extension * extension)220 bool ExtensionKeybindingRegistry::ExtensionMatchesFilter(
221 const extensions::Extension* extension)
222 {
223 switch (extension_filter_) {
224 case ALL_EXTENSIONS:
225 return true;
226 case PLATFORM_APPS_ONLY:
227 return extension->is_platform_app();
228 default:
229 NOTREACHED();
230 }
231 return false;
232 }
233
ExecuteCommands(const ui::Accelerator & accelerator,const std::string & extension_id)234 bool ExtensionKeybindingRegistry::ExecuteCommands(
235 const ui::Accelerator& accelerator,
236 const std::string& extension_id) {
237 EventTargets::iterator targets = event_targets_.find(accelerator);
238 if (targets == event_targets_.end() || targets->second.empty())
239 return false;
240
241 bool executed = false;
242 for (TargetList::const_iterator it = targets->second.begin();
243 it != targets->second.end(); it++) {
244 if (!extensions::EventRouter::Get(browser_context_)
245 ->ExtensionHasEventListener(it->first, kOnCommandEventName))
246 continue;
247
248 if (extension_id.empty() || it->first == extension_id) {
249 CommandExecuted(it->first, it->second);
250 executed = true;
251 }
252 }
253
254 return executed;
255 }
256
257 } // namespace extensions
258