Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(109)

Side by Side Diff: chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc

Issue 2901383002: Buffer cross frame paint timing updates. (Closed)
Patch Set: factor WeakMockTimer into a common location. Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h " 5 #include "chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h "
6 6
7 #include <ostream> 7 #include <ostream>
8 #include <utility> 8 #include <utility>
9 9
10 #include "base/logging.h" 10 #include "base/logging.h"
11 #include "base/metrics/histogram_macros.h" 11 #include "base/metrics/histogram_macros.h"
12 #include "base/optional.h" 12 #include "base/optional.h"
13 #include "chrome/browser/page_load_metrics/browser_page_track_decider.h" 13 #include "chrome/browser/page_load_metrics/browser_page_track_decider.h"
14 #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface. h" 14 #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface. h"
15 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" 15 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
16 #include "chrome/browser/page_load_metrics/page_load_tracker.h" 16 #include "chrome/browser/page_load_metrics/page_load_tracker.h"
17 #include "chrome/common/page_load_metrics/page_load_metrics_constants.h" 17 #include "chrome/common/page_load_metrics/page_load_metrics_constants.h"
18 #include "content/public/browser/navigation_handle.h" 18 #include "content/public/browser/navigation_handle.h"
19 #include "content/public/browser/render_frame_host.h" 19 #include "content/public/browser/render_frame_host.h"
20 20
21 namespace page_load_metrics { 21 namespace page_load_metrics {
22 22
23 namespace internal { 23 namespace internal {
24 24
25 const char kPageLoadTimingStatus[] = "PageLoad.Internal.PageLoadTimingStatus"; 25 const char kPageLoadTimingStatus[] = "PageLoad.Internal.PageLoadTimingStatus";
26 const char kPageLoadTimingDispatchStatus[] = 26 const char kPageLoadTimingDispatchStatus[] =
27 "PageLoad.Internal.PageLoadTimingStatus.AtTimingCallbackDispatch"; 27 "PageLoad.Internal.PageLoadTimingStatus.AtTimingCallbackDispatch";
28 const char kHistogramOutOfOrderTiming[] =
29 "PageLoad.Internal.OutOfOrderInterFrameTiming";
30 const char kHistogramOutOfOrderTimingBuffered[] =
31 "PageLoad.Internal.OutOfOrderInterFrameTiming.AfterBuffering";
28 32
29 } // namespace internal 33 } // namespace internal
30 34
31 namespace { 35 namespace {
32 36
33 // Helper to allow use of Optional<> values in LOG() messages. 37 // Helper to allow use of Optional<> values in LOG() messages.
34 std::ostream& operator<<(std::ostream& os, 38 std::ostream& operator<<(std::ostream& os,
35 const base::Optional<base::TimeDelta>& opt) { 39 const base::Optional<base::TimeDelta>& opt) {
36 if (opt) 40 if (opt)
37 os << opt.value(); 41 os << opt.value();
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 timing.paint_timing->first_meaningful_paint)) { 197 timing.paint_timing->first_meaningful_paint)) {
194 LOG(ERROR) << "Invalid first_paint " << timing.paint_timing->first_paint 198 LOG(ERROR) << "Invalid first_paint " << timing.paint_timing->first_paint
195 << " for first_meaningful_paint " 199 << " for first_meaningful_paint "
196 << timing.paint_timing->first_meaningful_paint; 200 << timing.paint_timing->first_meaningful_paint;
197 return internal::INVALID_ORDER_FIRST_PAINT_FIRST_MEANINGFUL_PAINT; 201 return internal::INVALID_ORDER_FIRST_PAINT_FIRST_MEANINGFUL_PAINT;
198 } 202 }
199 203
200 return internal::VALID; 204 return internal::VALID;
201 } 205 }
202 206
207 // If the updated value has an earlier time than the current value, log so we
208 // can keep track of how often this happens.
209 void LogIfOutOfOrderTiming(const base::Optional<base::TimeDelta>& current,
210 const base::Optional<base::TimeDelta>& update) {
211 if (!current || !update)
212 return;
213
214 if (update < current) {
215 PAGE_LOAD_HISTOGRAM(internal::kHistogramOutOfOrderTimingBuffered,
216 current.value() - update.value());
217 }
218 }
219
220 // PaintTimingMerger merges paint timing values received from different frames
221 // together.
222 class PaintTimingMerger {
223 public:
224 explicit PaintTimingMerger(mojom::PaintTiming* target) : target_(target) {}
225
226 // Merge paint timing values from |new_paint_timing| into the target
227 // PaintTiming.
228 void Merge(base::TimeDelta navigation_start_offset,
229 const mojom::PaintTiming& new_paint_timing,
230 bool is_main_frame);
231
232 // Whether we merged a new value, for a paint timing field we didn't
233 // previously have a value for in the target PaintTiming.
234 bool did_merge_new_timing_value() const {
235 return did_merge_new_timing_value_;
236 }
237
238 private:
239 void MaybeUpdateTimeDelta(
240 base::Optional<base::TimeDelta>* inout_existing_value,
241 base::TimeDelta navigation_start_offset,
242 const base::Optional<base::TimeDelta>& optional_candidate_new_value);
243
244 // The target PaintTiming we are merging values into.
245 mojom::PaintTiming* const target_;
246
247 // Whether we merged a new value, for a paint timing field we didn't
248 // previously have a value for in |target_|.
249 bool did_merge_new_timing_value_ = false;
250
251 DISALLOW_COPY_AND_ASSIGN(PaintTimingMerger);
252 };
253
203 // Updates *|inout_existing_value| with |optional_candidate_new_value|, if 254 // Updates *|inout_existing_value| with |optional_candidate_new_value|, if
204 // either *|inout_existing_value| isn't set, or |optional_candidate_new_value| < 255 // either *|inout_existing_value| isn't set, or |optional_candidate_new_value| <
205 // |inout_existing_value|. 256 // |inout_existing_value|.
206 void MaybeUpdateTimeDelta( 257 void PaintTimingMerger::MaybeUpdateTimeDelta(
207 base::Optional<base::TimeDelta>* inout_existing_value, 258 base::Optional<base::TimeDelta>* inout_existing_value,
208 base::TimeDelta navigation_start_offset, 259 base::TimeDelta navigation_start_offset,
209 const base::Optional<base::TimeDelta>& optional_candidate_new_value) { 260 const base::Optional<base::TimeDelta>& optional_candidate_new_value) {
210 // If we don't get a new value, there's nothing to do 261 // If we don't get a new value, there's nothing to do
211 if (!optional_candidate_new_value) 262 if (!optional_candidate_new_value)
212 return; 263 return;
213 264
214 // optional_candidate_new_value is relative to navigation start in its 265 // optional_candidate_new_value is relative to navigation start in its
215 // frame. We need to adjust it to be relative to navigation start in the main 266 // frame. We need to adjust it to be relative to navigation start in the main
216 // frame, so offset it by navigation_start_offset. 267 // frame, so offset it by navigation_start_offset.
217 base::TimeDelta candidate_new_value = 268 base::TimeDelta candidate_new_value =
218 navigation_start_offset + optional_candidate_new_value.value(); 269 navigation_start_offset + optional_candidate_new_value.value();
219 270
220 if (*inout_existing_value) { 271 if (*inout_existing_value) {
221 // If we have a new value, but it is after the existing value, then keep the 272 // If we have a new value, but it is after the existing value, then keep the
222 // existing value. 273 // existing value.
223 if (*inout_existing_value <= candidate_new_value) 274 if (*inout_existing_value <= candidate_new_value)
224 return; 275 return;
225 276
226 // We received a new timing event, but with a timestamp before the timestamp 277 // We received a new timing event, but with a timestamp before the timestamp
227 // of a timing update we had received previously. We expect this to happen 278 // of a timing update we had received previously. We expect this to happen
228 // occasionally, as inter-frame updates can arrive out of order. Record a 279 // occasionally, as inter-frame updates can arrive out of order. Record a
229 // histogram to track how frequently it happens, along with the magnitude 280 // histogram to track how frequently it happens, along with the magnitude
230 // of the delta. 281 // of the delta.
231 PAGE_LOAD_HISTOGRAM("PageLoad.Internal.OutOfOrderInterFrameTiming", 282 PAGE_LOAD_HISTOGRAM(internal::kHistogramOutOfOrderTiming,
232 inout_existing_value->value() - candidate_new_value); 283 inout_existing_value->value() - candidate_new_value);
284 } else {
285 did_merge_new_timing_value_ = true;
233 } 286 }
234 287
235 *inout_existing_value = candidate_new_value; 288 *inout_existing_value = candidate_new_value;
236 } 289 }
237 290
291 void PaintTimingMerger::Merge(base::TimeDelta navigation_start_offset,
292 const mojom::PaintTiming& new_paint_timing,
293 bool is_main_frame) {
294 MaybeUpdateTimeDelta(&target_->first_paint, navigation_start_offset,
295 new_paint_timing.first_paint);
296 MaybeUpdateTimeDelta(&target_->first_text_paint, navigation_start_offset,
297 new_paint_timing.first_text_paint);
298 MaybeUpdateTimeDelta(&target_->first_image_paint, navigation_start_offset,
299 new_paint_timing.first_image_paint);
300 MaybeUpdateTimeDelta(&target_->first_contentful_paint,
301 navigation_start_offset,
302 new_paint_timing.first_contentful_paint);
303 if (is_main_frame) {
304 // first meaningful paint is only tracked in the main frame.
Charlie Harrison 2017/05/30 14:49:07 First
Bryan McQuade 2017/05/30 15:30:22 done
305 target_->first_meaningful_paint = new_paint_timing.first_meaningful_paint;
306 }
307 }
308
238 } // namespace 309 } // namespace
239 310
240 PageLoadMetricsUpdateDispatcher::PageLoadMetricsUpdateDispatcher( 311 PageLoadMetricsUpdateDispatcher::PageLoadMetricsUpdateDispatcher(
241 PageLoadMetricsUpdateDispatcher::Client* client, 312 PageLoadMetricsUpdateDispatcher::Client* client,
242 content::NavigationHandle* navigation_handle, 313 content::NavigationHandle* navigation_handle,
243 PageLoadMetricsEmbedderInterface* embedder_interface) 314 PageLoadMetricsEmbedderInterface* embedder_interface)
244 : client_(client), 315 : client_(client),
245 embedder_interface_(embedder_interface), 316 embedder_interface_(embedder_interface),
317 timer_(embedder_interface->CreateTimer()),
246 navigation_start_(navigation_handle->NavigationStart()), 318 navigation_start_(navigation_handle->NavigationStart()),
247 current_merged_page_timing_(CreatePageLoadTiming()), 319 current_merged_page_timing_(CreatePageLoadTiming()),
248 pending_merged_page_timing_(CreatePageLoadTiming()), 320 pending_merged_page_timing_(CreatePageLoadTiming()),
249 main_frame_metadata_(mojom::PageLoadMetadata::New()), 321 main_frame_metadata_(mojom::PageLoadMetadata::New()),
250 subframe_metadata_(mojom::PageLoadMetadata::New()) {} 322 subframe_metadata_(mojom::PageLoadMetadata::New()) {}
251 323
252 PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {} 324 PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {
325 ShutDown();
326 }
327
328 void PageLoadMetricsUpdateDispatcher::ShutDown() {
Charlie Harrison 2017/05/30 14:49:07 As mentioned earlier, if we just used unique_ptr<P
Bryan McQuade 2017/05/30 15:30:23 My sense is these are two equivalent ways to accom
Charlie Harrison 2017/05/30 16:33:15 I hear you, and that argument makes sense. My pref
329 if (timer_ && timer_->IsRunning()) {
330 timer_->Stop();
331 DispatchTimingUpdates();
332 }
333 timer_ = nullptr;
334 }
253 335
254 void PageLoadMetricsUpdateDispatcher::UpdateMetrics( 336 void PageLoadMetricsUpdateDispatcher::UpdateMetrics(
255 content::RenderFrameHost* render_frame_host, 337 content::RenderFrameHost* render_frame_host,
256 const mojom::PageLoadTiming& new_timing, 338 const mojom::PageLoadTiming& new_timing,
257 const mojom::PageLoadMetadata& new_metadata) { 339 const mojom::PageLoadMetadata& new_metadata) {
258 if (render_frame_host->GetParent() == nullptr) { 340 if (render_frame_host->GetParent() == nullptr) {
259 UpdateMainFrameMetadata(new_metadata); 341 UpdateMainFrameMetadata(new_metadata);
260 UpdateMainFrameTiming(new_timing); 342 UpdateMainFrameTiming(new_timing);
261 } else { 343 } else {
262 UpdateSubFrameMetadata(new_metadata); 344 UpdateSubFrameMetadata(new_metadata);
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
296 const auto it = subframe_navigation_start_offset_.find( 378 const auto it = subframe_navigation_start_offset_.find(
297 render_frame_host->GetFrameTreeNodeId()); 379 render_frame_host->GetFrameTreeNodeId());
298 if (it == subframe_navigation_start_offset_.end()) { 380 if (it == subframe_navigation_start_offset_.end()) {
299 // We received timing information for an untracked load. Ignore it. 381 // We received timing information for an untracked load. Ignore it.
300 return; 382 return;
301 } 383 }
302 384
303 client_->OnSubFrameTimingChanged(new_timing); 385 client_->OnSubFrameTimingChanged(new_timing);
304 386
305 base::TimeDelta navigation_start_offset = it->second; 387 base::TimeDelta navigation_start_offset = it->second;
306 MergePaintTiming(navigation_start_offset, *(new_timing.paint_timing), 388 PaintTimingMerger merger(pending_merged_page_timing_->paint_timing.get());
307 false /* is_main_frame */); 389 merger.Merge(navigation_start_offset, *new_timing.paint_timing,
390 false /* is_main_frame */);
308 391
309 DispatchTimingUpdates(); 392 MaybeDispatchTimingUpdates(merger.did_merge_new_timing_value());
310 }
311
312 void PageLoadMetricsUpdateDispatcher::MergePaintTiming(
313 base::TimeDelta navigation_start_offset,
314 const mojom::PaintTiming& new_paint_timing,
315 bool is_main_frame) {
316 MaybeUpdateTimeDelta(&pending_merged_page_timing_->paint_timing->first_paint,
317 navigation_start_offset, new_paint_timing.first_paint);
318 MaybeUpdateTimeDelta(
319 &pending_merged_page_timing_->paint_timing->first_text_paint,
320 navigation_start_offset, new_paint_timing.first_text_paint);
321 MaybeUpdateTimeDelta(
322 &pending_merged_page_timing_->paint_timing->first_image_paint,
323 navigation_start_offset, new_paint_timing.first_image_paint);
324 MaybeUpdateTimeDelta(
325 &pending_merged_page_timing_->paint_timing->first_contentful_paint,
326 navigation_start_offset, new_paint_timing.first_contentful_paint);
327 if (is_main_frame) {
328 // first meaningful paint is only tracked in the main frame.
329 pending_merged_page_timing_->paint_timing->first_meaningful_paint =
330 new_paint_timing.first_meaningful_paint;
331 }
332 } 393 }
333 394
334 void PageLoadMetricsUpdateDispatcher::UpdateSubFrameMetadata( 395 void PageLoadMetricsUpdateDispatcher::UpdateSubFrameMetadata(
335 const mojom::PageLoadMetadata& subframe_metadata) { 396 const mojom::PageLoadMetadata& subframe_metadata) {
336 // Merge the subframe loading behavior flags with any we've already observed, 397 // Merge the subframe loading behavior flags with any we've already observed,
337 // possibly from other subframes. 398 // possibly from other subframes.
338 const int last_subframe_loading_behavior_flags = 399 const int last_subframe_loading_behavior_flags =
339 subframe_metadata_->behavior_flags; 400 subframe_metadata_->behavior_flags;
340 subframe_metadata_->behavior_flags |= subframe_metadata.behavior_flags; 401 subframe_metadata_->behavior_flags |= subframe_metadata.behavior_flags;
341 if (last_subframe_loading_behavior_flags == 402 if (last_subframe_loading_behavior_flags ==
(...skipping 25 matching lines...) Expand all
367 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING); 428 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING);
368 return; 429 return;
369 } 430 }
370 431
371 mojom::PaintTimingPtr last_paint_timing = 432 mojom::PaintTimingPtr last_paint_timing =
372 std::move(pending_merged_page_timing_->paint_timing); 433 std::move(pending_merged_page_timing_->paint_timing);
373 // Update the pending_merged_page_timing_, making sure to merge the previously 434 // Update the pending_merged_page_timing_, making sure to merge the previously
374 // observed |paint_timing|, which is tracked across all frames in the page. 435 // observed |paint_timing|, which is tracked across all frames in the page.
375 pending_merged_page_timing_ = new_timing.Clone(); 436 pending_merged_page_timing_ = new_timing.Clone();
376 pending_merged_page_timing_->paint_timing = std::move(last_paint_timing); 437 pending_merged_page_timing_->paint_timing = std::move(last_paint_timing);
377 MergePaintTiming(base::TimeDelta(), *new_timing.paint_timing,
378 true /* is_main_frame */);
379 438
380 DispatchTimingUpdates(); 439 PaintTimingMerger merger(pending_merged_page_timing_->paint_timing.get());
440 merger.Merge(base::TimeDelta(), *new_timing.paint_timing,
441 true /* is_main_frame */);
442
443 MaybeDispatchTimingUpdates(merger.did_merge_new_timing_value());
381 } 444 }
382 445
383 void PageLoadMetricsUpdateDispatcher::UpdateMainFrameMetadata( 446 void PageLoadMetricsUpdateDispatcher::UpdateMainFrameMetadata(
384 const mojom::PageLoadMetadata& new_metadata) { 447 const mojom::PageLoadMetadata& new_metadata) {
385 if (main_frame_metadata_->Equals(new_metadata)) 448 if (main_frame_metadata_->Equals(new_metadata))
386 return; 449 return;
387 450
388 // Ensure flags sent previously are still present in the new metadata fields. 451 // Ensure flags sent previously are still present in the new metadata fields.
389 const bool valid_behavior_descendent = 452 const bool valid_behavior_descendent =
390 (main_frame_metadata_->behavior_flags & new_metadata.behavior_flags) == 453 (main_frame_metadata_->behavior_flags & new_metadata.behavior_flags) ==
391 main_frame_metadata_->behavior_flags; 454 main_frame_metadata_->behavior_flags;
392 if (!valid_behavior_descendent) { 455 if (!valid_behavior_descendent) {
393 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT); 456 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT);
394 return; 457 return;
395 } 458 }
396 459
397 main_frame_metadata_ = new_metadata.Clone(); 460 main_frame_metadata_ = new_metadata.Clone();
398 client_->OnMainFrameMetadataChanged(); 461 client_->OnMainFrameMetadataChanged();
399 } 462 }
400 463
464 void PageLoadMetricsUpdateDispatcher::MaybeDispatchTimingUpdates(
465 bool did_merge_new_timing_value) {
466 // If we merged a new timing value, then we should buffer updates for
467 // |kBufferTimerDelayMillis|, to allow for any other out of order timings to
468 // arrive before we dispatch the minimum observed timings to observers.
469 if (did_merge_new_timing_value) {
470 timer_->Start(
471 FROM_HERE, base::TimeDelta::FromMilliseconds(kBufferTimerDelayMillis),
472 base::Bind(&PageLoadMetricsUpdateDispatcher::DispatchTimingUpdates,
473 base::Unretained(this)));
474 } else if (!timer_->IsRunning()) {
475 DispatchTimingUpdates();
476 }
477 }
478
401 void PageLoadMetricsUpdateDispatcher::DispatchTimingUpdates() { 479 void PageLoadMetricsUpdateDispatcher::DispatchTimingUpdates() {
480 DCHECK(!timer_->IsRunning());
481
402 if (pending_merged_page_timing_->paint_timing->first_paint) { 482 if (pending_merged_page_timing_->paint_timing->first_paint) {
403 if (!pending_merged_page_timing_->parse_timing->parse_start || 483 if (!pending_merged_page_timing_->parse_timing->parse_start ||
404 !pending_merged_page_timing_->document_timing->first_layout) { 484 !pending_merged_page_timing_->document_timing->first_layout) {
405 // When merging paint events across frames, we can sometimes encounter 485 // When merging paint events across frames, we can sometimes encounter
406 // cases where we've received a first paint event for a child frame before 486 // cases where we've received a first paint event for a child frame before
407 // receiving required earlier events in the main frame, due to buffering 487 // receiving required earlier events in the main frame, due to buffering
408 // in the render process which results in out of order delivery. For 488 // in the render process which results in out of order delivery. For
409 // example, we may receive a notification for a first paint in a child 489 // example, we may receive a notification for a first paint in a child
410 // frame before we've received a notification for parse start or first 490 // frame before we've received a notification for parse start or first
411 // layout in the main frame. In these cases, we delay sending timing 491 // layout in the main frame. In these cases, we delay sending timing
412 // updates until we've received all expected events (e.g. wait to receive 492 // updates until we've received all expected events (e.g. wait to receive
413 // a parse or layout event before dispatching a paint event), so observers 493 // a parse or layout event before dispatching a paint event), so observers
414 // can make assumptions about ordering of these events in their callbacks. 494 // can make assumptions about ordering of these events in their callbacks.
415 return; 495 return;
416 } 496 }
417 } 497 }
418 498
419 if (current_merged_page_timing_->Equals(*pending_merged_page_timing_)) 499 if (current_merged_page_timing_->Equals(*pending_merged_page_timing_))
420 return; 500 return;
501
502 LogIfOutOfOrderTiming(current_merged_page_timing_->paint_timing->first_paint,
503 pending_merged_page_timing_->paint_timing->first_paint);
504 LogIfOutOfOrderTiming(
505 current_merged_page_timing_->paint_timing->first_text_paint,
506 pending_merged_page_timing_->paint_timing->first_text_paint);
507 LogIfOutOfOrderTiming(
508 current_merged_page_timing_->paint_timing->first_image_paint,
509 pending_merged_page_timing_->paint_timing->first_image_paint);
510 LogIfOutOfOrderTiming(
511 current_merged_page_timing_->paint_timing->first_contentful_paint,
512 pending_merged_page_timing_->paint_timing->first_contentful_paint);
513
421 current_merged_page_timing_ = pending_merged_page_timing_->Clone(); 514 current_merged_page_timing_ = pending_merged_page_timing_->Clone();
422 515
423 internal::PageLoadTimingStatus status = 516 internal::PageLoadTimingStatus status =
424 IsValidPageLoadTiming(*pending_merged_page_timing_); 517 IsValidPageLoadTiming(*pending_merged_page_timing_);
425 UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingDispatchStatus, status, 518 UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingDispatchStatus, status,
426 internal::LAST_PAGE_LOAD_TIMING_STATUS); 519 internal::LAST_PAGE_LOAD_TIMING_STATUS);
427 520
428 client_->OnTimingChanged(); 521 client_->OnTimingChanged();
429 } 522 }
430 523
431 } // namespace page_load_metrics 524 } // namespace page_load_metrics
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698