Commit 9021d87d authored by jif's avatar jif Committed by Commit bot

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}
parent cdc03121
......@@ -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
......
......@@ -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
......
......@@ -83,6 +83,7 @@
}],
['OS == "ios"', {
'includes': [
'open_from_clipboard.gypi',
'webp_transcode.gypi',
],
}],
......
......@@ -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" />
......
# 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',
],
},
],
}
include_rules = [
"+components/omnibox",
"+grit/components_strings.h",
"+ui/base/l10n",
]
jif@chromium.org
\ No newline at end of file
// 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_
// 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_
// 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];
}
// 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;
}
// 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_
<?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>
......@@ -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>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment