File: frame_tree_data.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (336 lines) | stat: -rw-r--r-- 13,052 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


#include "components/page_load_metrics/browser/observers/ad_metrics/frame_tree_data.h"

#include <algorithm>
#include <limits>
#include <string>

#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "components/heavy_ad_intervention/heavy_ad_features.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/frame_data_utils.h"
#include "components/page_load_metrics/common/page_load_metrics.mojom.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "url/gurl.h"

namespace page_load_metrics {

namespace {

// A frame with area less than kMinimumVisibleFrameArea is not considered
// visible.
const int kMinimumVisibleFrameArea = 25;

// Controls what types of heavy ads will be unloaded by the intervention.
const base::FeatureParam<int> kHeavyAdUnloadPolicyParam = {
    &heavy_ad_intervention::features::kHeavyAdIntervention, "kUnloadPolicy",
    static_cast<int>(HeavyAdUnloadPolicy::kAll)};

// Calculates the depth from the outermost main page to the given `rfh` beyond
// page boundaries. This is used to ensure that the depth in the created tree is
// same with the actual page/frame tree structure.
unsigned int GetFullFrameDepth(content::RenderFrameHost* rfh) {
  unsigned int depth = rfh->GetFrameDepth();
  while ((rfh = rfh->GetMainFrame()->GetParentOrOuterDocument())) {
    depth += rfh->GetFrameDepth() + 1;
  }
  return depth;
}

}  // namespace

FrameTreeData::FrameTreeData(content::FrameTreeNodeId root_frame_tree_node_id,
                             int heavy_ad_network_threshold_noise)
    : root_frame_tree_node_id_(root_frame_tree_node_id),
      frame_size_(gfx::Size()),
      heavy_ad_network_threshold_noise_(heavy_ad_network_threshold_noise) {}

FrameTreeData::~FrameTreeData() = default;

void FrameTreeData::MaybeUpdateFrameDepth(
    content::RenderFrameHost* render_frame_host) {
  if (!render_frame_host)
    return;
  DCHECK_GE(GetFullFrameDepth(render_frame_host), root_frame_depth_);
  if (GetFullFrameDepth(render_frame_host) - root_frame_depth_ > frame_depth_)
    frame_depth_ = GetFullFrameDepth(render_frame_host) - root_frame_depth_;
}

void FrameTreeData::UpdateMemoryUsage(int64_t delta_bytes) {
  memory_usage_.UpdateUsage(delta_bytes);
}

bool FrameTreeData::ShouldRecordFrameForMetrics() const {
  return resource_data().bytes() != 0 || !GetTotalCpuUsage().is_zero() ||
         memory_usage_.max_bytes_used() > 0;
}

void FrameTreeData::RecordAdFrameLoadUkmEvent(ukm::SourceId source_id) const {
  if (!ShouldRecordFrameForMetrics())
    return;

  auto* ukm_recorder = ukm::UkmRecorder::Get();
  ukm::builders::AdFrameLoad builder(source_id);
  builder
      .SetLoading_NetworkBytes(
          ukm::GetExponentialBucketMinForBytes(resource_data().network_bytes()))
      .SetLoading_CacheBytes2(ukm::GetExponentialBucketMinForBytes(
          (resource_data().bytes() - resource_data().network_bytes())))
      .SetLoading_VideoBytes(ukm::GetExponentialBucketMinForBytes(
          resource_data().GetAdNetworkBytesForMime(ResourceMimeType::kVideo)))
      .SetLoading_JavascriptBytes(ukm::GetExponentialBucketMinForBytes(
          resource_data().GetAdNetworkBytesForMime(
              ResourceMimeType::kJavascript)))
      .SetLoading_ImageBytes(ukm::GetExponentialBucketMinForBytes(
          resource_data().GetAdNetworkBytesForMime(ResourceMimeType::kImage)))
      .SetLoading_NumResources(num_resources_);

  builder.SetCpuTime_Total(GetTotalCpuUsage().InMilliseconds());
  if (user_activation_status() == UserActivationStatus::kReceivedActivation) {
    builder.SetCpuTime_PreActivation(
        GetActivationCpuUsage(UserActivationStatus::kNoActivation)
            .InMilliseconds());
  }

  builder.SetCpuTime_PeakWindowedPercent(peak_cpu_.peak_windowed_percent());

  builder
      .SetVisibility_FrameWidth(
          ukm::GetExponentialBucketMinForCounts1000(frame_size().width()))
      .SetVisibility_FrameHeight(
          ukm::GetExponentialBucketMinForCounts1000(frame_size().height()))
      .SetVisibility_Hidden(is_display_none_);

  builder.SetStatus_CrossOrigin(static_cast<int>(origin_status()))
      .SetStatus_Media(static_cast<int>(media_status()))
      .SetStatus_UserActivation(static_cast<int>(user_activation_status()));

  builder.SetFrameDepth(frame_depth_);

  if (auto earliest_fcp = earliest_first_contentful_paint()) {
    builder.SetTiming_FirstContentfulPaint(earliest_fcp->InMilliseconds());
  }
  builder.Record(ukm_recorder->Get());
}

OriginStatusWithThrottling
FrameTreeData::GetCreativeOriginStatusWithThrottling() const {
  bool is_throttled = !first_eligible_to_paint().has_value();

  switch (creative_origin_status()) {
    case OriginStatus::kUnknown:
      return is_throttled ? OriginStatusWithThrottling::kUnknownAndThrottled
                          : OriginStatusWithThrottling::kUnknownAndUnthrottled;
    case OriginStatus::kSame:
      DCHECK(!is_throttled);
      return OriginStatusWithThrottling::kSameAndUnthrottled;
    case OriginStatus::kCross:
      DCHECK(!is_throttled);
      return OriginStatusWithThrottling::kCrossAndUnthrottled;
    // We expect the above values to cover all cases.
    default:
      NOTREACHED();
  }
}

void FrameTreeData::SetFirstEligibleToPaint(
    std::optional<base::TimeDelta> time_stamp) {
  if (time_stamp.has_value()) {
    // If the ad frame tree hasn't already received an earlier paint
    // eligibility stamp, mark it as eligible to paint. Since multiple frames
    // may report timestamps, we keep the earliest reported stamp.
    // Note that this timestamp (or lack thereof) is best-effort.
    if (!first_eligible_to_paint_.has_value() ||
        first_eligible_to_paint_.value() > time_stamp.value())
      first_eligible_to_paint_ = time_stamp;
  } else if (!earliest_first_contentful_paint_.has_value()) {
    // If a frame in this ad frame tree has already painted, there is no
    // further need to update paint eligibility. But if nothing has
    // painted and a null value is passed into the setter, that means the
    // frame is now render-throttled and we should reset the paint-eligiblity
    // value.
    first_eligible_to_paint_.reset();
  }
}

bool FrameTreeData::SetEarliestFirstContentfulPaint(
    std::optional<base::TimeDelta> time_stamp) {
  if (!time_stamp.has_value() || time_stamp.value().is_zero())
    return false;

  if (earliest_first_contentful_paint_.has_value() &&
      time_stamp.value() >= earliest_first_contentful_paint_.value())
    return false;

  earliest_first_contentful_paint_ = time_stamp;
  return true;
}

void FrameTreeData::SetEarliestFirstContentfulPaintSinceTopNavStart(
    base::TimeDelta time_since_top_nav_start) {
  if (!earliest_fcp_since_top_nav_start_ ||
      earliest_fcp_since_top_nav_start_ > time_since_top_nav_start) {
    earliest_fcp_since_top_nav_start_ = time_since_top_nav_start;
  }
}

void FrameTreeData::UpdateFrameVisibility() {
  visibility_ =
      !is_display_none_ &&
              frame_size_.GetCheckedArea().ValueOrDefault(
                  std::numeric_limits<int>::max()) >= kMinimumVisibleFrameArea
          ? FrameVisibility::kVisible
          : FrameVisibility::kNonVisible;
}

HeavyAdStatus FrameTreeData::ComputeHeavyAdStatus(
    bool use_network_threshold_noise,
    HeavyAdUnloadPolicy policy) const {
  if (policy == HeavyAdUnloadPolicy::kCpuOnly ||
      policy == HeavyAdUnloadPolicy::kAll) {
    // Check if the frame meets the peak CPU usage threshold.
    if (peak_windowed_cpu_percent() >=
        heavy_ad_thresholds::kMaxPeakWindowedPercent) {
      return HeavyAdStatus::kPeakCpu;
    }

    // Check if the frame meets the absolute CPU time threshold.
    if (GetTotalCpuUsage().InMilliseconds() >= heavy_ad_thresholds::kMaxCpuTime)
      return HeavyAdStatus::kTotalCpu;
  }

  if (policy == HeavyAdUnloadPolicy::kNetworkOnly ||
      policy == HeavyAdUnloadPolicy::kAll) {
    size_t network_threshold =
        heavy_ad_thresholds::kMaxNetworkBytes +
        (use_network_threshold_noise ? heavy_ad_network_threshold_noise_ : 0);

    // Check if the frame meets the network threshold, possible including noise.
    if (resource_data().network_bytes() >= network_threshold)
      return HeavyAdStatus::kNetwork;
  }
  return HeavyAdStatus::kNone;
}

void FrameTreeData::UpdateCpuUsage(base::TimeTicks update_time,
                                   base::TimeDelta update) {
  // Update the overall usage for all of the relevant buckets.
  cpu_usage_[static_cast<size_t>(user_activation_status_)] += update;

  // If the frame has been activated, then we don't update the peak usage.
  if (user_activation_status_ == UserActivationStatus::kReceivedActivation)
    return;

  // Update the peak usage.
  peak_cpu_.UpdatePeakWindowedPercent(update, update_time);
}

base::TimeDelta FrameTreeData::GetTotalCpuUsage() const {
  base::TimeDelta total_cpu_time;
  for (base::TimeDelta cpu_time : cpu_usage_)
    total_cpu_time += cpu_time;
  return total_cpu_time;
}

void FrameTreeData::UpdateForNavigation(
    content::RenderFrameHost* render_frame_host) {
  if (!render_frame_host)
    return;

  SetDisplayState(render_frame_host->IsFrameDisplayNone());
  if (render_frame_host->GetFrameSize())
    SetFrameSize(*(render_frame_host->GetFrameSize()));

  // For frames triggered on render, their origin is their parent's origin.
  origin_status_ =
      AdsPageLoadMetricsObserver::IsFrameSameOriginToOutermostMainFrame(
          render_frame_host)
          ? OriginStatus::kSame
          : OriginStatus::kCross;

  root_frame_depth_ = GetFullFrameDepth(render_frame_host);
}

void FrameTreeData::ProcessResourceLoadInFrame(
    const mojom::ResourceDataUpdatePtr& resource,
    int process_id,
    const ResourceTracker& resource_tracker) {
  content::GlobalRequestID global_id(process_id, resource->request_id);
  if (!resource_tracker.HasPreviousUpdateForResource(global_id))
    num_resources_++;
  resource_data_.ProcessResourceLoad(resource);
}

void FrameTreeData::AdjustAdBytes(int64_t unaccounted_ad_bytes,
                                  ResourceMimeType mime_type) {
  resource_data_.AdjustAdBytes(unaccounted_ad_bytes, mime_type);
}

void FrameTreeData::SetFrameSize(gfx::Size frame_size) {
  frame_size_ = frame_size;
  UpdateFrameVisibility();
}

void FrameTreeData::SetDisplayState(bool is_display_none) {
  is_display_none_ = is_display_none;
  UpdateFrameVisibility();
}

HeavyAdAction FrameTreeData::MaybeTriggerHeavyAdIntervention() {
  // TODO(johnidel): This method currently does a lot of heavy lifting: tracking
  // noised and unnoised metrics, determining feature action, and branching
  // based on configuration. Consider splitting this out and letting AdsPLMO do
  // more of the feature specific logic.
  //
  // If the intervention has already performed an action on this frame, do not
  // perform another. Metrics will have been calculated already.
  if (user_activation_status_ == UserActivationStatus::kReceivedActivation ||
      heavy_ad_action_ != HeavyAdAction::kNone) {
    return HeavyAdAction::kNone;
  }

  if (heavy_ad_status_with_noise_ == HeavyAdStatus::kNone) {
    heavy_ad_status_with_noise_ = ComputeHeavyAdStatus(
        true /* use_network_threshold_noise */, HeavyAdUnloadPolicy::kAll);
  }

  // Only activate the field trial if there is a heavy ad. Getting the feature
  // param value activates the trial, so we cannot limit activating the trial
  // based on the HeavyAdUnloadPolicy. Therefore, we just use a heavy ad of any
  // type as a gate for activating trial.
  if (heavy_ad_status_with_noise_ == HeavyAdStatus::kNone)
    return HeavyAdAction::kNone;

  heavy_ad_status_with_policy_ = ComputeHeavyAdStatus(
      true /* use_network_threshold_noise */,
      static_cast<HeavyAdUnloadPolicy>(kHeavyAdUnloadPolicyParam.Get()));

  if (heavy_ad_status_with_policy_ == HeavyAdStatus::kNone)
    return HeavyAdAction::kNone;

  // Only check if the feature is enabled once we have a heavy ad. This is done
  // to ensure that any experiment for this feature will only be comparing
  // groups who have seen a heavy ad.
  if (!base::FeatureList::IsEnabled(
          heavy_ad_intervention::features::kHeavyAdIntervention)) {
    // If the intervention is not enabled, we return whether reporting is
    // enabled.
    return base::FeatureList::IsEnabled(
               heavy_ad_intervention::features::kHeavyAdInterventionWarning)
               ? HeavyAdAction::kReport
               : HeavyAdAction::kNone;
  }

  return HeavyAdAction::kUnload;
}

}  // namespace page_load_metrics