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