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/api/commands/command_service.h"
6
7 #include <vector>
8
9 #include "base/lazy_instance.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/extensions/api/commands/commands.h"
16 #include "chrome/browser/extensions/extension_commands_global_registry.h"
17 #include "chrome/browser/extensions/extension_keybinding_registry.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/accelerator_utils.h"
20 #include "chrome/common/extensions/api/commands/commands_handler.h"
21 #include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
22 #include "chrome/common/pref_names.h"
23 #include "components/pref_registry/pref_registry_syncable.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "extensions/browser/extension_function_registry.h"
27 #include "extensions/browser/extension_prefs.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/browser/notification_types.h"
31 #include "extensions/common/feature_switch.h"
32 #include "extensions/common/manifest_constants.h"
33 #include "extensions/common/permissions/permissions_data.h"
34
35 namespace extensions {
36 namespace {
37
38 const char kExtension[] = "extension";
39 const char kCommandName[] = "command_name";
40 const char kGlobal[] = "global";
41
42 // A preference that stores keybinding state associated with extension commands.
43 const char kCommands[] = "commands";
44
45 // Preference key name for saving the extension-suggested key.
46 const char kSuggestedKey[] = "suggested_key";
47
48 // Preference key name for saving whether the extension-suggested key was
49 // actually assigned.
50 const char kSuggestedKeyWasAssigned[] = "was_assigned";
51
52 // A preference that indicates that the initial keybindings for the given
53 // extension have been set.
54 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
55
GetPlatformKeybindingKeyForAccelerator(const ui::Accelerator & accelerator,const std::string extension_id)56 std::string GetPlatformKeybindingKeyForAccelerator(
57 const ui::Accelerator& accelerator, const std::string extension_id) {
58 std::string key = Command::CommandPlatform() + ":" +
59 Command::AcceleratorToString(accelerator);
60
61 // Media keys have a 1-to-many relationship with targets, unlike regular
62 // shortcut (1-to-1 relationship). That means two or more extensions can
63 // register for the same media key so the extension ID needs to be added to
64 // the key to make sure the key is unique.
65 if (Command::IsMediaKey(accelerator))
66 key += ":" + extension_id;
67
68 return key;
69 }
70
IsForCurrentPlatform(const std::string & key)71 bool IsForCurrentPlatform(const std::string& key) {
72 return StartsWithASCII(key, Command::CommandPlatform() + ":", true);
73 }
74
SetInitialBindingsHaveBeenAssigned(ExtensionPrefs * prefs,const std::string & extension_id)75 void SetInitialBindingsHaveBeenAssigned(
76 ExtensionPrefs* prefs, const std::string& extension_id) {
77 prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
78 new base::FundamentalValue(true));
79 }
80
InitialBindingsHaveBeenAssigned(const ExtensionPrefs * prefs,const std::string & extension_id)81 bool InitialBindingsHaveBeenAssigned(
82 const ExtensionPrefs* prefs, const std::string& extension_id) {
83 bool assigned = false;
84 if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
85 kInitialBindingsHaveBeenAssigned,
86 &assigned))
87 return false;
88
89 return assigned;
90 }
91
92 // Merge |suggested_key_prefs| into the saved preferences for the extension. We
93 // merge rather than overwrite to preserve existing was_assigned preferences.
MergeSuggestedKeyPrefs(const std::string & extension_id,ExtensionPrefs * extension_prefs,scoped_ptr<base::DictionaryValue> suggested_key_prefs)94 void MergeSuggestedKeyPrefs(
95 const std::string& extension_id,
96 ExtensionPrefs* extension_prefs,
97 scoped_ptr<base::DictionaryValue> suggested_key_prefs) {
98 const base::DictionaryValue* current_prefs;
99 if (extension_prefs->ReadPrefAsDictionary(extension_id,
100 kCommands,
101 ¤t_prefs)) {
102 scoped_ptr<base::DictionaryValue> new_prefs(current_prefs->DeepCopy());
103 new_prefs->MergeDictionary(suggested_key_prefs.get());
104 suggested_key_prefs.reset(new_prefs.release());
105 }
106
107 extension_prefs->UpdateExtensionPref(extension_id,
108 kCommands,
109 suggested_key_prefs.release());
110 }
111
112 } // namespace
113
114 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)115 void CommandService::RegisterProfilePrefs(
116 user_prefs::PrefRegistrySyncable* registry) {
117 registry->RegisterDictionaryPref(
118 prefs::kExtensionCommands,
119 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
120 }
121
CommandService(content::BrowserContext * context)122 CommandService::CommandService(content::BrowserContext* context)
123 : profile_(Profile::FromBrowserContext(context)),
124 extension_registry_observer_(this) {
125 ExtensionFunctionRegistry::GetInstance()->
126 RegisterFunction<GetAllCommandsFunction>();
127
128 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
129 }
130
~CommandService()131 CommandService::~CommandService() {
132 }
133
134 static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> >
135 g_factory = LAZY_INSTANCE_INITIALIZER;
136
137 // static
138 BrowserContextKeyedAPIFactory<CommandService>*
GetFactoryInstance()139 CommandService::GetFactoryInstance() {
140 return g_factory.Pointer();
141 }
142
143 // static
Get(content::BrowserContext * context)144 CommandService* CommandService::Get(content::BrowserContext* context) {
145 return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
146 }
147
148 // static
RemovesBookmarkShortcut(const Extension * extension)149 bool CommandService::RemovesBookmarkShortcut(const Extension* extension) {
150 return UIOverrides::RemovesBookmarkShortcut(extension) &&
151 (extension->permissions_data()->HasAPIPermission(
152 APIPermission::kBookmarkManagerPrivate) ||
153 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
154 }
155
156 // static
RemovesBookmarkOpenPagesShortcut(const Extension * extension)157 bool CommandService::RemovesBookmarkOpenPagesShortcut(
158 const Extension* extension) {
159 return UIOverrides::RemovesBookmarkOpenPagesShortcut(extension) &&
160 (extension->permissions_data()->HasAPIPermission(
161 APIPermission::kBookmarkManagerPrivate) ||
162 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
163 }
164
GetBrowserActionCommand(const std::string & extension_id,QueryType type,Command * command,bool * active) const165 bool CommandService::GetBrowserActionCommand(const std::string& extension_id,
166 QueryType type,
167 Command* command,
168 bool* active) const {
169 return GetExtensionActionCommand(
170 extension_id, type, command, active, BROWSER_ACTION);
171 }
172
GetPageActionCommand(const std::string & extension_id,QueryType type,Command * command,bool * active) const173 bool CommandService::GetPageActionCommand(const std::string& extension_id,
174 QueryType type,
175 Command* command,
176 bool* active) const {
177 return GetExtensionActionCommand(
178 extension_id, type, command, active, PAGE_ACTION);
179 }
180
GetNamedCommands(const std::string & extension_id,QueryType type,CommandScope scope,CommandMap * command_map) const181 bool CommandService::GetNamedCommands(const std::string& extension_id,
182 QueryType type,
183 CommandScope scope,
184 CommandMap* command_map) const {
185 const ExtensionSet& extensions =
186 ExtensionRegistry::Get(profile_)->enabled_extensions();
187 const Extension* extension = extensions.GetByID(extension_id);
188 CHECK(extension);
189
190 command_map->clear();
191 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
192 if (!commands)
193 return false;
194
195 for (CommandMap::const_iterator iter = commands->begin();
196 iter != commands->end(); ++iter) {
197 // Look up to see if the user has overridden how the command should work.
198 Command saved_command =
199 FindCommandByName(extension_id, iter->second.command_name());
200 ui::Accelerator shortcut_assigned = saved_command.accelerator();
201
202 if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
203 continue;
204
205 Command command = iter->second;
206 if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
207 continue;
208
209 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
210 command.set_accelerator(shortcut_assigned);
211 command.set_global(saved_command.global());
212
213 (*command_map)[iter->second.command_name()] = command;
214 }
215
216 return true;
217 }
218
AddKeybindingPref(const ui::Accelerator & accelerator,std::string extension_id,std::string command_name,bool allow_overrides,bool global)219 bool CommandService::AddKeybindingPref(
220 const ui::Accelerator& accelerator,
221 std::string extension_id,
222 std::string command_name,
223 bool allow_overrides,
224 bool global) {
225 if (accelerator.key_code() == ui::VKEY_UNKNOWN)
226 return false;
227
228 // Nothing needs to be done if the existing command is the same as the desired
229 // new one.
230 Command existing_command = FindCommandByName(extension_id, command_name);
231 if (existing_command.accelerator() == accelerator &&
232 existing_command.global() == global)
233 return true;
234
235 // Media Keys are allowed to be used by named command only.
236 DCHECK(!Command::IsMediaKey(accelerator) ||
237 (command_name != manifest_values::kPageActionCommandEvent &&
238 command_name != manifest_values::kBrowserActionCommandEvent));
239
240 DictionaryPrefUpdate updater(profile_->GetPrefs(),
241 prefs::kExtensionCommands);
242 base::DictionaryValue* bindings = updater.Get();
243
244 std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
245 extension_id);
246
247 if (bindings->HasKey(key)) {
248 if (!allow_overrides)
249 return false; // Already taken.
250
251 // If the shortcut has been assigned to another command, it should be
252 // removed before overriding, so that |ExtensionKeybindingRegistry| can get
253 // a chance to do clean-up.
254 const base::DictionaryValue* item = NULL;
255 bindings->GetDictionary(key, &item);
256 std::string old_extension_id;
257 std::string old_command_name;
258 item->GetString(kExtension, &old_extension_id);
259 item->GetString(kCommandName, &old_command_name);
260 RemoveKeybindingPrefs(old_extension_id, old_command_name);
261 }
262
263 // If the command that is taking a new shortcut already has a shortcut, remove
264 // it before assigning the new one.
265 if (existing_command.accelerator().key_code() != ui::VKEY_UNKNOWN)
266 RemoveKeybindingPrefs(extension_id, command_name);
267
268 // Set the keybinding pref.
269 base::DictionaryValue* keybinding = new base::DictionaryValue();
270 keybinding->SetString(kExtension, extension_id);
271 keybinding->SetString(kCommandName, command_name);
272 keybinding->SetBoolean(kGlobal, global);
273
274 bindings->Set(key, keybinding);
275
276 // Set the was_assigned pref for the suggested key.
277 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
278 command_keys->SetBoolean(kSuggestedKeyWasAssigned, true);
279 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
280 new base::DictionaryValue);
281 suggested_key_prefs->Set(command_name, command_keys.release());
282 MergeSuggestedKeyPrefs(extension_id,
283 ExtensionPrefs::Get(profile_),
284 suggested_key_prefs.Pass());
285
286 std::pair<const std::string, const std::string> details =
287 std::make_pair(extension_id, command_name);
288 content::NotificationService::current()->Notify(
289 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
290 content::Source<Profile>(profile_),
291 content::Details<std::pair<const std::string, const std::string> >(
292 &details));
293
294 return true;
295 }
296
OnExtensionWillBeInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update,bool from_ephemeral,const std::string & old_name)297 void CommandService::OnExtensionWillBeInstalled(
298 content::BrowserContext* browser_context,
299 const Extension* extension,
300 bool is_update,
301 bool from_ephemeral,
302 const std::string& old_name) {
303 UpdateKeybindings(extension);
304 }
305
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension,extensions::UninstallReason reason)306 void CommandService::OnExtensionUninstalled(
307 content::BrowserContext* browser_context,
308 const Extension* extension,
309 extensions::UninstallReason reason) {
310 RemoveKeybindingPrefs(extension->id(), std::string());
311 }
312
UpdateKeybindingPrefs(const std::string & extension_id,const std::string & command_name,const std::string & keystroke)313 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
314 const std::string& command_name,
315 const std::string& keystroke) {
316 Command command = FindCommandByName(extension_id, command_name);
317
318 // The extension command might be assigned another shortcut. Remove that
319 // shortcut before proceeding.
320 RemoveKeybindingPrefs(extension_id, command_name);
321
322 ui::Accelerator accelerator =
323 Command::StringToAccelerator(keystroke, command_name);
324 AddKeybindingPref(accelerator, extension_id, command_name,
325 true, command.global());
326 }
327
SetScope(const std::string & extension_id,const std::string & command_name,bool global)328 bool CommandService::SetScope(const std::string& extension_id,
329 const std::string& command_name,
330 bool global) {
331 Command command = FindCommandByName(extension_id, command_name);
332 if (global == command.global())
333 return false;
334
335 // Pre-existing shortcuts must be removed before proceeding because the
336 // handlers for global and non-global extensions are not one and the same.
337 RemoveKeybindingPrefs(extension_id, command_name);
338 AddKeybindingPref(command.accelerator(), extension_id,
339 command_name, true, global);
340 return true;
341 }
342
FindCommandByName(const std::string & extension_id,const std::string & command) const343 Command CommandService::FindCommandByName(const std::string& extension_id,
344 const std::string& command) const {
345 const base::DictionaryValue* bindings =
346 profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
347 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
348 it.Advance()) {
349 const base::DictionaryValue* item = NULL;
350 it.value().GetAsDictionary(&item);
351
352 std::string extension;
353 item->GetString(kExtension, &extension);
354 if (extension != extension_id)
355 continue;
356 std::string command_name;
357 item->GetString(kCommandName, &command_name);
358 if (command != command_name)
359 continue;
360 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
361 std::string shortcut = it.key();
362 if (!IsForCurrentPlatform(shortcut))
363 continue;
364 bool global = false;
365 item->GetBoolean(kGlobal, &global);
366
367 std::vector<std::string> tokens;
368 base::SplitString(shortcut, ':', &tokens);
369 CHECK(tokens.size() >= 2);
370 shortcut = tokens[1];
371
372 return Command(command_name, base::string16(), shortcut, global);
373 }
374
375 return Command();
376 }
377
GetBoundExtensionCommand(const std::string & extension_id,const ui::Accelerator & accelerator,Command * command,ExtensionCommandType * command_type) const378 bool CommandService::GetBoundExtensionCommand(
379 const std::string& extension_id,
380 const ui::Accelerator& accelerator,
381 Command* command,
382 ExtensionCommandType* command_type) const {
383 const Extension* extension =
384 ExtensionRegistry::Get(profile_)
385 ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
386 CHECK(extension);
387
388 Command prospective_command;
389 CommandMap command_map;
390 bool active = false;
391 if (GetBrowserActionCommand(extension_id,
392 CommandService::ACTIVE_ONLY,
393 &prospective_command,
394 &active) &&
395 active && accelerator == prospective_command.accelerator()) {
396 if (command)
397 *command = prospective_command;
398 if (command_type)
399 *command_type = BROWSER_ACTION;
400 return true;
401 } else if (GetPageActionCommand(extension_id,
402 CommandService::ACTIVE_ONLY,
403 &prospective_command,
404 &active) &&
405 active && accelerator == prospective_command.accelerator()) {
406 if (command)
407 *command = prospective_command;
408 if (command_type)
409 *command_type = PAGE_ACTION;
410 return true;
411 } else if (GetNamedCommands(extension_id,
412 CommandService::ACTIVE_ONLY,
413 CommandService::REGULAR,
414 &command_map)) {
415 for (CommandMap::const_iterator it = command_map.begin();
416 it != command_map.end();
417 ++it) {
418 if (accelerator == it->second.accelerator()) {
419 if (command)
420 *command = it->second;
421 if (command_type)
422 *command_type = NAMED;
423 return true;
424 }
425 }
426 }
427 return false;
428 }
429
OverridesBookmarkShortcut(const Extension * extension) const430 bool CommandService::OverridesBookmarkShortcut(
431 const Extension* extension) const {
432 return RemovesBookmarkShortcut(extension) &&
433 GetBoundExtensionCommand(
434 extension->id(),
435 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
436 NULL,
437 NULL);
438 }
439
UpdateKeybindings(const Extension * extension)440 void CommandService::UpdateKeybindings(const Extension* extension) {
441 const ExtensionSet& extensions =
442 ExtensionRegistry::Get(profile_)->enabled_extensions();
443 // The extension is not added to the profile by this point on first install,
444 // so don't try to check for existing keybindings.
445 if (extensions.GetByID(extension->id()))
446 RemoveRelinquishedKeybindings(extension);
447 AssignKeybindings(extension);
448 UpdateExtensionSuggestedCommandPrefs(extension);
449 RemoveDefunctExtensionSuggestedCommandPrefs(extension);
450 }
451
RemoveRelinquishedKeybindings(const Extension * extension)452 void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) {
453 // Remove keybindings if they have been removed by the extension and the user
454 // has not modified them.
455 CommandMap existing_command_map;
456 if (GetNamedCommands(extension->id(),
457 CommandService::ACTIVE_ONLY,
458 CommandService::REGULAR,
459 &existing_command_map)) {
460 const CommandMap* new_command_map =
461 CommandsInfo::GetNamedCommands(extension);
462 for (CommandMap::const_iterator it = existing_command_map.begin();
463 it != existing_command_map.end(); ++it) {
464 std::string command_name = it->first;
465 if (new_command_map->find(command_name) == new_command_map->end() &&
466 !IsCommandShortcutUserModified(extension, command_name)) {
467 RemoveKeybindingPrefs(extension->id(), command_name);
468 }
469 }
470 }
471
472 Command existing_browser_action_command;
473 const Command* new_browser_action_command =
474 CommandsInfo::GetBrowserActionCommand(extension);
475 if (GetBrowserActionCommand(extension->id(),
476 CommandService::ACTIVE_ONLY,
477 &existing_browser_action_command,
478 NULL) &&
479 // The browser action command may be defaulted to an unassigned
480 // accelerator if a browser action is specified by the extension but a
481 // keybinding is not declared. See
482 // CommandsHandler::MaybeSetBrowserActionDefault.
483 (!new_browser_action_command ||
484 new_browser_action_command->accelerator().key_code() ==
485 ui::VKEY_UNKNOWN) &&
486 !IsCommandShortcutUserModified(
487 extension,
488 existing_browser_action_command.command_name())) {
489 RemoveKeybindingPrefs(extension->id(),
490 existing_browser_action_command.command_name());
491 }
492
493 Command existing_page_action_command;
494 if (GetPageActionCommand(extension->id(),
495 CommandService::ACTIVE_ONLY,
496 &existing_page_action_command,
497 NULL) &&
498 !CommandsInfo::GetPageActionCommand(extension) &&
499 !IsCommandShortcutUserModified(
500 extension,
501 existing_page_action_command.command_name())) {
502 RemoveKeybindingPrefs(extension->id(),
503 existing_page_action_command.command_name());
504 }
505 }
506
AssignKeybindings(const Extension * extension)507 void CommandService::AssignKeybindings(const Extension* extension) {
508 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
509 if (!commands)
510 return;
511
512 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
513 // TODO(wittman): remove use of this pref after M37 hits stable.
514 if (!InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
515 SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
516
517 for (CommandMap::const_iterator iter = commands->begin();
518 iter != commands->end(); ++iter) {
519 const Command command = iter->second;
520 if (CanAutoAssign(command, extension)) {
521 AddKeybindingPref(command.accelerator(),
522 extension->id(),
523 command.command_name(),
524 false, // Overwriting not allowed.
525 command.global());
526 }
527 }
528
529 const Command* browser_action_command =
530 CommandsInfo::GetBrowserActionCommand(extension);
531 if (browser_action_command &&
532 CanAutoAssign(*browser_action_command, extension)) {
533 AddKeybindingPref(browser_action_command->accelerator(),
534 extension->id(),
535 browser_action_command->command_name(),
536 false, // Overwriting not allowed.
537 false); // Not global.
538 }
539
540 const Command* page_action_command =
541 CommandsInfo::GetPageActionCommand(extension);
542 if (page_action_command && CanAutoAssign(*page_action_command, extension)) {
543 AddKeybindingPref(page_action_command->accelerator(),
544 extension->id(),
545 page_action_command->command_name(),
546 false, // Overwriting not allowed.
547 false); // Not global.
548 }
549 }
550
CanAutoAssign(const Command & command,const Extension * extension)551 bool CommandService::CanAutoAssign(const Command &command,
552 const Extension* extension) {
553 // Media Keys are non-exclusive, so allow auto-assigning them.
554 if (Command::IsMediaKey(command.accelerator()))
555 return true;
556
557 // Extensions are allowed to auto-assign updated keys if the user has not
558 // changed from the previous value.
559 if (IsCommandShortcutUserModified(extension, command.command_name()))
560 return false;
561
562 if (command.global()) {
563 using namespace extensions;
564 if (command.command_name() == manifest_values::kBrowserActionCommandEvent ||
565 command.command_name() == manifest_values::kPageActionCommandEvent)
566 return false; // Browser and page actions are not global in nature.
567
568 if (extension->permissions_data()->HasAPIPermission(
569 APIPermission::kCommandsAccessibility))
570 return true;
571
572 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
573 #if defined OS_MACOSX
574 if (!command.accelerator().IsCmdDown())
575 return false;
576 #else
577 if (!command.accelerator().IsCtrlDown())
578 return false;
579 #endif
580 if (!command.accelerator().IsShiftDown())
581 return false;
582 return (command.accelerator().key_code() >= ui::VKEY_0 &&
583 command.accelerator().key_code() <= ui::VKEY_9);
584 } else {
585 // Not a global command, check if Chrome shortcut and whether
586 // we can override it.
587 if (command.accelerator() ==
588 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
589 CommandService::RemovesBookmarkShortcut(extension)) {
590 // If this check fails it either means we have an API to override a
591 // key that isn't a ChromeAccelerator (and the API can therefore be
592 // deprecated) or the IsChromeAccelerator isn't consistently
593 // returning true for all accelerators.
594 DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
595 return true;
596 }
597
598 return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
599 }
600 }
601
UpdateExtensionSuggestedCommandPrefs(const Extension * extension)602 void CommandService::UpdateExtensionSuggestedCommandPrefs(
603 const Extension* extension) {
604 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
605 new base::DictionaryValue);
606
607 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
608 if (commands) {
609 for (CommandMap::const_iterator iter = commands->begin();
610 iter != commands->end(); ++iter) {
611 const Command command = iter->second;
612 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
613 command_keys->SetString(
614 kSuggestedKey,
615 Command::AcceleratorToString(command.accelerator()));
616 suggested_key_prefs->Set(command.command_name(), command_keys.release());
617 }
618 }
619
620 const Command* browser_action_command =
621 CommandsInfo::GetBrowserActionCommand(extension);
622 // The browser action command may be defaulted to an unassigned accelerator if
623 // a browser action is specified by the extension but a keybinding is not
624 // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
625 if (browser_action_command &&
626 browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
627 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
628 command_keys->SetString(
629 kSuggestedKey,
630 Command::AcceleratorToString(browser_action_command->accelerator()));
631 suggested_key_prefs->Set(browser_action_command->command_name(),
632 command_keys.release());
633 }
634
635 const Command* page_action_command =
636 CommandsInfo::GetPageActionCommand(extension);
637 if (page_action_command) {
638 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
639 command_keys->SetString(
640 kSuggestedKey,
641 Command::AcceleratorToString(page_action_command->accelerator()));
642 suggested_key_prefs->Set(page_action_command->command_name(),
643 command_keys.release());
644 }
645
646 // Merge into current prefs, if present.
647 MergeSuggestedKeyPrefs(extension->id(),
648 ExtensionPrefs::Get(profile_),
649 suggested_key_prefs.Pass());
650 }
651
RemoveDefunctExtensionSuggestedCommandPrefs(const Extension * extension)652 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
653 const Extension* extension) {
654 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
655 const base::DictionaryValue* current_prefs = NULL;
656 extension_prefs->ReadPrefAsDictionary(extension->id(),
657 kCommands,
658 ¤t_prefs);
659
660 if (current_prefs) {
661 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
662 current_prefs->DeepCopy());
663 const CommandMap* named_commands =
664 CommandsInfo::GetNamedCommands(extension);
665 const Command* browser_action_command =
666 CommandsInfo::GetBrowserActionCommand(extension);
667 for (base::DictionaryValue::Iterator it(*current_prefs);
668 !it.IsAtEnd(); it.Advance()) {
669 if (it.key() == manifest_values::kBrowserActionCommandEvent) {
670 // The browser action command may be defaulted to an unassigned
671 // accelerator if a browser action is specified by the extension but a
672 // keybinding is not declared. See
673 // CommandsHandler::MaybeSetBrowserActionDefault.
674 if (!browser_action_command ||
675 browser_action_command->accelerator().key_code() ==
676 ui::VKEY_UNKNOWN) {
677 suggested_key_prefs->Remove(it.key(), NULL);
678 }
679 } else if (it.key() == manifest_values::kPageActionCommandEvent) {
680 if (!CommandsInfo::GetPageActionCommand(extension))
681 suggested_key_prefs->Remove(it.key(), NULL);
682 } else if (named_commands) {
683 if (named_commands->find(it.key()) == named_commands->end())
684 suggested_key_prefs->Remove(it.key(), NULL);
685 }
686 }
687
688 extension_prefs->UpdateExtensionPref(extension->id(),
689 kCommands,
690 suggested_key_prefs.release());
691 }
692 }
693
IsCommandShortcutUserModified(const Extension * extension,const std::string & command_name)694 bool CommandService::IsCommandShortcutUserModified(
695 const Extension* extension,
696 const std::string& command_name) {
697 // Get the previous suggested key, if any.
698 ui::Accelerator suggested_key;
699 bool suggested_key_was_assigned = false;
700 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
701 const base::DictionaryValue* commands_prefs = NULL;
702 const base::DictionaryValue* suggested_key_prefs = NULL;
703 if (extension_prefs->ReadPrefAsDictionary(extension->id(),
704 kCommands,
705 &commands_prefs) &&
706 commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
707 std::string suggested_key_string;
708 if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
709 suggested_key = Command::StringToAccelerator(suggested_key_string,
710 command_name);
711 }
712
713 suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
714 &suggested_key_was_assigned);
715 }
716
717 // Get the active shortcut from the prefs, if any.
718 Command active_command = FindCommandByName(extension->id(), command_name);
719
720 return suggested_key_was_assigned ?
721 active_command.accelerator() != suggested_key :
722 active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
723 }
724
IsKeybindingChanging(const Extension * extension,const std::string & command_name)725 bool CommandService::IsKeybindingChanging(const Extension* extension,
726 const std::string& command_name) {
727 // Get the new assigned command, if any.
728 Command new_command;
729 if (command_name == manifest_values::kBrowserActionCommandEvent) {
730 new_command = *CommandsInfo::GetBrowserActionCommand(extension);
731 } else if (command_name == manifest_values::kPageActionCommandEvent) {
732 new_command = *CommandsInfo::GetPageActionCommand(extension);
733 } else { // This is a named command.
734 const CommandMap* named_commands =
735 CommandsInfo::GetNamedCommands(extension);
736 if (named_commands) {
737 CommandMap::const_iterator loc = named_commands->find(command_name);
738 if (loc != named_commands->end())
739 new_command = loc->second;
740 }
741 }
742
743 return Command::StringToAccelerator(
744 GetSuggestedKeyPref(extension, command_name), command_name) !=
745 new_command.accelerator();
746 }
747
GetSuggestedKeyPref(const Extension * extension,const std::string & command_name)748 std::string CommandService::GetSuggestedKeyPref(
749 const Extension* extension,
750 const std::string& command_name) {
751 // Get the previous suggested key, if any.
752 ui::Accelerator suggested_key;
753 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
754 const base::DictionaryValue* commands_prefs = NULL;
755 if (extension_prefs->ReadPrefAsDictionary(extension->id(),
756 kCommands,
757 &commands_prefs)) {
758 const base::DictionaryValue* suggested_key_prefs = NULL;
759 std::string suggested_key;
760 if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
761 suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
762 return suggested_key;
763 }
764 }
765
766 return std::string();
767 }
768
RemoveKeybindingPrefs(const std::string & extension_id,const std::string & command_name)769 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
770 const std::string& command_name) {
771 DictionaryPrefUpdate updater(profile_->GetPrefs(),
772 prefs::kExtensionCommands);
773 base::DictionaryValue* bindings = updater.Get();
774
775 typedef std::vector<std::string> KeysToRemove;
776 KeysToRemove keys_to_remove;
777 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
778 it.Advance()) {
779 // Removal of keybinding preference should be limited to current platform.
780 if (!IsForCurrentPlatform(it.key()))
781 continue;
782
783 const base::DictionaryValue* item = NULL;
784 it.value().GetAsDictionary(&item);
785
786 std::string extension;
787 item->GetString(kExtension, &extension);
788
789 if (extension == extension_id) {
790 // If |command_name| is specified, delete only that command. Otherwise,
791 // delete all commands.
792 if (!command_name.empty()) {
793 std::string command;
794 item->GetString(kCommandName, &command);
795 if (command_name != command)
796 continue;
797 }
798
799 keys_to_remove.push_back(it.key());
800 }
801 }
802
803 for (KeysToRemove::const_iterator it = keys_to_remove.begin();
804 it != keys_to_remove.end(); ++it) {
805 std::string key = *it;
806 bindings->Remove(key, NULL);
807
808 std::pair<const std::string, const std::string> details =
809 std::make_pair(extension_id, command_name);
810 content::NotificationService::current()->Notify(
811 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
812 content::Source<Profile>(profile_),
813 content::Details<std::pair<const std::string, const std::string> >(
814 &details));
815 }
816 }
817
GetExtensionActionCommand(const std::string & extension_id,QueryType query_type,Command * command,bool * active,ExtensionCommandType action_type) const818 bool CommandService::GetExtensionActionCommand(
819 const std::string& extension_id,
820 QueryType query_type,
821 Command* command,
822 bool* active,
823 ExtensionCommandType action_type) const {
824 const ExtensionSet& extensions =
825 ExtensionRegistry::Get(profile_)->enabled_extensions();
826 const Extension* extension = extensions.GetByID(extension_id);
827 CHECK(extension);
828
829 if (active)
830 *active = false;
831
832 const Command* requested_command = NULL;
833 switch (action_type) {
834 case BROWSER_ACTION:
835 requested_command = CommandsInfo::GetBrowserActionCommand(extension);
836 break;
837 case PAGE_ACTION:
838 requested_command = CommandsInfo::GetPageActionCommand(extension);
839 break;
840 case NAMED:
841 NOTREACHED();
842 return false;
843 }
844 if (!requested_command)
845 return false;
846
847 // Look up to see if the user has overridden how the command should work.
848 Command saved_command =
849 FindCommandByName(extension_id, requested_command->command_name());
850 ui::Accelerator shortcut_assigned = saved_command.accelerator();
851
852 if (active)
853 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
854
855 if (query_type == ACTIVE_ONLY &&
856 shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
857 return false;
858
859 *command = *requested_command;
860 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
861 command->set_accelerator(shortcut_assigned);
862
863 return true;
864 }
865
866 template <>
867 void
DeclareFactoryDependencies()868 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
869 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
870 }
871
872 } // namespace extensions
873