chromium/ios/chrome/browser/metrics/model/web_state_list_metrics_browser_agent.mm

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/metrics/model/web_state_list_metrics_browser_agent.h"

#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/navigation_metrics/navigation_metrics.h"
#import "components/profile_metrics/browser_profile_type.h"
#import "ios/chrome/browser/crash_report/model/crash_keys_helper.h"
#import "ios/chrome/browser/crash_report/model/crash_loop_detection_util.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service_factory.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/web_state_list/all_web_state_observation_forwarder.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/web_state_list/model/session_metrics.h"
#import "ios/web/public/browser_state.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_state.h"

BROWSER_USER_DATA_KEY_IMPL(WebStateListMetricsBrowserAgent)

WebStateListMetricsBrowserAgent::WebStateListMetricsBrowserAgent(
    Browser* browser,
    SessionMetrics* session_metrics)
    : web_state_list_(browser->GetWebStateList()),
      session_metrics_(session_metrics) {
  DCHECK(web_state_list_);
  DCHECK(session_metrics_);
  browser->AddObserver(this);
  web_state_list_->AddObserver(this);
  web_state_forwarder_ =
      std::make_unique<AllWebStateObservationForwarder>(web_state_list_, this);

  ChromeBrowserState* browser_state = browser->GetBrowserState();
  session_restoration_service_observation_.Observe(
      SessionRestorationServiceFactory::GetForBrowserState(browser_state));

  is_off_record_ = browser_state->IsOffTheRecord();
  is_inactive_ = browser->IsInactive();
}

WebStateListMetricsBrowserAgent::~WebStateListMetricsBrowserAgent() = default;

#pragma mark - SessionRestorationObserver

void WebStateListMetricsBrowserAgent::WillStartSessionRestoration(
    Browser* browser) {
  // Ignore the event if it does not correspond to the browser this
  // object is bound to (which can happen with the optimised session
  // storage code).
  if (browser->GetWebStateList() != web_state_list_) {
    return;
  }

  metric_collection_paused_ = true;
}

void WebStateListMetricsBrowserAgent::SessionRestorationFinished(
    Browser* browser,
    const std::vector<web::WebState*>& restored_web_states) {
  // Ignore the event if it does not correspond to the browser this
  // object is bound to (which can happen with the optimised session
  // storage code).
  if (browser->GetWebStateList() != web_state_list_) {
    return;
  }

  metric_collection_paused_ = false;
}

#pragma mark - WebStateListObserver

void WebStateListMetricsBrowserAgent::WebStateListWillChange(
    WebStateList* web_state_list,
    const WebStateListChangeDetach& detach_change,
    const WebStateListStatus& status) {
  if (!detach_change.is_closing()) {
    return;
  }

  if (metric_collection_paused_) {
    return;
  }

  base::TimeDelta age_at_deletion =
      base::Time::Now() - detach_change.detached_web_state()->GetCreationTime();
  base::UmaHistogramCustomTimes("Tab.AgeAtDeletion", age_at_deletion,
                                base::Minutes(1), base::Days(24), 50);

  if (detach_change.is_user_action()) {
    base::RecordAction(base::UserMetricsAction("MobileTabClosed"));
  }
}

void WebStateListMetricsBrowserAgent::WebStateListDidChange(
    WebStateList* web_state_list,
    const WebStateListChange& change,
    const WebStateListStatus& status) {
  if (web_state_list->IsBatchInProgress()) {
    return;
  }
  if (metric_collection_paused_) {
    return;
  }

  if (status.active_web_state_change()) {
    session_metrics_->OnWebStateActivated();
    if (change.type() == WebStateListChange::Type::kReplace) {
      return;
    }

    base::RecordAction(base::UserMetricsAction("MobileTabSwitched"));
  }
  switch (change.type()) {
    case WebStateListChange::Type::kInsert:
    case WebStateListChange::Type::kDetach:
    case WebStateListChange::Type::kMove: {
      UpdateCrashkeysTabCount();
      break;
    }
    case WebStateListChange::Type::kStatusOnly:
    case WebStateListChange::Type::kReplace:
    case WebStateListChange::Type::kGroupCreate:
    case WebStateListChange::Type::kGroupVisualDataUpdate:
    case WebStateListChange::Type::kGroupMove:
    case WebStateListChange::Type::kGroupDelete:
      break;
  }
}

void WebStateListMetricsBrowserAgent::BatchOperationEnded(
    WebStateList* web_state_list) {
  if (metric_collection_paused_) {
    return;
  }
  UpdateCrashkeysTabCount();
}

#pragma mark - WebStateObserver

void WebStateListMetricsBrowserAgent::DidFinishNavigation(
    web::WebState* web_state,
    web::NavigationContext* navigation_context) {
  if (!navigation_context->HasCommitted()) {
    return;
  }

  if (!navigation_context->IsSameDocument() &&
      !web_state->GetBrowserState()->IsOffTheRecord()) {
    int tab_count = static_cast<int>(web_state_list_->count());
    UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", tab_count, 1, 200, 50);
  }

  web::NavigationItem* item =
      web_state->GetNavigationManager()->GetLastCommittedItem();
  navigation_metrics::RecordPrimaryMainFrameNavigation(
      item ? item->GetVirtualURL() : GURL(),
      navigation_context->IsSameDocument(),
      web_state->GetBrowserState()->IsOffTheRecord(),
      profile_metrics::GetBrowserProfileType(web_state->GetBrowserState()));
}

void WebStateListMetricsBrowserAgent::PageLoaded(
    web::WebState* web_state,
    web::PageLoadCompletionStatus load_completion_status) {
  base::UmaHistogramBoolean("Tab.HasThemeColor",
                            web_state->GetThemeColor() != nil);
  // By default the underpage color is white. Only consider color as custom if
  // it is not white.
  CGFloat red = 1;
  CGFloat green = 1;
  CGFloat blue = 1;
  CGFloat alpha = 1;
  UIColor* color = web_state->GetUnderPageBackgroundColor();
  [color getRed:&red green:&green blue:&blue alpha:&alpha];
  BOOL isWhite = red > 0.999 && green > 0.999 && blue > 0.999 && alpha > 0.99;
  base::UmaHistogramBoolean("Tab.HasCustomUnderPageBackgroundColor", isWhite);
}

#pragma mark - BrowserObserver

void WebStateListMetricsBrowserAgent::BrowserDestroyed(Browser* browser) {
  DCHECK_EQ(browser->GetWebStateList(), web_state_list_);

  session_restoration_service_observation_.Reset();

  web_state_forwarder_.reset(nullptr);
  web_state_list_->RemoveObserver(this);
  web_state_list_ = nullptr;

  browser->RemoveObserver(this);
}

void WebStateListMetricsBrowserAgent::UpdateCrashkeysTabCount() {
  if (is_off_record_) {
    crash_keys::SetIncognitoTabCount(web_state_list_->count());
    return;
  }
  if (is_inactive_) {
    crash_keys::SetInactiveTabCount(web_state_list_->count());
    return;
  }

  crash_keys::SetRegularTabCount(web_state_list_->count());
}