diff options
author | jif <jif@chromium.org> | 2015-02-19 07:40:12 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-02-19 15:41:00 +0000 |
commit | 9021d87d62b29e0b1ee4625ad0006b500cc3f683 (patch) | |
tree | 26b9d87c78c7919c238efac100981e7833097957 | |
parent | cdc03121d7c798a87c4c90fd16981065ed06c5db (diff) | |
download | chromium_src-9021d87d62b29e0b1ee4625ad0006b500cc3f683.zip chromium_src-9021d87d62b29e0b1ee4625ad0006b500cc3f683.tar.gz chromium_src-9021d87d62b29e0b1ee4625ad0006b500cc3f683.tar.bz2 |
Upstream iOS' Open from Clipboard component.
The component's goal is to provide omnibox suggestions based on the
clipboard's content.
BUG=None.
Review URL: https://codereview.chromium.org/930323003
Cr-Commit-Position: refs/heads/master@{#317055}
-rw-r--r-- | build/ios/grit_whitelist.txt | 1 | ||||
-rw-r--r-- | components/OWNERS | 3 | ||||
-rw-r--r-- | components/components.gyp | 1 | ||||
-rw-r--r-- | components/components_strings.grd | 1 | ||||
-rw-r--r-- | components/open_from_clipboard.gypi | 28 | ||||
-rw-r--r-- | components/open_from_clipboard/DEPS | 5 | ||||
-rw-r--r-- | components/open_from_clipboard/OWNERS | 1 | ||||
-rw-r--r-- | components/open_from_clipboard/clipboard_recent_content.h | 43 | ||||
-rw-r--r-- | components/open_from_clipboard/clipboard_recent_content_ios.h | 53 | ||||
-rw-r--r-- | components/open_from_clipboard/clipboard_recent_content_ios.mm | 162 | ||||
-rw-r--r-- | components/open_from_clipboard/clipboard_url_provider.cc | 59 | ||||
-rw-r--r-- | components/open_from_clipboard/clipboard_url_provider.h | 27 | ||||
-rw-r--r-- | components/open_from_clipboard_strings.grdp | 6 | ||||
-rw-r--r-- | tools/metrics/actions/actions.xml | 10 |
14 files changed, 400 insertions, 0 deletions
diff --git a/build/ios/grit_whitelist.txt b/build/ios/grit_whitelist.txt index c1edfdf..ab1fb5d 100644 --- a/build/ios/grit_whitelist.txt +++ b/build/ios/grit_whitelist.txt @@ -719,6 +719,7 @@ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE_AND_URL IDS_LIBADDRESSINPUT_VILLAGE_TOWNSHIP IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL +IDS_LINK_FROM_CLIPBOARD IDS_LOGIN_DIALOG_OK_BUTTON_LABEL IDS_LOGIN_DIALOG_PASSWORD_FIELD IDS_LOGIN_DIALOG_TITLE diff --git a/components/OWNERS b/components/OWNERS index 6946515..d563137 100644 --- a/components/OWNERS +++ b/components/OWNERS @@ -145,6 +145,9 @@ per-file onc.gypi=gspencer@chromium.org per-file onc.gypi=pneubeck@chromium.org per-file onc.gypi=stevenjb@chromium.org +per-file open_from_clipboard.gypi=jif@chromium.org +per-file open_from_clipboard_strings.grdp=jif@chromium.org + per-file packed_ct_ev_whitelist.gypi=eranm@chromium.org per-file packed_ct_ev_whitelist.gypi=rsleevi@chromium.org diff --git a/components/components.gyp b/components/components.gyp index 974d292..39e07f7 100644 --- a/components/components.gyp +++ b/components/components.gyp @@ -83,6 +83,7 @@ }], ['OS == "ios"', { 'includes': [ + 'open_from_clipboard.gypi', 'webp_transcode.gypi', ], }], diff --git a/components/components_strings.grd b/components/components_strings.grd index 35d1c59..e48591a 100644 --- a/components/components_strings.grd +++ b/components/components_strings.grd @@ -174,6 +174,7 @@ <part file="dom_distiller_strings.grdp" /> <part file="error_page_strings.grdp" /> <part file="omnibox_strings.grdp" /> + <part file="open_from_clipboard_strings.grdp" /> <part file="password_manager_strings.grdp" /> <part file="pdf_strings.grdp" /> <part file="policy_strings.grdp" /> diff --git a/components/open_from_clipboard.gypi b/components/open_from_clipboard.gypi new file mode 100644 index 0000000..103e6d0 --- /dev/null +++ b/components/open_from_clipboard.gypi @@ -0,0 +1,28 @@ +# Copyright 2015 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. + +{ + 'targets': [ + { + 'target_name': 'open_from_clipboard', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../url/url.gyp:url_lib', + 'components_strings.gyp:components_strings', + 'omnibox', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'open_from_clipboard/clipboard_recent_content.h', + 'open_from_clipboard/clipboard_recent_content_ios.h', + 'open_from_clipboard/clipboard_recent_content_ios.mm', + 'open_from_clipboard/clipboard_url_provider.cc', + 'open_from_clipboard/clipboard_url_provider.h', + ], + }, + ], +} diff --git a/components/open_from_clipboard/DEPS b/components/open_from_clipboard/DEPS new file mode 100644 index 0000000..30a8913 --- /dev/null +++ b/components/open_from_clipboard/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+components/omnibox", + "+grit/components_strings.h", + "+ui/base/l10n", +] diff --git a/components/open_from_clipboard/OWNERS b/components/open_from_clipboard/OWNERS new file mode 100644 index 0000000..48eb126 --- /dev/null +++ b/components/open_from_clipboard/OWNERS @@ -0,0 +1 @@ +jif@chromium.org
\ No newline at end of file diff --git a/components/open_from_clipboard/clipboard_recent_content.h b/components/open_from_clipboard/clipboard_recent_content.h new file mode 100644 index 0000000..31c274c --- /dev/null +++ b/components/open_from_clipboard/clipboard_recent_content.h @@ -0,0 +1,43 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_H_ +#define COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_H_ + +#include <string> + +#include "base/macros.h" + +class GURL; + +// Helper class returning an URL if the content of the clipboard can be turned +// into an URL, and if it estimates that the content of the clipboard is not too +// old. +class ClipboardRecentContent { + public: + // Returns an instance of the ClipboardContent singleton. + static ClipboardRecentContent* GetInstance(); + + // Returns true if the clipboard contains a recent URL, and copies it in + // |url|. Otherwise, returns false. |url| must not be null. + virtual bool GetRecentURLFromClipboard(GURL* url) const = 0; + + // Sets which URL scheme this app can be opened with. Used by + // ClipboardRecentContent to determine whether or not the clipboard contains + // a relevant URL. |application_scheme| may be empty. + void set_application_scheme(const std::string& application_scheme) { + application_scheme_ = application_scheme; + } + + protected: + ClipboardRecentContent() {} + virtual ~ClipboardRecentContent() {} + // Contains the URL scheme opening the app. May be empty. + std::string application_scheme_; + + private: + DISALLOW_COPY_AND_ASSIGN(ClipboardRecentContent); +}; + +#endif // COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_H_ diff --git a/components/open_from_clipboard/clipboard_recent_content_ios.h b/components/open_from_clipboard/clipboard_recent_content_ios.h new file mode 100644 index 0000000..5c3ff46 --- /dev/null +++ b/components/open_from_clipboard/clipboard_recent_content_ios.h @@ -0,0 +1,53 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_IOS_H_ +#define COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_IOS_H_ + +#include "base/mac/scoped_nsobject.h" +#include "components/open_from_clipboard/clipboard_recent_content.h" +#include "url/gurl.h" + +@class NSDate; +@class PasteboardNotificationListenerBridge; + +template <typename T> +struct DefaultSingletonTraits; + +// IOS implementation of ClipboardRecentContent +class ClipboardRecentContentIOS : public ClipboardRecentContent { + public: + static ClipboardRecentContentIOS* GetInstance(); + // Notifies that the content of the pasteboard may have changed. + void PasteboardChanged(); + + // ClipboardRecentContent implementation. + bool GetRecentURLFromClipboard(GURL* url) const override; + + private: + friend struct DefaultSingletonTraits<ClipboardRecentContentIOS>; + + ClipboardRecentContentIOS(); + ~ClipboardRecentContentIOS() override; + // Loads information from the user defaults about the latest clipboard entry. + void LoadFromUserDefaults(); + // Saves information to the user defaults about the latest clipboard entry. + void SaveToUserDefaults(); + // Returns the URL contained in the clipboard (if any). + GURL URLFromPasteboard(); + + // The pasteboard's change count. Increases everytime the pasteboard changes. + NSInteger lastPasteboardChangeCount_; + // Estimation of the date when the pasteboard changed. + base::scoped_nsobject<NSDate> lastPasteboardChangeDate_; + // Cache of the GURL contained in the pasteboard (if any). + GURL urlFromPasteboardCache_; + // Bridge to receive notification when the pasteboard changes. + base::scoped_nsobject<PasteboardNotificationListenerBridge> + notificationBridge_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardRecentContentIOS); +}; + +#endif // COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_IOS_H_ diff --git a/components/open_from_clipboard/clipboard_recent_content_ios.mm b/components/open_from_clipboard/clipboard_recent_content_ios.mm new file mode 100644 index 0000000..9ec6f07 --- /dev/null +++ b/components/open_from_clipboard/clipboard_recent_content_ios.mm @@ -0,0 +1,162 @@ +// Copyright 2015 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 "components/open_from_clipboard/clipboard_recent_content_ios.h" + +#import <UIKit/UIKit.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/metrics/user_metrics.h" +#include "base/strings/sys_string_conversions.h" +#include "url/gurl.h" +#include "url/url_constants.h" + +ClipboardRecentContent* ClipboardRecentContent::GetInstance() { + return ClipboardRecentContentIOS::GetInstance(); +} + +// Bridge that forwards pasteboard change notifications to its delegate. +@interface PasteboardNotificationListenerBridge : NSObject { + ClipboardRecentContentIOS* _delegate; +} +@end + +@implementation PasteboardNotificationListenerBridge + +- (id)initWithDelegate:(ClipboardRecentContentIOS*)delegate { + DCHECK(delegate); + self = [super init]; + if (self) { + _delegate = delegate; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(pasteboardChangedNotification:) + name:UIPasteboardChangedNotification + object:[UIPasteboard generalPasteboard]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(pasteboardChangedNotification:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:UIPasteboardChangedNotification + object:[UIPasteboard generalPasteboard]]; + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:UIApplicationDidBecomeActiveNotification + object:[UIPasteboard generalPasteboard]]; + [super dealloc]; +} + +- (void)pasteboardChangedNotification:(NSNotification*)notification { + _delegate->PasteboardChanged(); +} + +@end + +namespace { +// Key used to store the pasteboard's current change count. If when resuming +// chrome the pasteboard's change count is different from the stored one, then +// it means that the pasteboard's content has changed. +NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount"; +// Key used to store the last date at which it was detected that the pasteboard +// changed. It is used to evaluate the age of the pasteboard's content. +NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate"; +NSTimeInterval kMaximumAgeOfClipboardInSeconds = 6 * 60 * 60; +// Schemes accepted by the ClipboardRecentContentIOS. +const char* kAuthorizedSchemes[] = { + url::kHttpScheme, + url::kHttpsScheme, + url::kDataScheme, + url::kAboutScheme, +}; +} // namespace + +ClipboardRecentContentIOS* ClipboardRecentContentIOS::GetInstance() { + return Singleton<ClipboardRecentContentIOS>::get(); +} + +bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) const { + DCHECK(url); + if (-[lastPasteboardChangeDate_ timeIntervalSinceNow] > + kMaximumAgeOfClipboardInSeconds) { + return false; + } + if (urlFromPasteboardCache_.is_valid()) { + *url = urlFromPasteboardCache_; + return true; + } + return false; +} + +void ClipboardRecentContentIOS::PasteboardChanged() { + if ([UIPasteboard generalPasteboard].changeCount != + lastPasteboardChangeCount_) { + urlFromPasteboardCache_ = URLFromPasteboard(); + if (!urlFromPasteboardCache_.is_empty()) { + base::RecordAction( + base::UserMetricsAction("MobileOmniboxClipboardChanged")); + } + lastPasteboardChangeDate_.reset([[NSDate date] retain]); + lastPasteboardChangeCount_ = [UIPasteboard generalPasteboard].changeCount; + SaveToUserDefaults(); + } +} + +ClipboardRecentContentIOS::ClipboardRecentContentIOS() + : ClipboardRecentContent() { + urlFromPasteboardCache_ = URLFromPasteboard(); + LoadFromUserDefaults(); + if ([UIPasteboard generalPasteboard].changeCount != + lastPasteboardChangeCount_) { + PasteboardChanged(); + } + notificationBridge_.reset( + [[PasteboardNotificationListenerBridge alloc] initWithDelegate:this]); +} + +ClipboardRecentContentIOS::~ClipboardRecentContentIOS() { +} + +GURL ClipboardRecentContentIOS::URLFromPasteboard() { + const std::string clipboard = + base::SysNSStringToUTF8([[UIPasteboard generalPasteboard] string]); + GURL gurl = GURL(clipboard); + if (gurl.is_valid()) { + for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) { + if (gurl.SchemeIs(kAuthorizedSchemes[i])) { + return gurl; + } + } + if (!application_scheme_.empty() && + gurl.SchemeIs(application_scheme_.c_str())) { + return gurl; + } + } + return GURL::EmptyGURL(); +} + +void ClipboardRecentContentIOS::LoadFromUserDefaults() { + lastPasteboardChangeCount_ = [[NSUserDefaults standardUserDefaults] + integerForKey:kPasteboardChangeCountKey]; + lastPasteboardChangeDate_.reset([[[NSUserDefaults standardUserDefaults] + objectForKey:kPasteboardChangeDateKey] retain]); + DCHECK(!lastPasteboardChangeDate_ || + [lastPasteboardChangeDate_ isKindOfClass:[NSDate class]]); +} + +void ClipboardRecentContentIOS::SaveToUserDefaults() { + [[NSUserDefaults standardUserDefaults] setInteger:lastPasteboardChangeCount_ + forKey:kPasteboardChangeCountKey]; + [[NSUserDefaults standardUserDefaults] setObject:lastPasteboardChangeDate_ + forKey:kPasteboardChangeDateKey]; +} diff --git a/components/open_from_clipboard/clipboard_url_provider.cc b/components/open_from_clipboard/clipboard_url_provider.cc new file mode 100644 index 0000000..33fdf38 --- /dev/null +++ b/components/open_from_clipboard/clipboard_url_provider.cc @@ -0,0 +1,59 @@ +// Copyright 2015 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 "components/open_from_clipboard/clipboard_url_provider.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "components/omnibox/autocomplete_input.h" +#include "components/open_from_clipboard/clipboard_recent_content.h" +#include "grit/components_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { +// Score defining the priority of the suggestion. 1600 guarantees that the +// suggestion will always be first. See table in autocomplete_provider.h. +const int kClipboardURLProviderRelevance = 1600; +} // namespace + +ClipboardURLProvider::ClipboardURLProvider( + ClipboardRecentContent* clipboard_recent_content) + : AutocompleteProvider(AutocompleteProvider::TYPE_SHORTCUTS), + clipboard_recent_content_(clipboard_recent_content) { + DCHECK(clipboard_recent_content_); +} + +ClipboardURLProvider::~ClipboardURLProvider() { +} + +void ClipboardURLProvider::Start(const AutocompleteInput& input, + bool minimal_changes, + bool called_due_to_focus) { + matches_.clear(); + // Attempt to add an AutocompleteMatch only if the user has not entered + // anything in the omnibox. + if (input.cursor_position() == base::string16::npos) { + GURL url; + if (clipboard_recent_content_->GetRecentURLFromClipboard(&url)) { + DCHECK(url.is_valid()); + AutocompleteMatch match(this, kClipboardURLProviderRelevance, false, + AutocompleteMatchType::NAVSUGGEST_PERSONALIZED); + match.allowed_to_be_default_match = true; + match.destination_url = url; + match.contents.assign(base::UTF8ToUTF16(url.spec())); + AutocompleteMatch::ClassifyLocationInString( + base::string16::npos, 0, match.contents.length(), + ACMatchClassification::URL, &match.contents_class); + + match.description.assign( + l10n_util::GetStringUTF16(IDS_LINK_FROM_CLIPBOARD)); + AutocompleteMatch::ClassifyLocationInString( + base::string16::npos, 0, match.description.length(), + ACMatchClassification::URL, &match.description_class); + + matches_.push_back(match); + } + } + done_ = true; +} diff --git a/components/open_from_clipboard/clipboard_url_provider.h b/components/open_from_clipboard/clipboard_url_provider.h new file mode 100644 index 0000000..38122cc --- /dev/null +++ b/components/open_from_clipboard/clipboard_url_provider.h @@ -0,0 +1,27 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_URL_PROVIDER_H_ +#define COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_URL_PROVIDER_H_ + +#include "components/omnibox/autocomplete_provider.h" + +class ClipboardRecentContent; + +// Autocomplete provider offering content based on the clipboard's content. +class ClipboardURLProvider : public AutocompleteProvider { + public: + ClipboardURLProvider(ClipboardRecentContent* clipboard_recent_content); + void Start(const AutocompleteInput& input, + bool minimal_changes, + bool called_due_to_focus) override; + + private: + ~ClipboardURLProvider() override; + ClipboardRecentContent* clipboard_recent_content_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardURLProvider); +}; + +#endif // COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_URL_PROVIDER_H_ diff --git a/components/open_from_clipboard_strings.grdp b/components/open_from_clipboard_strings.grdp new file mode 100644 index 0000000..c6ec2b5 --- /dev/null +++ b/components/open_from_clipboard_strings.grdp @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<grit-part> + <message name="IDS_LINK_FROM_CLIPBOARD" desc="The label in the omnibox dropdown explaining that the link has been extracted from the user's clipboard. [Length: 21em]"> + Link you copied + </message> +</grit-part> diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml index d222a02..95674aa 100644 --- a/tools/metrics/actions/actions.xml +++ b/tools/metrics/actions/actions.xml @@ -7592,6 +7592,16 @@ should be able to be added at any place in this file. <description>Please enter the description of this user action.</description> </action> +<action name="MobileOmniboxClipboardChanged"> + <owner>jif@chromium.org</owner> + <description> + Emitted when Chrome detects that the clipboard contains a new URL. This + occurs either when Chrome enters the foreground and notices that the content + of the clipboard changed, or when the clipboard changes while Chrome is in + the foreground. + </description> +</action> + <action name="MobileOmniboxRefineSuggestion"> <owner>Please list the metric's owners. Add more owner tags as needed.</owner> <description>Please enter the description of this user action.</description> |