Commit 687503b4 authored by asanka@chromium.org's avatar asanka@chromium.org

Implement additional UI changes for dangerous download warnings.

When a download is detected as malicious by the safe browsing service, the download shelf now displays a warning message along with a 'Discard' button and a drop down menu with additional actions.

BUG=102540
TEST=Initiating a download that is flagged as malicious by the safe browsing service causes the new malicious download warning to be displayed on the download shelf.


Review URL: http://codereview.chromium.org/8757007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@112954 0039d316-1c4b-4281-b951-d872f2087c98
parent 2c40e1bb
......@@ -2328,11 +2328,11 @@ are declared in build/common.gypi.
</message>
<message name="IDS_PROMPT_MALICIOUS_DOWNLOAD_URL"
desc="Message shown to the user to validate the download when the download url is classified to lead to malware by the safebrowsing database.">
This file is malicious. Do you want to keep it anyway?
This file appears malicious.
</message>
<message name="IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT"
desc="Message shown to the user to validate the download when the download content is classified to lead to malware by safebrowsing.">
This file is malicious. Do you want to keep <ph name="FILE_NAME">$1<ex>malware.exe</ex></ph> anyway?
<ph name="FILE_NAME">$1<ex>malware.exe</ex></ph> appears malicious.
</message>
<message name="IDS_CONFIRM_DOWNLOAD"
desc="Text for the button used to validate the downloading of a dangerous download.">
......@@ -2449,6 +2449,18 @@ are declared in build/common.gypi.
desc="Download context menu resume download">
&amp;Resume
</message>
<message name="IDS_DOWNLOAD_MENU_DISCARD"
desc="Download context menu discard malicious download">
&amp;Discard
</message>
<message name="IDS_DOWNLOAD_MENU_KEEP"
desc="Download context menu keep malicious download">
&amp;Keep
</message>
<message name="IDS_DOWNLOAD_MENU_LEARN_MORE"
desc="Download context menu show information about download scanning">
&amp;Learn more
</message>
</if>
<if expr="pp_ifdef('use_titlecase')">
<message name="IDS_DOWNLOAD_MENU_OPEN_WHEN_COMPLETE"
......@@ -2475,6 +2487,18 @@ are declared in build/common.gypi.
desc="In Title Case: Download context menu resume download">
&amp;Resume
</message>
<message name="IDS_DOWNLOAD_MENU_DISCARD"
desc="In Title Case: Download context menu discard malicious download">
&amp;Discard
</message>
<message name="IDS_DOWNLOAD_MENU_KEEP"
desc="In Title Case: Download context menu keep malicious download">
&amp;Keep
</message>
<message name="IDS_DOWNLOAD_MENU_LEARN_MORE"
desc="In Title Case: Download context menu show information about download scanning">
&amp;Learn More
</message>
</if>
<!-- GRIT doesn't support nesting <if>...</if> blocks, so these can't -->
<!-- go within the above titlecase checks, instead markup must use -->
......
......@@ -6,9 +6,13 @@
#include "chrome/browser/download/download_item_model.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/url_constants.h"
#include "content/browser/download/download_item.h"
#include "content/browser/download/download_manager.h"
#include "content/browser/tab_contents/page_navigator.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
......@@ -21,8 +25,22 @@ DownloadShelfContextMenu::DownloadShelfContextMenu(
}
ui::SimpleMenuModel* DownloadShelfContextMenu::GetMenuModel() {
return download_item_->IsComplete() ? GetFinishedMenuModel()
: GetInProgressMenuModel();
ui::SimpleMenuModel* model = NULL;
if (download_item_->GetSafetyState() == DownloadItem::DANGEROUS) {
if (download_item_->GetDangerType() == DownloadStateInfo::DANGEROUS_URL ||
download_item_->GetDangerType() ==
DownloadStateInfo::DANGEROUS_CONTENT) {
model = GetMaliciousMenuModel();
} else {
NOTREACHED();
}
} else if (download_item_->IsComplete()) {
model = GetFinishedMenuModel();
} else {
model = GetInProgressMenuModel();
}
return model;
}
bool DownloadShelfContextMenu::IsCommandIdEnabled(int command_id) const {
......@@ -82,6 +100,22 @@ void DownloadShelfContextMenu::ExecuteCommand(int command_id) {
if (download_item_->IsPartialDownload())
download_item_->TogglePause();
break;
case DISCARD:
download_item_->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD);
break;
case KEEP:
download_item_->DangerousDownloadValidated();
break;
case LEARN_MORE: {
Browser* browser = BrowserList::GetLastActive();
DCHECK(browser && browser->is_type_tabbed());
GURL learn_more_url(chrome::kDownloadScanningLearnMoreURL);
OpenURLParams params(google_util::AppendGoogleLocaleParam(learn_more_url),
GURL(), NEW_FOREGROUND_TAB,
content::PAGE_TRANSITION_TYPED, false);
browser->OpenURL(params);
break;
}
default:
NOTREACHED();
}
......@@ -113,6 +147,12 @@ string16 DownloadShelfContextMenu::GetLabelForCommandId(int command_id) const {
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_RESUME_ITEM);
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_PAUSE_ITEM);
}
case DISCARD:
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_DISCARD);
case KEEP:
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_KEEP);
case LEARN_MORE:
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_MENU_LEARN_MORE);
default:
NOTREACHED();
break;
......@@ -161,3 +201,20 @@ ui::SimpleMenuModel* DownloadShelfContextMenu::GetFinishedMenuModel() {
return finished_download_menu_model_.get();
}
ui::SimpleMenuModel* DownloadShelfContextMenu::GetMaliciousMenuModel() {
if (malicious_download_menu_model_.get())
return malicious_download_menu_model_.get();
malicious_download_menu_model_.reset(new ui::SimpleMenuModel(this));
malicious_download_menu_model_->AddItemWithStringId(
DISCARD, IDS_DOWNLOAD_MENU_DISCARD);
malicious_download_menu_model_->AddItemWithStringId(
KEEP, IDS_DOWNLOAD_MENU_KEEP);
malicious_download_menu_model_->AddSeparator();
malicious_download_menu_model_->AddItemWithStringId(
LEARN_MORE, IDS_DOWNLOAD_MENU_LEARN_MORE);
return malicious_download_menu_model_.get();
}
......@@ -25,6 +25,9 @@ class DownloadShelfContextMenu : public ui::SimpleMenuModel::Delegate {
ALWAYS_OPEN_TYPE, // Default this file extension to always open.
CANCEL, // Cancel the download.
TOGGLE_PAUSE, // Temporarily pause a download.
DISCARD, // Discard the malicious download.
KEEP, // Keep the malicious download.
LEARN_MORE, // Show information about download scanning.
MENU_LAST
};
......@@ -53,11 +56,13 @@ class DownloadShelfContextMenu : public ui::SimpleMenuModel::Delegate {
private:
ui::SimpleMenuModel* GetInProgressMenuModel();
ui::SimpleMenuModel* GetFinishedMenuModel();
ui::SimpleMenuModel* GetMaliciousMenuModel();
// We show slightly different menus if the download is in progress vs. if the
// download has finished.
scoped_ptr<ui::SimpleMenuModel> in_progress_download_menu_model_;
scoped_ptr<ui::SimpleMenuModel> finished_download_menu_model_;
scoped_ptr<ui::SimpleMenuModel> malicious_download_menu_model_;
// A model to control the cancel behavior.
BaseDownloadItemModel* download_model_;
......
......@@ -91,6 +91,7 @@ DownloadItemView::DownloadItemView(DownloadItem* download,
status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
body_state_(NORMAL),
drop_down_state_(NORMAL),
mode_(NORMAL_MODE),
progress_angle_(download_util::kStartAngleDegrees),
drop_down_pressed_(false),
dragging_(false),
......@@ -102,7 +103,7 @@ DownloadItemView::DownloadItemView(DownloadItem* download,
dangerous_download_label_sized_(false),
disabled_while_opening_(false),
creation_time_(base::Time::Now()),
ALLOW_THIS_IN_INITIALIZER_LIST(reenable_method_factory_(this)) {
ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
DCHECK(download_);
download_->AddObserver(this);
......@@ -181,6 +182,8 @@ DownloadItemView::DownloadItemView(DownloadItem* download,
};
dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
malicious_mode_body_image_set_ = normal_body_image_set;
LoadIcon();
tooltip_text_ = download_->GetFileNameToReportUser().LossyDisplayName();
......@@ -196,23 +199,13 @@ DownloadItemView::DownloadItemView(DownloadItem* download,
else
box_y_ = kVerticalPadding;
gfx::Size size = GetPreferredSize();
if (base::i18n::IsRTL()) {
// Drop down button is glued to the left of the download shelf.
drop_down_x_left_ = 0;
drop_down_x_right_ = normal_drop_down_image_set_.top->width();
} else {
// Drop down button is glued to the right of the download shelf.
drop_down_x_left_ =
size.width() - normal_drop_down_image_set_.top->width();
drop_down_x_right_ = size.width();
}
body_hover_animation_.reset(new ui::SlideAnimation(this));
drop_hover_animation_.reset(new ui::SlideAnimation(this));
UpdateDropDownButtonPosition();
if (download->GetSafetyState() == DownloadItem::DANGEROUS)
EnterDangerousMode();
ShowWarningDialog();
UpdateAccessibleName();
set_accessibility_focusable(true);
......@@ -222,8 +215,6 @@ DownloadItemView::DownloadItemView(DownloadItem* download,
}
DownloadItemView::~DownloadItemView() {
if (context_menu_.get())
context_menu_->Stop();
icon_consumer_.CancelAllRequests();
StopDownloadProgress();
download_->RemoveObserver(this);
......@@ -263,13 +254,13 @@ void DownloadItemView::OnExtractIconComplete(IconManager::Handle handle,
void DownloadItemView::OnDownloadUpdated(DownloadItem* download) {
DCHECK(download == download_);
if (body_state_ == DANGEROUS &&
if (IsShowingWarningDialog() &&
download->GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED) {
// We have been approved.
ClearDangerousMode();
} else if (body_state_ != DANGEROUS &&
ClearWarningDialog();
} else if (!IsShowingWarningDialog() &&
download->GetSafetyState() == DownloadItem::DANGEROUS) {
EnterDangerousMode();
ShowWarningDialog();
// Force the shelf to layout again as our size has changed.
parent_->Layout();
SchedulePaint();
......@@ -329,7 +320,7 @@ void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&DownloadItemView::Reenable,
reenable_method_factory_.GetWeakPtr()),
weak_ptr_factory_.GetWeakPtr()),
kDisabledOnOpenDuration);
// Notify our parent.
......@@ -340,11 +331,14 @@ void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
// In dangerous mode we have to layout our buttons.
void DownloadItemView::Layout() {
if (IsDangerousMode()) {
if (IsShowingWarningDialog()) {
BodyImageSet* body_image_set =
(mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
&malicious_mode_body_image_set_;
dangerous_download_label_->SetEnabledColor(
GetThemeProvider()->GetColor(ThemeService::COLOR_BOOKMARK_TEXT));
int x = kLeftPadding + dangerous_mode_body_image_set_.top_left->width() +
int x = kLeftPadding + body_image_set->top_left->width() +
warning_icon_->width() + kLabelPadding;
int y = (height() - dangerous_download_label_->height()) / 2;
dangerous_download_label_->SetBounds(x, y,
......@@ -353,8 +347,10 @@ void DownloadItemView::Layout() {
gfx::Size button_size = GetButtonSize();
x += dangerous_download_label_->width() + kLabelPadding;
y = (height() - button_size.height()) / 2;
if (save_button_) {
save_button_->SetBounds(x, y, button_size.width(), button_size.height());
x += button_size.width() + kButtonPadding;
}
discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
}
}
......@@ -367,8 +363,11 @@ gfx::Size DownloadItemView::GetPreferredSize() {
// Then we increase the size if the progress icon doesn't fit.
height = std::max<int>(height, download_util::kSmallProgressIconSize);
if (IsDangerousMode()) {
width = kLeftPadding + dangerous_mode_body_image_set_.top_left->width();
if (IsShowingWarningDialog()) {
BodyImageSet* body_image_set =
(mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
&malicious_mode_body_image_set_;
width = kLeftPadding + body_image_set->top_left->width();
width += warning_icon_->width() + kLabelPadding;
width += dangerous_download_label_->width() + kLabelPadding;
gfx::Size button_size = GetButtonSize();
......@@ -377,8 +376,12 @@ gfx::Size DownloadItemView::GetPreferredSize() {
// Then we make sure the warning icon fits.
height = std::max<int>(height, 2 * kVerticalPadding +
warning_icon_->height());
width += button_size.width() * 2 + kButtonPadding;
width += dangerous_mode_body_image_set_.top_right->width();
if (save_button_)
width += button_size.width() + kButtonPadding;
width += button_size.width();
width += body_image_set->top_right->width();
if (mode_ == MALICIOUS_MODE)
width += normal_drop_down_image_set_.top->width();
} else {
width = kLeftPadding + normal_body_image_set_.top_left->width();
width += download_util::kSmallProgressIconSize;
......@@ -393,7 +396,7 @@ gfx::Size DownloadItemView::GetPreferredSize() {
// over the drop-down region.
bool DownloadItemView::OnMousePressed(const views::MouseEvent& event) {
// Mouse should not activate us in dangerous mode.
if (IsDangerousMode())
if (mode_ == DANGEROUS_MODE)
return true;
// Stop any completion animation.
......@@ -409,7 +412,9 @@ bool DownloadItemView::OnMousePressed(const views::MouseEvent& event) {
// keyboard invocation. I.e. we want the menu to always be positioned
// next to the drop down button instead of the next to the pointer.
ShowContextMenu(event.location(), false);
} else {
// Once called, it is possible that *this was deleted (e.g.: due to
// invoking the 'Discard' action.)
} else if (!IsShowingWarningDialog()) {
SetState(PUSHED, NORMAL);
}
}
......@@ -419,7 +424,7 @@ bool DownloadItemView::OnMousePressed(const views::MouseEvent& event) {
// Handle drag (file copy) operations.
bool DownloadItemView::OnMouseDragged(const views::MouseEvent& event) {
// Mouse should not activate us in dangerous mode.
if (IsDangerousMode())
if (IsShowingWarningDialog())
return true;
if (!starting_drag_) {
......@@ -447,11 +452,12 @@ bool DownloadItemView::OnMouseDragged(const views::MouseEvent& event) {
void DownloadItemView::OnMouseReleased(const views::MouseEvent& event) {
// Mouse should not activate us in dangerous mode.
if (IsDangerousMode())
if (mode_ == DANGEROUS_MODE)
return;
if (event.IsOnlyLeftMouseButton() &&
!InDropDownButtonXCoordinateRange(event.x())) {
!InDropDownButtonXCoordinateRange(event.x()) &&
!IsShowingWarningDialog()) {
OpenDownload();
}
......@@ -460,7 +466,7 @@ void DownloadItemView::OnMouseReleased(const views::MouseEvent& event) {
void DownloadItemView::OnMouseCaptureLost() {
// Mouse should not activate us in dangerous mode.
if (IsDangerousMode())
if (mode_ == DANGEROUS_MODE)
return;
if (dragging_) {
......@@ -474,15 +480,17 @@ void DownloadItemView::OnMouseCaptureLost() {
void DownloadItemView::OnMouseMoved(const views::MouseEvent& event) {
// Mouse should not activate us in dangerous mode.
if (IsDangerousMode())
if (mode_ == DANGEROUS_MODE)
return;
bool on_body = !InDropDownButtonXCoordinateRange(event.x());
SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
if (on_body) {
if (!IsShowingWarningDialog())
body_hover_animation_->Show();
drop_hover_animation_->Hide();
} else {
if (!IsShowingWarningDialog())
body_hover_animation_->Hide();
drop_hover_animation_->Show();
}
......@@ -490,17 +498,18 @@ void DownloadItemView::OnMouseMoved(const views::MouseEvent& event) {
void DownloadItemView::OnMouseExited(const views::MouseEvent& event) {
// Mouse should not activate us in dangerous mode.
if (IsDangerousMode())
if (mode_ == DANGEROUS_MODE)
return;
SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
if (!IsShowingWarningDialog())
body_hover_animation_->Hide();
drop_hover_animation_->Hide();
}
bool DownloadItemView::OnKeyPressed(const views::KeyEvent& event) {
// Key press should not activate us in dangerous mode.
if (IsDangerousMode())
if (IsShowingWarningDialog())
return true;
if (event.key_code() == ui::VKEY_SPACE ||
......@@ -544,24 +553,20 @@ void DownloadItemView::ShowContextMenu(const gfx::Point& p,
point.SetPoint(drop_down_x_left_, box_y_);
size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
}
// Post a task to release the button. When we call the Run method on the menu
// below, it runs an inner message loop that might cause us to be deleted.
// Posting a task with a WeakPtr lets us safely handle the button release.
MessageLoop::current()->PostNonNestableTask(
FROM_HERE,
base::Bind(&DownloadItemView::ReleaseDropDown,
weak_ptr_factory_.GetWeakPtr()));
views::View::ConvertPointToScreen(this, &point);
if (!context_menu_.get())
context_menu_.reset(new DownloadShelfContextMenuView(model_.get()));
// When we call the Run method on the menu, it runs an inner message loop
// that might causes us to be deleted.
if (context_menu_->Run(GetWidget()->GetTopLevelWidget(),
gfx::Rect(point, size)))
return; // We have been deleted! Don't access 'this'.
// If the menu action was to remove the download, this view will also be
// invalid so we must not access 'this' in this case.
if (context_menu_->download_item()) {
drop_down_pressed_ = false;
// Showing the menu blocks. Here we revert the state.
SetState(NORMAL, NORMAL);
}
context_menu_->Run(GetWidget()->GetTopLevelWidget(),
gfx::Rect(point, size));
// We could be deleted now.
}
void DownloadItemView::GetAccessibleState(ui::AccessibleViewState* state) {
......@@ -583,7 +588,7 @@ void DownloadItemView::ButtonPressed(
download_->Cancel(true);
download_->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD);
// WARNING: we are deleted at this point. Don't access 'this'.
} else if (sender == save_button_) {
} else if (save_button_ && sender == save_button_) {
// The user has confirmed a dangerous download. We'd record how quickly the
// user did this to detect whether we're being clickjacked.
UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
......@@ -599,33 +604,74 @@ void DownloadItemView::AnimationProgressed(const ui::Animation* animation) {
SchedulePaint();
}
// The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
// and MALICIOUS_MODE).
//
// NORMAL_MODE: We are displaying an in-progress or completed download.
// .-------------------------------+-.
// | [icon] Filename |v|
// | [ ] Status | |
// `-------------------------------+-'
// | | \_ Drop down button. Invokes menu. Responds
// | | to mouse. (NORMAL, HOT or PUSHED).
// | \_ Icon is overlaid on top of in-progress animation.
// \_ Both the body and the drop down button respond to mouse hover and can be
// pushed (NORMAL, HOT or PUSHED).
//
// DANGEROUS_MODE: The file could be potentially dangerous.
// .-------------------------------------------------------.
// | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
// | [ ] [destroy your computer..] [ ] [ ] |
// `-------------------------------------------------------'
// | | | | \_ No drop down button.
// | | | \_ Buttons are views::TextButtons.
// | | \_ Text is in a label (dangerous_download_label_)
// | \_ Warning icon. No progress animation.
// \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
//
// MALICIOUS_MODE: The file is known malware.
// .---------------------------------------------+-.
// | [ - ] [This file is malicious.] [ Discard ] |v|
// | [ ] [ ] [ ] | |-.
// `---------------------------------------------+-' |
// | | | | Drop down button. Responds to
// | | | | mouse.(NORMAL, HOT or PUSHED)
// | | | \_ Button is a views::TextButton.
// | | \_ Text is in a label (dangerous_download_label_)
// | \_ Warning icon. No progress animation.
// \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
//
void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
BodyImageSet* body_image_set = NULL;
switch (body_state_) {
case NORMAL:
case HOT:
body_image_set = &normal_body_image_set_;
break;
case PUSHED:
switch (mode_) {
case NORMAL_MODE:
if (body_state_ == PUSHED)
body_image_set = &pushed_body_image_set_;
else // NORMAL or HOT
body_image_set = &normal_body_image_set_;
break;
case DANGEROUS:
case DANGEROUS_MODE:
body_image_set = &dangerous_mode_body_image_set_;
break;
case MALICIOUS_MODE:
body_image_set = &malicious_mode_body_image_set_;
break;
default:
NOTREACHED();
}
DropDownImageSet* drop_down_image_set = NULL;
switch (drop_down_state_) {
case NORMAL:
case HOT:
drop_down_image_set = &normal_drop_down_image_set_;
break;
case PUSHED:
switch (mode_) {
case NORMAL_MODE:
case MALICIOUS_MODE:
if (drop_down_state_ == PUSHED)
drop_down_image_set = &pushed_drop_down_image_set_;
else // NORMAL or HOT
drop_down_image_set = &normal_drop_down_image_set_;
break;
case DANGEROUS:
drop_down_image_set = NULL; // No drop-down in dangerous mode.
case DANGEROUS_MODE:
// We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
// drop_down_image_set == NULL.
break;
default:
NOTREACHED();
......@@ -642,8 +688,9 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
if (center_width <= 0)
return;
// Draw status before button image to effectively lighten text.
if (!IsDangerousMode()) {
// Draw status before button image to effectively lighten text. No status for
// warning dialogs.
if (!IsShowingWarningDialog()) {
if (!status_text_.empty()) {
int mirrored_x = GetMirroredXWithWidthInView(
download_util::kSmallProgressIconSize, kTextWidth);
......@@ -695,8 +742,9 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
body_image_set->bottom_right,
x, box_y_, box_height_, body_image_set->top_right->width());
// Overlay our body hot state.
if (body_hover_animation_->GetCurrentValue() > 0) {
// Overlay our body hot state. Warning dialogs don't display body a hot state.
if (!IsShowingWarningDialog() &&
body_hover_animation_->GetCurrentValue() > 0) {
canvas->SaveLayerAlpha(
static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
canvas->GetSkCanvas()->drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode);
......@@ -753,7 +801,7 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
// Print the text, left aligned and always print the file extension.
// Last value of x was the end of the right image, just before the button.
// Note that in dangerous mode we use a label (as the text is multi-line).
if (!IsDangerousMode()) {
if (!IsShowingWarningDialog()) {
string16 filename;
if (!disabled_while_opening_) {
filename = ui::ElideFilename(download_->GetFileNameToReportUser(),
......@@ -792,7 +840,7 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
gfx::Image* image = im->LookupIcon(download_->GetUserVerifiedFilePath(),
IconLoader::SMALL);
const SkBitmap* icon = NULL;
if (IsDangerousMode())
if (IsShowingWarningDialog())
icon = warning_icon_;
else if (image)
icon = *image;
......@@ -803,7 +851,7 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
// loaded, in which case LookupIcon will always be NULL. The loading will be
// triggered only when we think the status might change.
if (icon) {
if (!IsDangerousMode()) {
if (!IsShowingWarningDialog()) {
if (download_->IsInProgress()) {
download_util::PaintDownloadProgress(canvas, this, 0, 0,
progress_angle_,
......@@ -827,7 +875,7 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
// Draw the icon image.
int icon_x, icon_y;
if (IsDangerousMode()) {
if (IsShowingWarningDialog()) {
icon_x = kLeftPadding + body_image_set->top_left->width();
icon_y = (height() - icon->height()) / 2;
} else {
......@@ -847,6 +895,7 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
}
void DownloadItemView::OpenDownload() {
DCHECK(!IsShowingWarningDialog());
// We're interested in how long it takes users to open downloads. If they
// open downloads super quickly, we should be concerned about clickjacking.
UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
......@@ -897,6 +946,16 @@ void DownloadItemView::PaintBitmaps(gfx::Canvas* canvas,
}
void DownloadItemView::SetState(State body_state, State drop_down_state) {
// If we are showing a warning dialog, we don't change body state.
if (IsShowingWarningDialog()) {
body_state = NORMAL;
// Current body_state_ should always be NORMAL for warning dialogs.
DCHECK(body_state_ == NORMAL);
// We shouldn't be calling SetState if we are in DANGEROUS_MODE.
DCHECK(mode_ != DANGEROUS_MODE);
}
// Avoid extra SchedulePaint()s if the state is going to be the same.
if (body_state_ == body_state && drop_down_state_ == drop_down_state)
return;
......@@ -905,17 +964,20 @@ void DownloadItemView::SetState(State body_state, State drop_down_state) {
SchedulePaint();
}
void DownloadItemView::ClearDangerousMode() {
void DownloadItemView::ClearWarningDialog() {
DCHECK(download_->GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED &&
body_state_ == DANGEROUS && drop_down_state_ == DANGEROUS);
(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE));
mode_ = NORMAL_MODE;
body_state_ = NORMAL;
drop_down_state_ = NORMAL;
// Remove the views used by the dangerous mode.
// Remove the views used by the warning dialog.
if (save_button_) {
RemoveChildView(save_button_);
delete save_button_;
save_button_ = NULL;
}
RemoveChildView(discard_button_);
delete discard_button_;
discard_button_ = NULL;
......@@ -923,10 +985,12 @@ void DownloadItemView::ClearDangerousMode() {
delete dangerous_download_label_;
dangerous_download_label_ = NULL;
dangerous_download_label_sized_ = false;
cached_button_size_.SetSize(0,0);
// Set the accessible name back to the status and filename instead of the
// download warning.
UpdateAccessibleName();
UpdateDropDownButtonPosition();
// We need to load the icon now that the download_ has the real path.
LoadIcon();
......@@ -937,20 +1001,29 @@ void DownloadItemView::ClearDangerousMode() {
parent_->SchedulePaint();
}
void DownloadItemView::EnterDangerousMode() {
DCHECK(body_state_ != DANGEROUS && drop_down_state_ != DANGEROUS);
void DownloadItemView::ShowWarningDialog() {
DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
if (download_->GetDangerType() == DownloadStateInfo::DANGEROUS_URL ||
download_->GetDangerType() == DownloadStateInfo::DANGEROUS_CONTENT) {
mode_ = MALICIOUS_MODE;
} else {
DCHECK(download_->GetDangerType() == DownloadStateInfo::DANGEROUS_FILE);
mode_ = DANGEROUS_MODE;
}
body_state_ = NORMAL;
drop_down_state_ = NORMAL;
tooltip_text_.clear();
body_state_ = DANGEROUS;
drop_down_state_ = DANGEROUS;
if (mode_ == DANGEROUS_MODE) {
save_button_ = new views::NativeTextButton(this,
l10n_util::GetStringUTF16(
ChromeDownloadManagerDelegate::IsExtensionDownload(download_) ?
IDS_CONTINUE_EXTENSION_DOWNLOAD : IDS_CONFIRM_DOWNLOAD));
save_button_->set_ignore_minimum_size(true);
AddChildView(save_button_);
}
discard_button_ = new views::NativeTextButton(
this, l10n_util::GetStringUTF16(IDS_DISCARD_DOWNLOAD));
discard_button_->set_ignore_minimum_size(true);
AddChildView(save_button_);
AddChildView(discard_button_);
// Ensure the file name is not too long.
......@@ -982,11 +1055,11 @@ void DownloadItemView::EnterDangerousMode() {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
// The dangerous download label text and icon are different
// under different cases.
if (download_->GetDangerType() == DownloadStateInfo::DANGEROUS_URL ||
download_->GetDangerType() == DownloadStateInfo::DANGEROUS_CONTENT) {
if (mode_ == MALICIOUS_MODE) {
warning_icon_ = rb.GetBitmapNamed(IDR_SAFEBROWSING_WARNING);
} else {
DCHECK(download_->GetDangerType() == DownloadStateInfo::DANGEROUS_FILE);
// The download file has dangerous file type (e.g.: an executable).
warning_icon_ = rb.GetBitmapNamed(IDR_WARNING);
}
string16 dangerous_label;
......@@ -1017,15 +1090,15 @@ void DownloadItemView::EnterDangerousMode() {
dangerous_download_label_ = new views::Label(dangerous_label);
dangerous_download_label_->SetMultiLine(true);
dangerous_download_label_->SetHorizontalAlignment(
views::Label::ALIGN_LEFT);
dangerous_download_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
AddChildView(dangerous_download_label_);
SizeLabelToMinWidth();
UpdateDropDownButtonPosition();
}
gfx::Size DownloadItemView::GetButtonSize() {
DCHECK(save_button_ && discard_button_);
DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
gfx::Size size;
// We cache the size when successfully retrieved, not for performance reasons
......@@ -1035,6 +1108,7 @@ gfx::Size DownloadItemView::GetButtonSize() {
if (cached_button_size_.width() != 0)
return cached_button_size_;
if (save_button_)
size = save_button_->GetMinimumSize();
gfx::Size discard_size = discard_button_->GetMinimumSize();
......@@ -1062,8 +1136,6 @@ void DownloadItemView::SizeLabelToMinWidth() {
// current width.
dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
gfx::Size size;
int min_width = -1;
// Using BREAK_WORD can work in most cases, but it can also break
// lines where it should not. Using BREAK_LINE is safer although
// slower for Chinese/Japanese. This is not perf-critical at all, though.
......@@ -1073,7 +1145,15 @@ void DownloadItemView::SizeLabelToMinWidth() {
string16 current_text = text;
string16 prev_text = text;
while (iter.Advance()) {
gfx::Size size = dangerous_download_label_->GetPreferredSize();
int min_width = size.width();
// Go through the string and try each line break (starting with no line break)
// searching for the optimal line break position. Stop if we find one that
// yields one that is less than kDangerousTextWidth wide. This is to prevent
// a short string (e.g.: "This file is malicious") from being broken up
// unnecessarily.
while (iter.Advance() && min_width > kDangerousTextWidth) {
size_t pos = iter.pos();
if (pos >= text.length())
break;
......@@ -1088,9 +1168,6 @@ void DownloadItemView::SizeLabelToMinWidth() {
dangerous_download_label_->SetText(current_text);
size = dangerous_download_label_->GetPreferredSize();
if (min_width == -1)
min_width = size.width();
// If the width is growing again, it means we passed the optimal width spot.
if (size.width() > min_width) {
dangerous_download_label_->SetText(prev_text);
......@@ -1104,11 +1181,6 @@ void DownloadItemView::SizeLabelToMinWidth() {
current_text = text;
}
// If we have a line with no line breaking opportunity (which is very
// unlikely), we won't cut it.
if (min_width == -1)
size = dangerous_download_label_->GetPreferredSize();
dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
dangerous_download_label_sized_ = true;
}
......@@ -1118,6 +1190,11 @@ void DownloadItemView::Reenable() {
SetEnabled(true); // Triggers a repaint.
}
void DownloadItemView::ReleaseDropDown() {
drop_down_pressed_ = false;
SetState(NORMAL, NORMAL);
}
bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
if (x > drop_down_x_left_ && x < drop_down_x_right_)
return true;
......@@ -1126,7 +1203,7 @@ bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
void DownloadItemView::UpdateAccessibleName() {
string16 new_name;
if (download_->GetSafetyState() == DownloadItem::DANGEROUS) {
if (IsShowingWarningDialog()) {
new_name = dangerous_download_label_->GetText();
} else {
new_name = status_text_ + char16(' ') +
......@@ -1143,3 +1220,17 @@ void DownloadItemView::UpdateAccessibleName() {
}
}
}
void DownloadItemView::UpdateDropDownButtonPosition() {
gfx::Size size = GetPreferredSize();
if (base::i18n::IsRTL()) {
// Drop down button is glued to the left of the download shelf.
drop_down_x_left_ = 0;
drop_down_x_right_ = normal_drop_down_image_set_.top->width();
} else {
// Drop down button is glued to the right of the download shelf.
drop_down_x_left_ =
size.width() - normal_drop_down_image_set_.top->width();
drop_down_x_right_ = size.width();
}
}
......@@ -109,8 +109,13 @@ class DownloadItemView : public views::ButtonListener,
enum State {
NORMAL = 0,
HOT,
PUSHED,
DANGEROUS
PUSHED
};
enum Mode {
NORMAL_MODE = 0, // Showing download item.
DANGEROUS_MODE, // Displaying the dangerous download warning.
MALICIOUS_MODE // Displaying the malicious download warning.
};
// The image set associated with the part containing the icon and text.
......@@ -153,13 +158,16 @@ class DownloadItemView : public views::ButtonListener,
void SetState(State body_state, State drop_down_state);
// Whether we are in the dangerous mode.
bool IsDangerousMode() { return body_state_ == DANGEROUS; }
bool IsShowingWarningDialog() {
return mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE;
}
// Reverts from dangerous mode to normal download mode.
void ClearDangerousMode();
void ClearWarningDialog();
// Start displaying the dangerous download warning.
void EnterDangerousMode();
// Start displaying the dangerous download warning or the malicious download
// warning.
void ShowWarningDialog();
// Sets |size| with the size of the Save and Discard buttons (they have the
// same size).
......@@ -174,6 +182,9 @@ class DownloadItemView : public views::ButtonListener,
// open the downloaded file.
void Reenable();
// Releases drop down button after showing a context menu.
void ReleaseDropDown();
// Given |x|, returns whether |x| is within the x coordinate range of
// the drop-down button or not.
bool InDropDownButtonXCoordinateRange(int x);
......@@ -183,11 +194,15 @@ class DownloadItemView : public views::ButtonListener,
// dangerous download warning message (if any).
void UpdateAccessibleName();
// Update the location of the drop down button.
void UpdateDropDownButtonPosition();
// The different images used for the background.
BodyImageSet normal_body_image_set_;
BodyImageSet hot_body_image_set_;
BodyImageSet pushed_body_image_set_;
BodyImageSet dangerous_mode_body_image_set_;
BodyImageSet malicious_mode_body_image_set_;
DropDownImageSet normal_drop_down_image_set_;
DropDownImageSet hot_drop_down_image_set_;
DropDownImageSet pushed_drop_down_image_set_;
......@@ -214,6 +229,9 @@ class DownloadItemView : public views::ButtonListener,
State body_state_;
State drop_down_state_;
// Mode of the download item view.
Mode mode_;
// In degrees, for downloads with no known total size.
int progress_angle_;
......@@ -278,7 +296,7 @@ class DownloadItemView : public views::ButtonListener,
// Method factory used to delay reenabling of the item when opening the
// downloaded file.
base::WeakPtrFactory<DownloadItemView> reenable_method_factory_;
base::WeakPtrFactory<DownloadItemView> weak_ptr_factory_;
// The currently running download context menu.
scoped_ptr<DownloadShelfContextMenuView> context_menu_;
......
......@@ -21,7 +21,7 @@ DownloadShelfContextMenuView::DownloadShelfContextMenuView(
DownloadShelfContextMenuView::~DownloadShelfContextMenuView() {}
bool DownloadShelfContextMenuView::Run(views::Widget* parent_widget,
void DownloadShelfContextMenuView::Run(views::Widget* parent_widget,
const gfx::Rect& rect) {
views::MenuModelAdapter menu_model_adapter(GetMenuModel());
menu_runner_.reset(new views::MenuRunner(menu_model_adapter.CreateMenu()));
......@@ -32,14 +32,15 @@ bool DownloadShelfContextMenuView::Run(views::Widget* parent_widget,
position = views::MenuItemView::TOPRIGHT;
else
position = views::MenuItemView::TOPLEFT;
return menu_runner_->RunMenuAt(
// The return value of RunMenuAt indicates whether the MenuRunner was deleted
// while running the menu, which indicates that the containing view may have
// been deleted. We ignore the return value because our caller already assumes
// that the view could be deleted by the time we return from here.
ignore_result(menu_runner_->RunMenuAt(
parent_widget,
NULL,
rect,
position,
views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED;
}
void DownloadShelfContextMenuView::Stop() {
set_download_item(NULL);
views::MenuRunner::HAS_MNEMONICS));
}
......@@ -27,15 +27,9 @@ class DownloadShelfContextMenuView : public DownloadShelfContextMenu {
explicit DownloadShelfContextMenuView(BaseDownloadItemModel* model);
virtual ~DownloadShelfContextMenuView();
// Returns true if menu has been deleted. |rect| is the bounding area for
// positioning the menu in screen coordinates. The menu will be positioned
// above or below but not overlapping |rect|.
bool Run(views::Widget* parent_widget,
const gfx::Rect& rect) WARN_UNUSED_RESULT;
// This method runs when the caller has been deleted and we should not attempt
// to access download_item().
void Stop();
// |rect| is the bounding area for positioning the menu in screen coordinates.
// The menu will be positioned above or below but not overlapping |rect|.
void Run(views::Widget* parent_widget, const gfx::Rect& rect);
private:
scoped_ptr<views::MenuRunner> menu_runner_;
......
......@@ -322,6 +322,9 @@ const char kLearnMoreRegisterProtocolHandlerURL[] =
const char kSyncLearnMoreURL[] =
"http://www.google.com/support/chrome/bin/answer.py?answer=165139";
const char kDownloadScanningLearnMoreURL[] =
"http://www.google.com/support/chrome/bin/answer.py?answer=99020";
#if defined(OS_CHROMEOS)
const char kCloudPrintLearnMoreURL[] =
"https://www.google.com/support/chromeos/bin/topic.py?topic=29023";
......
......@@ -293,6 +293,9 @@ extern const char kLearnMoreRegisterProtocolHandlerURL[];
// The URL for the "Learn more" page for sync setup on the personal stuff page.
extern const char kSyncLearnMoreURL[];
// The URL for the "Learn more" page for download scanning.
extern const char kDownloadScanningLearnMoreURL[];
// "Debug" pages which are dangerous and not for general consumption.
extern const char* const kChromeDebugURLs[];
extern int kNumberOfChromeDebugURLs;
......
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