// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/extension_keybinding_registry.h" #include #include "base/values.h" #include "chrome/browser/extensions/active_tab_permission_granter.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/command.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/notification_types.h" #include "extensions/common/extension_set.h" #include "extensions/common/manifest_constants.h" namespace { const char kOnCommandEventName[] = "commands.onCommand"; } // namespace namespace extensions { ExtensionKeybindingRegistry::ExtensionKeybindingRegistry( content::BrowserContext* context, ExtensionFilter extension_filter, Delegate* delegate) : browser_context_(context), extension_filter_(extension_filter), delegate_(delegate), extension_registry_observer_(this), shortcut_handling_suspended_(false) { extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); Profile* profile = Profile::FromBrowserContext(browser_context_); registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED, content::Source(profile->GetOriginalProfile())); registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED, content::Source(profile->GetOriginalProfile())); } ExtensionKeybindingRegistry::~ExtensionKeybindingRegistry() { } void ExtensionKeybindingRegistry::SetShortcutHandlingSuspended(bool suspended) { shortcut_handling_suspended_ = suspended; OnShortcutHandlingSuspended(suspended); } void ExtensionKeybindingRegistry::RemoveExtensionKeybinding( const Extension* extension, const std::string& command_name) { EventTargets::iterator it = event_targets_.begin(); while (it != event_targets_.end()) { TargetList& target_list = it->second; TargetList::iterator target = target_list.begin(); while (target != target_list.end()) { if (target->first == extension->id() && (command_name.empty() || command_name == target->second)) target = target_list.erase(target); else target++; } EventTargets::iterator old = it++; if (target_list.empty()) { // Let each platform-specific implementation get a chance to clean up. RemoveExtensionKeybindingImpl(old->first, command_name); event_targets_.erase(old); // If a specific command_name was requested, it has now been deleted so no // further work is required. if (!command_name.empty()) break; } } } void ExtensionKeybindingRegistry::Init() { ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); if (!registry) return; // ExtensionRegistry can be null during testing. for (const scoped_refptr& extension : registry->enabled_extensions()) if (ExtensionMatchesFilter(extension.get())) AddExtensionKeybindings(extension.get(), std::string()); } bool ExtensionKeybindingRegistry::ShouldIgnoreCommand( const std::string& command) const { return command == manifest_values::kPageActionCommandEvent || command == manifest_values::kBrowserActionCommandEvent; } bool ExtensionKeybindingRegistry::NotifyEventTargets( const ui::Accelerator& accelerator) { return ExecuteCommands(accelerator, std::string()); } void ExtensionKeybindingRegistry::CommandExecuted( const std::string& extension_id, const std::string& command) { const Extension* extension = ExtensionRegistry::Get(browser_context_) ->enabled_extensions() .GetByID(extension_id); if (!extension) return; // Grant before sending the event so that the permission is granted before // the extension acts on the command. NOTE: The Global Commands handler does // not set the delegate as it deals only with named commands (not page/browser // actions that are associated with the current page directly). ActiveTabPermissionGranter* granter = delegate_ ? delegate_->GetActiveTabPermissionGranter() : NULL; if (granter) granter->GrantIfRequested(extension); scoped_ptr args(new base::ListValue()); args->Append(new base::StringValue(command)); scoped_ptr event(new Event(events::COMMANDS_ON_COMMAND, kOnCommandEventName, std::move(args))); event->restrict_to_browser_context = browser_context_; event->user_gesture = EventRouter::USER_GESTURE_ENABLED; EventRouter::Get(browser_context_) ->DispatchEventToExtension(extension_id, std::move(event)); } bool ExtensionKeybindingRegistry::IsAcceleratorRegistered( const ui::Accelerator& accelerator) const { return event_targets_.find(accelerator) != event_targets_.end(); } void ExtensionKeybindingRegistry::AddEventTarget( const ui::Accelerator& accelerator, const std::string& extension_id, const std::string& command_name) { event_targets_[accelerator].push_back( std::make_pair(extension_id, command_name)); // Shortcuts except media keys have only one target in the list. See comment // about |event_targets_|. if (!extensions::Command::IsMediaKey(accelerator)) DCHECK_EQ(1u, event_targets_[accelerator].size()); } bool ExtensionKeybindingRegistry::GetFirstTarget( const ui::Accelerator& accelerator, std::string* extension_id, std::string* command_name) const { EventTargets::const_iterator targets = event_targets_.find(accelerator); if (targets == event_targets_.end()) return false; DCHECK(!targets->second.empty()); TargetList::const_iterator first_target = targets->second.begin(); *extension_id = first_target->first; *command_name = first_target->second; return true; } bool ExtensionKeybindingRegistry::IsEventTargetsEmpty() const { return event_targets_.empty(); } void ExtensionKeybindingRegistry::ExecuteCommand( const std::string& extension_id, const ui::Accelerator& accelerator) { ExecuteCommands(accelerator, extension_id); } void ExtensionKeybindingRegistry::OnExtensionLoaded( content::BrowserContext* browser_context, const Extension* extension) { if (ExtensionMatchesFilter(extension)) AddExtensionKeybindings(extension, std::string()); } void ExtensionKeybindingRegistry::OnExtensionUnloaded( content::BrowserContext* browser_context, const Extension* extension, UnloadedExtensionInfo::Reason reason) { if (ExtensionMatchesFilter(extension)) RemoveExtensionKeybinding(extension, std::string()); } void ExtensionKeybindingRegistry::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED: case extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED: { ExtensionCommandRemovedDetails* payload = content::Details(details).ptr(); const Extension* extension = ExtensionRegistry::Get(browser_context_) ->enabled_extensions() .GetByID(payload->extension_id); // During install and uninstall the extension won't be found. We'll catch // those events above, with the LOADED/UNLOADED, so we ignore this event. if (!extension) return; if (ExtensionMatchesFilter(extension)) { if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED) { // Component extensions triggers OnExtensionLoaded for extension // installs as well as loads. This can cause adding of multiple key // targets. if (extension->location() == Manifest::COMPONENT) return; AddExtensionKeybindings(extension, payload->command_name); } else { RemoveExtensionKeybinding(extension, payload->command_name); } } break; } default: NOTREACHED(); break; } } bool ExtensionKeybindingRegistry::ExtensionMatchesFilter( const extensions::Extension* extension) { switch (extension_filter_) { case ALL_EXTENSIONS: return true; case PLATFORM_APPS_ONLY: return extension->is_platform_app(); default: NOTREACHED(); } return false; } bool ExtensionKeybindingRegistry::ExecuteCommands( const ui::Accelerator& accelerator, const std::string& extension_id) { EventTargets::iterator targets = event_targets_.find(accelerator); if (targets == event_targets_.end() || targets->second.empty()) return false; bool executed = false; for (TargetList::const_iterator it = targets->second.begin(); it != targets->second.end(); it++) { if (!extensions::EventRouter::Get(browser_context_) ->ExtensionHasEventListener(it->first, kOnCommandEventName)) continue; if (extension_id.empty() || it->first == extension_id) { CommandExecuted(it->first, it->second); executed = true; } } return executed; } } // namespace extensions