clipboard_history_controller_impl.cc 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  1. // Copyright 2020 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. #include "ash/clipboard/clipboard_history_controller_impl.h"
  5. #include <map>
  6. #include <memory>
  7. #include <set>
  8. #include <vector>
  9. #include "ash/accelerators/accelerator_controller_impl.h"
  10. #include "ash/clipboard/clipboard_history_menu_model_adapter.h"
  11. #include "ash/clipboard/clipboard_history_resource_manager.h"
  12. #include "ash/clipboard/clipboard_history_util.h"
  13. #include "ash/clipboard/clipboard_nudge_constants.h"
  14. #include "ash/clipboard/clipboard_nudge_controller.h"
  15. #include "ash/clipboard/scoped_clipboard_history_pause_impl.h"
  16. #include "ash/constants/ash_features.h"
  17. #include "ash/display/display_util.h"
  18. #include "ash/public/cpp/clipboard_image_model_factory.h"
  19. #include "ash/public/cpp/style/scoped_light_mode_as_default.h"
  20. #include "ash/public/cpp/window_tree_host_lookup.h"
  21. #include "ash/shell.h"
  22. #include "ash/wm/window_util.h"
  23. #include "base/barrier_closure.h"
  24. #include "base/bind.h"
  25. #include "base/callback_forward.h"
  26. #include "base/check_op.h"
  27. #include "base/metrics/histogram_functions.h"
  28. #include "base/metrics/histogram_macros.h"
  29. #include "base/metrics/user_metrics.h"
  30. #include "base/notreached.h"
  31. #include "base/one_shot_event.h"
  32. #include "base/strings/utf_string_conversions.h"
  33. #include "base/synchronization/lock.h"
  34. #include "base/task/bind_post_task.h"
  35. #include "base/task/thread_pool.h"
  36. #include "base/threading/sequenced_task_runner_handle.h"
  37. #include "base/threading/thread_task_runner_handle.h"
  38. #include "base/unguessable_token.h"
  39. #include "base/values.h"
  40. #include "third_party/abseil-cpp/absl/types/optional.h"
  41. #include "ui/aura/window_tree_host.h"
  42. #include "ui/base/accelerators/accelerator.h"
  43. #include "ui/base/clipboard/clipboard_data.h"
  44. #include "ui/base/clipboard/clipboard_non_backed.h"
  45. #include "ui/base/clipboard/scoped_clipboard_writer.h"
  46. #include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
  47. #include "ui/base/ime/input_method.h"
  48. #include "ui/base/ime/text_input_client.h"
  49. #include "ui/base/models/image_model.h"
  50. #include "ui/base/models/simple_menu_model.h"
  51. #include "ui/base/webui/web_ui_util.h"
  52. #include "ui/events/event.h"
  53. #include "ui/events/event_constants.h"
  54. #include "ui/events/keycodes/keyboard_codes_posix.h"
  55. #include "ui/events/types/event_type.h"
  56. #include "ui/gfx/geometry/rect.h"
  57. #include "ui/views/controls/menu/menu_controller.h"
  58. namespace ash {
  59. namespace {
  60. constexpr char kImageDataKey[] = "imageData";
  61. constexpr char kTextDataKey[] = "textData";
  62. constexpr char kFormatDataKey[] = "displayFormat";
  63. constexpr char kPngFormat[] = "png";
  64. constexpr char kHtmlFormat[] = "html";
  65. constexpr char kTextFormat[] = "text";
  66. constexpr char kFileFormat[] = "file";
  67. ui::ClipboardNonBacked* GetClipboard() {
  68. auto* clipboard = ui::ClipboardNonBacked::GetForCurrentThread();
  69. DCHECK(clipboard);
  70. return clipboard;
  71. }
  72. // Encodes `bitmap` and maps the corresponding ClipboardHistoryItem ID, `id, to
  73. // the resulting PNG in `encoded_pngs`. This function should run on a background
  74. // thread.
  75. void EncodeBitmapToPNG(
  76. base::OnceClosure barrier_callback,
  77. std::map<base::UnguessableToken, std::vector<uint8_t>>* const encoded_pngs,
  78. base::UnguessableToken id,
  79. SkBitmap bitmap) {
  80. auto png = ui::ClipboardData::EncodeBitmapData(bitmap);
  81. // Don't acquire the lock until after the image encoding has finished.
  82. static base::NoDestructor<base::Lock> map_lock;
  83. base::AutoLock lock(*map_lock);
  84. encoded_pngs->emplace(id, std::move(png));
  85. std::move(barrier_callback).Run();
  86. }
  87. // Emits a user action indicating that the clipboard history item at menu index
  88. // `command_id` was pasted.
  89. void RecordMenuIndexPastedUserAction(int command_id) {
  90. // Per guidance in user_metrics.h, use string literals for action names.
  91. switch (command_id) {
  92. case 1:
  93. base::RecordAction(
  94. base::UserMetricsAction("Ash_ClipboardHistory_PastedItem1"));
  95. break;
  96. case 2:
  97. base::RecordAction(
  98. base::UserMetricsAction("Ash_ClipboardHistory_PastedItem2"));
  99. break;
  100. case 3:
  101. base::RecordAction(
  102. base::UserMetricsAction("Ash_ClipboardHistory_PastedItem3"));
  103. break;
  104. case 4:
  105. base::RecordAction(
  106. base::UserMetricsAction("Ash_ClipboardHistory_PastedItem4"));
  107. break;
  108. case 5:
  109. base::RecordAction(
  110. base::UserMetricsAction("Ash_ClipboardHistory_PastedItem5"));
  111. break;
  112. default:
  113. NOTREACHED();
  114. }
  115. }
  116. using ClipboardHistoryPasteType =
  117. ash::ClipboardHistoryControllerImpl::ClipboardHistoryPasteType;
  118. bool IsPlainTextPaste(ClipboardHistoryPasteType paste_type) {
  119. switch (paste_type) {
  120. case ClipboardHistoryPasteType::kPlainTextAccelerator:
  121. case ClipboardHistoryPasteType::kPlainTextKeystroke:
  122. case ClipboardHistoryPasteType::kPlainTextMouse:
  123. case ClipboardHistoryPasteType::kPlainTextTouch:
  124. case ClipboardHistoryPasteType::kPlainTextVirtualKeyboard:
  125. return true;
  126. case ClipboardHistoryPasteType::kRichTextAccelerator:
  127. case ClipboardHistoryPasteType::kRichTextKeystroke:
  128. case ClipboardHistoryPasteType::kRichTextMouse:
  129. case ClipboardHistoryPasteType::kRichTextTouch:
  130. case ClipboardHistoryPasteType::kRichTextVirtualKeyboard:
  131. return false;
  132. }
  133. }
  134. } // namespace
  135. // ClipboardHistoryControllerImpl::AcceleratorTarget ---------------------------
  136. class ClipboardHistoryControllerImpl::AcceleratorTarget
  137. : public ui::AcceleratorTarget {
  138. public:
  139. explicit AcceleratorTarget(ClipboardHistoryControllerImpl* controller)
  140. : controller_(controller),
  141. delete_selected_(ui::Accelerator(
  142. /*key_code=*/ui::VKEY_BACK,
  143. /*modifiers=*/ui::EF_NONE,
  144. /*key_state=*/ui::Accelerator::KeyState::PRESSED)),
  145. tab_navigation_(ui::Accelerator(
  146. /*key_code=*/ui::VKEY_TAB,
  147. /*modifiers=*/ui::EF_NONE,
  148. /*key_state=*/ui::Accelerator::KeyState::PRESSED)),
  149. shift_tab_navigation_(ui::Accelerator(
  150. /*key_code=*/ui::VKEY_TAB,
  151. /*modifiers=*/ui::EF_SHIFT_DOWN,
  152. /*key_state=*/ui::Accelerator::KeyState::PRESSED)) {}
  153. AcceleratorTarget(const AcceleratorTarget&) = delete;
  154. AcceleratorTarget& operator=(const AcceleratorTarget&) = delete;
  155. ~AcceleratorTarget() override = default;
  156. void OnMenuShown() {
  157. Shell::Get()->accelerator_controller()->Register(
  158. {delete_selected_, tab_navigation_, shift_tab_navigation_},
  159. /*accelerator_target=*/this);
  160. }
  161. void OnMenuClosed() {
  162. Shell::Get()->accelerator_controller()->Unregister(
  163. delete_selected_, /*accelerator_target=*/this);
  164. Shell::Get()->accelerator_controller()->Unregister(
  165. tab_navigation_, /*accelerator_target=*/this);
  166. Shell::Get()->accelerator_controller()->Unregister(
  167. shift_tab_navigation_, /*accelerator_target=*/this);
  168. }
  169. private:
  170. // ui::AcceleratorTarget:
  171. bool AcceleratorPressed(const ui::Accelerator& accelerator) override {
  172. if (accelerator == delete_selected_) {
  173. HandleDeleteSelected(accelerator.modifiers());
  174. } else if (accelerator == tab_navigation_) {
  175. HandleTab();
  176. } else if (accelerator == shift_tab_navigation_) {
  177. HandleShiftTab();
  178. } else {
  179. NOTREACHED();
  180. return false;
  181. }
  182. return true;
  183. }
  184. bool CanHandleAccelerators() const override {
  185. return controller_->IsMenuShowing() || controller_->CanShowMenu();
  186. }
  187. void HandleDeleteSelected(int event_flags) {
  188. DCHECK(controller_->IsMenuShowing());
  189. controller_->DeleteSelectedMenuItemIfAny();
  190. }
  191. void HandleTab() {
  192. DCHECK(controller_->IsMenuShowing());
  193. controller_->AdvancePseudoFocus(/*reverse=*/false);
  194. }
  195. void HandleShiftTab() {
  196. DCHECK(controller_->IsMenuShowing());
  197. controller_->AdvancePseudoFocus(/*reverse=*/true);
  198. }
  199. // The controller responsible for showing the Clipboard History menu.
  200. ClipboardHistoryControllerImpl* const controller_;
  201. // The accelerator to delete the selected menu item. It is only registered
  202. // while the menu is showing.
  203. const ui::Accelerator delete_selected_;
  204. // Move the pseudo focus forward.
  205. const ui::Accelerator tab_navigation_;
  206. // Moves the pseudo focus backward.
  207. const ui::Accelerator shift_tab_navigation_;
  208. };
  209. // ClipboardHistoryControllerImpl::MenuDelegate --------------------------------
  210. class ClipboardHistoryControllerImpl::MenuDelegate
  211. : public ui::SimpleMenuModel::Delegate {
  212. public:
  213. explicit MenuDelegate(ClipboardHistoryControllerImpl* controller)
  214. : controller_(controller) {}
  215. MenuDelegate(const MenuDelegate&) = delete;
  216. MenuDelegate& operator=(const MenuDelegate&) = delete;
  217. // ui::SimpleMenuModel::Delegate:
  218. void ExecuteCommand(int command_id, int event_flags) override {
  219. controller_->ExecuteCommand(command_id, event_flags);
  220. }
  221. private:
  222. // The controller responsible for showing the Clipboard History menu.
  223. ClipboardHistoryControllerImpl* const controller_;
  224. };
  225. // ClipboardHistoryControllerImpl ----------------------------------------------
  226. ClipboardHistoryControllerImpl::ClipboardHistoryControllerImpl()
  227. : clipboard_history_(std::make_unique<ClipboardHistory>()),
  228. resource_manager_(std::make_unique<ClipboardHistoryResourceManager>(
  229. clipboard_history_.get())),
  230. accelerator_target_(std::make_unique<AcceleratorTarget>(this)),
  231. menu_delegate_(std::make_unique<MenuDelegate>(this)),
  232. nudge_controller_(
  233. std::make_unique<ClipboardNudgeController>(clipboard_history_.get(),
  234. this)) {
  235. clipboard_history_->AddObserver(this);
  236. resource_manager_->AddObserver(this);
  237. }
  238. ClipboardHistoryControllerImpl::~ClipboardHistoryControllerImpl() {
  239. resource_manager_->RemoveObserver(this);
  240. clipboard_history_->RemoveObserver(this);
  241. }
  242. void ClipboardHistoryControllerImpl::Shutdown() {
  243. nudge_controller_.reset();
  244. }
  245. bool ClipboardHistoryControllerImpl::IsMenuShowing() const {
  246. return context_menu_ && context_menu_->IsRunning();
  247. }
  248. void ClipboardHistoryControllerImpl::ToggleMenuShownByAccelerator() {
  249. if (IsMenuShowing()) {
  250. // Before hiding the menu, paste the selected menu item, or the first item
  251. // if none is selected.
  252. PasteMenuItemData(context_menu_->GetSelectedMenuItemCommand().value_or(
  253. ClipboardHistoryUtil::kFirstItemCommandId),
  254. ClipboardHistoryPasteType::kRichTextAccelerator);
  255. return;
  256. }
  257. if (ClipboardHistoryUtil::IsEnabledInCurrentMode() && IsEmpty()) {
  258. nudge_controller_->ShowNudge(ClipboardNudgeType::kZeroStateNudge);
  259. return;
  260. }
  261. ShowMenu(CalculateAnchorRect(), ui::MENU_SOURCE_KEYBOARD,
  262. crosapi::mojom::ClipboardHistoryControllerShowSource::kAccelerator);
  263. }
  264. void ClipboardHistoryControllerImpl::AddObserver(
  265. ClipboardHistoryController::Observer* observer) {
  266. observers_.AddObserver(observer);
  267. }
  268. void ClipboardHistoryControllerImpl::RemoveObserver(
  269. ClipboardHistoryController::Observer* observer) {
  270. observers_.RemoveObserver(observer);
  271. }
  272. void ClipboardHistoryControllerImpl::ShowMenu(
  273. const gfx::Rect& anchor_rect,
  274. ui::MenuSourceType source_type,
  275. crosapi::mojom::ClipboardHistoryControllerShowSource show_source) {
  276. if (IsMenuShowing() || !CanShowMenu())
  277. return;
  278. // Close the running context menu if any before showing the clipboard history
  279. // menu. Because the clipboard history menu should not be nested.
  280. auto* active_menu_instance = views::MenuController::GetActiveInstance();
  281. if (active_menu_instance)
  282. active_menu_instance->Cancel(views::MenuController::ExitType::kAll);
  283. context_menu_ = ClipboardHistoryMenuModelAdapter::Create(
  284. menu_delegate_.get(),
  285. base::BindRepeating(&ClipboardHistoryControllerImpl::OnMenuClosed,
  286. base::Unretained(this)),
  287. clipboard_history_.get(), resource_manager_.get());
  288. context_menu_->Run(anchor_rect, source_type);
  289. DCHECK(IsMenuShowing());
  290. accelerator_target_->OnMenuShown();
  291. base::UmaHistogramEnumeration("Ash.ClipboardHistory.ContextMenu.ShowMenu",
  292. show_source);
  293. // The first menu item should be selected as default after the clipboard
  294. // history menu shows. Note that the menu item is selected asynchronously
  295. // to avoid the interference from synthesized mouse events.
  296. menu_task_timer_.Start(
  297. FROM_HERE, base::TimeDelta(),
  298. base::BindOnce(
  299. [](const base::WeakPtr<ClipboardHistoryControllerImpl>&
  300. controller_weak_ptr) {
  301. if (!controller_weak_ptr)
  302. return;
  303. controller_weak_ptr->context_menu_->SelectMenuItemWithCommandId(
  304. ClipboardHistoryUtil::kFirstItemCommandId);
  305. if (controller_weak_ptr->initial_item_selected_callback_for_test_) {
  306. controller_weak_ptr->initial_item_selected_callback_for_test_
  307. .Run();
  308. }
  309. },
  310. weak_ptr_factory_.GetWeakPtr()));
  311. for (auto& observer : observers_)
  312. observer.OnClipboardHistoryMenuShown(show_source);
  313. }
  314. gfx::Rect ClipboardHistoryControllerImpl::GetMenuBoundsInScreenForTest() const {
  315. return context_menu_->GetMenuBoundsInScreenForTest();
  316. }
  317. void ClipboardHistoryControllerImpl::GetHistoryValuesForTest(
  318. GetHistoryValuesCallback callback) const {
  319. GetHistoryValues(/*item_id_filter=*/std::set<std::string>(),
  320. std::move(callback));
  321. }
  322. void ClipboardHistoryControllerImpl::BlockGetHistoryValuesForTest() {
  323. get_history_values_blocker_for_test_.reset();
  324. get_history_values_blocker_for_test_ = std::make_unique<base::OneShotEvent>();
  325. }
  326. void ClipboardHistoryControllerImpl::ResumeGetHistoryValuesForTest() {
  327. DCHECK(get_history_values_blocker_for_test_);
  328. get_history_values_blocker_for_test_->Signal();
  329. }
  330. bool ClipboardHistoryControllerImpl::ShouldShowNewFeatureBadge() const {
  331. return chromeos::features::IsClipboardHistoryContextMenuNudgeEnabled() &&
  332. nudge_controller_->ShouldShowNewFeatureBadge();
  333. }
  334. void ClipboardHistoryControllerImpl::MarkNewFeatureBadgeShown() {
  335. nudge_controller_->MarkNewFeatureBadgeShown();
  336. }
  337. void ClipboardHistoryControllerImpl::OnScreenshotNotificationCreated() {
  338. nudge_controller_->MarkScreenshotNotificationShown();
  339. }
  340. bool ClipboardHistoryControllerImpl::CanShowMenu() const {
  341. return !IsEmpty() && ClipboardHistoryUtil::IsEnabledInCurrentMode();
  342. }
  343. bool ClipboardHistoryControllerImpl::IsEmpty() const {
  344. return clipboard_history_->IsEmpty();
  345. }
  346. std::unique_ptr<ScopedClipboardHistoryPause>
  347. ClipboardHistoryControllerImpl::CreateScopedPause() {
  348. return std::make_unique<ScopedClipboardHistoryPauseImpl>(
  349. clipboard_history_.get());
  350. }
  351. void ClipboardHistoryControllerImpl::GetHistoryValues(
  352. const std::set<std::string>& item_id_filter,
  353. GetHistoryValuesCallback callback) const {
  354. // Map of ClipboardHistoryItem IDs to their corresponding bitmaps.
  355. std::map<base::UnguessableToken, SkBitmap> bitmaps_to_be_encoded;
  356. // Get the clipboard data for each clipboard history item.
  357. for (auto& item : clipboard_history_->GetItems()) {
  358. // If the |item_id_filter| contains values, then only return the clipboard
  359. // items included in it.
  360. if (!item_id_filter.empty() &&
  361. item_id_filter.find(item.id().ToString()) == item_id_filter.end()) {
  362. continue;
  363. }
  364. if (ash::ClipboardHistoryUtil::CalculateDisplayFormat(item.data()) ==
  365. ash::ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kPng) {
  366. const auto& maybe_png = item.data().maybe_png();
  367. if (!maybe_png.has_value()) {
  368. // The clipboard contains an image which has not yet been encoded to a
  369. // PNG.
  370. auto maybe_bitmap = item.data().GetBitmapIfPngNotEncoded();
  371. DCHECK(maybe_bitmap.has_value());
  372. bitmaps_to_be_encoded.emplace(item.id(),
  373. std::move(maybe_bitmap.value()));
  374. }
  375. }
  376. }
  377. // Map of ClipboardHistoryItem ID to its encoded PNG. Since encoding images
  378. // may happen on separate threads, a lock is used to ensure thread-safe
  379. // insertion into `encoded_pngs`.
  380. auto encoded_pngs = std::make_unique<
  381. std::map<base::UnguessableToken, std::vector<uint8_t>>>();
  382. auto* encoded_pngs_ptr = encoded_pngs.get();
  383. // Post back to this sequence once all images have been encoded.
  384. base::RepeatingClosure barrier = base::BarrierClosure(
  385. bitmaps_to_be_encoded.size(),
  386. base::BindPostTask(
  387. base::SequencedTaskRunnerHandle::Get(),
  388. base::BindOnce(
  389. &ClipboardHistoryControllerImpl::GetHistoryValuesWithEncodedPNGs,
  390. weak_ptr_factory_.GetWeakPtr(), item_id_filter,
  391. std::move(callback), std::move(encoded_pngs))));
  392. // Encode images on background threads.
  393. for (auto id_and_bitmap : bitmaps_to_be_encoded) {
  394. base::ThreadPool::PostTask(
  395. FROM_HERE, base::BindOnce(&EncodeBitmapToPNG, barrier, encoded_pngs_ptr,
  396. std::move(id_and_bitmap.first),
  397. std::move(id_and_bitmap.second)));
  398. }
  399. if (!new_bitmap_to_write_while_encoding_for_test_.isNull()) {
  400. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  401. scw.WriteImage(new_bitmap_to_write_while_encoding_for_test_);
  402. new_bitmap_to_write_while_encoding_for_test_.reset();
  403. }
  404. }
  405. void ClipboardHistoryControllerImpl::GetHistoryValuesWithEncodedPNGs(
  406. const std::set<std::string>& item_id_filter,
  407. GetHistoryValuesCallback callback,
  408. std::unique_ptr<std::map<base::UnguessableToken, std::vector<uint8_t>>>
  409. encoded_pngs) {
  410. // If a test is performing some work that must be done before history values
  411. // are returned, wait to run this function until that work is finished.
  412. if (get_history_values_blocker_for_test_ &&
  413. !get_history_values_blocker_for_test_->is_signaled()) {
  414. get_history_values_blocker_for_test_->Post(
  415. FROM_HERE,
  416. base::BindOnce(
  417. &ClipboardHistoryControllerImpl::GetHistoryValuesWithEncodedPNGs,
  418. weak_ptr_factory_.GetWeakPtr(), item_id_filter, std::move(callback),
  419. std::move(encoded_pngs)));
  420. return;
  421. }
  422. base::Value item_results(base::Value::Type::LIST);
  423. DCHECK(encoded_pngs);
  424. // Check after asynchronous PNG encoding finishes to make sure we have not
  425. // entered a state where clipboard history is disabled, e.g., a locked screen.
  426. if (!ClipboardHistoryUtil::IsEnabledInCurrentMode()) {
  427. std::move(callback).Run(std::move(item_results));
  428. return;
  429. }
  430. bool all_images_encoded = true;
  431. // Get the clipboard data for each clipboard history item.
  432. for (auto& item : clipboard_history_->GetItems()) {
  433. // If the |item_id_filter| contains values, then only return the clipboard
  434. // items included in it.
  435. if (!item_id_filter.empty() &&
  436. item_id_filter.find(item.id().ToString()) == item_id_filter.end()) {
  437. continue;
  438. }
  439. base::Value item_value(base::Value::Type::DICTIONARY);
  440. switch (ash::ClipboardHistoryUtil::CalculateDisplayFormat(item.data())) {
  441. case ash::ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kPng: {
  442. if (!item.data().maybe_png().has_value()) {
  443. // The clipboard contains an image which has not yet been encoded to a
  444. // PNG. Hopefully we just finished encoding and the PNG can be found
  445. // in `encoded_pngs`, otherwise this item was added while other PNGs
  446. // were being encoded.
  447. auto png_it = encoded_pngs->find(item.id());
  448. if (png_it == encoded_pngs->end()) {
  449. // Can't find the encoded PNG. We'll need to restart
  450. // GetHistoryValues from the top, but allow this for loop to finish
  451. // to let PNGs we've already encoded get set to their appropriate
  452. // clipboards, to avoid re-encoding.
  453. all_images_encoded = false;
  454. } else {
  455. item.data().SetPngDataAfterEncoding(std::move(png_it->second));
  456. }
  457. }
  458. const auto& maybe_png = item.data().maybe_png();
  459. if (maybe_png.has_value()) {
  460. item_value.SetKey(kImageDataKey, base::Value(webui::GetPngDataUrl(
  461. maybe_png.value().data(),
  462. maybe_png.value().size())));
  463. item_value.SetKey(kFormatDataKey, base::Value(kPngFormat));
  464. }
  465. break;
  466. }
  467. case ash::ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kHtml: {
  468. const SkBitmap& bitmap =
  469. *(resource_manager_->GetImageModel(item).GetImage().ToSkBitmap());
  470. item_value.SetKey(kImageDataKey,
  471. base::Value(webui::GetBitmapDataUrl(bitmap)));
  472. item_value.SetKey(kFormatDataKey, base::Value(kHtmlFormat));
  473. break;
  474. }
  475. case ash::ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kText:
  476. item_value.SetKey(kTextDataKey, base::Value(item.data().text()));
  477. item_value.SetKey(kFormatDataKey, base::Value(kTextFormat));
  478. break;
  479. case ash::ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kFile: {
  480. std::string file_name =
  481. base::UTF16ToUTF8(resource_manager_->GetLabel(item));
  482. item_value.SetKey(kTextDataKey, base::Value(file_name));
  483. ScopedLightModeAsDefault scoped_light_mode_as_default;
  484. std::string data_url = webui::GetBitmapDataUrl(
  485. *ClipboardHistoryUtil::GetIconForFileClipboardItem(item, file_name)
  486. .bitmap());
  487. item_value.SetKey(kImageDataKey, base::Value(data_url));
  488. item_value.SetKey(kFormatDataKey, base::Value(kFileFormat));
  489. break;
  490. }
  491. }
  492. item_value.SetKey("id", base::Value(item.id().ToString()));
  493. item_value.SetKey("timeCopied",
  494. base::Value(item.time_copied().ToJsTimeIgnoringNull()));
  495. item_results.Append(std::move(item_value));
  496. }
  497. if (!all_images_encoded) {
  498. GetHistoryValues(item_id_filter, std::move(callback));
  499. return;
  500. }
  501. std::move(callback).Run(std::move(item_results));
  502. }
  503. std::vector<std::string> ClipboardHistoryControllerImpl::GetHistoryItemIds()
  504. const {
  505. std::vector<std::string> item_ids;
  506. for (const auto& item : history()->GetItems()) {
  507. item_ids.push_back(item.id().ToString());
  508. }
  509. return item_ids;
  510. }
  511. bool ClipboardHistoryControllerImpl::PasteClipboardItemById(
  512. const std::string& item_id) {
  513. if (currently_pasting_)
  514. return false;
  515. auto* active_window = window_util::GetActiveWindow();
  516. if (!active_window)
  517. return false;
  518. for (const auto& item : history()->GetItems()) {
  519. if (item.id().ToString() == item_id) {
  520. base::SequencedTaskRunnerHandle::Get()->PostTask(
  521. FROM_HERE,
  522. base::BindOnce(
  523. &ClipboardHistoryControllerImpl::PasteClipboardHistoryItem,
  524. weak_ptr_factory_.GetWeakPtr(), active_window, item,
  525. ClipboardHistoryPasteType::kRichTextVirtualKeyboard));
  526. return true;
  527. }
  528. }
  529. return false;
  530. }
  531. bool ClipboardHistoryControllerImpl::DeleteClipboardItemById(
  532. const std::string& item_id) {
  533. for (const auto& item : history()->GetItems()) {
  534. if (item.id().ToString() == item_id) {
  535. DeleteClipboardHistoryItem(item);
  536. return true;
  537. }
  538. }
  539. return false;
  540. }
  541. void ClipboardHistoryControllerImpl::OnClipboardHistoryItemAdded(
  542. const ClipboardHistoryItem& item,
  543. bool is_duplicate) {
  544. for (auto& observer : observers_)
  545. observer.OnClipboardHistoryItemListAddedOrRemoved();
  546. }
  547. void ClipboardHistoryControllerImpl::OnClipboardHistoryItemRemoved(
  548. const ClipboardHistoryItem& item) {
  549. for (auto& observer : observers_)
  550. observer.OnClipboardHistoryItemListAddedOrRemoved();
  551. }
  552. void ClipboardHistoryControllerImpl::OnClipboardHistoryCleared() {
  553. // Prevent clipboard contents getting restored if the Clipboard is cleared
  554. // soon after a `PasteMenuItemData()`.
  555. weak_ptr_factory_.InvalidateWeakPtrs();
  556. if (!IsMenuShowing())
  557. return;
  558. context_menu_->Cancel();
  559. }
  560. void ClipboardHistoryControllerImpl::OnOperationConfirmed(bool copy) {
  561. static int confirmed_paste_count = 0;
  562. // Here we assume that a paste operation from the clipboard history menu never
  563. // interleaves with a user-initiated copy or paste operation from another
  564. // source, such as pressing the ctrl-v accelerator or clicking a context menu
  565. // option. In other words, when `pastes_to_be_confirmed_` is positive, the
  566. // next confirmed operation is expected to be a paste from clipboard history.
  567. // This assumption should hold in most cases given that the clipboard history
  568. // menu is always closed after one paste, and it usually takes a relatively
  569. // long time for a user to perform the next copy or paste. For this metric, we
  570. // tolerate a small margin of error.
  571. if (pastes_to_be_confirmed_ > 0 && !copy) {
  572. ++confirmed_paste_count;
  573. --pastes_to_be_confirmed_;
  574. } else {
  575. // Note that both copies and pastes from the standard clipboard cause the
  576. // clipboard history consecutive paste count to be emitted and reset.
  577. if (confirmed_paste_count > 0) {
  578. base::UmaHistogramCounts100("Ash.ClipboardHistory.ConsecutivePastes",
  579. confirmed_paste_count);
  580. confirmed_paste_count = 0;
  581. }
  582. if (copy) {
  583. // Record copy actions once they are confirmed, rather than when clipboard
  584. // data first changes, to allow multiple data changes to be debounced into
  585. // a single copy operation. This ensures that each user-initiated copy is
  586. // recorded only once. See `ClipboardHistory::OnDataChanged()` for further
  587. // explanation.
  588. base::RecordAction(base::UserMetricsAction("Ash_Clipboard_CopiedItem"));
  589. } else {
  590. // Pastes from clipboard history are already recorded in
  591. // `PasteMenuItemData()`. Here, we record just pastes from the standard
  592. // clipboard, to see how standard clipboard pastes interleave with
  593. // clipboard history pastes.
  594. base::RecordAction(base::UserMetricsAction("Ash_Clipboard_PastedItem"));
  595. }
  596. // Verify that this operation did not interleave with a clipboard history
  597. // paste.
  598. DCHECK_EQ(pastes_to_be_confirmed_, 0);
  599. // Whether or not the non-interleaving assumption has held, always reset
  600. // `pastes_to_be_confirmed_` to prevent standard clipboard pastes from
  601. // possibly being counted as clipboard history pastes, which could
  602. // significantly affect the clipboard history consecutive pastes metric.
  603. pastes_to_be_confirmed_ = 0;
  604. }
  605. if (confirmed_operation_callback_for_test_)
  606. confirmed_operation_callback_for_test_.Run(/*success=*/true);
  607. }
  608. void ClipboardHistoryControllerImpl::OnCachedImageModelUpdated(
  609. const std::vector<base::UnguessableToken>& menu_item_ids) {
  610. for (auto& observer : observers_)
  611. observer.OnClipboardHistoryItemsUpdated(menu_item_ids);
  612. }
  613. void ClipboardHistoryControllerImpl::ExecuteCommand(int command_id,
  614. int event_flags) {
  615. DCHECK(context_menu_);
  616. DCHECK_GE(command_id, ClipboardHistoryUtil::kFirstItemCommandId);
  617. DCHECK_LE(command_id, ClipboardHistoryUtil::kMaxItemCommandId);
  618. using Action = ClipboardHistoryUtil::Action;
  619. Action action = context_menu_->GetActionForCommandId(command_id);
  620. switch (action) {
  621. case Action::kPaste:
  622. // Create a scope for the variables used in this case so that they can be
  623. // deallocated from the stack.
  624. {
  625. bool paste_plain_text = event_flags & ui::EF_SHIFT_DOWN;
  626. // There are no specific flags that indicate a paste triggered by a
  627. // keystroke, so assume by default that keystroke was the event source
  628. // and then check for the other known possibilities. This assumption may
  629. // cause pastes from unknown sources to be incorrectly captured as
  630. // keystroke pastes, but we do not expect such cases to significantly
  631. // alter metrics.
  632. ClipboardHistoryPasteType paste_type =
  633. paste_plain_text ? ClipboardHistoryPasteType::kPlainTextKeystroke
  634. : ClipboardHistoryPasteType::kRichTextKeystroke;
  635. if (event_flags & ui::EF_MOUSE_BUTTON) {
  636. paste_type = paste_plain_text
  637. ? ClipboardHistoryPasteType::kPlainTextMouse
  638. : ClipboardHistoryPasteType::kRichTextMouse;
  639. } else if (event_flags & ui::EF_FROM_TOUCH) {
  640. paste_type = paste_plain_text
  641. ? ClipboardHistoryPasteType::kPlainTextTouch
  642. : ClipboardHistoryPasteType::kRichTextTouch;
  643. }
  644. PasteMenuItemData(command_id, paste_type);
  645. return;
  646. }
  647. case Action::kDelete:
  648. DeleteItemWithCommandId(command_id);
  649. return;
  650. case Action::kSelect:
  651. context_menu_->SelectMenuItemWithCommandId(command_id);
  652. return;
  653. case Action::kSelectItemHoveredByMouse:
  654. context_menu_->SelectMenuItemHoveredByMouse();
  655. return;
  656. case Action::kEmpty:
  657. NOTREACHED();
  658. return;
  659. }
  660. }
  661. void ClipboardHistoryControllerImpl::PasteMenuItemData(
  662. int command_id,
  663. ClipboardHistoryPasteType paste_type) {
  664. // Record the paste item's history list index in a histogram to get a
  665. // distribution of where in the list users paste from.
  666. UMA_HISTOGRAM_ENUMERATION(
  667. "Ash.ClipboardHistory.ContextMenu.MenuOptionSelected", command_id,
  668. ClipboardHistoryUtil::kMaxCommandId);
  669. // Record the paste item's history list index as a user action to analyze
  670. // usage patterns, e.g., how frequently the same index is pasted multiple
  671. // times in a row.
  672. RecordMenuIndexPastedUserAction(command_id);
  673. // Deactivate ClipboardImageModelFactory prior to pasting to ensure that any
  674. // modifications to the clipboard for HTML rendering purposes are reversed.
  675. ClipboardImageModelFactory::Get()->Deactivate();
  676. // Force close the context menu. Failure to do so before dispatching our
  677. // synthetic key event will result in the context menu consuming the event.
  678. DCHECK(context_menu_);
  679. context_menu_->Cancel();
  680. auto* active_window = window_util::GetActiveWindow();
  681. if (!active_window)
  682. return;
  683. const ClipboardHistoryItem& selected_item =
  684. context_menu_->GetItemFromCommandId(command_id);
  685. base::SequencedTaskRunnerHandle::Get()->PostTask(
  686. FROM_HERE,
  687. base::BindOnce(&ClipboardHistoryControllerImpl::PasteClipboardHistoryItem,
  688. weak_ptr_factory_.GetWeakPtr(), active_window,
  689. selected_item, paste_type));
  690. }
  691. void ClipboardHistoryControllerImpl::PasteClipboardHistoryItem(
  692. aura::Window* intended_window,
  693. ClipboardHistoryItem item,
  694. ClipboardHistoryPasteType paste_type) {
  695. // It's possible that the window could change or we could enter a disabled
  696. // mode after posting the `PasteClipboardHistoryItem()` task.
  697. if (!intended_window || intended_window != window_util::GetActiveWindow() ||
  698. !ClipboardHistoryUtil::IsEnabledInCurrentMode()) {
  699. if (confirmed_operation_callback_for_test_)
  700. confirmed_operation_callback_for_test_.Run(/*success=*/false);
  701. return;
  702. }
  703. // Get information about the data to be pasted.
  704. bool paste_plain_text = IsPlainTextPaste(paste_type);
  705. auto* clipboard = GetClipboard();
  706. ui::DataTransferEndpoint data_dst(ui::EndpointType::kClipboardHistory);
  707. const auto* current_clipboard_data = clipboard->GetClipboardData(&data_dst);
  708. // Clipboard history pastes are performed by temporarily writing data to the
  709. // system clipboard, if necessary, and then issuing a standard paste.
  710. // Determine the data we should temporarily write to the clipboard, if any, so
  711. // that we can paste the selected history item.
  712. std::unique_ptr<ui::ClipboardData> data_to_paste;
  713. if (paste_plain_text) {
  714. data_to_paste = std::make_unique<ui::ClipboardData>();
  715. data_to_paste->set_commit_time(item.data().commit_time());
  716. data_to_paste->set_text(item.data().text());
  717. ui::DataTransferEndpoint* data_src = item.data().source();
  718. if (data_src) {
  719. data_to_paste->set_source(
  720. std::make_unique<ui::DataTransferEndpoint>(*data_src));
  721. }
  722. } else if (!current_clipboard_data ||
  723. *current_clipboard_data != item.data()) {
  724. data_to_paste = std::make_unique<ui::ClipboardData>(item.data());
  725. }
  726. // Pausing clipboard history while manipulating the clipboard prevents the
  727. // paste item from being added to clipboard history. In cases where we
  728. // actually want the paste item to end up at the top of history, we accomplish
  729. // that by specifying that reorders on paste can go through. Plain text pastes
  730. // can cause reorders, but only in the buffer restoration step, as the plain
  731. // text data that reaches clipboard history cannot reliably identify the item
  732. // that should be reordered. In all cases, reorders should only be allowed
  733. // when the experimental behavior is enabled.
  734. using PauseBehavior = ClipboardHistoryUtil::PauseBehavior;
  735. auto pause_behavior =
  736. !paste_plain_text && features::IsClipboardHistoryReorderEnabled()
  737. ? PauseBehavior::kAllowReorderOnPaste
  738. : PauseBehavior::kDefault;
  739. std::unique_ptr<ui::ClipboardData> replaced_data;
  740. // If necessary, replace the clipboard's current data before issuing a paste.
  741. if (data_to_paste) {
  742. ScopedClipboardHistoryPauseImpl scoped_pause(clipboard_history_.get(),
  743. pause_behavior);
  744. replaced_data =
  745. GetClipboard()->WriteClipboardData(std::move(data_to_paste));
  746. }
  747. auto* host = GetWindowTreeHostForDisplay(
  748. display::Screen::GetScreen()->GetDisplayForNewWindows().id());
  749. DCHECK(host);
  750. ++pastes_to_be_confirmed_;
  751. ui::KeyEvent ctrl_press(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL, ui::EF_NONE);
  752. host->DeliverEventToSink(&ctrl_press);
  753. ui::KeyEvent v_press(ui::ET_KEY_PRESSED, ui::VKEY_V, ui::EF_CONTROL_DOWN);
  754. host->DeliverEventToSink(&v_press);
  755. ui::KeyEvent v_release(ui::ET_KEY_RELEASED, ui::VKEY_V, ui::EF_CONTROL_DOWN);
  756. host->DeliverEventToSink(&v_release);
  757. ui::KeyEvent ctrl_release(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL, ui::EF_NONE);
  758. host->DeliverEventToSink(&ctrl_release);
  759. base::UmaHistogramEnumeration("Ash.ClipboardHistory.PasteType", paste_type);
  760. for (auto& observer : observers_)
  761. observer.OnClipboardHistoryPasted();
  762. // If the clipboard was not changed or we intend for clipboard history to
  763. // remain reordered after the paste, then we are done modifying the clipboard
  764. // buffer.
  765. if (!replaced_data || pause_behavior == PauseBehavior::kAllowReorderOnPaste)
  766. return;
  767. // `currently_pasting_` only needs to be set when clipboard history and the
  768. // clipboard buffer are not in a consistent state for subsequent pastes.
  769. currently_pasting_ = true;
  770. // We only reach this point if the clipboard needs to be overwritten again,
  771. // either because we issued a plain text paste or because we pasted a
  772. // clipboard history item whose data was not originally on the clipboard and
  773. // reorder behavior is disabled. To know what data should go on the clipboard
  774. // and how that update should affect clipboard history, we check which of
  775. // three possible states currently applies:
  776. //
  777. // 1. the buffer is populated with a plain text version of the clipboard's
  778. // original data, so the original data should be restored with clipboard
  779. // history paused,
  780. // 2. the buffer is populated with a plain text version of a different
  781. // clipboard history item's data and reorder behavior is enabled, so the
  782. // pasted item's full data should replace the clipboard data while
  783. // signaling a reorder to clipboard history, or
  784. // 3. the buffer is populated with a different clipboard history item's full
  785. // data and reorder behavior is disabled, so the clipboard's original
  786. // data should be restored with clipboard history paused.
  787. //
  788. // Note that the buffer cannot hold a different clipboard history item's full
  789. // data with reorder behavior enabled, because in that case we would have
  790. // already allowed the clipboard history modification to go through as a
  791. // reorder during the pre-paste clipboard overwrite.
  792. pause_behavior = features::IsClipboardHistoryReorderEnabled() &&
  793. item.data() != *replaced_data
  794. ? PauseBehavior::kAllowReorderOnPaste
  795. : PauseBehavior::kDefault;
  796. auto data_to_restore = pause_behavior == PauseBehavior::kAllowReorderOnPaste
  797. ? std::make_unique<ui::ClipboardData>(item.data())
  798. : std::move(replaced_data);
  799. // Replace the clipboard data. Some apps take a long time to receive the paste
  800. // event, and some apps will read from the clipboard multiple times per paste.
  801. // Wait a bit before writing `data_to_restore` back to the clipboard.
  802. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  803. FROM_HERE,
  804. base::BindOnce(
  805. [](const base::WeakPtr<ClipboardHistoryControllerImpl>& weak_ptr,
  806. std::unique_ptr<ui::ClipboardData> data_to_restore,
  807. PauseBehavior pause_behavior) {
  808. std::unique_ptr<ScopedClipboardHistoryPauseImpl> scoped_pause;
  809. if (weak_ptr) {
  810. weak_ptr->currently_pasting_ = false;
  811. // When restoring the original clipboard content, pause clipboard
  812. // history to avoid committing data already at the top of the
  813. // clipboard history list. When restoring an item not originally
  814. // at the top of the clipboard history list, do not pause history
  815. // entirely, but do pause metrics so that the reorder is not
  816. // erroneously interpreted as a copy event.
  817. scoped_pause = std::make_unique<ScopedClipboardHistoryPauseImpl>(
  818. weak_ptr->clipboard_history_.get(), pause_behavior);
  819. }
  820. GetClipboard()->WriteClipboardData(std::move(data_to_restore));
  821. },
  822. weak_ptr_factory_.GetWeakPtr(), std::move(data_to_restore),
  823. pause_behavior),
  824. buffer_restoration_delay_for_test_.value_or(base::Milliseconds(200)));
  825. }
  826. void ClipboardHistoryControllerImpl::DeleteSelectedMenuItemIfAny() {
  827. DCHECK(context_menu_);
  828. auto selected_command = context_menu_->GetSelectedMenuItemCommand();
  829. // Return early if no item is selected.
  830. if (!selected_command.has_value())
  831. return;
  832. DeleteItemWithCommandId(*selected_command);
  833. }
  834. void ClipboardHistoryControllerImpl::DeleteItemWithCommandId(int command_id) {
  835. DCHECK(context_menu_);
  836. // Pressing VKEY_DELETE is handled here via AcceleratorTarget because the
  837. // contextual menu consumes the key event. Record the "pressing the delete
  838. // button" histogram here because this action does the same thing as
  839. // activating the button directly via click/tap. There is no special handling
  840. // for pasting an item via VKEY_RETURN because in that case the menu does not
  841. // process the key event.
  842. const auto& to_be_deleted_item =
  843. context_menu_->GetItemFromCommandId(command_id);
  844. DeleteClipboardHistoryItem(to_be_deleted_item);
  845. // If the item to be deleted is the last one, close the whole menu.
  846. if (context_menu_->GetMenuItemsCount() == 1) {
  847. context_menu_->Cancel();
  848. return;
  849. }
  850. context_menu_->RemoveMenuItemWithCommandId(command_id);
  851. }
  852. void ClipboardHistoryControllerImpl::DeleteClipboardHistoryItem(
  853. const ClipboardHistoryItem& item) {
  854. ClipboardHistoryUtil::RecordClipboardHistoryItemDeleted(item);
  855. clipboard_history_->RemoveItemForId(item.id());
  856. }
  857. void ClipboardHistoryControllerImpl::AdvancePseudoFocus(bool reverse) {
  858. DCHECK(context_menu_);
  859. context_menu_->AdvancePseudoFocus(reverse);
  860. }
  861. gfx::Rect ClipboardHistoryControllerImpl::CalculateAnchorRect() const {
  862. display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  863. auto* host = ash::GetWindowTreeHostForDisplay(display.id());
  864. // Some web apps render the caret in an IFrame, and we will not get the
  865. // bounds in that case.
  866. // TODO(https://crbug.com/1099930): Show the menu in the middle of the
  867. // webview if the bounds are empty.
  868. ui::TextInputClient* text_input_client =
  869. host->GetInputMethod()->GetTextInputClient();
  870. // `text_input_client` may be null. For example, in clamshell mode and without
  871. // any window open.
  872. const gfx::Rect textfield_bounds =
  873. text_input_client ? text_input_client->GetCaretBounds() : gfx::Rect();
  874. // Note that the width of caret's bounds may be zero in some views (such as
  875. // the search bar of Google search web page). So we cannot use
  876. // gfx::Size::IsEmpty() here. In addition, the applications using IFrame may
  877. // provide unreliable `textfield_bounds` which are not fully contained by the
  878. // display bounds.
  879. const bool textfield_bounds_are_valid =
  880. textfield_bounds.size() != gfx::Size() &&
  881. IsRectContainedByAnyDisplay(textfield_bounds);
  882. if (textfield_bounds_are_valid)
  883. return textfield_bounds;
  884. return gfx::Rect(display::Screen::GetScreen()->GetCursorScreenPoint(),
  885. gfx::Size());
  886. }
  887. void ClipboardHistoryControllerImpl::OnMenuClosed() {
  888. accelerator_target_->OnMenuClosed();
  889. // Reset `context_menu_` in the asynchronous way. Because the menu may be
  890. // accessed after `OnMenuClosed()` is called.
  891. menu_task_timer_.Start(
  892. FROM_HERE, base::TimeDelta(),
  893. base::BindOnce(
  894. [](const base::WeakPtr<ClipboardHistoryControllerImpl>&
  895. controller_weak_ptr) {
  896. if (controller_weak_ptr)
  897. controller_weak_ptr->context_menu_.reset();
  898. },
  899. weak_ptr_factory_.GetWeakPtr()));
  900. }
  901. } // namespace ash