| Index: chrome/browser/banners/app_banner_manager.cc
|
| diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
|
| index 4e8aac325a4d7d6460252913e438260b6874969f..2757f5cb5ac99beb1a0576bea768ebea3d80836d 100644
|
| --- a/chrome/browser/banners/app_banner_manager.cc
|
| +++ b/chrome/browser/banners/app_banner_manager.cc
|
| @@ -4,96 +4,309 @@
|
|
|
| #include "chrome/browser/banners/app_banner_manager.h"
|
|
|
| -#include "chrome/browser/banners/app_banner_data_fetcher.h"
|
| -#include "chrome/browser/banners/app_banner_debug_log.h"
|
| +#include "base/bind.h"
|
| +#include "base/callback.h"
|
| +#include "base/command_line.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/time/time.h"
|
| +#include "chrome/browser/banners/app_banner_metrics.h"
|
| #include "chrome/browser/banners/app_banner_settings_helper.h"
|
| +#include "chrome/browser/browser_process.h"
|
| #include "chrome/browser/engagement/site_engagement_service.h"
|
| +#include "chrome/browser/installable/installable_logging.h"
|
| +#include "chrome/browser/installable/installable_manager.h"
|
| #include "chrome/browser/profiles/profile.h"
|
| +#include "chrome/common/chrome_switches.h"
|
| +#include "chrome/common/render_messages.h"
|
| +#include "components/rappor/rappor_utils.h"
|
| #include "content/public/browser/navigation_handle.h"
|
| #include "content/public/browser/render_frame_host.h"
|
| #include "content/public/browser/web_contents.h"
|
| -#include "content/public/common/frame_navigate_params.h"
|
| #include "content/public/common/origin_util.h"
|
| +#include "third_party/WebKit/public/platform/modules/app_banner/WebAppBannerPromptReply.h"
|
| +#include "third_party/skia/include/core/SkBitmap.h"
|
| +#include "ui/display/display.h"
|
| +#include "ui/display/screen.h"
|
|
|
| namespace {
|
| +
|
| bool gDisableSecureCheckForTesting = false;
|
| +int gCurrentRequestID = -1;
|
| +base::LazyInstance<base::TimeDelta> gTimeDeltaForTesting =
|
| + LAZY_INSTANCE_INITIALIZER;
|
| +
|
| +// Returns |size_in_px| in dp, i.e. divided by the current device scale factor.
|
| +int ConvertIconSizeFromPxToDp(int size_in_px) {
|
| + return size_in_px /
|
| + display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
|
| +}
|
| +
|
| +InstallableParams ParamsToGetManifest() {
|
| + return InstallableParams();
|
| +}
|
| +
|
| +// Returns an InstallableParams object that requests all checks necessary for
|
| +// a web app banner.
|
| +InstallableParams ParamsToPerformInstallableCheck(int ideal_icon_size_in_dp,
|
| + int minimum_icon_size_in_dp) {
|
| + InstallableParams params;
|
| + params.ideal_icon_size_in_dp = ideal_icon_size_in_dp;
|
| + params.minimum_icon_size_in_dp = minimum_icon_size_in_dp;
|
| + params.check_installable = true;
|
| + params.fetch_valid_icon = true;
|
| +
|
| + return params;
|
| +}
|
| +
|
| } // anonymous namespace
|
|
|
| namespace banners {
|
|
|
| +// static
|
| void AppBannerManager::DisableSecureSchemeCheckForTesting() {
|
| gDisableSecureCheckForTesting = true;
|
| }
|
|
|
| +// static
|
| +base::Time AppBannerManager::GetCurrentTime() {
|
| + return base::Time::Now() + gTimeDeltaForTesting.Get();
|
| +}
|
| +
|
| +// static
|
| +void AppBannerManager::SetTimeDeltaForTesting(int days) {
|
| + gTimeDeltaForTesting.Get() = base::TimeDelta::FromDays(days);
|
| +}
|
| +
|
| +// static
|
| void AppBannerManager::SetEngagementWeights(double direct_engagement,
|
| double indirect_engagement) {
|
| AppBannerSettingsHelper::SetEngagementWeights(direct_engagement,
|
| indirect_engagement);
|
| }
|
|
|
| +// static
|
| bool AppBannerManager::URLsAreForTheSamePage(const GURL& first,
|
| const GURL& second) {
|
| return first.GetWithEmptyPath() == second.GetWithEmptyPath() &&
|
| first.path() == second.path() && first.query() == second.query();
|
| }
|
|
|
| +void AppBannerManager::RequestAppBanner(const GURL& validated_url,
|
| + bool is_debug_mode) {
|
| + content::WebContents* contents = web_contents();
|
| + if (contents->GetMainFrame()->GetParent()) {
|
| + ReportError(contents, NOT_IN_MAIN_FRAME);
|
| + return;
|
| + }
|
| +
|
| + // Don't start a redundant banner request.
|
| + if (is_active_ &&
|
| + URLsAreForTheSamePage(validated_url, contents->GetLastCommittedURL())) {
|
| + return;
|
| + }
|
| +
|
| + // A secure origin is required to show banners, so exit early if we see the
|
| + // URL is invalid.
|
| + if (!content::IsOriginSecure(validated_url) &&
|
| + !gDisableSecureCheckForTesting) {
|
| + ReportError(contents, NOT_FROM_SECURE_ORIGIN);
|
| + return;
|
| + }
|
| +
|
| + is_debug_mode_ = is_debug_mode;
|
| + is_active_ = true;
|
| +
|
| + // We start by requesting the manifest from the InstallableManager. The
|
| + // default-constructed params will have all other fields as false.
|
| + manager_->GetData(
|
| + ParamsToGetManifest(),
|
| + base::Bind(&AppBannerManager::OnDidGetManifest, GetWeakPtr()));
|
| +}
|
| +
|
| +base::Closure AppBannerManager::FetchWebappSplashScreenImageCallback(
|
| + const std::string& webapp_id) {
|
| + return base::Closure();
|
| +}
|
| +
|
| AppBannerManager::AppBannerManager(content::WebContents* web_contents)
|
| : content::WebContentsObserver(web_contents),
|
| SiteEngagementObserver(nullptr),
|
| - data_fetcher_(nullptr),
|
| + manager_(nullptr),
|
| + event_request_id_(-1),
|
| + is_active_(false),
|
| banner_request_queued_(false),
|
| load_finished_(false),
|
| + was_canceled_by_page_(false),
|
| + page_requested_prompt_(false),
|
| + is_debug_mode_(false),
|
| weak_factory_(this) {
|
| + // Ensure the InstallableManager exists since we have a hard dependency on it.
|
| + InstallableManager::CreateForWebContents(web_contents);
|
| + manager_ = InstallableManager::FromWebContents(web_contents);
|
| + DCHECK(manager_);
|
| +
|
| AppBannerSettingsHelper::UpdateFromFieldTrial();
|
| }
|
|
|
| -AppBannerManager::~AppBannerManager() {
|
| - CancelActiveFetcher();
|
| +AppBannerManager::~AppBannerManager() { }
|
| +
|
| +std::string AppBannerManager::GetAppIdentifier() {
|
| + DCHECK(!manifest_.IsEmpty());
|
| + return manifest_.start_url.spec();
|
| }
|
|
|
| -void AppBannerManager::ReplaceWebContents(content::WebContents* web_contents) {
|
| - content::WebContentsObserver::Observe(web_contents);
|
| - if (data_fetcher_.get())
|
| - data_fetcher_.get()->ReplaceWebContents(web_contents);
|
| +std::string AppBannerManager::GetBannerType() {
|
| + return "web";
|
| }
|
|
|
| -bool AppBannerManager::IsFetcherActive() {
|
| - return data_fetcher_ && data_fetcher_->is_active();
|
| +std::string AppBannerManager::GetErrorParam(InstallableErrorCode code) {
|
| + if (code == NO_ACCEPTABLE_ICON || code == MANIFEST_MISSING_SUITABLE_ICON) {
|
| + return base::IntToString(InstallableManager::GetMinimumIconSizeInPx());
|
| + }
|
| +
|
| + return std::string();
|
| }
|
|
|
| -void AppBannerManager::RequestAppBanner(const GURL& validated_url,
|
| - bool is_debug_mode) {
|
| - content::WebContents* contents = web_contents();
|
| - if (contents->GetMainFrame()->GetParent()) {
|
| - OutputDeveloperNotShownMessage(contents, kNotLoadedInMainFrame,
|
| - is_debug_mode);
|
| +int AppBannerManager::GetIdealIconSizeInDp() {
|
| + return ConvertIconSizeFromPxToDp(
|
| + InstallableManager::GetMinimumIconSizeInPx());
|
| +}
|
| +
|
| +int AppBannerManager::GetMinimumIconSizeInDp() {
|
| + return ConvertIconSizeFromPxToDp(
|
| + InstallableManager::GetMinimumIconSizeInPx());
|
| +}
|
| +
|
| +base::WeakPtr<AppBannerManager> AppBannerManager::GetWeakPtr() {
|
| + return weak_factory_.GetWeakPtr();
|
| +}
|
| +
|
| +bool AppBannerManager::IsDebugMode() const {
|
| + return is_debug_mode_ ||
|
| + base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kBypassAppBannerEngagementChecks);
|
| +}
|
| +
|
| +bool AppBannerManager::IsWebAppInstalled(
|
| + content::BrowserContext* browser_context,
|
| + const GURL& start_url) {
|
| + return false;
|
| +}
|
| +
|
| +void AppBannerManager::OnDidGetManifest(const InstallableData& data) {
|
| + if (data.error_code != NO_ERROR_DETECTED) {
|
| + ReportError(web_contents(), data.error_code);
|
| + Stop();
|
| + }
|
| +
|
| + if (!is_active_)
|
| return;
|
| +
|
| + DCHECK(!data.manifest_url.is_empty());
|
| + DCHECK(!data.manifest.IsEmpty());
|
| +
|
| + manifest_url_ = data.manifest_url;
|
| + manifest_ = data.manifest;
|
| + app_title_ = (manifest_.name.is_null()) ? manifest_.short_name.string()
|
| + : manifest_.name.string();
|
| +
|
| + PerformInstallableCheck();
|
| +}
|
| +
|
| +void AppBannerManager::PerformInstallableCheck() {
|
| + if (IsWebAppInstalled(web_contents()->GetBrowserContext(),
|
| + manifest_.start_url) &&
|
| + !IsDebugMode()) {
|
| + Stop();
|
| }
|
|
|
| - if (data_fetcher_.get() && data_fetcher_->is_active() &&
|
| - URLsAreForTheSamePage(data_fetcher_->validated_url(), validated_url) &&
|
| - !is_debug_mode) {
|
| + if (!is_active_)
|
| return;
|
| +
|
| + // Fetch and verify the other required information.
|
| + manager_->GetData(ParamsToPerformInstallableCheck(GetIdealIconSizeInDp(),
|
| + GetMinimumIconSizeInDp()),
|
| + base::Bind(&AppBannerManager::OnDidPerformInstallableCheck,
|
| + GetWeakPtr()));
|
| +}
|
| +
|
| +void AppBannerManager::OnDidPerformInstallableCheck(
|
| + const InstallableData& data) {
|
| + if (data.is_installable)
|
| + TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_REQUESTED);
|
| +
|
| + if (data.error_code != NO_ERROR_DETECTED) {
|
| + if (data.error_code == NO_MATCHING_SERVICE_WORKER)
|
| + TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER);
|
| +
|
| + ReportError(web_contents(), data.error_code);
|
| + Stop();
|
| }
|
|
|
| - // A secure origin is required to show banners, so exit early if we see the
|
| - // URL is invalid.
|
| - if (!content::IsOriginSecure(validated_url) &&
|
| - !gDisableSecureCheckForTesting) {
|
| - OutputDeveloperNotShownMessage(contents, kNotServedFromSecureOrigin,
|
| - is_debug_mode);
|
| + if (!is_active_)
|
| return;
|
| +
|
| + DCHECK(data.is_installable);
|
| + DCHECK(!data.icon_url.is_empty());
|
| + DCHECK(data.icon);
|
| +
|
| + icon_url_ = data.icon_url;
|
| + icon_.reset(new SkBitmap(*data.icon));
|
| +
|
| + SendBannerPromptRequest();
|
| +}
|
| +
|
| +void AppBannerManager::RecordDidShowBanner(const std::string& event_name) {
|
| + content::WebContents* contents = web_contents();
|
| + DCHECK(contents);
|
| +
|
| + AppBannerSettingsHelper::RecordBannerEvent(
|
| + contents, validated_url_, GetAppIdentifier(),
|
| + AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW,
|
| + GetCurrentTime());
|
| + rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
|
| + event_name,
|
| + contents->GetLastCommittedURL());
|
| +}
|
| +
|
| +void AppBannerManager::ReportError(content::WebContents* web_contents,
|
| + InstallableErrorCode code) {
|
| + if (IsDebugMode())
|
| + LogErrorToConsole(web_contents, code, GetErrorParam(code));
|
| +}
|
| +
|
| +void AppBannerManager::Stop() {
|
| + if (was_canceled_by_page_ && !page_requested_prompt_) {
|
| + TrackBeforeInstallEvent(
|
| + BEFORE_INSTALL_EVENT_PROMPT_NOT_CALLED_AFTER_PREVENT_DEFAULT);
|
| }
|
|
|
| - // Kick off the data retrieval pipeline.
|
| - data_fetcher_ =
|
| - CreateAppBannerDataFetcher(weak_factory_.GetWeakPtr(), is_debug_mode);
|
| - data_fetcher_->Start(validated_url, last_transition_type_);
|
| + is_active_ = false;
|
| + was_canceled_by_page_ = false;
|
| + page_requested_prompt_ = false;
|
| + referrer_.erase();
|
| }
|
|
|
| -void AppBannerManager::DidStartNavigation(
|
| - content::NavigationHandle* navigation_handle) {
|
| - if (!navigation_handle->IsInMainFrame())
|
| +void AppBannerManager::SendBannerPromptRequest() {
|
| + RecordCouldShowBanner();
|
| +
|
| + // Given all of the other checks that have been made, the only possible reason
|
| + // for stopping now is that the app has been added to the homescreen.
|
| + if (!IsDebugMode() && !CheckIfShouldShowBanner())
|
| + Stop();
|
| +
|
| + if (!is_active_)
|
| + return;
|
| +
|
| + TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_CREATED);
|
| + event_request_id_ = ++gCurrentRequestID;
|
| + content::RenderFrameHost* frame = web_contents()->GetMainFrame();
|
| + frame->Send(new ChromeViewMsg_AppBannerPromptRequest(
|
| + frame->GetRoutingID(), event_request_id_, GetBannerType()));
|
| +}
|
| +
|
| +void AppBannerManager::DidStartNavigation(content::NavigationHandle* handle) {
|
| + if (!handle->IsInMainFrame())
|
| return;
|
|
|
| load_finished_ = false;
|
| @@ -107,10 +320,13 @@ void AppBannerManager::DidStartNavigation(
|
| }
|
| }
|
|
|
| -void AppBannerManager::DidFinishNavigation(
|
| - content::NavigationHandle* navigation_handle) {
|
| - if (navigation_handle->HasCommitted())
|
| - last_transition_type_ = navigation_handle->GetPageTransition();
|
| +void AppBannerManager::DidFinishNavigation(content::NavigationHandle* handle) {
|
| + if (handle->IsInMainFrame() && handle->HasCommitted()) {
|
| + last_transition_type_ = handle->GetPageTransition();
|
| + active_media_players_.clear();
|
| + if (is_active_)
|
| + Stop();
|
| + }
|
| }
|
|
|
| void AppBannerManager::DidFinishLoad(
|
| @@ -121,12 +337,11 @@ void AppBannerManager::DidFinishLoad(
|
| return;
|
|
|
| load_finished_ = true;
|
| + validated_url_ = validated_url;
|
| if (!AppBannerSettingsHelper::ShouldUseSiteEngagementScore() ||
|
| banner_request_queued_) {
|
| banner_request_queued_ = false;
|
|
|
| - // The third argument is the is_debug_mode boolean value, which is true only
|
| - // when it is triggered by the developer's action in DevTools.
|
| RequestAppBanner(validated_url, false /* is_debug_mode */);
|
| }
|
| }
|
| @@ -141,11 +356,8 @@ void AppBannerManager::MediaStoppedPlaying(const MediaPlayerId& id) {
|
| active_media_players_.end());
|
| }
|
|
|
| -bool AppBannerManager::HandleNonWebApp(const std::string& platform,
|
| - const GURL& url,
|
| - const std::string& id,
|
| - bool is_debug_mode) {
|
| - return false;
|
| +void AppBannerManager::WebContentsDestroyed() {
|
| + Stop();
|
| }
|
|
|
| void AppBannerManager::OnEngagementIncreased(content::WebContents* contents,
|
| @@ -172,10 +384,99 @@ void AppBannerManager::OnEngagementIncreased(content::WebContents* contents,
|
| }
|
| }
|
|
|
| -void AppBannerManager::CancelActiveFetcher() {
|
| - if (data_fetcher_) {
|
| - data_fetcher_->Cancel();
|
| - data_fetcher_ = nullptr;
|
| +void AppBannerManager::RecordCouldShowBanner() {
|
| + content::WebContents* contents = web_contents();
|
| + DCHECK(contents);
|
| +
|
| + AppBannerSettingsHelper::RecordBannerCouldShowEvent(
|
| + contents, validated_url_, GetAppIdentifier(),
|
| + GetCurrentTime(), last_transition_type_);
|
| +}
|
| +
|
| +bool AppBannerManager::CheckIfShouldShowBanner() {
|
| + content::WebContents* contents = web_contents();
|
| + DCHECK(contents);
|
| +
|
| + return AppBannerSettingsHelper::ShouldShowBanner(
|
| + contents, validated_url_, GetAppIdentifier(), GetCurrentTime());
|
| +}
|
| +
|
| +bool AppBannerManager::OnMessageReceived(
|
| + const IPC::Message& message,
|
| + content::RenderFrameHost* render_frame_host) {
|
| + bool handled = true;
|
| +
|
| + IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(AppBannerManager, message, render_frame_host)
|
| + IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AppBannerPromptReply,
|
| + OnBannerPromptReply)
|
| + IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RequestShowAppBanner,
|
| + OnRequestShowAppBanner)
|
| + IPC_MESSAGE_UNHANDLED(handled = false)
|
| + IPC_END_MESSAGE_MAP()
|
| +
|
| + return handled;
|
| +}
|
| +
|
| +void AppBannerManager::OnBannerPromptReply(
|
| + content::RenderFrameHost* render_frame_host,
|
| + int request_id,
|
| + blink::WebAppBannerPromptReply reply,
|
| + std::string referrer) {
|
| + content::WebContents* contents = web_contents();
|
| + if (request_id != event_request_id_)
|
| + return;
|
| +
|
| + // The renderer might have requested the prompt to be canceled.
|
| + // They may request that it is redisplayed later, so don't Stop() here.
|
| + // However, log that the cancelation was requested, so Stop() can be
|
| + // called if a redisplay isn't asked for.
|
| + //
|
| + // We use the additional page_requested_prompt_ variable because the redisplay
|
| + // request may be received *before* the Cancel prompt reply (e.g. if redisplay
|
| + // is requested in the beforeinstallprompt event handler).
|
| + referrer_ = referrer;
|
| + if (reply == blink::WebAppBannerPromptReply::Cancel &&
|
| + !page_requested_prompt_) {
|
| + TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_PREVENT_DEFAULT_CALLED);
|
| + was_canceled_by_page_ = true;
|
| + ReportError(contents, RENDERER_CANCELLED);
|
| + return;
|
| + }
|
| +
|
| + // If we haven't yet returned, but either of |was_canceled_by_page_| or
|
| + // |page_requested_prompt_| is true, the page has requested a delayed showing
|
| + // of the prompt. Otherwise, the prompt was never canceled by the page.
|
| + if (was_canceled_by_page_ || page_requested_prompt_) {
|
| + TrackBeforeInstallEvent(
|
| + BEFORE_INSTALL_EVENT_PROMPT_CALLED_AFTER_PREVENT_DEFAULT);
|
| + was_canceled_by_page_ = false;
|
| + } else {
|
| + TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_NO_ACTION);
|
| + }
|
| +
|
| + AppBannerSettingsHelper::RecordMinutesFromFirstVisitToShow(
|
| + contents, validated_url_, GetAppIdentifier(), GetCurrentTime());
|
| +
|
| + DCHECK(!manifest_url_.is_empty());
|
| + DCHECK(!manifest_.IsEmpty());
|
| + DCHECK(!icon_url_.is_empty());
|
| + DCHECK(icon_.get());
|
| + TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_COMPLETE);
|
| + ShowBanner();
|
| + is_active_ = false;
|
| +}
|
| +
|
| +void AppBannerManager::OnRequestShowAppBanner(
|
| + content::RenderFrameHost* render_frame_host,
|
| + int request_id) {
|
| + if (was_canceled_by_page_) {
|
| + // Simulate a non-canceled OnBannerPromptReply to show the delayed banner.
|
| + // Don't reset |was_canceled_by_page_| yet for metrics purposes.
|
| + OnBannerPromptReply(render_frame_host, request_id,
|
| + blink::WebAppBannerPromptReply::None, referrer_);
|
| + } else {
|
| + // Log that the prompt request was made for when we get the prompt reply.
|
| + page_requested_prompt_ = true;
|
| }
|
| }
|
|
|
|
|