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