Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 "ui/base/clipboard/clipboard.h" | |
| 6 | |
| 7 #include <jni.h> | |
| 8 | |
| 9 #include "base/android/jni_android.h" | |
| 10 #include "base/android/scoped_java_ref.h" | |
| 11 #include "base/lazy_instance.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/memory/scoped_ptr.h" | |
| 14 #include "base/utf_string_conversions.h" | |
| 15 #include "third_party/skia/include/core/SkBitmap.h" | |
| 16 #include "ui/gfx/size.h" | |
| 17 | |
| 18 // Important note: | |
| 19 // Android's clipboard system only supports text content, so use it only when | |
| 20 // text is added to or retrieved from the system. For other data types, store | |
| 21 // the value in a map. This has the consequence that the clipboard's contents | |
| 22 // will only be available within the current process. | |
| 23 | |
| 24 // Global contents map and its lock. | |
| 25 using base::android::AttachCurrentThread; | |
| 26 using base::android::CheckException; | |
| 27 using base::android::ClearException; | |
| 28 using base::android::ScopedJavaLocalRef; | |
| 29 | |
| 30 namespace ui { | |
| 31 | |
| 32 namespace { | |
| 33 // As Android only supports text in the clipboard, the following map will be | |
| 34 // used for other kinds of data. Use the lock to make this thread-safe. | |
| 35 // TODO(beverloo): http://crbug.com/112286 Investigate whether the locks in | |
| 36 // this file are required. | |
| 37 typedef std::map<std::string, std::string> ClipboardMap; | |
| 38 ClipboardMap* g_clipboard_map = NULL; | |
| 39 base::LazyInstance<base::Lock> g_clipboard_map_lock = LAZY_INSTANCE_INITIALIZER; | |
| 40 | |
| 41 // Various format we support | |
| 42 const char* const kPlainTextFormat = "text"; | |
|
dcheng
2012/02/01 19:02:04
I recall that in the past, there was a slight pref
Peter Beverloo
2012/02/02 12:16:11
Done.
| |
| 43 const char* const kHTMLFormat = "html"; | |
| 44 const char* const kBitmapFormat = "bitmap"; | |
| 45 const char* const kWebKitSmartPasteFormat = "webkit_smart"; | |
| 46 const char* const kBookmarkFormat = "bookmark"; | |
| 47 const char* const kMimeTypeWebCustomData = "chromium/x-web-custom-data"; | |
| 48 | |
| 49 } // namespace | |
| 50 | |
| 51 Clipboard::FormatType::FormatType() { | |
| 52 } | |
| 53 | |
| 54 Clipboard::FormatType::FormatType(const std::string& native_format) | |
| 55 : data_(native_format) { | |
| 56 } | |
| 57 | |
| 58 Clipboard::FormatType::~FormatType() { | |
| 59 } | |
| 60 | |
| 61 std::string Clipboard::FormatType::Serialize() const { | |
| 62 return data_; | |
| 63 } | |
| 64 | |
| 65 // static | |
| 66 Clipboard::FormatType Clipboard::FormatType::Deserialize( | |
| 67 const std::string& serialization) { | |
| 68 return FormatType(serialization); | |
| 69 } | |
| 70 | |
| 71 bool Clipboard::FormatType::Equals(const FormatType& other) const { | |
| 72 return data_ == other.data_; | |
| 73 } | |
| 74 | |
| 75 // The clipboard object on the Android platform is simply wrapping the Java | |
| 76 // object for the text data format. For non-text format, a global map is used. | |
| 77 Clipboard::Clipboard() | |
| 78 : clipboard_manager_(NULL), | |
| 79 set_text_(NULL), | |
| 80 has_text_(NULL), | |
| 81 get_text_(NULL), | |
| 82 to_string_(NULL) { | |
| 83 JNIEnv* env = AttachCurrentThread(); | |
| 84 DCHECK(env); | |
| 85 | |
| 86 // Get the context | |
| 87 jobject context = env->NewLocalRef(base::android::GetApplicationContext()); | |
| 88 | |
| 89 if (!context) { | |
| 90 // Should be during testing only | |
| 91 // Get the ActivityThread class | |
| 92 ScopedJavaLocalRef<jclass> activity_thread_class(env, | |
| 93 env->FindClass("android/app/ActivityThread")); | |
| 94 DCHECK(activity_thread_class.obj()); | |
| 95 | |
| 96 // Try to get the current activity thread. | |
| 97 jmethodID current_activity_method_id = | |
| 98 env->GetStaticMethodID(activity_thread_class.obj(), | |
| 99 "currentActivityThread", | |
| 100 "()Landroid/app/ActivityThread;"); | |
| 101 DCHECK(current_activity_method_id); | |
| 102 jobject current_activity = | |
| 103 env->CallStaticObjectMethod(activity_thread_class.obj(), | |
| 104 current_activity_method_id); | |
| 105 if (ClearException(env)) | |
| 106 current_activity = NULL; | |
| 107 | |
| 108 if (!current_activity) { | |
| 109 // There is no current activity, create one | |
| 110 ScopedJavaLocalRef<jclass> looper_class(env, | |
| 111 env->FindClass("android/os/Looper")); | |
| 112 jmethodID prepare_method_id = env->GetStaticMethodID(looper_class.obj(), | |
| 113 "prepareMainLooper", "()V"); | |
| 114 env->CallStaticVoidMethod(looper_class.obj(), prepare_method_id); | |
| 115 CheckException(env); | |
| 116 | |
| 117 jmethodID system_main_method_id = | |
| 118 env->GetStaticMethodID(activity_thread_class.obj(), "systemMain", | |
| 119 "()Landroid/app/ActivityThread;"); | |
| 120 DCHECK(system_main_method_id); | |
| 121 | |
| 122 current_activity = env->CallStaticObjectMethod( | |
| 123 activity_thread_class.obj(), system_main_method_id); | |
| 124 DCHECK(current_activity); | |
| 125 CheckException(env); | |
| 126 } | |
| 127 | |
| 128 // Get the context | |
| 129 jmethodID get_system_context_id = | |
| 130 env->GetMethodID(activity_thread_class.obj(), | |
| 131 "getSystemContext", "()Landroid/app/ContextImpl;"); | |
| 132 | |
| 133 DCHECK(get_system_context_id); | |
| 134 context = env->CallObjectMethod(current_activity, | |
| 135 get_system_context_id); | |
| 136 DCHECK(context); | |
| 137 | |
| 138 env->DeleteLocalRef(current_activity); | |
| 139 } | |
| 140 | |
| 141 // Get the context class | |
| 142 jclass context_class = env->FindClass("android/content/Context"); | |
| 143 DCHECK(context_class); | |
| 144 // Get the system service method | |
| 145 jmethodID get_system_service = | |
| 146 env->GetMethodID(context_class, "getSystemService", | |
| 147 "(Ljava/lang/String;)Ljava/lang/Object;"); | |
| 148 DCHECK(get_system_service); | |
| 149 env->DeleteLocalRef(context_class); | |
| 150 | |
| 151 // Retrieve the system service | |
| 152 jstring service_name = env->NewStringUTF("clipboard"); | |
| 153 jobject cm = env->CallObjectMethod(context, get_system_service, service_name); | |
| 154 if (ClearException(env)) | |
| 155 cm = NULL; | |
| 156 DCHECK(cm); | |
| 157 | |
| 158 // Make a global reference for our clipboard manager. | |
| 159 clipboard_manager_ = env->NewGlobalRef(cm); | |
| 160 DCHECK(clipboard_manager_); | |
| 161 env->DeleteLocalRef(cm); | |
| 162 | |
| 163 // Retain a few methods we'll keep using | |
| 164 jclass clipboard_class = env->FindClass("android/text/ClipboardManager"); | |
| 165 DCHECK(clipboard_class); | |
| 166 set_text_ = env->GetMethodID(clipboard_class, "setText", | |
| 167 "(Ljava/lang/CharSequence;)V"); | |
| 168 DCHECK(set_text_); | |
| 169 has_text_ = env->GetMethodID(clipboard_class, "hasText", "()Z"); | |
| 170 DCHECK(has_text_); | |
| 171 get_text_ = env->GetMethodID(clipboard_class, "getText", | |
| 172 "()Ljava/lang/CharSequence;"); | |
| 173 DCHECK(get_text_); | |
| 174 | |
| 175 // Will need to call toString as CharSequence is not always a String | |
| 176 jclass charsequence_class = env->FindClass("java/lang/CharSequence"); | |
| 177 DCHECK(charsequence_class); | |
| 178 to_string_ = env->GetMethodID(charsequence_class, "toString", | |
| 179 "()Ljava/lang/String;"); | |
| 180 DCHECK(to_string_); | |
| 181 env->DeleteLocalRef(clipboard_class); | |
| 182 env->DeleteLocalRef(charsequence_class); | |
| 183 | |
| 184 // Finally cleanup all our local reference so they don't stay around if this | |
| 185 // code was never called from Java. (unit test case) | |
| 186 env->DeleteLocalRef(service_name); | |
| 187 env->DeleteLocalRef(context); | |
| 188 | |
| 189 // Create the object map if we are the first clipboard | |
| 190 g_clipboard_map_lock.Get().Acquire(); | |
| 191 if (!g_clipboard_map) | |
| 192 g_clipboard_map = new ClipboardMap; | |
| 193 g_clipboard_map_lock.Get().Release(); | |
| 194 } | |
| 195 | |
| 196 Clipboard::~Clipboard() { | |
| 197 // Delete the clipboard manager global ref | |
| 198 if (clipboard_manager_) { | |
| 199 JNIEnv* env = AttachCurrentThread(); | |
| 200 env->DeleteGlobalRef(clipboard_manager_); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 // Main entry point used to write several values in the clipboard. | |
| 205 void Clipboard::WriteObjects(const ObjectMap& objects) { | |
| 206 Clear(); | |
| 207 for (ObjectMap::const_iterator iter = objects.begin(); | |
| 208 iter != objects.end(); ++iter) { | |
| 209 DispatchObject(static_cast<ObjectType>(iter->first), iter->second); | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 uint64 Clipboard::GetSequenceNumber(Clipboard::Buffer /* buffer */) { | |
| 214 // TODO: Implement this. For now this interface will advertise | |
| 215 // that the clipboard never changes. That's fine as long as we | |
| 216 // don't rely on this signal. | |
| 217 return 0; | |
| 218 } | |
| 219 | |
| 220 bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, | |
| 221 Clipboard::Buffer buffer) const { | |
| 222 DCHECK_EQ(buffer, BUFFER_STANDARD); | |
| 223 | |
| 224 if (!format.compare(kPlainTextFormat)) | |
| 225 return IsTextAvailableFromAndroid(); | |
| 226 | |
| 227 ValidateInternalClipboard(); | |
| 228 | |
| 229 base::AutoLock lock(g_clipboard_map_lock.Get()); | |
| 230 return g_clipboard_map->find(format.ToString()) != g_clipboard_map->end(); | |
| 231 } | |
| 232 | |
| 233 void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types, | |
| 234 bool* contains_filenames) const { | |
| 235 if (!types || !contains_filenames) { | |
| 236 NOTREACHED(); | |
| 237 return; | |
| 238 } | |
| 239 | |
| 240 // This is unimplemented on the other platforms (e.g. win, linux). | |
| 241 NOTIMPLEMENTED(); | |
| 242 | |
| 243 types->clear(); | |
| 244 *contains_filenames = false; | |
| 245 } | |
| 246 | |
| 247 void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { | |
| 248 JNIEnv* env = AttachCurrentThread(); | |
| 249 | |
| 250 result->clear(); | |
| 251 if (env->CallBooleanMethod(clipboard_manager_, has_text_)) { | |
| 252 jstring tmp_string = | |
| 253 static_cast<jstring>(env->CallObjectMethod( | |
| 254 env->CallObjectMethod(clipboard_manager_, get_text_), to_string_)); | |
| 255 jboolean is_copy = JNI_FALSE; | |
| 256 const char* tmp_string_val = env->GetStringUTFChars(tmp_string, &is_copy); | |
| 257 jsize len = env->GetStringUTFLength(tmp_string); | |
| 258 UTF8ToUTF16(tmp_string_val, len, result); | |
| 259 env->ReleaseStringUTFChars(tmp_string, tmp_string_val); | |
| 260 env->DeleteLocalRef(static_cast<jobject>(tmp_string)); | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, | |
| 265 std::string* result) const { | |
| 266 JNIEnv* env = AttachCurrentThread(); | |
| 267 | |
| 268 result->clear(); | |
| 269 if (env->CallBooleanMethod(clipboard_manager_, has_text_)) { | |
| 270 jstring tmp_string = | |
| 271 static_cast<jstring>(env->CallObjectMethod( | |
| 272 env->CallObjectMethod(clipboard_manager_, get_text_), to_string_)); | |
| 273 jboolean is_copy = JNI_FALSE; | |
| 274 const char* tmp_string_val = env->GetStringUTFChars(tmp_string, &is_copy); | |
| 275 jsize len = env->GetStringUTFLength(tmp_string); | |
| 276 *result = std::string(tmp_string_val, len); | |
| 277 env->ReleaseStringUTFChars(tmp_string, tmp_string_val); | |
| 278 env->DeleteLocalRef(static_cast<jobject>(tmp_string)); | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 // Note: |src_url| isn't really used. It is only implemented in Windows | |
| 283 void Clipboard::ReadHTML(Clipboard::Buffer buffer, | |
| 284 string16* markup, | |
| 285 std::string* src_url, | |
| 286 uint32* fragment_start, | |
| 287 uint32* fragment_end) const { | |
| 288 markup->clear(); | |
| 289 if (src_url) | |
| 290 src_url->clear(); | |
| 291 *fragment_start = 0; | |
| 292 *fragment_end = 0; | |
| 293 | |
| 294 std::string input; | |
| 295 | |
| 296 ValidateInternalClipboard(); | |
| 297 | |
| 298 g_clipboard_map_lock.Get().Acquire(); | |
| 299 ClipboardMap::const_iterator it = g_clipboard_map->find(kHTMLFormat); | |
| 300 if (it != g_clipboard_map->end()) | |
| 301 input = it->second; | |
| 302 g_clipboard_map_lock.Get().Release(); | |
| 303 | |
| 304 if (input.empty()) | |
| 305 return; | |
| 306 | |
| 307 *fragment_end = static_cast<uint32>(input.length()); | |
| 308 | |
| 309 UTF8ToUTF16(input.c_str(), input.length(), markup); | |
| 310 } | |
| 311 | |
| 312 SkBitmap Clipboard::ReadImage(Buffer buffer) const { | |
| 313 NOTIMPLEMENTED(); | |
| 314 return SkBitmap(); | |
| 315 } | |
| 316 | |
| 317 void Clipboard::ReadCustomData(Buffer buffer, | |
| 318 const string16& type, | |
| 319 string16* result) const { | |
| 320 NOTIMPLEMENTED(); | |
| 321 } | |
| 322 | |
| 323 void Clipboard::ReadBookmark(string16* title, std::string* url) const { | |
| 324 NOTIMPLEMENTED(); | |
| 325 } | |
| 326 | |
| 327 void Clipboard::ReadData(const Clipboard::FormatType& format, | |
| 328 std::string* result) const { | |
| 329 result->clear(); | |
| 330 | |
| 331 ValidateInternalClipboard(); | |
| 332 | |
| 333 g_clipboard_map_lock.Get().Acquire(); | |
| 334 ClipboardMap::const_iterator it = g_clipboard_map->find(format.ToString()); | |
| 335 if (it != g_clipboard_map->end()) | |
| 336 result->assign(it->second); | |
| 337 g_clipboard_map_lock.Get().Release(); | |
| 338 } | |
| 339 | |
| 340 // static | |
| 341 Clipboard::FormatType Clipboard::GetFormatType( | |
| 342 const std::string& format_string) { | |
| 343 return FormatType::Deserialize(format_string); | |
| 344 } | |
| 345 | |
| 346 // static | |
| 347 const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { | |
| 348 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); | |
| 349 return type; | |
| 350 } | |
| 351 | |
| 352 // static | |
| 353 const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { | |
| 354 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPlainTextFormat)); | |
| 355 return type; | |
| 356 } | |
| 357 | |
| 358 // static | |
| 359 const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { | |
| 360 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebKitSmartPasteFormat)); | |
| 361 return type; | |
| 362 } | |
| 363 | |
| 364 // static | |
| 365 const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { | |
| 366 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kHTMLFormat)); | |
| 367 return type; | |
| 368 } | |
| 369 | |
| 370 // static | |
| 371 const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { | |
| 372 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kBitmapFormat)); | |
| 373 return type; | |
| 374 } | |
| 375 | |
| 376 // static | |
| 377 const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { | |
| 378 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); | |
| 379 return type; | |
| 380 } | |
| 381 | |
| 382 void Clipboard::WriteText(const char* text_data, size_t text_len) { | |
| 383 // Write the text in the Android Clipboard | |
| 384 JNIEnv* env = AttachCurrentThread(); | |
| 385 DCHECK(env); | |
| 386 | |
| 387 std::string data(text_data, text_len); | |
| 388 jstring str = env->NewStringUTF(data.c_str()); | |
| 389 DCHECK(str); | |
| 390 | |
| 391 env->CallVoidMethod(clipboard_manager_, set_text_, str); | |
| 392 env->DeleteLocalRef(str); | |
| 393 | |
| 394 // Then write it in our internal data structure. We keep it there to check if | |
| 395 // another app performed a copy. See ValidateInternalClipboard() | |
| 396 Set(kPlainTextFormat, std::string(text_data, text_len)); | |
| 397 } | |
| 398 | |
| 399 void Clipboard::WriteHTML(const char* markup_data, | |
| 400 size_t markup_len, | |
| 401 const char* url_data, | |
| 402 size_t url_len) { | |
| 403 Set(kHTMLFormat, std::string(markup_data, markup_len)); | |
| 404 } | |
| 405 | |
| 406 // Note: according to other platforms implementations, this really writes the | |
| 407 // URL spec | |
| 408 void Clipboard::WriteBookmark(const char* title_data, size_t title_len, | |
| 409 const char* url_data, size_t url_len) { | |
| 410 Set(kBookmarkFormat, std::string(url_data, url_len)); | |
| 411 } | |
| 412 | |
| 413 // Write an extra flavor that signifies WebKit was the last to modify the | |
| 414 // pasteboard. This flavor has no data. | |
| 415 void Clipboard::WriteWebSmartPaste() { | |
| 416 Set(kWebKitSmartPasteFormat, std::string()); | |
| 417 } | |
| 418 | |
| 419 // All platforms use gfx::Size for size data but it is passed as a const char* | |
| 420 // Further, pixel_data is expected to be 32 bits per pixel | |
| 421 // Note: we implement this to pass all unit tests but it is currently unclear | |
| 422 // how some code would consume this. | |
| 423 void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { | |
| 424 const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); | |
| 425 int bm_size = size->width() * size->height() * 4; | |
| 426 int total_size = (sizeof(int) * 2) + bm_size; | |
| 427 scoped_array<char> buffer(new char[total_size]); | |
| 428 | |
| 429 char* p = buffer.get(); | |
| 430 int n = size->width(); | |
| 431 memcpy(p, (unsigned char*)&n, sizeof(int)); | |
|
dcheng
2012/02/01 19:02:04
Are these casts necessary? It may be (slightly) sa
Peter Beverloo
2012/02/02 12:16:11
Done.
| |
| 432 p += sizeof(int); | |
| 433 n = size->height(); | |
| 434 memcpy(p, (unsigned char*)&n, sizeof(int)); | |
| 435 p += sizeof(int); | |
| 436 memcpy(p, pixel_data, bm_size); | |
| 437 | |
| 438 Set(kBitmapFormat, std::string(buffer.get(), total_size)); | |
| 439 } | |
| 440 | |
| 441 void Clipboard::WriteData(const Clipboard::FormatType& format, | |
| 442 const char* data_data, size_t data_len) { | |
| 443 Set(format.ToString(), std::string(data_data, data_len)); | |
| 444 } | |
| 445 | |
| 446 bool Clipboard::IsTextAvailableFromAndroid() const { | |
| 447 JNIEnv* env = AttachCurrentThread(); | |
| 448 return env->CallBooleanMethod(clipboard_manager_, has_text_); | |
| 449 } | |
| 450 | |
| 451 void Clipboard::ValidateInternalClipboard() const { | |
| 452 JNIEnv* env = AttachCurrentThread(); | |
| 453 | |
| 454 // First collect what text we currently have in our internal clipboard | |
| 455 bool has_internal_text; | |
| 456 std::string internal_text; | |
| 457 g_clipboard_map_lock.Get().Acquire(); | |
| 458 ClipboardMap::const_iterator it = g_clipboard_map->find(kPlainTextFormat); | |
| 459 if (it != g_clipboard_map->end()) { | |
| 460 has_internal_text = true; | |
| 461 internal_text = it->second; | |
| 462 } else { | |
| 463 has_internal_text = false; | |
| 464 } | |
| 465 g_clipboard_map_lock.Get().Release(); | |
| 466 | |
| 467 if (IsTextAvailableFromAndroid()) { | |
| 468 // Make sure the text in the Android Clipboard matches what we think it | |
| 469 // should be. | |
| 470 jstring tmp_string = | |
| 471 static_cast<jstring>(env->CallObjectMethod( | |
| 472 env->CallObjectMethod(clipboard_manager_, get_text_), to_string_)); | |
| 473 jboolean is_copy = JNI_FALSE; | |
| 474 const char* tmp_string_val = env->GetStringUTFChars(tmp_string, &is_copy); | |
| 475 jsize len = env->GetStringUTFLength(tmp_string); | |
| 476 | |
| 477 std::string android_text(tmp_string_val, len); | |
| 478 | |
| 479 // If the android text doesn't match what we think it should be, our | |
| 480 // internal representation is no longer valid. | |
| 481 if (android_text.compare(internal_text)) | |
| 482 ClearInternalClipboard(); | |
| 483 | |
| 484 env->ReleaseStringUTFChars(tmp_string, tmp_string_val); | |
| 485 env->DeleteLocalRef(static_cast<jobject>(tmp_string)); | |
| 486 } else { | |
| 487 // If Android has no text but we have some internal text, our internal | |
| 488 // representation is no longer valid. | |
| 489 if (has_internal_text) | |
| 490 ClearInternalClipboard(); | |
| 491 } | |
| 492 } | |
| 493 | |
| 494 void Clipboard::Clear() { | |
| 495 JNIEnv* env = AttachCurrentThread(); | |
| 496 env->CallVoidMethod(clipboard_manager_, set_text_, NULL); | |
| 497 ClearInternalClipboard(); | |
| 498 } | |
| 499 | |
| 500 void Clipboard::ClearInternalClipboard() const { | |
| 501 base::AutoLock lock(g_clipboard_map_lock.Get()); | |
| 502 g_clipboard_map->clear(); | |
| 503 } | |
| 504 | |
| 505 void Clipboard::Set(const std::string& key, const std::string& value) { | |
| 506 base::AutoLock lock(g_clipboard_map_lock.Get()); | |
| 507 (*g_clipboard_map)[key] = value; | |
| 508 } | |
| 509 | |
| 510 } // namespace ui | |
| OLD | NEW |