Commit f85a34b7 authored by cthomp@chromium.org's avatar cthomp@chromium.org

Experience sampling instrumentation for dangerous downloads warnings

BUG=384635

Review URL: https://codereview.chromium.org/402293002

Cr-Commit-Position: refs/heads/master@{#290076}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@290076 0039d316-1c4b-4281-b951-d872f2087c98
parent fc103daf
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h" #include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_stats.h" #include "chrome/browser/download/download_stats.h"
#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
#include "chrome/browser/ui/tab_modal_confirm_dialog.h" #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
#include "content/public/browser/download_danger_type.h" #include "content/public/browser/download_danger_type.h"
...@@ -17,6 +18,8 @@ ...@@ -17,6 +18,8 @@
#include "grit/generated_resources.h" #include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
using extensions::ExperienceSamplingEvent;
namespace { namespace {
// TODO(wittman): Create a native web contents modal dialog implementation of // TODO(wittman): Create a native web contents modal dialog implementation of
...@@ -56,6 +59,8 @@ class DownloadDangerPromptImpl : public DownloadDangerPrompt, ...@@ -56,6 +59,8 @@ class DownloadDangerPromptImpl : public DownloadDangerPrompt,
bool show_context_; bool show_context_;
OnDone done_; OnDone done_;
scoped_ptr<ExperienceSamplingEvent> sampling_event_;
DISALLOW_COPY_AND_ASSIGN(DownloadDangerPromptImpl); DISALLOW_COPY_AND_ASSIGN(DownloadDangerPromptImpl);
}; };
...@@ -71,6 +76,14 @@ DownloadDangerPromptImpl::DownloadDangerPromptImpl( ...@@ -71,6 +76,14 @@ DownloadDangerPromptImpl::DownloadDangerPromptImpl(
DCHECK(!done_.is_null()); DCHECK(!done_.is_null());
download_->AddObserver(this); download_->AddObserver(this);
RecordOpenedDangerousConfirmDialog(download_->GetDangerType()); RecordOpenedDangerousConfirmDialog(download_->GetDangerType());
// ExperienceSampling: A malicious download warning is being shown to the
// user, so we start a new SamplingEvent and track it.
sampling_event_.reset(new ExperienceSamplingEvent(
ExperienceSamplingEvent::kDownloadDangerPrompt,
download->GetURL(),
download->GetReferrerUrl(),
download->GetBrowserContext()));
} }
DownloadDangerPromptImpl::~DownloadDangerPromptImpl() { DownloadDangerPromptImpl::~DownloadDangerPromptImpl() {
...@@ -203,14 +216,20 @@ base::string16 DownloadDangerPromptImpl::GetCancelButtonTitle() { ...@@ -203,14 +216,20 @@ base::string16 DownloadDangerPromptImpl::GetCancelButtonTitle() {
} }
void DownloadDangerPromptImpl::OnAccepted() { void DownloadDangerPromptImpl::OnAccepted() {
// ExperienceSampling: User proceeded through the warning.
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
RunDone(ACCEPT); RunDone(ACCEPT);
} }
void DownloadDangerPromptImpl::OnCanceled() { void DownloadDangerPromptImpl::OnCanceled() {
// ExperienceSampling: User canceled the warning.
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
RunDone(CANCEL); RunDone(CANCEL);
} }
void DownloadDangerPromptImpl::OnClosed() { void DownloadDangerPromptImpl::OnClosed() {
// ExperienceSampling: User canceled the warning.
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
RunDone(DISMISS); RunDone(DISMISS);
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "chrome/browser/download/download_danger_prompt.h" #include "chrome/browser/download/download_danger_prompt.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_tabstrip.h"
...@@ -14,11 +15,13 @@ ...@@ -14,11 +15,13 @@
#include "content/public/test/mock_download_item.h" #include "content/public/test/mock_download_item.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using ::testing::_; using ::testing::_;
using ::testing::ByRef; using ::testing::ByRef;
using ::testing::Eq; using ::testing::Eq;
using ::testing::Return; using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SaveArg; using ::testing::SaveArg;
class DownloadDangerPromptTest : public InProcessBrowserTest { class DownloadDangerPromptTest : public InProcessBrowserTest {
...@@ -100,6 +103,13 @@ class DownloadDangerPromptTest : public InProcessBrowserTest { ...@@ -100,6 +103,13 @@ class DownloadDangerPromptTest : public InProcessBrowserTest {
}; };
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, TestAll) { IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, TestAll) {
// ExperienceSampling: Set default actions for DownloadItem methods we need.
ON_CALL(download(), GetURL()).WillByDefault(ReturnRef(GURL::EmptyGURL()));
ON_CALL(download(), GetReferrerUrl())
.WillByDefault(ReturnRef(GURL::EmptyGURL()));
ON_CALL(download(), GetBrowserContext())
.WillByDefault(Return(browser()->profile()));
OpenNewTab(); OpenNewTab();
// Clicking the Accept button should invoke the ACCEPT action. // Clicking the Accept button should invoke the ACCEPT action.
......
...@@ -17,10 +17,17 @@ namespace extensions { ...@@ -17,10 +17,17 @@ namespace extensions {
// static // static
const char ExperienceSamplingEvent::kProceed[] = "proceed"; const char ExperienceSamplingEvent::kProceed[] = "proceed";
const char ExperienceSamplingEvent::kDeny[] = "deny"; const char ExperienceSamplingEvent::kDeny[] = "deny";
const char ExperienceSamplingEvent::kIgnore[] = "ignore";
const char ExperienceSamplingEvent::kCancel[] = "cancel"; const char ExperienceSamplingEvent::kCancel[] = "cancel";
const char ExperienceSamplingEvent::kReload[] = "reload"; const char ExperienceSamplingEvent::kReload[] = "reload";
// static // static
const char ExperienceSamplingEvent::kMaliciousDownload[] =
"download_warning_malicious";
const char ExperienceSamplingEvent::kDangerousDownload[] =
"download_warning_dangerous";
const char ExperienceSamplingEvent::kDownloadDangerPrompt[] =
"download_danger_prompt";
const char ExperienceSamplingEvent::kExtensionInstallDialog[] = const char ExperienceSamplingEvent::kExtensionInstallDialog[] =
"extension_install_dialog_"; "extension_install_dialog_";
......
...@@ -22,10 +22,14 @@ class ExperienceSamplingEvent { ...@@ -22,10 +22,14 @@ class ExperienceSamplingEvent {
// String constants for user decision events. // String constants for user decision events.
static const char kProceed[]; static const char kProceed[];
static const char kDeny[]; static const char kDeny[];
static const char kIgnore[];
static const char kCancel[]; static const char kCancel[];
static const char kReload[]; static const char kReload[];
// String constants for event names. // String constants for event names.
static const char kMaliciousDownload[];
static const char kDangerousDownload[];
static const char kDownloadDangerPrompt[];
static const char kExtensionInstallDialog[]; static const char kExtensionInstallDialog[];
// The Create() functions can return an empty scoped_ptr if they cannot find // The Create() functions can return an empty scoped_ptr if they cannot find
......
...@@ -21,6 +21,10 @@ class DownloadItem; ...@@ -21,6 +21,10 @@ class DownloadItem;
class PageNavigator; class PageNavigator;
} }
namespace extensions {
class ExperienceSamplingEvent;
}
namespace gfx { namespace gfx {
class FontList; class FontList;
} }
...@@ -71,6 +75,10 @@ class MenuModel; ...@@ -71,6 +75,10 @@ class MenuModel;
kNormal, kNormal,
kDangerous kDangerous
} state_; } state_;
// ExperienceSampling: This tracks dangerous/malicious downloads warning UI
// and the user's decisions about it.
scoped_ptr<extensions::ExperienceSamplingEvent> sampling_event_;
}; };
// Initialize controller for |downloadItem|. // Initialize controller for |downloadItem|.
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "chrome/browser/download/chrome_download_manager_delegate.h" #include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_item_model.h" #include "chrome/browser/download/download_item_model.h"
#include "chrome/browser/download/download_shelf_context_menu.h" #include "chrome/browser/download/download_shelf_context_menu.h"
#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
#import "chrome/browser/themes/theme_properties.h" #import "chrome/browser/themes/theme_properties.h"
#import "chrome/browser/themes/theme_service.h" #import "chrome/browser/themes/theme_service.h"
#import "chrome/browser/ui/cocoa/download/download_item_button.h" #import "chrome/browser/ui/cocoa/download/download_item_button.h"
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
using content::DownloadItem; using content::DownloadItem;
using extensions::ExperienceSamplingEvent;
namespace { namespace {
...@@ -117,6 +119,8 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu { ...@@ -117,6 +119,8 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu {
} }
- (void)dealloc { - (void)dealloc {
if (sampling_event_.get())
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kIgnore);
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
[progressView_ setController:nil]; [progressView_ setController:nil];
[[self view] removeFromSuperview]; [[self view] removeFromSuperview];
...@@ -144,6 +148,17 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu { ...@@ -144,6 +148,17 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu {
[self setState:kDangerous]; [self setState:kDangerous];
// ExperienceSampling: Dangerous or malicious download warning is being shown
// to the user, so we start a new SamplingEvent and track it.
std::string event_name = downloadModel->MightBeMalicious()
? ExperienceSamplingEvent::kMaliciousDownload
: ExperienceSamplingEvent::kDangerousDownload;
sampling_event_.reset(new ExperienceSamplingEvent(
event_name,
downloadModel->download()->GetURL(),
downloadModel->download()->GetReferrerUrl(),
downloadModel->download()->GetBrowserContext()));
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
NSImage* alertIcon; NSImage* alertIcon;
...@@ -320,6 +335,11 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu { ...@@ -320,6 +335,11 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu {
// user did this to detect whether we're being clickjacked. // user did this to detect whether we're being clickjacked.
UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
base::Time::Now() - creationTime_); base::Time::Now() - creationTime_);
// ExperienceSampling: User chose to proceed with dangerous download.
if (sampling_event_.get()) {
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
sampling_event_.reset(NULL);
}
// This will change the state and notify us. // This will change the state and notify us.
bridge_->download_model()->download()->ValidateDangerousDownload(); bridge_->download_model()->download()->ValidateDangerousDownload();
} }
...@@ -333,6 +353,11 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu { ...@@ -333,6 +353,11 @@ class DownloadShelfContextMenuMac : public DownloadShelfContextMenu {
} }
- (IBAction)dismissMaliciousDownload:(id)sender { - (IBAction)dismissMaliciousDownload:(id)sender {
// ExperienceSampling: User dismissed the dangerous download.
if (sampling_event_.get()) {
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
sampling_event_.reset(NULL);
}
[self remove]; [self remove];
// WARNING: we are deleted at this point. // WARNING: we are deleted at this point.
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "chrome/browser/download/download_danger_prompt.h" #include "chrome/browser/download/download_danger_prompt.h"
#include "chrome/browser/download/download_stats.h" #include "chrome/browser/download/download_stats.h"
#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
#include "chrome/browser/ui/views/constrained_window_views.h" #include "chrome/browser/ui/views/constrained_window_views.h"
#include "chrome/grit/chromium_strings.h" #include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
...@@ -21,6 +22,8 @@ ...@@ -21,6 +22,8 @@
#include "ui/views/window/dialog_client_view.h" #include "ui/views/window/dialog_client_view.h"
#include "ui/views/window/dialog_delegate.h" #include "ui/views/window/dialog_delegate.h"
using extensions::ExperienceSamplingEvent;
namespace { namespace {
const int kMessageWidth = 320; const int kMessageWidth = 320;
...@@ -69,6 +72,8 @@ class DownloadDangerPromptViews : public DownloadDangerPrompt, ...@@ -69,6 +72,8 @@ class DownloadDangerPromptViews : public DownloadDangerPrompt,
bool show_context_; bool show_context_;
OnDone done_; OnDone done_;
scoped_ptr<ExperienceSamplingEvent> sampling_event_;
views::View* contents_view_; views::View* contents_view_;
}; };
...@@ -117,6 +122,14 @@ DownloadDangerPromptViews::DownloadDangerPromptViews( ...@@ -117,6 +122,14 @@ DownloadDangerPromptViews::DownloadDangerPromptViews(
layout->AddView(message_body_label); layout->AddView(message_body_label);
RecordOpenedDangerousConfirmDialog(download_->GetDangerType()); RecordOpenedDangerousConfirmDialog(download_->GetDangerType());
// ExperienceSampling: A malicious download warning is being shown to the
// user, so we start a new SamplingEvent and track it.
sampling_event_.reset(new ExperienceSamplingEvent(
ExperienceSamplingEvent::kDownloadDangerPrompt,
item->GetURL(),
item->GetReferrerUrl(),
item->GetBrowserContext()));
} }
// DownloadDangerPrompt methods: // DownloadDangerPrompt methods:
...@@ -170,18 +183,24 @@ ui::ModalType DownloadDangerPromptViews::GetModalType() const { ...@@ -170,18 +183,24 @@ ui::ModalType DownloadDangerPromptViews::GetModalType() const {
bool DownloadDangerPromptViews::Cancel() { bool DownloadDangerPromptViews::Cancel() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// ExperienceSampling: User canceled the warning.
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
RunDone(CANCEL); RunDone(CANCEL);
return true; return true;
} }
bool DownloadDangerPromptViews::Accept() { bool DownloadDangerPromptViews::Accept() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// ExperienceSampling: User proceeded through the warning.
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
RunDone(ACCEPT); RunDone(ACCEPT);
return true; return true;
} }
bool DownloadDangerPromptViews::Close() { bool DownloadDangerPromptViews::Close() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// ExperienceSampling: User canceled the warning.
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
RunDone(DISMISS); RunDone(DISMISS);
return true; return true;
} }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "chrome/browser/download/download_item_model.h" #include "chrome/browser/download/download_item_model.h"
#include "chrome/browser/download/download_stats.h" #include "chrome/browser/download/download_stats.h"
#include "chrome/browser/download/drag_download_item.h" #include "chrome/browser/download/drag_download_item.h"
#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/download_feedback_service.h" #include "chrome/browser/safe_browsing/download_feedback_service.h"
#include "chrome/browser/safe_browsing/download_protection_service.h" #include "chrome/browser/safe_browsing/download_protection_service.h"
...@@ -54,6 +55,7 @@ ...@@ -54,6 +55,7 @@
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
using content::DownloadItem; using content::DownloadItem;
using extensions::ExperienceSamplingEvent;
// TODO(paulg): These may need to be adjusted when download progress // TODO(paulg): These may need to be adjusted when download progress
// animation is added, and also possibly to take into account // animation is added, and also possibly to take into account
...@@ -230,6 +232,12 @@ DownloadItemView::DownloadItemView(DownloadItem* download_item, ...@@ -230,6 +232,12 @@ DownloadItemView::DownloadItemView(DownloadItem* download_item,
DownloadItemView::~DownloadItemView() { DownloadItemView::~DownloadItemView() {
StopDownloadProgress(); StopDownloadProgress();
download()->RemoveObserver(this); download()->RemoveObserver(this);
// ExperienceSampling: If the user took no action to remove the warning
// before it disappeared, then the user effectively dismissed the download
// without keeping it.
if (sampling_event_.get())
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kIgnore);
} }
// Progress animation handlers. // Progress animation handlers.
...@@ -550,6 +558,12 @@ void DownloadItemView::ButtonPressed(views::Button* sender, ...@@ -550,6 +558,12 @@ void DownloadItemView::ButtonPressed(views::Button* sender,
// The user has confirmed a dangerous download. We'd record how quickly the // The user has confirmed a dangerous download. We'd record how quickly the
// user did this to detect whether we're being clickjacked. // user did this to detect whether we're being clickjacked.
UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration); UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
// ExperienceSampling: User chose to proceed with a dangerous download.
if (sampling_event_.get()) {
sampling_event_->CreateUserDecisionEvent(
ExperienceSamplingEvent::kProceed);
sampling_event_.reset(NULL);
}
// This will change the state and notify us. // This will change the state and notify us.
download()->ValidateDangerousDownload(); download()->ValidateDangerousDownload();
return; return;
...@@ -559,6 +573,11 @@ void DownloadItemView::ButtonPressed(views::Button* sender, ...@@ -559,6 +573,11 @@ void DownloadItemView::ButtonPressed(views::Button* sender,
DCHECK_EQ(discard_button_, sender); DCHECK_EQ(discard_button_, sender);
if (model_.IsMalicious()) { if (model_.IsMalicious()) {
UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration); UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
// ExperienceSampling: User chose to dismiss the dangerous download.
if (sampling_event_.get()) {
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
sampling_event_.reset(NULL);
}
shelf_->RemoveDownloadView(this); shelf_->RemoveDownloadView(this);
return; return;
} }
...@@ -1127,6 +1146,11 @@ void DownloadItemView::ClearWarningDialog() { ...@@ -1127,6 +1146,11 @@ void DownloadItemView::ClearWarningDialog() {
body_state_ = NORMAL; body_state_ = NORMAL;
drop_down_state_ = NORMAL; drop_down_state_ = NORMAL;
// ExperienceSampling: User proceeded through the warning.
if (sampling_event_.get()) {
sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
sampling_event_.reset(NULL);
}
// Remove the views used by the warning dialog. // Remove the views used by the warning dialog.
if (save_button_) { if (save_button_) {
RemoveChildView(save_button_); RemoveChildView(save_button_);
...@@ -1170,6 +1194,17 @@ void DownloadItemView::ShowWarningDialog() { ...@@ -1170,6 +1194,17 @@ void DownloadItemView::ShowWarningDialog() {
#endif #endif
mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE; mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
// ExperienceSampling: Dangerous or malicious download warning is being shown
// to the user, so we start a new SamplingEvent and track it.
std::string event_name = model_.MightBeMalicious()
? ExperienceSamplingEvent::kMaliciousDownload
: ExperienceSamplingEvent::kDangerousDownload;
sampling_event_.reset(
new ExperienceSamplingEvent(event_name,
download()->GetURL(),
download()->GetReferrerUrl(),
download()->GetBrowserContext()));
body_state_ = NORMAL; body_state_ = NORMAL;
drop_down_state_ = NORMAL; drop_down_state_ = NORMAL;
if (mode_ == DANGEROUS_MODE) { if (mode_ == DANGEROUS_MODE) {
......
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
class DownloadShelfView; class DownloadShelfView;
class DownloadShelfContextMenuView; class DownloadShelfContextMenuView;
namespace extensions {
class ExperienceSamplingEvent;
}
namespace gfx { namespace gfx {
class Image; class Image;
class ImageSkia; class ImageSkia;
...@@ -339,6 +343,10 @@ class DownloadItemView : public views::ButtonListener, ...@@ -339,6 +343,10 @@ class DownloadItemView : public views::ButtonListener,
// and reload the icon. // and reload the icon.
base::FilePath last_download_item_path_; base::FilePath last_download_item_path_;
// ExperienceSampling: This tracks dangerous/malicious downloads warning UI
// and the user's decisions about it.
scoped_ptr<extensions::ExperienceSamplingEvent> sampling_event_;
DISALLOW_COPY_AND_ASSIGN(DownloadItemView); DISALLOW_COPY_AND_ASSIGN(DownloadItemView);
}; };
......
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