diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc index 29adec1..0650011 100644 --- a/chrome/browser/download/chrome_download_manager_delegate.cc +++ b/chrome/browser/download/chrome_download_manager_delegate.cc @@ -249,6 +249,8 @@ void ChromeDownloadManagerDelegate::ReturnNextId( callback.Run(next_download_id_++); } +// Advanced Chrome +/* bool ChromeDownloadManagerDelegate::DetermineDownloadTarget( DownloadItem* download, const content::DownloadTargetCallback& callback) { @@ -265,6 +267,50 @@ bool ChromeDownloadManagerDelegate::DetermineDownloadTarget( target_determined_callback); return true; } +*/ +// Advanced Chrome + +// Advanced Chrome +bool ChromeDownloadManagerDelegate::DetermineDownloadTarget( + DownloadItem* download, + const content::DownloadTargetCallback& callback) { + content::WebContents* web_contents = download->GetWebContents(); + Browser* browser = web_contents ? chrome::FindBrowserWithWebContents(web_contents) : NULL; + if (browser) { + if (download->GetTargetDisposition() != DownloadItem::TARGET_DISPOSITION_PROMPT) { + browser->OnDownloadOptions(download); + } else { + SetupDownload(download->GetId(), 1, callback); + } + } + return true; +} +// Advanced Chrome + +// Advanced Chrome +void ChromeDownloadManagerDelegate::SetupDownload( + int32 download_id, + int32 download_option, + const content::DownloadTargetCallback& callback) { + + DownloadItem* download = + download_manager_->GetDownload(download_id); + + download->SetOperation(download_option); + + DownloadTargetDeterminer::CompletionCallback target_determined_callback = + base::Bind(&ChromeDownloadManagerDelegate::OnDownloadTargetDetermined, + weak_ptr_factory_.GetWeakPtr(), + download->GetId(), + callback); + DownloadTargetDeterminer::Start( + download, + GetPlatformDownloadPath(profile_, download, PLATFORM_TARGET_PATH), + download_prefs_.get(), + this, + target_determined_callback); +} +// Advanced Chrome bool ChromeDownloadManagerDelegate::ShouldOpenFileBasedOnExtension( const base::FilePath& path) { diff --git a/chrome/browser/download/chrome_download_manager_delegate.h b/chrome/browser/download/chrome_download_manager_delegate.h index d3eee8f..bc35f60 100644 --- a/chrome/browser/download/chrome_download_manager_delegate.h +++ b/chrome/browser/download/chrome_download_manager_delegate.h @@ -56,6 +56,14 @@ class ChromeDownloadManagerDelegate // content::DownloadManagerDelegate void Shutdown() override; void GetNextId(const content::DownloadIdCallback& callback) override; + +// Advanced Chrome + void SetupDownload( + int32 download_id, + int32 download_option, + const content::DownloadTargetCallback& callback); +// Advanced Chrome + bool DetermineDownloadTarget( content::DownloadItem* item, const content::DownloadTargetCallback& callback) override; diff --git a/chrome/browser/download/download_commands.cc b/chrome/browser/download/download_commands.cc index abc6a05..fbeff11 100644 --- a/chrome/browser/download/download_commands.cc +++ b/chrome/browser/download/download_commands.cc @@ -75,6 +75,12 @@ gfx::Image DownloadCommands::GetCommandIcon(Command command) { bool DownloadCommands::IsCommandEnabled(Command command) const { switch (command) { + +// Advanced Chrome + case HIDE_DOWNLOAD_ITEM_VIEW: + return true; +// Advanced Chrome + case SHOW_IN_FOLDER: return download_item_->CanShowInFolder(); case OPEN_WHEN_COMPLETE: @@ -147,6 +153,13 @@ bool DownloadCommands::IsCommandVisible(Command command) const { void DownloadCommands::ExecuteCommand(Command command) { switch (command) { + +// Advanced Chrome + case HIDE_DOWNLOAD_ITEM_VIEW: + download_item_->SetHidden(true); + break; +// Advanced Chrome + case SHOW_IN_FOLDER: download_item_->ShowDownloadInShell(); break; diff --git a/chrome/browser/download/download_commands.h b/chrome/browser/download/download_commands.h index 1a39886..f6d46df 100644 --- a/chrome/browser/download/download_commands.h +++ b/chrome/browser/download/download_commands.h @@ -15,6 +15,11 @@ class DownloadCommands { public: enum Command { + +// Advanced Chrome + HIDE_DOWNLOAD_ITEM_VIEW, +// Advanced Chrome + SHOW_IN_FOLDER = 1, // Open a folder view window with the item selected. OPEN_WHEN_COMPLETE, // Open the download when it's finished. ALWAYS_OPEN_TYPE, // Default this file extension to always open. diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc index 6ef6de5..4686b46 100644 --- a/chrome/browser/download/download_item_model.cc +++ b/chrome/browser/download/download_item_model.cc @@ -678,6 +678,8 @@ base::string16 DownloadItemModel::GetInProgressStatusString() const { l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED)); } +// Advanced Chrome +/* // A download scheduled to be opened when complete: "Opening in 10 secs" if (download_->GetOpenWhenComplete()) { if (!time_remaining_known) @@ -696,6 +698,20 @@ base::string16 DownloadItemModel::GetInProgressStatusString() const { ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, ui::TimeFormat::LENGTH_SHORT, time_remaining)); } +*/ +// Advanced Chrome + +// Advanced Chrome + int64 speed = download_->CurrentSpeed(); + base::string16 time_remaining_plus_speed = ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, ui::TimeFormat::LENGTH_SHORT, time_remaining) + L", " + ui::FormatSpeed(speed); + if (time_remaining_known) { + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio, + time_remaining_plus_speed); + } + if (GetCompletedBytes() > 0) + return size_ratio + L", " + ui::FormatSpeed(speed); +// Advanced Chrome // In progress download with no known time left and non-zero completed bytes: // "100/120 MB" or "100 MB" diff --git a/chrome/browser/download/download_options_item_model.cc b/chrome/browser/download/download_options_item_model.cc new file mode 100644 index 0000000..39f75ff --- /dev/null +++ b/chrome/browser/download/download_options_item_model.cc @@ -0,0 +1,740 @@ +// Copyright (c) 2012 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. + +// Advanced Chrome + +#include "chrome/browser/download/download_options_item_model.h" + +#include "base/i18n/number_formatting.h" +#include "base/i18n/rtl.h" +#include "base/metrics/field_trial.h" +#include "base/strings/string16.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/supports_user_data.h" +#include "base/time/time.h" +#include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/browser/download/download_crx_util.h" +#include "chrome/browser/download/download_history.h" +#include "chrome/browser/download/download_service.h" +#include "chrome/browser/download/download_service_factory.h" +#include "chrome/browser/download/download_stats.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/safe_browsing/download_feedback_service.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "content/public/browser/download_danger_type.h" +#include "content/public/browser/download_interrupt_reasons.h" +#include "content/public/browser/download_item.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/l10n/time_format.h" +#include "ui/base/text/bytes_formatting.h" +#include "ui/gfx/text_elider.h" + +using base::TimeDelta; +using content::DownloadItem; + +namespace { + +// Per DownloadItem data used by DownloadOptionsItemModel. The model doesn't keep any +// state since there could be multiple models associated with a single +// DownloadItem, and the lifetime of the model is shorter than the DownloadItem. +class DownloadOptionsItemModelData : public base::SupportsUserData::Data { + public: + // Get the DownloadOptionsItemModelData object for |download|. Returns NULL if + // there's no model data. + static const DownloadOptionsItemModelData* Get(const DownloadItem* download); + + // Get the DownloadOptionsItemModelData object for |download|. Creates a model data + // object if not found. Always returns a non-NULL pointer, unless OOM. + static DownloadOptionsItemModelData* GetOrCreate(DownloadItem* download); + + // Whether the download should be displayed in the download shelf. True by + // default. + bool should_show_in_shelf_; + + // Whether the UI has been notified about this download. + bool was_ui_notified_; + + // Whether the download should be opened in the browser vs. the system handler + // for the file type. + bool should_prefer_opening_in_browser_; + + // Whether the download should be considered dangerous if SafeBrowsing doesn't + // come up with a verdict. + bool is_dangerous_file_based_on_type_; + + // Whether the download is currently being revived. + bool is_being_revived_; + + private: + DownloadOptionsItemModelData(); + ~DownloadOptionsItemModelData() override {} + + static const char kKey[]; +}; + +// static +const char DownloadOptionsItemModelData::kKey[] = "DownloadOptionsItemModelData key"; + +// static +const DownloadOptionsItemModelData* DownloadOptionsItemModelData::Get( + const DownloadItem* download) { + return static_cast(download->GetUserData(kKey)); +} + +// static +DownloadOptionsItemModelData* DownloadOptionsItemModelData::GetOrCreate( + DownloadItem* download) { + DownloadOptionsItemModelData* data = + static_cast(download->GetUserData(kKey)); + if (data == NULL) { + data = new DownloadOptionsItemModelData(); + download->SetUserData(kKey, data); + } + return data; +} + +DownloadOptionsItemModelData::DownloadOptionsItemModelData() + : should_show_in_shelf_(true), + was_ui_notified_(false), + should_prefer_opening_in_browser_(false), + is_dangerous_file_based_on_type_(false), + is_being_revived_(false) { +} + +base::string16 InterruptReasonStatusMessage(int reason) { + int string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS; + + switch (static_cast(reason)) { + case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_ACCESS_DENIED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_DISK_FULL; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_PATH_TOO_LONG; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FILE_TOO_LARGE; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_VIRUS; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_TEMPORARY_PROBLEM; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_BLOCKED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SECURITY_CHECK_FAILED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FILE_TOO_SHORT; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST: + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_ERROR; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_TIMEOUT; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_DISCONNECTED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_DOWN; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_PROBLEM; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NO_FILE; + break; + case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED: + string_id = IDS_DOWNLOAD_STATUS_CANCELLED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SHUTDOWN; + break; + case content::DOWNLOAD_INTERRUPT_REASON_CRASH: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_CRASH; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_UNAUTHORIZED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_CERT_PROBLEM; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FORBIDDEN; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NONE: + NOTREACHED(); + // fallthrough + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE: + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION: + case content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS; + } + + return l10n_util::GetStringUTF16(string_id); +} + +base::string16 InterruptReasonMessage(int reason) { + int string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS; + base::string16 status_text; + + switch (static_cast(reason)) { + case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_ACCESS_DENIED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_DISK_FULL; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_PATH_TOO_LONG; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_LARGE; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_VIRUS; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_TEMPORARY_PROBLEM; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_BLOCKED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SECURITY_CHECK_FAILED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_SHORT; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST: + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_ERROR; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_TIMEOUT; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_DISCONNECTED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_DOWN; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_PROBLEM; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NO_FILE; + break; + case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED: + string_id = IDS_DOWNLOAD_STATUS_CANCELLED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SHUTDOWN; + break; + case content::DOWNLOAD_INTERRUPT_REASON_CRASH: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_CRASH; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_UNAUTHORIZED; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_CERT_PROBLEM; + break; + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN: + string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FORBIDDEN; + break; + case content::DOWNLOAD_INTERRUPT_REASON_NONE: + NOTREACHED(); + // fallthrough + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE: + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION: + case content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED: + string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS; + } + + status_text = l10n_util::GetStringUTF16(string_id); + + return status_text; +} + +} // namespace + +// ----------------------------------------------------------------------------- +// DownloadOptionsItemModel + +DownloadOptionsItemModel::DownloadOptionsItemModel(DownloadItem* download) + : download_(download) {} + +DownloadOptionsItemModel::~DownloadOptionsItemModel() {} + +base::string16 DownloadOptionsItemModel::GetInterruptReasonText() const { + if (download_->GetState() != DownloadItem::INTERRUPTED || + download_->GetLastReason() == + content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) { + return base::string16(); + } + return InterruptReasonMessage(download_->GetLastReason()); +} + +base::string16 DownloadOptionsItemModel::GetStatusText() const { + base::string16 status_text; + switch (download_->GetState()) { + case DownloadItem::IN_PROGRESS: + status_text = GetInProgressStatusString(); + break; + case DownloadItem::COMPLETE: + if (download_->GetFileExternallyRemoved()) { + status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED); + } else { + status_text.clear(); + } + break; + case DownloadItem::CANCELLED: + status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED); + break; + case DownloadItem::INTERRUPTED: { + content::DownloadInterruptReason reason = download_->GetLastReason(); + if (reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) { + base::string16 interrupt_reason = InterruptReasonStatusMessage(reason); + status_text = l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_STATUS_INTERRUPTED, interrupt_reason); + } else { + // Same as DownloadItem::CANCELLED. + status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED); + } + break; + } + default: + NOTREACHED(); + } + + return status_text; +} + +base::string16 DownloadOptionsItemModel::GetTabProgressStatusText() const { + int64 total = GetTotalBytes(); + int64 size = download_->GetReceivedBytes(); + base::string16 received_size = ui::FormatBytes(size); + base::string16 amount = received_size; + + // Adjust both strings for the locale direction since we don't yet know which + // string we'll end up using for constructing the final progress string. + base::i18n::AdjustStringForLocaleDirection(&amount); + + if (total) { + base::string16 total_text = ui::FormatBytes(total); + base::i18n::AdjustStringForLocaleDirection(&total_text); + + base::i18n::AdjustStringForLocaleDirection(&received_size); + amount = l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_TAB_PROGRESS_SIZE, received_size, total_text); + } else { + amount.assign(received_size); + } + int64 current_speed = download_->CurrentSpeed(); + base::string16 speed_text = ui::FormatSpeed(current_speed); + base::i18n::AdjustStringForLocaleDirection(&speed_text); + + base::TimeDelta remaining; + base::string16 time_remaining; + if (download_->IsPaused()) { + time_remaining = l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED); + } else if (download_->TimeRemaining(&remaining)) { + time_remaining = ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, + ui::TimeFormat::LENGTH_SHORT, + remaining); + } + + if (time_remaining.empty()) { + base::i18n::AdjustStringForLocaleDirection(&amount); + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_TAB_PROGRESS_STATUS_TIME_UNKNOWN, speed_text, amount); + } + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_TAB_PROGRESS_STATUS, speed_text, amount, time_remaining); +} + +base::string16 DownloadOptionsItemModel::GetTooltipText(const gfx::FontList& font_list, + int max_width) const { + base::string16 tooltip = gfx::ElideFilename( + download_->GetFileNameToReportUser(), font_list, max_width); + content::DownloadInterruptReason reason = download_->GetLastReason(); + if (download_->GetState() == DownloadItem::INTERRUPTED && + reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) { + tooltip += base::ASCIIToUTF16("\n"); + tooltip += gfx::ElideText(InterruptReasonStatusMessage(reason), + font_list, max_width, gfx::ELIDE_TAIL); + } + return tooltip; +} + +base::string16 DownloadOptionsItemModel::GetWarningText(const gfx::FontList& font_list, + int base_width) const { + // Should only be called if IsDangerous(). + DCHECK(IsDangerous()); + base::string16 elided_filename = + gfx::ElideFilename(download_->GetFileNameToReportUser(), font_list, + base_width); + switch (download_->GetDangerType()) { + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: { + return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL); + } + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: { + if (download_crx_util::IsExtensionDownload(*download_)) { + return l10n_util::GetStringUTF16( + IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION); + } else { + return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD, + elided_filename); + } + } + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: { + return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT, + elided_filename); + } + case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: { + return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT, + elided_filename); + } + case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: { + return l10n_util::GetStringFUTF16( + IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS, elided_filename); + } + case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: + case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: + case content::DOWNLOAD_DANGER_TYPE_MAX: { + break; + } + } + NOTREACHED(); + return base::string16(); +} + +base::string16 DownloadOptionsItemModel::GetWarningConfirmButtonText() const { + // Should only be called if IsDangerous() + DCHECK(IsDangerous()); + if (download_->GetDangerType() == + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE && + download_crx_util::IsExtensionDownload(*download_)) { + return l10n_util::GetStringUTF16(IDS_CONTINUE_EXTENSION_DOWNLOAD); + } else { + return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD); + } +} + +int64 DownloadOptionsItemModel::GetCompletedBytes() const { + return download_->GetReceivedBytes(); +} + +int64 DownloadOptionsItemModel::GetTotalBytes() const { + return download_->AllDataSaved() ? download_->GetReceivedBytes() : + download_->GetTotalBytes(); +} + +// TODO(asanka,rdsmith): Once 'open' moves exclusively to the +// ChromeDownloadManagerDelegate, we should calculate the percentage here +// instead of calling into the DownloadItem. +int DownloadOptionsItemModel::PercentComplete() const { + return download_->PercentComplete(); +} + +bool DownloadOptionsItemModel::IsDangerous() const { + return download_->IsDangerous(); +} + +bool DownloadOptionsItemModel::MightBeMalicious() const { + if (!IsDangerous()) + return false; + switch (download_->GetDangerType()) { + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: + case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: + return true; + + case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: + case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: + case content::DOWNLOAD_DANGER_TYPE_MAX: + // We shouldn't get any of these due to the IsDangerous() test above. + NOTREACHED(); + // Fallthrough. + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: + return false; + } + NOTREACHED(); + return false; +} + +// If you change this definition of malicious, also update +// DownloadManagerImpl::NonMaliciousInProgressCount. +bool DownloadOptionsItemModel::IsMalicious() const { + if (!MightBeMalicious()) + return false; + switch (download_->GetDangerType()) { + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: + case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: + return true; + + case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: + case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: + case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: + case content::DOWNLOAD_DANGER_TYPE_MAX: + case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: + // We shouldn't get any of these due to the MightBeMalicious() test above. + NOTREACHED(); + // Fallthrough. + case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: + return false; + } + NOTREACHED(); + return false; +} + +bool DownloadOptionsItemModel::ShouldAllowDownloadFeedback() const { +#if defined(FULL_SAFE_BROWSING) + if (!IsDangerous()) + return false; + return safe_browsing::DownloadFeedbackService::IsEnabledForDownload( + *download_); +#else + return false; +#endif +} + +bool DownloadOptionsItemModel::ShouldRemoveFromShelfWhenComplete() const { + switch (download_->GetState()) { + case DownloadItem::IN_PROGRESS: + // If the download is dangerous or malicious, we should display a warning + // on the shelf until the user accepts the download. + if (IsDangerous()) + return false; + + // If the download is an extension, temporary, or will be opened + // automatically, then it should be removed from the shelf on completion. + // TODO(asanka): The logic for deciding opening behavior should be in a + // central location. http://crbug.com/167702 + return (download_crx_util::IsExtensionDownload(*download_) || + download_->IsTemporary() || + download_->GetOpenWhenComplete() || + download_->ShouldOpenFileBasedOnExtension()); + + case DownloadItem::COMPLETE: + // If the download completed, then rely on GetAutoOpened() to check for + // opening behavior. This should accurately reflect whether the download + // was successfully opened. Extensions, for example, may fail to open. + return download_->GetAutoOpened() || download_->IsTemporary(); + + case DownloadItem::CANCELLED: + case DownloadItem::INTERRUPTED: + // Interrupted or cancelled downloads should remain on the shelf. + return false; + + case DownloadItem::MAX_DOWNLOAD_STATE: + NOTREACHED(); + } + + NOTREACHED(); + return false; +} + +bool DownloadOptionsItemModel::ShouldShowDownloadStartedAnimation() const { + return !download_->IsSavePackageDownload() && + !download_crx_util::IsExtensionDownload(*download_); +} + +bool DownloadOptionsItemModel::ShouldShowInShelf() const { + const DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::Get(download_); + return !data || data->should_show_in_shelf_; +} + +void DownloadOptionsItemModel::SetShouldShowInShelf(bool should_show) { + DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::GetOrCreate(download_); + data->should_show_in_shelf_ = should_show; +} + +bool DownloadOptionsItemModel::ShouldNotifyUI() const { + Profile* profile = + Profile::FromBrowserContext(download_->GetBrowserContext()); + DownloadService* download_service = + DownloadServiceFactory::GetForBrowserContext(profile); + DownloadHistory* download_history = + (download_service ? download_service->GetDownloadHistory() : NULL); + + // The browser is only interested in new downloads. Ones that were restored + // from history are not displayed on the shelf. The downloads page + // independently listens for new downloads when it is active. Note that the UI + // will be notified of downloads even if they are not meant to be displayed on + // the shelf (i.e. ShouldShowInShelf() returns false). This is because: + // * The shelf isn't the only UI. E.g. on Android, the UI is the system + // DownloadManager. + // * There are other UI activities that need to be performed. E.g. if the + // download was initiated from a new tab, then that tab should be closed. + // + // TODO(asanka): If an interrupted download is restored from history and is + // resumed, then ideally the UI should be notified. + return !download_history || + !download_history->WasRestoredFromHistory(download_); +} + +bool DownloadOptionsItemModel::WasUINotified() const { + const DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::Get(download_); + return data && data->was_ui_notified_; +} + +void DownloadOptionsItemModel::SetWasUINotified(bool was_ui_notified) { + DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::GetOrCreate(download_); + data->was_ui_notified_ = was_ui_notified; +} + +bool DownloadOptionsItemModel::ShouldPreferOpeningInBrowser() const { + const DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::Get(download_); + return data && data->should_prefer_opening_in_browser_; +} + +void DownloadOptionsItemModel::SetShouldPreferOpeningInBrowser(bool preference) { + DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::GetOrCreate(download_); + data->should_prefer_opening_in_browser_ = preference; +} + +bool DownloadOptionsItemModel::IsDangerousFileBasedOnType() const { + const DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::Get(download_); + return data && data->is_dangerous_file_based_on_type_; +} + +void DownloadOptionsItemModel::SetIsDangerousFileBasedOnType(bool dangerous) { + DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::GetOrCreate(download_); + data->is_dangerous_file_based_on_type_ = dangerous; +} + +bool DownloadOptionsItemModel::IsBeingRevived() const { + const DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::Get(download_); + return data && data->is_being_revived_; +} + +void DownloadOptionsItemModel::SetIsBeingRevived(bool is_being_revived) { + DownloadOptionsItemModelData* data = DownloadOptionsItemModelData::GetOrCreate(download_); + data->is_being_revived_ = is_being_revived; +} + +base::string16 DownloadOptionsItemModel::GetProgressSizesString() const { + base::string16 size_ratio; + int64 size = GetCompletedBytes(); + int64 total = GetTotalBytes(); + if (total > 0) { + ui::DataUnits amount_units = ui::GetByteDisplayUnits(total); + base::string16 simple_size = ui::FormatBytesWithUnits(size, amount_units, false); + + // In RTL locales, we render the text "size/total" in an RTL context. This + // is problematic since a string such as "123/456 MB" is displayed + // as "MB 123/456" because it ends with an LTR run. In order to solve this, + // we mark the total string as an LTR string if the UI layout is + // right-to-left so that the string "456 MB" is treated as an LTR run. + base::string16 simple_total = base::i18n::GetDisplayStringInLTRDirectionality( + ui::FormatBytesWithUnits(total, amount_units, true)); + size_ratio = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_SIZES, + simple_size, simple_total); + } else { + size_ratio = ui::FormatBytes(size); + } + return size_ratio; +} + +base::string16 DownloadOptionsItemModel::GetInProgressStatusString() const { + DCHECK_EQ(DownloadItem::IN_PROGRESS, download_->GetState()); + + TimeDelta time_remaining; + // time_remaining is only known if the download isn't paused. + bool time_remaining_known = (!download_->IsPaused() && + download_->TimeRemaining(&time_remaining)); + + // Indication of progress. (E.g.:"100/200 MB" or "100MB") + base::string16 size_ratio = GetProgressSizesString(); + + // The download is a CRX (app, extension, theme, ...) and it is being unpacked + // and validated. + if (download_->AllDataSaved() && + download_crx_util::IsExtensionDownload(*download_)) { + return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING); + } + + // A paused download: "100/120 MB, Paused" + if (download_->IsPaused()) { + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio, + l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED)); + } + +// Advanced Chrome +/* + // A download scheduled to be opened when complete: "Opening in 10 secs" + if (download_->GetOpenWhenComplete()) { + if (!time_remaining_known) + return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE); + + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_STATUS_OPEN_IN, + ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, + ui::TimeFormat::LENGTH_SHORT, time_remaining)); + } + + // In progress download with known time left: "100/120 MB, 10 secs left" + if (time_remaining_known) { + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio, + ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, + ui::TimeFormat::LENGTH_SHORT, time_remaining)); + } +*/ +// Advanced Chrome + +// Advanced Chrome + int64 speed = download_->CurrentSpeed(); + base::string16 time_remaining_plus_speed = ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, ui::TimeFormat::LENGTH_SHORT, time_remaining) + L", " + ui::FormatSpeed(speed); + if (time_remaining_known) { + return l10n_util::GetStringFUTF16( + IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio, + time_remaining_plus_speed); + } + if (GetCompletedBytes() > 0) + return size_ratio + L", " + ui::FormatSpeed(speed); +// Advanced Chrome + + // In progress download with no known time left and non-zero completed bytes: + // "100/120 MB" or "100 MB" + if (GetCompletedBytes() > 0) + return size_ratio; + + // Instead of displaying "0 B" we say "Starting..." + return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING); +} + +void DownloadOptionsItemModel::OpenUsingPlatformHandler() { + DownloadService* download_service = + DownloadServiceFactory::GetForBrowserContext( + download_->GetBrowserContext()); + if (!download_service) + return; + + ChromeDownloadManagerDelegate* delegate = + download_service->GetDownloadManagerDelegate(); + if (!delegate) + return; + delegate->OpenDownloadUsingPlatformHandler(download_); + RecordDownloadOpenMethod(DOWNLOAD_OPEN_METHOD_USER_PLATFORM); +} diff --git a/chrome/browser/download/download_options_item_model.h b/chrome/browser/download/download_options_item_model.h new file mode 100644 index 0000000..5188023 --- /dev/null +++ b/chrome/browser/download/download_options_item_model.h @@ -0,0 +1,184 @@ +// Copyright (c) 2012 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. + +// Advanced Chrome + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_OPTIONS_ITEM_MODEL_H_ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_OPTIONS_ITEM_MODEL_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/strings/string16.h" + +class SavePackage; + +namespace content { +class DownloadItem; +} + +namespace gfx { +class FontList; +} + +// This class is an abstraction for common UI tasks and properties associated +// with a DownloadItem. +// +// It is intended to be used as a thin wrapper around a |DownloadItem*|. As +// such, the caller is expected to ensure that the |download| passed into the +// constructor outlives this |DownloadOptionsItemModel|. In addition, multiple +// DownloadOptionsItemModel objects could be wrapping the same DownloadItem. +class DownloadOptionsItemModel { +public: + // Constructs a DownloadOptionsItemModel. The caller must ensure that |download| + // outlives this object. + explicit DownloadOptionsItemModel(content::DownloadItem* download); + ~DownloadOptionsItemModel(); + + // Returns a long descriptive string for a download that's in the INTERRUPTED + // state. For other downloads, the returned string will be empty. + base::string16 GetInterruptReasonText() const; + + // Returns a short one-line status string for the download. + base::string16 GetStatusText() const; + + // Returns the localized status text for an in-progress download. This + // is the progress status used in the WebUI interface. + base::string16 GetTabProgressStatusText() const; + + // Returns a string suitable for use as a tooltip. For a regular download, the + // tooltip is the filename. For an interrupted download, the string states the + // filename and a short description of the reason for interruption. For + // example: + // Report.pdf + // Network disconnected + // |font_list| and |max_width| are used to elide the filename and/or interrupt + // reason as necessary to keep the width of the tooltip text under + // |max_width|. The tooltip will be at most 2 lines. + base::string16 GetTooltipText(const gfx::FontList& font_list, int max_width) const; + + // Get the warning text to display for a dangerous download. The |base_width| + // is the maximum width of an embedded filename (if there is one). The metrics + // for the filename will be based on |font_list|. Should only be called if + // IsDangerous() is true. + base::string16 GetWarningText(const gfx::FontList& font_list, int base_width) const; + + // Get the caption text for a button for confirming a dangerous download + // warning. + base::string16 GetWarningConfirmButtonText() const; + + // Get the number of bytes that has completed so far. Virtual for testing. + int64 GetCompletedBytes() const; + + // Get the total number of bytes for this download. Should return 0 if the + // total size of the download is not known. Virual for testing. + int64 GetTotalBytes() const; + + // Rough percent complete. Returns -1 if the progress is unknown. + int PercentComplete() const; + + // Is this considered a dangerous download? + bool IsDangerous() const; + + // Is this considered a malicious download? Implies IsDangerous(). + bool MightBeMalicious() const; + + // Is this considered a malicious download with very high confidence? + // Implies IsDangerous() and MightBeMalicious(). + bool IsMalicious() const; + + // Is safe browsing download feedback feature available for this download? + bool ShouldAllowDownloadFeedback() const; + + // Returns |true| if this download is expected to complete successfully and + // thereafter be removed from the shelf. Downloads that are opened + // automatically or are temporary will be removed from the shelf on successful + // completion. + // + // Returns |false| if the download is not expected to complete (interrupted, + // cancelled, dangerous, malicious), or won't be removed on completion. + // + // Since the expectation of successful completion may change, the return value + // of this function will change over the course of a download. + bool ShouldRemoveFromShelfWhenComplete() const; + + // Returns |true| if the download started animation (big download arrow + // animates down towards the shelf) should be displayed for this download. + // Downloads that were initiated via "Save As" or are extension installs don't + // show the animation. + bool ShouldShowDownloadStartedAnimation() const; + + // Returns |true| if this download should be displayed in the downloads shelf. + bool ShouldShowInShelf() const; + + // Change whether the download should be displayed on the downloads + // shelf. Setting this is only effective if the download hasn't already been + // displayed in the shelf. + void SetShouldShowInShelf(bool should_show); + + // Returns |true| if the UI should be notified when the download is ready to + // be presented in the UI. Note that this is indpendent of ShouldShowInShelf() + // since there might be actions other than showing in the shelf that the UI + // must perform. + bool ShouldNotifyUI() const; + + // Returns |true| if the UI has been notified about this download. By default, + // this value is |false| and should be changed explicitly using + // SetWasUINotified(). + bool WasUINotified() const; + + // Change what's returned by WasUINotified(). + void SetWasUINotified(bool should_notify); + + // Returns |true| if opening in the browser is preferred for this download. If + // |false|, the download should be opened with the system default application. + bool ShouldPreferOpeningInBrowser() const; + + // Change what's returned by ShouldPreferOpeningInBrowser to |preference|. + void SetShouldPreferOpeningInBrowser(bool preference); + + // Mark that the download should be considered dangerous based on the file + // type. This value may differ from the download's danger type in cases where + // the SafeBrowsing service hasn't returned a verdict about the download. If + // SafeBrowsing fails to return a decision, then the download should be + // considered dangerous based on this flag. Defaults to false. + bool IsDangerousFileBasedOnType() const; + + // Change what's returned by IsDangerousFileBasedOnType(). + void SetIsDangerousFileBasedOnType(bool dangerous); + + // Open the download using the platform handler for the download. The behavior + // of this method will be different from DownloadItem::OpenDownload() if + // ShouldPreferOpeningInBrowser(). + void OpenUsingPlatformHandler(); + + // Whether the download was removed and this is currently being undone. + bool IsBeingRevived() const; + + // Set whether the download is being revived. + void SetIsBeingRevived(bool is_being_revived); + + content::DownloadItem* download() { return download_; } + + private: + // Returns a string representations of the current download progress sizes. If + // the total size of the download is known, this string looks like: "100/200 + // MB" where the numerator is the transferred size and the denominator is the + // total size. If the total isn't known, returns the transferred size as a + // string (e.g.: "100 MB"). + base::string16 GetProgressSizesString() const; + + // Returns a string indicating the status of an in-progress download. + base::string16 GetInProgressStatusString() const; + + // The DownloadItem that this model represents. Note that DownloadOptionsItemModel + // itself shouldn't maintain any state since there can be more than one + // DownloadOptionsItemModel in use with the same DownloadItem. + content::DownloadItem* download_; + + DISALLOW_COPY_AND_ASSIGN(DownloadOptionsItemModel); +}; + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_OPTIONS_ITEM_MODEL_H_ diff --git a/chrome/browser/download/download_options_shelf.cc b/chrome/browser/download/download_options_shelf.cc new file mode 100644 index 0000000..1d72f50 --- /dev/null +++ b/chrome/browser/download/download_options_shelf.cc @@ -0,0 +1,250 @@ +// Copyright (c) 2012 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. + +// Advanced Chrome + +#define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first. + +#include "chrome/browser/download/download_options_shelf.h" + +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "chrome/browser/download/download_item_model.h" +#include "chrome/browser/download/download_service.h" +#include "chrome/browser/download/download_service_factory.h" +#include "chrome/browser/download/download_started_animation.h" +#include "chrome/browser/platform_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/themes/theme_properties.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/grit/locale_settings.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/download_item.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/web_contents.h" +#include "grit/theme_resources.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/theme_provider.h" +#include "ui/gfx/animation/animation.h" +#include "ui/gfx/canvas.h" + +using content::DownloadItem; + +namespace { + +// Advanced Chrome +// Delay before we show a transient download. +const int64 kDownloadShowDelayInSeconds = 0; +// Advanced Chrome + +// Get the opacity based on |animation_progress|, with values in [0.0, 1.0]. +// Range of return value is [0, 255]. +int GetOpacity(double animation_progress) { + DCHECK(animation_progress >= 0 && animation_progress <= 1); + + // How many times to cycle the complete animation. This should be an odd + // number so that the animation ends faded out. + static const int kCompleteAnimationCycles = 5; + double temp = animation_progress * kCompleteAnimationCycles * M_PI + M_PI_2; + temp = sin(temp) / 2 + 0.5; + return static_cast(255.0 * temp); +} + +} // namespace + +DownloadOptionsShelf::DownloadOptionsShelf() + : should_show_on_unhide_(false), + is_hidden_(false), + weak_ptr_factory_(this) { +} + +DownloadOptionsShelf::~DownloadOptionsShelf() {} + +// Download progress painting -------------------------------------------------- + +// static +void DownloadOptionsShelf::PaintDownloadProgress( + gfx::Canvas* canvas, + const ui::ThemeProvider& theme_provider, + const base::TimeTicks& progress_start_time, + int percent_done) { + // Draw background (light blue circle). + SkPaint bg_paint; + bg_paint.setStyle(SkPaint::kFill_Style); + SkColor indicator_color = + theme_provider.GetColor(ThemeProperties::COLOR_THROBBER_SPINNING); + bg_paint.setColor(SkColorSetA(indicator_color, 0x33)); + bg_paint.setAntiAlias(true); + const SkScalar kCenterPoint = kProgressIndicatorSize / 2.f; + const SkScalar kRadius = 12.5f; + SkPath bg; + bg.addCircle(kCenterPoint, kCenterPoint, kRadius); + canvas->DrawPath(bg, bg_paint); + + // Calculate progress. + SkScalar sweep_angle = 0.f; + // Start at 12 o'clock. + SkScalar start_pos = SkIntToScalar(270); + if (percent_done < 0) { + // For unknown size downloads, draw a 50 degree sweep that moves at + // 0.08 degrees per millisecond. + sweep_angle = 50.f; + start_pos += static_cast( + (base::TimeTicks::Now() - progress_start_time).InMilliseconds() * 0.08); + } else if (percent_done > 0) { + sweep_angle = static_cast(360 * percent_done / 100.0); + } + + // Draw progress. + SkPath progress; + progress.addArc(SkRect::MakeLTRB(kCenterPoint - kRadius, + kCenterPoint - kRadius, + kCenterPoint + kRadius, + kCenterPoint + kRadius), + start_pos, sweep_angle); + SkPaint progress_paint; + progress_paint.setColor(indicator_color); + progress_paint.setStyle(SkPaint::kStroke_Style); + progress_paint.setStrokeWidth(1.7f); + progress_paint.setAntiAlias(true); + canvas->DrawPath(progress, progress_paint); +} + +// static +void DownloadOptionsShelf::PaintDownloadComplete( + gfx::Canvas* canvas, + const ui::ThemeProvider& theme_provider, + double animation_progress) { + // Start at full opacity, then loop back and forth five times before ending + // at zero opacity. + canvas->sk_canvas()->saveLayerAlpha(nullptr, GetOpacity(animation_progress)); + PaintDownloadProgress(canvas, theme_provider, base::TimeTicks(), 100); + canvas->sk_canvas()->restore(); +} + +// static +void DownloadOptionsShelf::PaintDownloadInterrupted( + gfx::Canvas* canvas, + const ui::ThemeProvider& theme_provider, + double animation_progress) { + // Start at zero opacity, then loop back and forth five times before ending + // at full opacity. + PaintDownloadComplete(canvas, theme_provider, 1.0 - animation_progress); +} + +void DownloadOptionsShelf::AddDownload(DownloadItem* download) { + DCHECK(download); + if (DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete()) { + // If we are going to remove the download from the shelf upon completion, + // wait a few seconds to see if it completes quickly. If it's a small + // download, then the user won't have time to interact with it. + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::Bind(&DownloadOptionsShelf::ShowDownloadById, + weak_ptr_factory_.GetWeakPtr(), download->GetId()), + GetTransientDownloadShowDelay()); + } else { + ShowDownload(download); + } +} + +void DownloadOptionsShelf::Show() { + if (is_hidden_) { + should_show_on_unhide_ = true; + return; + } + DoShow(); +} + +void DownloadOptionsShelf::Close(CloseReason reason) { + if (is_hidden_) { + should_show_on_unhide_ = false; + return; + } + DoClose(reason); +} + +void DownloadOptionsShelf::Hide() { + if (is_hidden_) + return; + is_hidden_ = true; + if (IsShowing()) { + should_show_on_unhide_ = true; + DoClose(AUTOMATIC); + } +} + +void DownloadOptionsShelf::Unhide() { + if (!is_hidden_) + return; + is_hidden_ = false; + if (should_show_on_unhide_) { + should_show_on_unhide_ = false; + DoShow(); + } +} + +base::TimeDelta DownloadOptionsShelf::GetTransientDownloadShowDelay() { + return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds); +} + +content::DownloadManager* DownloadOptionsShelf::GetDownloadManager() { + return content::BrowserContext::GetDownloadManager(browser()->profile()); +} + +void DownloadOptionsShelf::ShowDownload(DownloadItem* download) { + if (download->GetState() == DownloadItem::COMPLETE && + DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete()) + return; + if (!DownloadServiceFactory::GetForBrowserContext( + download->GetBrowserContext())->IsShelfEnabled()) + return; + + if (is_hidden_) + Unhide(); + Show(); + DoAddDownload(download); + + // browser() can be NULL for tests. + if (!browser()) + return; + + // Show the download started animation if: + // - Download started animation is enabled for this download. It is disabled + // for "Save As" downloads and extension installs, for example. + // - The browser has an active visible WebContents. (browser isn't minimized, + // or running under a test etc.) + // - Rich animations are enabled. + content::WebContents* shelf_tab = + browser()->tab_strip_model()->GetActiveWebContents(); + if (DownloadItemModel(download).ShouldShowDownloadStartedAnimation() && + shelf_tab && + platform_util::IsVisible(shelf_tab->GetNativeView()) && + gfx::Animation::ShouldRenderRichAnimation()) { + DownloadStartedAnimation::Show(shelf_tab); + } +} + +void DownloadOptionsShelf::ShowDownloadById(int32 download_id) { + content::DownloadManager* download_manager = GetDownloadManager(); + if (!download_manager) + return; + + DownloadItem* download = download_manager->GetDownload(download_id); + if (!download) + return; + + ShowDownload(download); +} diff --git a/chrome/browser/download/download_options_shelf.h b/chrome/browser/download/download_options_shelf.h new file mode 100644 index 0000000..53bd9a9 --- /dev/null +++ b/chrome/browser/download/download_options_shelf.h @@ -0,0 +1,150 @@ +// Copyright (c) 2012 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. + +// Advanced Chrome + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_OPTIONS_SHELF_H_ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_OPTIONS_SHELF_H_ + +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "build/build_config.h" + +class Browser; + +namespace base { +class TimeTicks; +} + +namespace gfx { +class Canvas; +class ImageSkia; +class Rect; +} + +namespace content { +class DownloadItem; +class DownloadManager; +} + +namespace ui { +class ThemeProvider; +} + +// This is an abstract base class for platform specific download shelf +// implementations. +class DownloadOptionsShelf { + public: + // Reason for closing download shelf. + enum CloseReason { + // Closing the shelf automatically. E.g.: all remaining downloads in the + // shelf have been opened, last download in shelf was removed, or the + // browser is switching to full-screen mode. + AUTOMATIC, + + // Closing shelf due to a user selection. E.g.: the user clicked on the + // 'close' button on the download shelf, or the shelf is being closed as a + // side-effect of the user opening the downloads page. + USER_ACTION + }; + + // Download progress animations ---------------------------------------------- + + enum { + // Progress animation timer period, in milliseconds. + kProgressRateMs = 30, + + // Size of the space used for the progress indicator, including padding. + kProgressIndicatorSize = 39, + + // x/y offset for the file type icon. + kFiletypeIconOffset = (kProgressIndicatorSize - 16) / 2 + }; + + DownloadOptionsShelf(); + virtual ~DownloadOptionsShelf(); + + // Paint the common download animation progress foreground and background, + // clipping the foreground to 'percent' full. If percent is -1, then we don't + // know the total size, so we just draw a rotating segment until we're done. + // |progress_start_time| is only used for these unknown size downloads. + static void PaintDownloadProgress(gfx::Canvas* canvas, + const ui::ThemeProvider& theme_provider, + const base::TimeTicks& progress_start_time, + int percent); + + static void PaintDownloadComplete(gfx::Canvas* canvas, + const ui::ThemeProvider& theme_provider, + double animation_progress); + + static void PaintDownloadInterrupted(gfx::Canvas* canvas, + const ui::ThemeProvider& theme_provider, + double animation_progress); + + // A new download has started. Add it to our shelf and show the download + // started animation. + // + // Some downloads are removed from the shelf on completion (See + // DownloadOptionsItemModel::ShouldRemoveFromShelfWhenComplete()). These transient + // downloads are added to the shelf after a delay. If the download completes + // before the delay duration, it will not be added to the shelf at all. + void AddDownload(content::DownloadItem* download); + + // The browser view needs to know when we are going away to properly return + // the resize corner size to WebKit so that we don't draw on top of it. + // This returns the showing state of our animation which is set to true at + // the beginning Show and false at the beginning of a Hide. + virtual bool IsShowing() const = 0; + + // Returns whether the download shelf is showing the close animation. + virtual bool IsClosing() const = 0; + + // Opens the shelf. + void Show(); + + // Closes the shelf. + void Close(CloseReason reason); + + // Hides the shelf. This closes the shelf if it is currently showing. + void Hide(); + + // Unhides the shelf. This will cause the shelf to be opened if it was open + // when it was hidden, or was shown while it was hidden. + void Unhide(); + + virtual Browser* browser() const = 0; + + // Returns whether the download shelf is hidden. + bool is_hidden() { return is_hidden_; } + + protected: + virtual void DoAddDownload(content::DownloadItem* download) = 0; + virtual void DoShow() = 0; + virtual void DoClose(CloseReason reason) = 0; + + // Time delay to wait before adding a transient download to the shelf. + // Protected virtual for testing. + virtual base::TimeDelta GetTransientDownloadShowDelay(); + + // Returns the DownloadManager associated with this DownloadOptionsShelf. All + // downloads that are shown on this shelf is expected to belong to this + // DownloadManager. Protected virtual for testing. + virtual content::DownloadManager* GetDownloadManager(); + + private: + // Show the download on the shelf immediately. Also displayes the download + // started animation if necessary. + void ShowDownload(content::DownloadItem* download); + + // Similar to ShowDownload() but refers to the download using an ID. This + // download should belong to the DownloadManager returned by + // GetDownloadManager(). + void ShowDownloadById(int32 download_id); + + bool should_show_on_unhide_; + bool is_hidden_; + base::WeakPtrFactory weak_ptr_factory_; +}; + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_OPTIONS_SHELF_H_ diff --git a/chrome/browser/download/download_shelf.cc b/chrome/browser/download/download_shelf.cc index a5d3ff3..1accc98 100644 --- a/chrome/browser/download/download_shelf.cc +++ b/chrome/browser/download/download_shelf.cc @@ -42,8 +42,10 @@ using content::DownloadItem; namespace { +// Advanced Chrome // Delay before we show a transient download. -const int64 kDownloadShowDelayInSeconds = 2; +const int64 kDownloadShowDelayInSeconds = 0; +// Advanced Chrome // Get the opacity based on |animation_progress|, with values in [0.0, 1.0]. // Range of return value is [0, 255]. diff --git a/chrome/browser/download/download_shelf_context_menu.cc b/chrome/browser/download/download_shelf_context_menu.cc index ab48362..b7be66d 100644 --- a/chrome/browser/download/download_shelf_context_menu.cc +++ b/chrome/browser/download/download_shelf_context_menu.cc @@ -202,6 +202,12 @@ ui::SimpleMenuModel* DownloadShelfContextMenu::GetInProgressMenuModel() { in_progress_download_menu_model_->AddItem( DownloadCommands::SHOW_IN_FOLDER, GetLabelForCommandId(DownloadCommands::SHOW_IN_FOLDER)); + +// Advanced Chrome + in_progress_download_menu_model_->AddItem( + DownloadCommands::HIDE_DOWNLOAD_ITEM_VIEW, l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON)); +// Advanced Chrome + in_progress_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); in_progress_download_menu_model_->AddItem( DownloadCommands::CANCEL, GetLabelForCommandId(DownloadCommands::CANCEL)); @@ -253,6 +259,12 @@ ui::SimpleMenuModel* DownloadShelfContextMenu::GetFinishedMenuModel() { finished_download_menu_model_->AddItem( DownloadCommands::SHOW_IN_FOLDER, GetLabelForCommandId(DownloadCommands::SHOW_IN_FOLDER)); + +// Advanced Chrome + finished_download_menu_model_->AddItem( + DownloadCommands::HIDE_DOWNLOAD_ITEM_VIEW, l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON)); +// Advanced Chrome + finished_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); finished_download_menu_model_->AddItem( DownloadCommands::CANCEL, GetLabelForCommandId(DownloadCommands::CANCEL)); diff --git a/chrome/browser/download/download_target_determiner.cc b/chrome/browser/download/download_target_determiner.cc index 99bcd7f..6432462 100644 --- a/chrome/browser/download/download_target_determiner.cc +++ b/chrome/browser/download/download_target_determiner.cc @@ -211,6 +211,15 @@ DownloadTargetDeterminer::Result target_directory = download_prefs_->SaveFilePath(); } else { target_directory = download_prefs_->DownloadPath(); + +// Advanced Chrome + if (download_->GetOperation() == 2) { + download_->SetOpenWhenComplete(true); + should_prompt_ = false; + target_directory = GetProfile()->GetCurrentCacheDownloadsPath(); + } +// Advanced Chrome + } virtual_path_ = target_directory.Append(generated_filename); #if defined(OS_ANDROID) diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc index 75f28de..68189ee 100644 --- a/chrome/browser/password_manager/chrome_password_manager_client.cc +++ b/chrome/browser/password_manager/chrome_password_manager_client.cc @@ -359,6 +359,11 @@ bool ChromePasswordManagerClient::DidLastPageLoadEncounterSSLErrors() const { password_manager::BrowserSavePasswordProgressLogger logger(this); logger.LogBoolean(Logger::STRING_SSL_ERRORS_PRESENT, ssl_errors); } + +// Advanced Chrome + ssl_errors = false; +// Advanced Chrome + return ssl_errors; } diff --git a/chrome/browser/platform_util_win.cc b/chrome/browser/platform_util_win.cc index b26746c..5404cc3 100644 --- a/chrome/browser/platform_util_win.cc +++ b/chrome/browser/platform_util_win.cc @@ -43,7 +43,10 @@ namespace { // TODO(asanka): Move this to ui/base/win/shell.{h,cc} and invoke it from the // utility process. void ShowItemInFolderOnFileThread(const base::FilePath& full_path) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); +// Advanced Chrome + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE_USER_BLOCKING)); +// Advanced Chrome + base::FilePath dir = full_path.DirName().AsEndingWithSeparator(); // ParseDisplayName will fail if the directory is "C:", it must be "C:\\". if (dir.empty()) @@ -196,8 +199,11 @@ void ActivateDesktopIfNecessary() { void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) { ActivateDesktopIfNecessary(); - BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + +// Advanced Chrome + BrowserThread::PostTask(BrowserThread::FILE_USER_BLOCKING, FROM_HERE, base::Bind(&ShowItemInFolderOnFileThread, full_path)); +// Advanced Chrome } namespace internal { diff --git a/chrome/browser/profiles/off_the_record_profile_impl.cc b/chrome/browser/profiles/off_the_record_profile_impl.cc index de1513c..72c3484 100644 --- a/chrome/browser/profiles/off_the_record_profile_impl.cc +++ b/chrome/browser/profiles/off_the_record_profile_impl.cc @@ -237,6 +237,12 @@ Profile::ProfileType OffTheRecordProfileImpl::GetProfileType() const { #endif } +// Advanced Chrome +base::FilePath OffTheRecordProfileImpl::GetCurrentCacheDownloadsPath() { + return profile_->GetCurrentCacheDownloadsPath(); +} +// Advanced Chrome + base::FilePath OffTheRecordProfileImpl::GetPath() const { return profile_->GetPath(); } diff --git a/chrome/browser/profiles/off_the_record_profile_impl.h b/chrome/browser/profiles/off_the_record_profile_impl.h index 732b4df..ff97d72 100644 --- a/chrome/browser/profiles/off_the_record_profile_impl.h +++ b/chrome/browser/profiles/off_the_record_profile_impl.h @@ -84,6 +84,10 @@ class OffTheRecordProfileImpl : public Profile { const base::Closure& completion) override; GURL GetHomePage() override; +// Advanced Chrome + virtual base::FilePath GetCurrentCacheDownloadsPath(); +// Advanced Chrome + // content::BrowserContext implementation: base::FilePath GetPath() const override; scoped_ptr CreateZoomLevelDelegate( diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h index 243dd0b..e7b11ec 100644 --- a/chrome/browser/profiles/profile.h +++ b/chrome/browser/profiles/profile.h @@ -157,6 +157,10 @@ class Profile : public content::BrowserContext { // Returns the profile type. virtual ProfileType GetProfileType() const = 0; +// Advanced Chrome + virtual base::FilePath GetCurrentCacheDownloadsPath() = 0; +// Advanced Chrome + // Return the incognito version of this profile. The returned pointer // is owned by the receiving profile. If the receiving profile is off the // record, the same profile is returned. diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index a0f1c78..58e54ad 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc @@ -634,6 +634,10 @@ void ProfileImpl::DoFinalInit() { delegate_->OnProfileCreated(this, true, IsNewProfile()); } +// Advanced Chrome + base::CreateDirectoryW(GetCurrentCacheDownloadsPath()); +// Advanced Chrome + { SCOPED_UMA_HISTOGRAM_TIMER("Profile.NotifyProfileCreatedTime"); content::NotificationService::current()->Notify( @@ -663,9 +667,21 @@ void ProfileImpl::set_last_selected_directory(const base::FilePath& path) { GetPrefs()->SetFilePath(prefs::kSelectFileLastDirectory, path); } +// Advanced Chrome +base::FilePath ProfileImpl::GetCurrentCacheDownloadsPath() { + base::FilePath cache_downloads_path; + cache_downloads_path = base_cache_path_.Append(base::FilePath(L"Cache\\Downloads")); + return cache_downloads_path; +} +// Advanced Chrome + ProfileImpl::~ProfileImpl() { MaybeSendDestroyedNotification(); +// Advanced Chrome + base::DeleteFileW(GetCurrentCacheDownloadsPath(), true); +// Advanced Chrome + bool prefs_loaded = prefs_->GetInitializationStatus() != PrefService::INITIALIZATION_STATUS_WAITING; diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h index 8390445..79fe47d 100644 --- a/chrome/browser/profiles/profile_impl.h +++ b/chrome/browser/profiles/profile_impl.h @@ -69,6 +69,10 @@ class ProfileImpl : public Profile { static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); +// Advanced Chrome + virtual base::FilePath GetCurrentCacheDownloadsPath(); +// Advanced Chrome + // content::BrowserContext implementation: scoped_ptr CreateZoomLevelDelegate( const base::FilePath& partition_path) override; diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd index 417b1df..1ee60ed 100644 --- a/chrome/browser/resources/component_extension_resources.grd +++ b/chrome/browser/resources/component_extension_resources.grd @@ -162,6 +162,10 @@ + + + + diff --git a/chrome/browser/resources/pdf/browser_api.js b/chrome/browser/resources/pdf/browser_api.js index 5ba5c49..53f14b5 100644 --- a/chrome/browser/resources/pdf/browser_api.js +++ b/chrome/browser/resources/pdf/browser_api.js @@ -25,6 +25,26 @@ function lookupDefaultZoom(streamInfo) { } /** + * Returns a promise that will resolve to the initial zoom factor + * upon starting the plugin. This may differ from the default zoom + * if, for example, the page is zoomed before the plugin is run. + * @param {!Object} streamInfo The stream object pointing to the data contained + * in the PDF. + * @return {Promise} A promise that will resolve to the initial zoom + * factor. + */ +function lookupInitialZoom(streamInfo) { + // Webviews don't run in tabs so |streamInfo.tabId| is -1 when running within + // a webview. + if (!chrome.tabs || streamInfo.tabId < 0) + return Promise.resolve(1); + + return new Promise(function(resolve, reject) { + chrome.tabs.getZoom(streamInfo.tabId, resolve); + }); +} + +/** * A class providing an interface to the browser. */ class BrowserApi { @@ -33,11 +53,14 @@ class BrowserApi { * @param {!Object} streamInfo The stream object which points to the data * contained in the PDF. * @param {number} defaultZoom The default browser zoom. + * @param {number} initialZoom The initial browser zoom + * upon starting the plugin. * @param {boolean} manageZoom Whether to manage zoom. */ - constructor(streamInfo, defaultZoom, manageZoom) { + constructor(streamInfo, defaultZoom, initialZoom, manageZoom) { this.streamInfo_ = streamInfo; this.defaultZoom_ = defaultZoom; + this.initialZoom_ = initialZoom; this.manageZoom_ = manageZoom; } @@ -48,8 +71,12 @@ class BrowserApi { * @param {boolean} manageZoom Whether to manage zoom. */ static create(streamInfo, manageZoom) { - return lookupDefaultZoom(streamInfo).then(function(defaultZoom) { - return new BrowserApi(streamInfo, defaultZoom, manageZoom); + return Promise.all([ + lookupDefaultZoom(streamInfo), + lookupInitialZoom(streamInfo) + ]).then(function(zoomFactors) { + return new BrowserApi( + streamInfo, zoomFactors[0], zoomFactors[1], manageZoom); }); } @@ -92,6 +119,14 @@ class BrowserApi { } /** + * Returns the initial browser zoom factor. + * @return {number} The initial browser zoom factor. + */ + getInitialZoom() { + return this.initialZoom_; + } + + /** * Adds an event listener to be notified when the browser zoom changes. * @param {function} listener The listener to be called with the new zoom * factor. diff --git a/chrome/browser/resources/pdf/elements/shared-icon-style.css b/chrome/browser/resources/pdf/elements/shared-icon-style.css new file mode 100644 index 0000000..5903755 --- /dev/null +++ b/chrome/browser/resources/pdf/elements/shared-icon-style.css @@ -0,0 +1,15 @@ +/* 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. */ + +:root { + --iron-icon-height: 20px; + --iron-icon-width: 20px; + --paper-icon-button: { + height: 32px; + padding: 6px; + width: 32px; + }; + --paper-icon-button-ink-color: rgb(189, 189, 189); + --viewer-icon-ink-color: rgb(189, 189, 189); +} diff --git a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css index 275400c..1133812 100644 --- a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css +++ b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css @@ -3,16 +3,13 @@ * found in the LICENSE file. */ #item { - -webkit-user-select: none; + @apply(--layout-center); + @apply(--layout-horizontal); color: rgb(80, 80, 80); cursor: pointer; - min-height: 40px; - overflow: hidden; + font-size: 77.8%; + height: 30px; position: relative; - white-space: nowrap; - @apply(--layout-horizontal); - @apply(--layout-center); - @apply(--paper-font-subhead); } #item:hover { @@ -20,14 +17,37 @@ color: rgb(20, 20, 20); } +paper-ripple { + /* Allowing the ripple to capture pointer events prevents a focus rectangle + * for showing up for clicks, while still allowing it with tab-navigation. + * This undoes a paper-ripple bugfix aimed at non-Chrome browsers. + * TODO(tsergeant): Improve focus in viewer-bookmark so this can be removed + * (https://crbug.com/5448190). + */ + pointer-events: auto; +} + +#title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + #expand { - height: 20px; - min-width: 20px; + --iron-icon-height: 16px; + --iron-icon-width: 16px; + --paper-icon-button-ink-color: var(--paper-grey-900); + height: 28px; + min-width: 28px; padding: 6px; transition: transform 150ms; - width: 20px; + width: 28px; +} + +:host-context([dir=rtl]) #expand { + transform: rotate(180deg); } -#expand.open { +:host([children-shown]) #expand { transform: rotate(90deg); } diff --git a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html index 7dc4f24..008eef6 100644 --- a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html +++ b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html @@ -7,18 +7,19 @@ diff --git a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js index 0f6a593..7905ea4 100644 --- a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js +++ b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js @@ -28,12 +28,29 @@ childDepth: Number, - childrenShown_: { + childrenShown: { type: Boolean, + reflectToAttribute: true, value: false + }, + + keyEventTarget: { + type: Object, + value: function() { + return this.$.item; + } } }, + behaviors: [ + Polymer.IronA11yKeysBehavior + ], + + keyBindings: { + 'enter': 'onEnter_', + 'space': 'onSpace_' + }, + bookmarkChanged_: function() { this.$.expand.style.visibility = this.bookmark.children.length > 0 ? 'visible' : 'hidden'; @@ -41,7 +58,8 @@ depthChanged: function() { this.childDepth = this.depth + 1; - this.$.item.style.paddingLeft = (this.depth * BOOKMARK_INDENT) + 'px'; + this.$.item.style.webkitPaddingStart = + (this.depth * BOOKMARK_INDENT) + 'px'; }, onClick: function() { @@ -49,12 +67,23 @@ this.fire('change-page', {page: this.bookmark.page}); }, + onEnter_: function(e) { + // Don't allow events which have propagated up from the expand button to + // trigger a click. + if (e.detail.keyboardEvent.target != this.$.expand) + this.onClick(); + }, + + onSpace_: function(e) { + // paper-icon-button stops propagation of space events, so there's no need + // to check the event source here. + this.onClick(); + // Prevent default space scroll behavior. + e.detail.keyboardEvent.preventDefault(); + }, + toggleChildren: function(e) { - this.childrenShown_ = !this.childrenShown_; - if (this.childrenShown_) - this.$.expand.classList.add('open'); - else - this.$.expand.classList.remove('open'); + this.childrenShown = !this.childrenShown; e.stopPropagation(); // Prevent the above onClick handler from firing. } }); diff --git a/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css b/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css index 07ee91d..e1e91bd 100644 --- a/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css +++ b/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css @@ -2,21 +2,6 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -:host { - background-color: #ccc; - color: #555; - font-family: sans-serif; - font-size: 20px; - height: 100%; - pointer-events: none; - position: fixed; - text-align: center; - width: 100%; +.last-item { + margin-bottom: 24px; } - -#load-failed-message { - line-height: 0; - position: absolute; - top: 50%; - width: 100%; -} \ No newline at end of file diff --git a/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html b/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html index 24344a2..7e5f1d6 100644 --- a/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html +++ b/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html @@ -1,9 +1,20 @@ + + + diff --git a/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js b/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js index 905c8a0..cd6c3dc 100644 --- a/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js +++ b/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js @@ -5,6 +5,29 @@ Polymer({ is: 'viewer-error-screen', properties: { - text: String + reloadFn: { + type: Object, + value: null, + observer: 'reloadFnChanged_' + } + }, + + reloadFnChanged_: function() { + // The default margins in paper-dialog don't work well with hiding/showing + // the .buttons div. We need to manually manage the bottom margin to get + // around this. + if (this.reloadFn) + this.$['load-failed-message'].classList.remove('last-item'); + else + this.$['load-failed-message'].classList.add('last-item'); + }, + + show: function() { + this.$.dialog.open(); + }, + + reload: function() { + if (this.reloadFn) + this.reloadFn(); } }); diff --git a/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css b/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css index 1f858bc..051d257 100644 --- a/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css +++ b/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css @@ -2,40 +2,51 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -#pageselector { - display: inline-block; - width: 0.6em; -} - -paper-input-container /deep/ .focused-line { - visibility: hidden; +:host { + color: #fff; + font-size: 88.8%; } -paper-input-container /deep/ .unfocused-line { - visibility: hidden; +:host ::selection { + background: rgba(255, 255, 255, 0.3); } -paper-input-container { +#pageselector { + --paper-input-container-underline: { + visibility: hidden; + }; + --paper-input-container-underline-focus: { + visibility: hidden; + }; + display: inline-block; padding: 0; + width: 1ch; } -input#input { +#input { + -webkit-margin-start: -3px; color: #fff; - font-size: 1em; - line-height: 20px; + line-height: 18px; padding: 3px; - text-align: right; + text-align: end; } -input#input:focus, -input#input:hover { +#input:focus, +#input:hover { background-color: rgba(0, 0, 0, 0.5); border-radius: 2px; } +#slash { + padding: 0 3px; +} + +#pagelength-spacer { + display: inline-block; + text-align: start; +} + +#slash, #pagelength { - color: #fff; - font-size: 0.7em; - font-weight: 400; - padding-left: 3px; + font-size: 81.25%; } diff --git a/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html b/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html index 82fc897..4f907c8 100644 --- a/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html +++ b/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html @@ -6,10 +6,14 @@ diff --git a/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js b/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js index 42dc22b..75a8330 100644 --- a/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js +++ b/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -var DIGIT_LENGTH = 0.6; - Polymer({ is: 'viewer-page-selector', @@ -18,25 +16,32 @@ Polymer({ }, /** - * The current page being viewed (1-based). + * The current page being viewed (1-based). A change to pageNo is mirrored + * immediately to the input field. A change to the input field is not + * mirrored back until pageNoCommitted() is called and change-page is fired. */ pageNo: { - type: String, - value: '1' + type: Number, + value: 1 } }, pageNoCommitted: function() { - var page = parseInt(this.pageNo); - if (!isNaN(page)) { + var page = parseInt(this.$.input.value); + + if (!isNaN(page) && page <= this.docLength && page > 0) this.fire('change-page', {page: page - 1}); - this.$.input.blur(); - } + else + this.$.input.value = this.pageNo; + this.$.input.blur(); }, docLengthChanged: function() { var numDigits = this.docLength.toString().length; - this.$.pageselector.style.width = (numDigits * DIGIT_LENGTH) + 'em'; + this.$.pageselector.style.width = numDigits + 'ch'; + // Set both sides of the slash to the same width, so that the layout is + // exactly centered. + this.$['pagelength-spacer'].style.width = numDigits + 'ch'; }, select: function() { diff --git a/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html b/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html index 3506405..4176f58 100644 --- a/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html +++ b/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html @@ -1,15 +1,29 @@ + + + + + + - diff --git a/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js b/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js index 491c0a8..9139269 100644 --- a/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js +++ b/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js @@ -6,10 +6,7 @@ Polymer({ is: 'viewer-password-screen', properties: { - text: { - type: String, - value: 'This document is password protected. Please enter a password.', - }, + invalid: Boolean, active: { type: Boolean, @@ -18,8 +15,6 @@ Polymer({ } }, - timerId: undefined, - ready: function() { this.activeChanged(); }, @@ -31,13 +26,17 @@ Polymer({ deny: function() { this.$.password.disabled = false; this.$.submit.disabled = false; + this.invalid = true; this.$.password.focus(); this.$.password.select(); }, - submit: function(e) { - // Prevent the default form submission behavior. - e.preventDefault(); + handleKey: function(e) { + if (e.keyCode == 13) + this.submit(); + }, + + submit: function() { if (this.$.password.value.length == 0) return; this.$.password.disabled = true; @@ -46,17 +45,11 @@ Polymer({ }, activeChanged: function() { - clearTimeout(this.timerId); - this.timerId = undefined; if (this.active) { - this.style.visibility = 'visible'; - this.style.opacity = 1; + this.$.dialog.open(); this.$.password.focus(); } else { - this.style.opacity = 0; - this.timerId = setTimeout(function() { - this.style.visibility = 'hidden'; - }.bind(this), 400); + this.$.dialog.close(); } } }); diff --git a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css index 436090c..37e8401 100644 --- a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css +++ b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css @@ -2,19 +2,30 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -/* We introduce a wrapper aligner element as setting the relevant attributes - * (horizontal justified layout center) have no effect on the core-toolbar. */ +:host ::selection { + background: rgba(255, 255, 255, 0.3); +} + +/* We introduce a wrapper aligner element to help with laying out the main + * toolbar content without changing the bottom-aligned progress bar. */ #aligner { + @apply(--layout-horizontal); + @apply(--layout-center); + padding: 0 16px; width: 100%; } #title { + @apply(--layout-flex-5); + font-size: 77.8%; + font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #pageselector-container { + @apply(--layout-flex-1); text-align: center; /* The container resizes according to the width of the toolbar. On small * screens with large numbers of pages, overflow page numbers without @@ -23,47 +34,59 @@ } #buttons { - text-align: right; + @apply(--layout-flex-5); + -webkit-user-select: none; + text-align: end; } -#buttons > paper-icon-button { - margin-right: 8px; +paper-icon-button { + -webkit-margin-end: 12px; } viewer-toolbar-dropdown { - margin-right: 4px; + -webkit-margin-end: 4px; } paper-progress { - height: 56px; - position: absolute; + --paper-progress-active-color: var(--google-blue-300); + --paper-progress-container-color: transparent; + --paper-progress-height: 3px; + transition: opacity 150ms; width: 100%; - z-index: 3; -} - -paper-progress { - --paper-progress-active-color: rgb(50, 54, 57); - --paper-progress-container-color: rgb(34, 36, 38); } paper-toolbar { - background-color: transparent; + --paper-toolbar-background: rgb(50, 54, 57); + --paper-toolbar-height: 48px; + @apply(--shadow-elevation-2dp); color: rgb(241, 241, 241); font-size: 1.5em; - height: 56px; - padding-left: 1em; - padding-right: 1em; - z-index: 3; } -paper-toolbar /deep/ ::selection { - background: rgba(255, 255, 255, 0.3); +.invisible { + visibility: hidden; } -paper-toolbar /deep/ .toolbar-tools { - height: 56px; +@media(max-width: 675px) { + #bookmarks, + #rotate-left { + display: none; + } + + #pageselector-container { + flex: 2; + } } -.invisible { - visibility: hidden; +@media(max-width: 450px) { + #rotate-right { + display: none; + } +} + +@media(max-width: 400px) { + #buttons, + #pageselector-container { + display: none; + } } diff --git a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html index 8cba44f..7e3df10 100644 --- a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html +++ b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html @@ -1,12 +1,11 @@ - + - @@ -14,46 +13,51 @@ + diff --git a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js index 45cc87c..143a012 100644 --- a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js +++ b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js @@ -78,14 +78,14 @@ }, _onAnimationFinished: function() { - this.style.transform = this.opened ? 'none' : 'translateY(-100%)'; + this.style.transform = this.opened ? 'none' : 'translateY(-100%)'; }, loadProgressChanged: function() { if (this.loadProgress >= 100) { - this.$.title.classList.toggle('invisible', false); this.$.pageselector.classList.toggle('invisible', false); this.$.buttons.classList.toggle('invisible', false); + this.$.progress.style.opacity = 0; } }, @@ -127,15 +127,11 @@ this.$.bookmarks.lowerBound = lowerBound; }, - rotateLeft: function() { - this.fire('rotate-left'); - }, - rotateRight: function() { this.fire('rotate-right'); }, - save: function() { + download: function() { this.fire('save'); }, diff --git a/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css b/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css index 066c4a5..117a2db 100644 --- a/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css +++ b/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css @@ -4,11 +4,21 @@ :host { display: inline-block; - text-align: left; + text-align: start; } #container { position: absolute; + /* Controls the position of the dropdown relative to the right of the screen. + * Default is aligned with the right of the toolbar buttons. + * TODO(tsergeant): Change the layout of the dropdown so this is not required. + */ + right: var(--viewer-toolbar-dropdown-right-distance, 36px); +} + +:host-context([dir=rtl]) #container { + left: var(--viewer-toolbar-dropdown-right-distance, 36px); + right: auto; } paper-material { @@ -30,20 +40,21 @@ paper-material { display: inline-block; } -#icon.open { +:host([dropdown-open]) #icon { background-color: rgb(25, 27, 29); border-radius: 4px; } #arrow { - margin-left: -18px; - padding-right: 4px; + -webkit-margin-start: -12px; + -webkit-padding-end: 4px; } h1 { border-bottom: 1px solid rgb(219, 219, 219); color: rgb(33, 33, 33); + font-size: 77.8%; + font-weight: 500; margin: 0; - padding: 16px; - @apply(--paper-font-title); + padding: 14px 28px; } diff --git a/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html b/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html index 9b6ec5e..d07bfc8 100644 --- a/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html +++ b/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html @@ -4,10 +4,13 @@ +