File: command.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (276 lines) | stat: -rw-r--r-- 10,317 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/common/command.h"

#include <stddef.h>

#include <memory>
#include <string>

#include "base/check.h"
#include "base/functional/callback_forward.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/android_buildflags.h"
#include "build/build_config.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "ui/base/accelerators/command.h"
#include "ui/base/accelerators/command_constants.h"

namespace extensions {

namespace errors = manifest_errors;
namespace keys = manifest_keys;
namespace values = manifest_values;

namespace {

static const char kMissing[] = "Missing";

static const char kCommandKeyNotSupported[] =
    "Command key is not supported. Note: Ctrl means Command on Mac";

// For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
// platforms leave the shortcut untouched.
std::string NormalizeShortcutSuggestion(std::string_view suggestion,
                                        std::string_view platform) {
  bool normalize = false;
  if (platform == ui::kKeybindingPlatformMac) {
    normalize = true;
  } else if (platform == ui::kKeybindingPlatformDefault) {
#if BUILDFLAG(IS_MAC)
    normalize = true;
#endif
  }

  if (!normalize) {
    return std::string{suggestion};
  }

  std::vector<std::string_view> tokens = base::SplitStringPiece(
      suggestion, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  for (auto& token : tokens) {
    if (token == ui::kKeyCtrl) {
      token = ui::kKeyCommand;
    } else if (token == ui::kKeyMacCtrl) {
      token = ui::kKeyCtrl;
    }
  }
  return base::JoinString(tokens, "+");
}

void SetAcceleratorParseErrorMessage(std::u16string* error,
                                     int index,
                                     std::string_view platform_key,
                                     std::string_view accelerator_string,
                                     ui::AcceleratorParseError parse_error) {
  error->clear();
  switch (parse_error) {
    case ui::AcceleratorParseError::kMalformedInput:
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidKeyBinding, base::NumberToString(index), platform_key,
          accelerator_string);
      break;
    case ui::AcceleratorParseError::kMediaKeyWithModifier:
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidKeyBindingMediaKeyWithModifier,
          base::NumberToString(index), platform_key, accelerator_string);
      break;
    case ui::AcceleratorParseError::kUnsupportedPlatform:
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidKeyBindingUnknownPlatform,
          base::NumberToString(index), platform_key);
      break;
  }
}

}  // namespace

Command::Command(std::string_view command_name,
                 std::u16string_view description,
                 std::string_view accelerator,
                 bool global)
    : ui::Command(command_name, description, global) {
  if (!accelerator.empty()) {
    std::u16string error;
    AcceleratorParseErrorCallback on_parse_error =
        base::BindOnce(SetAcceleratorParseErrorMessage, &error, 0,
                       CommandPlatform(), accelerator);
    set_accelerator(ParseImpl(accelerator, CommandPlatform(),
                              !IsActionRelatedCommand(command_name),
                              std::move(on_parse_error)));
  }
}

// static
std::string Command::CommandPlatform() {
#if BUILDFLAG(IS_WIN)
  return ui::kKeybindingPlatformWin;
#elif BUILDFLAG(IS_MAC)
  return ui::kKeybindingPlatformMac;
#elif BUILDFLAG(IS_CHROMEOS)
  return ui::kKeybindingPlatformChromeOs;
#elif BUILDFLAG(IS_LINUX)
  return ui::kKeybindingPlatformLinux;
#elif BUILDFLAG(IS_FUCHSIA)
  // TODO(crbug.com/40220501): Change this once we decide what string should be
  // used for Fuchsia.
  return ui::kKeybindingPlatformLinux;
#elif BUILDFLAG(IS_DESKTOP_ANDROID)
  // For now, we use linux keybindings on desktop android.
  // TODO(https://crbug.com/356905053): Should this be ChromeOS keybindings?
  return ui::kKeybindingPlatformLinux;
#else
#error Unsupported platform
#endif
}

// static
ui::Accelerator Command::StringToAccelerator(std::string_view accelerator,
                                             std::string_view command_name) {
  std::u16string error;
  AcceleratorParseErrorCallback on_parse_error =
      base::BindOnce(SetAcceleratorParseErrorMessage, &error, 0,
                     CommandPlatform(), accelerator);
  ui::Accelerator parsed = ParseImpl(accelerator, CommandPlatform(),
                                     !IsActionRelatedCommand(command_name),
                                     std::move(on_parse_error));
  return parsed;
}

// static
bool Command::IsActionRelatedCommand(std::string_view command_name) {
  return command_name == values::kActionCommandEvent ||
         command_name == values::kBrowserActionCommandEvent ||
         command_name == values::kPageActionCommandEvent;
}

bool Command::Parse(const base::Value::Dict& command,
                    std::string_view command_name,
                    int index,
                    std::u16string* error) {
  DCHECK(!command_name.empty());

  std::u16string description;
  if (!IsActionRelatedCommand(command_name)) {
    const std::string* description_ptr = command.FindString(keys::kDescription);
    if (!description_ptr || description_ptr->empty()) {
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidKeyBindingDescription, base::NumberToString(index));
      return false;
    }
    description = base::UTF8ToUTF16(*description_ptr);
  }

  // We'll build up a map of platform-to-shortcut suggestions.
  using SuggestionMap = std::map<const std::string, std::string>;
  SuggestionMap suggestions;

  // First try to parse the |suggested_key| as a dictionary.

  if (const base::Value::Dict* suggested_key_dict =
          command.FindDict(keys::kSuggestedKey)) {
    for (const auto item : *suggested_key_dict) {
      // For each item in the dictionary, extract the platforms specified.
      const std::string* suggested_key_string = item.second.GetIfString();
      if (suggested_key_string && !suggested_key_string->empty()) {
        // Found a platform, add it to the suggestions list.
        suggestions[item.first] = *suggested_key_string;
      } else {
        *error = ErrorUtils::FormatErrorMessageUTF16(
            errors::kInvalidKeyBinding, base::NumberToString(index),
            keys::kSuggestedKey, kMissing);
        return false;
      }
    }
  } else {
    // No dictionary was found, fall back to using just a string, so developers
    // don't have to specify a dictionary if they just want to use one default
    // for all platforms.
    const std::string* suggested_key_string =
        command.FindString(keys::kSuggestedKey);
    if (suggested_key_string && !suggested_key_string->empty()) {
      // If only a single string is provided, it must be default for all.
      suggestions[ui::kKeybindingPlatformDefault] = *suggested_key_string;
    } else {
      suggestions[ui::kKeybindingPlatformDefault] = "";
    }
  }

  // Check if this is a global or a regular shortcut.
  bool global = command.FindBoolByDottedPath(keys::kGlobal).value_or(false);

  // Normalize the suggestions.
  for (auto iter = suggestions.begin(); iter != suggestions.end(); ++iter) {
    // Before we normalize Ctrl to Command we must detect when the developer
    // specified Command in the Default section, which will work on Mac after
    // normalization but only fail on other platforms when they try it out on
    // other platforms, which is not what we want.
    if (iter->first == ui::kKeybindingPlatformDefault &&
        iter->second.find("Command+") != std::string::npos) {
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidKeyBinding, base::NumberToString(index),
          keys::kSuggestedKey, kCommandKeyNotSupported);
      return false;
    }

    suggestions[iter->first] =
        NormalizeShortcutSuggestion(iter->second, iter->first);
  }

  std::string platform = CommandPlatform();
  std::string key = platform;
  if (suggestions.find(key) == suggestions.end()) {
    key = ui::kKeybindingPlatformDefault;
  }
  if (suggestions.find(key) == suggestions.end()) {
    *error = ErrorUtils::FormatErrorMessageUTF16(
        errors::kInvalidKeyBindingMissingPlatform, base::NumberToString(index),
        keys::kSuggestedKey, platform);
    return false;  // No platform specified and no fallback. Bail.
  }

  // For developer convenience, we parse all the suggestions (and complain about
  // errors for platforms other than the current one) but use only what we need.
  std::map<const std::string, std::string>::const_iterator iter =
      suggestions.begin();
  for (; iter != suggestions.end(); ++iter) {
    ui::Accelerator accelerator;
    if (!iter->second.empty()) {
      // Note that we pass iter->first to pretend we are on a platform we're not
      // on.
      AcceleratorParseErrorCallback on_parse_error =
          base::BindOnce(SetAcceleratorParseErrorMessage, error, index,
                         iter->first, iter->second);
      accelerator = ParseImpl(iter->second, iter->first,
                              !IsActionRelatedCommand(command_name),
                              std::move(on_parse_error));
      if (accelerator.key_code() == ui::VKEY_UNKNOWN) {
        if (error->empty()) {
          *error = ErrorUtils::FormatErrorMessageUTF16(
              errors::kInvalidKeyBinding, base::NumberToString(index),
              iter->first, iter->second);
        }
        return false;
      }
    }

    if (iter->first == key) {
      // This platform is our platform, so grab this key.
      set_accelerator(accelerator);
      set_command_name(command_name);
      set_description(description);
      set_global(global);
    }
  }
  return true;
}

}  // namespace extensions