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/chrome_notification_types.h"
16 #include "chrome/browser/extensions/api/commands/commands.h"
17 #include "chrome/browser/extensions/extension_commands_global_registry.h"
18 #include "chrome/browser/extensions/extension_keybinding_registry.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/accelerator_utils.h"
21 #include "chrome/common/extensions/api/commands/commands_handler.h"
22 #include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
23 #include "chrome/common/pref_names.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_service.h"
27 #include "extensions/browser/extension_function_registry.h"
28 #include "extensions/browser/extension_prefs.h"
29 #include "extensions/browser/extension_registry.h"
30 #include "extensions/browser/extension_system.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 chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
290 content::Source<Profile>(profile_),
291 content::Details<
292 std::pair<const std::string, const std::string> >(&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)306 void CommandService::OnExtensionUninstalled(
307 content::BrowserContext* browser_context,
308 const Extension* extension) {
309 RemoveKeybindingPrefs(extension->id(), std::string());
310 }
311
UpdateKeybindingPrefs(const std::string & extension_id,const std::string & command_name,const std::string & keystroke)312 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
313 const std::string& command_name,
314 const std::string& keystroke) {
315 Command command = FindCommandByName(extension_id, command_name);
316
317 // The extension command might be assigned another shortcut. Remove that
318 // shortcut before proceeding.
319 RemoveKeybindingPrefs(extension_id, command_name);
320
321 ui::Accelerator accelerator =
322 Command::StringToAccelerator(keystroke, command_name);
323 AddKeybindingPref(accelerator, extension_id, command_name,
324 true, command.global());
325 }
326
SetScope(const std::string & extension_id,const std::string & command_name,bool global)327 bool CommandService::SetScope(const std::string& extension_id,
328 const std::string& command_name,
329 bool global) {
330 Command command = FindCommandByName(extension_id, command_name);
331 if (global == command.global())
332 return false;
333
334 // Pre-existing shortcuts must be removed before proceeding because the
335 // handlers for global and non-global extensions are not one and the same.
336 RemoveKeybindingPrefs(extension_id, command_name);
337 AddKeybindingPref(command.accelerator(), extension_id,
338 command_name, true, global);
339 return true;
340 }
341
FindCommandByName(const std::string & extension_id,const std::string & command) const342 Command CommandService::FindCommandByName(const std::string& extension_id,
343 const std::string& command) const {
344 const base::DictionaryValue* bindings =
345 profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
346 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
347 it.Advance()) {
348 const base::DictionaryValue* item = NULL;
349 it.value().GetAsDictionary(&item);
350
351 std::string extension;
352 item->GetString(kExtension, &extension);
353 if (extension != extension_id)
354 continue;
355 std::string command_name;
356 item->GetString(kCommandName, &command_name);
357 if (command != command_name)
358 continue;
359 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
360 std::string shortcut = it.key();
361 if (!IsForCurrentPlatform(shortcut))
362 continue;
363 bool global = false;
364 if (FeatureSwitch::global_commands()->IsEnabled())
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 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
569 #if defined OS_MACOSX
570 if (!command.accelerator().IsCmdDown())
571 return false;
572 #else
573 if (!command.accelerator().IsCtrlDown())
574 return false;
575 #endif
576 if (!command.accelerator().IsShiftDown())
577 return false;
578 return (command.accelerator().key_code() >= ui::VKEY_0 &&
579 command.accelerator().key_code() <= ui::VKEY_9);
580 } else {
581 // Not a global command, check if Chrome shortcut and whether
582 // we can override it.
583 if (command.accelerator() ==
584 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
585 CommandService::RemovesBookmarkShortcut(extension)) {
586 // If this check fails it either means we have an API to override a
587 // key that isn't a ChromeAccelerator (and the API can therefore be
588 // deprecated) or the IsChromeAccelerator isn't consistently
589 // returning true for all accelerators.
590 DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
591 return true;
592 }
593
594 return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
595 }
596 }
597
UpdateExtensionSuggestedCommandPrefs(const Extension * extension)598 void CommandService::UpdateExtensionSuggestedCommandPrefs(
599 const Extension* extension) {
600 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
601 new base::DictionaryValue);
602
603 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
604 if (commands) {
605 for (CommandMap::const_iterator iter = commands->begin();
606 iter != commands->end(); ++iter) {
607 const Command command = iter->second;
608 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
609 command_keys->SetString(
610 kSuggestedKey,
611 Command::AcceleratorToString(command.accelerator()));
612 suggested_key_prefs->Set(command.command_name(), command_keys.release());
613 }
614 }
615
616 const Command* browser_action_command =
617 CommandsInfo::GetBrowserActionCommand(extension);
618 // The browser action command may be defaulted to an unassigned accelerator if
619 // a browser action is specified by the extension but a keybinding is not
620 // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
621 if (browser_action_command &&
622 browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
623 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
624 command_keys->SetString(
625 kSuggestedKey,
626 Command::AcceleratorToString(browser_action_command->accelerator()));
627 suggested_key_prefs->Set(browser_action_command->command_name(),
628 command_keys.release());
629 }
630
631 const Command* page_action_command =
632 CommandsInfo::GetPageActionCommand(extension);
633 if (page_action_command) {
634 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
635 command_keys->SetString(
636 kSuggestedKey,
637 Command::AcceleratorToString(page_action_command->accelerator()));
638 suggested_key_prefs->Set(page_action_command->command_name(),
639 command_keys.release());
640 }
641
642 // Merge into current prefs, if present.
643 MergeSuggestedKeyPrefs(extension->id(),
644 ExtensionPrefs::Get(profile_),
645 suggested_key_prefs.Pass());
646 }
647
RemoveDefunctExtensionSuggestedCommandPrefs(const Extension * extension)648 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
649 const Extension* extension) {
650 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
651 const base::DictionaryValue* current_prefs = NULL;
652 extension_prefs->ReadPrefAsDictionary(extension->id(),
653 kCommands,
654 ¤t_prefs);
655
656 if (current_prefs) {
657 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
658 current_prefs->DeepCopy());
659 const CommandMap* named_commands =
660 CommandsInfo::GetNamedCommands(extension);
661 const Command* browser_action_command =
662 CommandsInfo::GetBrowserActionCommand(extension);
663 for (base::DictionaryValue::Iterator it(*current_prefs);
664 !it.IsAtEnd(); it.Advance()) {
665 if (it.key() == manifest_values::kBrowserActionCommandEvent) {
666 // The browser action command may be defaulted to an unassigned
667 // accelerator if a browser action is specified by the extension but a
668 // keybinding is not declared. See
669 // CommandsHandler::MaybeSetBrowserActionDefault.
670 if (!browser_action_command ||
671 browser_action_command->accelerator().key_code() ==
672 ui::VKEY_UNKNOWN) {
673 suggested_key_prefs->Remove(it.key(), NULL);
674 }
675 } else if (it.key() == manifest_values::kPageActionCommandEvent) {
676 if (!CommandsInfo::GetPageActionCommand(extension))
677 suggested_key_prefs->Remove(it.key(), NULL);
678 } else if (named_commands) {
679 if (named_commands->find(it.key()) == named_commands->end())
680 suggested_key_prefs->Remove(it.key(), NULL);
681 }
682 }
683
684 extension_prefs->UpdateExtensionPref(extension->id(),
685 kCommands,
686 suggested_key_prefs.release());
687 }
688 }
689
IsCommandShortcutUserModified(const Extension * extension,const std::string & command_name)690 bool CommandService::IsCommandShortcutUserModified(
691 const Extension* extension,
692 const std::string& command_name) {
693 // Get the previous suggested key, if any.
694 ui::Accelerator suggested_key;
695 bool suggested_key_was_assigned = false;
696 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
697 const base::DictionaryValue* commands_prefs = NULL;
698 const base::DictionaryValue* suggested_key_prefs = NULL;
699 if (extension_prefs->ReadPrefAsDictionary(extension->id(),
700 kCommands,
701 &commands_prefs) &&
702 commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
703 std::string suggested_key_string;
704 if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
705 suggested_key = Command::StringToAccelerator(suggested_key_string,
706 command_name);
707 }
708
709 suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
710 &suggested_key_was_assigned);
711 }
712
713 // Get the active shortcut from the prefs, if any.
714 Command active_command = FindCommandByName(extension->id(), command_name);
715
716 return suggested_key_was_assigned ?
717 active_command.accelerator() != suggested_key :
718 active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
719 }
720
IsKeybindingChanging(const Extension * extension,const std::string & command_name)721 bool CommandService::IsKeybindingChanging(const Extension* extension,
722 const std::string& command_name) {
723 // Get the new assigned command, if any.
724 Command new_command;
725 if (command_name == manifest_values::kBrowserActionCommandEvent) {
726 new_command = *CommandsInfo::GetBrowserActionCommand(extension);
727 } else if (command_name == manifest_values::kPageActionCommandEvent) {
728 new_command = *CommandsInfo::GetPageActionCommand(extension);
729 } else { // This is a named command.
730 const CommandMap* named_commands =
731 CommandsInfo::GetNamedCommands(extension);
732 if (named_commands) {
733 CommandMap::const_iterator loc = named_commands->find(command_name);
734 if (loc != named_commands->end())
735 new_command = loc->second;
736 }
737 }
738
739 return Command::StringToAccelerator(
740 GetSuggestedKeyPref(extension, command_name), command_name) !=
741 new_command.accelerator();
742 }
743
GetSuggestedKeyPref(const Extension * extension,const std::string & command_name)744 std::string CommandService::GetSuggestedKeyPref(
745 const Extension* extension,
746 const std::string& command_name) {
747 // Get the previous suggested key, if any.
748 ui::Accelerator suggested_key;
749 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
750 const base::DictionaryValue* commands_prefs = NULL;
751 if (extension_prefs->ReadPrefAsDictionary(extension->id(),
752 kCommands,
753 &commands_prefs)) {
754 const base::DictionaryValue* suggested_key_prefs = NULL;
755 std::string suggested_key;
756 if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
757 suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
758 return suggested_key;
759 }
760 }
761
762 return std::string();
763 }
764
RemoveKeybindingPrefs(const std::string & extension_id,const std::string & command_name)765 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
766 const std::string& command_name) {
767 DictionaryPrefUpdate updater(profile_->GetPrefs(),
768 prefs::kExtensionCommands);
769 base::DictionaryValue* bindings = updater.Get();
770
771 typedef std::vector<std::string> KeysToRemove;
772 KeysToRemove keys_to_remove;
773 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
774 it.Advance()) {
775 // Removal of keybinding preference should be limited to current platform.
776 if (!IsForCurrentPlatform(it.key()))
777 continue;
778
779 const base::DictionaryValue* item = NULL;
780 it.value().GetAsDictionary(&item);
781
782 std::string extension;
783 item->GetString(kExtension, &extension);
784
785 if (extension == extension_id) {
786 // If |command_name| is specified, delete only that command. Otherwise,
787 // delete all commands.
788 if (!command_name.empty()) {
789 std::string command;
790 item->GetString(kCommandName, &command);
791 if (command_name != command)
792 continue;
793 }
794
795 keys_to_remove.push_back(it.key());
796 }
797 }
798
799 for (KeysToRemove::const_iterator it = keys_to_remove.begin();
800 it != keys_to_remove.end(); ++it) {
801 std::string key = *it;
802 bindings->Remove(key, NULL);
803
804 std::pair<const std::string, const std::string> details =
805 std::make_pair(extension_id, command_name);
806 content::NotificationService::current()->Notify(
807 chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
808 content::Source<Profile>(profile_),
809 content::Details<
810 std::pair<const std::string, const std::string> >(&details));
811 }
812 }
813
GetExtensionActionCommand(const std::string & extension_id,QueryType query_type,Command * command,bool * active,ExtensionCommandType action_type) const814 bool CommandService::GetExtensionActionCommand(
815 const std::string& extension_id,
816 QueryType query_type,
817 Command* command,
818 bool* active,
819 ExtensionCommandType action_type) const {
820 const ExtensionSet& extensions =
821 ExtensionRegistry::Get(profile_)->enabled_extensions();
822 const Extension* extension = extensions.GetByID(extension_id);
823 CHECK(extension);
824
825 if (active)
826 *active = false;
827
828 const Command* requested_command = NULL;
829 switch (action_type) {
830 case BROWSER_ACTION:
831 requested_command = CommandsInfo::GetBrowserActionCommand(extension);
832 break;
833 case PAGE_ACTION:
834 requested_command = CommandsInfo::GetPageActionCommand(extension);
835 break;
836 case NAMED:
837 NOTREACHED();
838 return false;
839 }
840 if (!requested_command)
841 return false;
842
843 // Look up to see if the user has overridden how the command should work.
844 Command saved_command =
845 FindCommandByName(extension_id, requested_command->command_name());
846 ui::Accelerator shortcut_assigned = saved_command.accelerator();
847
848 if (active)
849 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
850
851 if (query_type == ACTIVE_ONLY &&
852 shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
853 return false;
854
855 *command = *requested_command;
856 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
857 command->set_accelerator(shortcut_assigned);
858
859 return true;
860 }
861
862 template <>
863 void
DeclareFactoryDependencies()864 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
865 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
866 }
867
868 } // namespace extensions
869