Chromium Code Reviews| Index: components/page_load_metrics/renderer/page_load_metrics_render_frame_observer.cc |
| diff --git a/components/page_load_metrics/renderer/page_load_metrics_render_frame_observer.cc b/components/page_load_metrics/renderer/page_load_metrics_render_frame_observer.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..078a9fe9b41d2533f15751216fedbdb97b637102 |
| --- /dev/null |
| +++ b/components/page_load_metrics/renderer/page_load_metrics_render_frame_observer.cc |
| @@ -0,0 +1,219 @@ |
| +// 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. |
| + |
| +#include "base/time/time.h" |
| +#include "components/page_load_metrics/common/page_load_metrics_messages.h" |
| +#include "components/page_load_metrics/renderer/page_load_metrics_render_frame_observer.h" |
| +#include "content/public/renderer/render_frame.h" |
| +#include "third_party/WebKit/public/platform/WebURLResponse.h" |
| +#include "third_party/WebKit/public/web/WebDataSource.h" |
| +#include "third_party/WebKit/public/web/WebDocument.h" |
| +#include "third_party/WebKit/public/web/WebLocalFrame.h" |
| +#include "third_party/WebKit/public/web/WebPerformance.h" |
| +#include "url/gurl.h" |
| + |
| +using base::Time; |
| + |
| +namespace page_load_metrics { |
| + |
| +namespace { |
| + |
| +// PerformanceTiming is the concrete implementation of the Timing interface |
| +// that gets its timing data from a blink::WebPerformance instance. |
| +class PerformanceTiming : public PageLoadMetricsRenderFrameObserver::Timing { |
| + public: |
| + explicit PerformanceTiming(const blink::WebPerformance& performance) |
| + : performance_(performance) {} |
| + |
| + Time GetNavigationStart() const override { |
| + return Time::FromDoubleT(performance_.navigationStart()); |
| + } |
| + |
| + Time GetDOMContentLoadedEvent() const override { |
| + return Time::FromDoubleT(performance_.domContentLoadedEventStart()); |
| + } |
| + |
| + Time GetLoadEvent() const override { |
| + return Time::FromDoubleT(performance_.loadEventStart()); |
| + } |
| + |
| + Time GetFirstLayout() const override { |
| + return Time::FromDoubleT(performance_.firstLayout()); |
| + } |
| + |
| + private: |
| + blink::WebPerformance performance_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PerformanceTiming); |
| +}; |
| + |
| +} // namespace |
| + |
| +// PageMetricsSender is a helper class responsible for sending page load |
| +// metrics over the lifetime of a webpage. A PageMetricsSender instance is |
| +// active from the time a provisional load is committed, until the time the |
| +// provisional load for the next page is started. |
| +class PageLoadMetricsRenderFrameObserver::PageMetricsSender { |
| + public: |
| + explicit PageMetricsSender(PageLoadMetricsRenderFrameObserver* parent); |
| + void MaybeSendMetrics(const Timing& timing); |
| + |
| + private: |
| + void Send(IPC::Message* message) { parent_->Send(message); } |
| + int routing_id() const { return parent_->routing_id(); } |
| + |
| + void MaybeSendFirstLayout(const Timing& timing); |
| + void MaybeSendDOMContentLoadedEvent(const Timing& timing); |
| + void MaybeSendLoadEvent(const Timing& timing); |
| + |
| + PageLoadMetricsRenderFrameObserver* const parent_; |
| + bool did_send_first_layout_; |
| + bool did_send_dom_content_loaded_event_; |
| + bool did_send_load_event_; |
|
kinuko
2015/08/18 10:37:39
So this object's created for each page/document ma
Bryan McQuade
2015/08/19 11:45:14
The Blink WebPerformance object that exposes the t
|
| +}; |
| + |
| +PageLoadMetricsRenderFrameObserver::PageLoadMetricsRenderFrameObserver( |
| + content::RenderFrame* render_frame) |
| + : content::RenderFrameObserver(render_frame) {} |
| + |
| +PageLoadMetricsRenderFrameObserver::~PageLoadMetricsRenderFrameObserver() {} |
| + |
| +// TODO(bmcquade): use DidChangePerformanceTiming instead, once available. |
| +void PageLoadMetricsRenderFrameObserver::DidFinishDocumentLoad() { |
| + MaybeSendMetrics(); |
| +} |
| + |
| +// TODO(bmcquade): use DidChangePerformanceTiming instead, once available. |
|
Randy Smith (Not in Mondays)
2015/08/17 19:31:07
Is there a pointer to issue # or CL to include her
Bryan McQuade
2015/08/19 11:45:14
Sure - this should actually be addressed before I
|
| +void PageLoadMetricsRenderFrameObserver::DidFinishLoad() { |
| + MaybeSendMetrics(); |
| +} |
| + |
| +void PageLoadMetricsRenderFrameObserver::DidStartProvisionalLoad() { |
| + // A provisional page load has started. Shut down metrics reporting until the |
| + // next page load is committed. Note that a provisoinal load may fail. We |
| + // simply stop reporting metrics in that case, until the next successful page |
| + // load is committed. |
| + page_metrics_sender_.reset(); |
| +} |
| + |
| +void PageLoadMetricsRenderFrameObserver::DidCommitProvisionalLoad( |
| + bool is_new_navigation, bool is_same_page_navigation) { |
| + // Same-page navigations (e.g. an in-document navigation from a fragment |
| + // link) aren't full page loads, since they don't go to network to load the |
| + // main HTML resource. |
| + if (is_same_page_navigation) |
| + return; |
|
Randy Smith (Not in Mondays)
2015/08/17 19:31:07
Does this mean that a same page navigation will nu
Bryan McQuade
2015/08/19 11:45:14
DidStartProvisionalLoad doesn't get invoked for sa
|
| + |
| + if (ShouldSendMetrics()) |
| + page_metrics_sender_.reset(new PageMetricsSender(this)); |
|
Randy Smith (Not in Mondays)
2015/08/17 19:31:07
I'd like a comment somewhere (not sure where, but
Bryan McQuade
2015/08/25 13:53:35
Done.
|
| + else |
| + page_metrics_sender_.reset(); |
|
Randy Smith (Not in Mondays)
2015/08/17 19:31:07
Under what circumstances will this have an effect?
Bryan McQuade
2015/08/19 11:45:14
Good point. A DCHECK seems much better here. Will
|
| +} |
| + |
| +void PageLoadMetricsRenderFrameObserver::MaybeSendMetrics() { |
| + if (!page_metrics_sender_) |
| + return; |
| + |
| + scoped_ptr<Timing> timing(CreateTiming()); |
| + page_metrics_sender_->MaybeSendMetrics(*timing); |
| +} |
| + |
| +bool PageLoadMetricsRenderFrameObserver::ShouldSendMetrics() { |
| + const blink::WebLocalFrame& frame = *render_frame()->GetWebFrame(); |
| + // We only generate historgrams for main frames. |
| + if (frame.parent()) |
| + return false; |
| + |
| + const blink::WebDocument& document = frame.document(); |
| + // Ignore non-HTTP schemes (e.g. chrome://). |
| + const GURL& url = document.url(); |
| + if (!url.SchemeIs("http") && !url.SchemeIs("https")) |
|
kinuko
2015/08/18 10:37:39
nit: !url.SchemeIsHTTPOrHTTPS()
Bryan McQuade
2015/08/25 13:53:35
Thanks, did not know about that. Fixed.
|
| + return false; |
| + |
| + const blink::WebURLResponse& url_response = frame.dataSource()->response(); |
| + // Ignore multipart responses (e.g. MHTML). |
| + if (url_response.isMultipartPayload()) |
| + return false; |
| + |
| + // Ignore non-HTML documents (e.g. SVG). Note that images are treated by |
| + // Blink as HTML documents, so to exclude images, we must perform |
| + // additional mime type checking below. |
| + if (!document.isHTMLDocument() && !document.isXHTMLDocument()) |
| + return false; |
| + |
| + // Ignore non-HTML mime types (e.g. images). |
| + std::string mime_type = url_response.mimeType().utf8(); |
| + if (mime_type != "text/html" && mime_type != "application/xhtml+xml") |
| + return false; |
| + |
| + return true; |
| +} |
| + |
| +scoped_ptr<PageLoadMetricsRenderFrameObserver::Timing> |
| +PageLoadMetricsRenderFrameObserver::CreateTiming() { |
| + blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| + scoped_ptr<Timing> timing(new PerformanceTiming( |
| + frame ? frame->performance() : blink::WebPerformance())); |
| + return timing.Pass(); |
| +} |
| + |
| +PageLoadMetricsRenderFrameObserver::PageMetricsSender::PageMetricsSender( |
| + PageLoadMetricsRenderFrameObserver* parent) |
| + : parent_(parent), |
| + did_send_first_layout_(false), |
| + did_send_dom_content_loaded_event_(false), |
| + did_send_load_event_(false) {} |
| + |
| +void PageLoadMetricsRenderFrameObserver::PageMetricsSender::MaybeSendMetrics( |
| + const Timing& timing) { |
| + MaybeSendFirstLayout(timing); |
| + MaybeSendDOMContentLoadedEvent(timing); |
| + MaybeSendLoadEvent(timing); |
|
Randy Smith (Not in Mondays)
2015/08/17 19:31:08
I'll happily accept this architecture if you tell
Bryan McQuade
2015/08/19 11:45:14
It's generally hard to predict when in the page lo
|
| +} |
| + |
| +void PageLoadMetricsRenderFrameObserver::PageMetricsSender:: |
| + MaybeSendFirstLayout(const Timing& timing) { |
| + if (did_send_first_layout_) |
| + return; |
| + |
| + if (timing.GetNavigationStart().is_null() || |
| + timing.GetFirstLayout().is_null()) |
| + return; |
| + |
| + Send(new PageLoadMetricsMsg_FirstLayout( |
| + routing_id(), |
| + timing.GetFirstLayout() - timing.GetNavigationStart())); |
| + did_send_first_layout_ = true; |
| +} |
| + |
| +void PageLoadMetricsRenderFrameObserver::PageMetricsSender:: |
| + MaybeSendDOMContentLoadedEvent(const Timing& timing) { |
| + if (did_send_dom_content_loaded_event_) |
| + return; |
| + |
| + if (timing.GetNavigationStart().is_null() || |
| + timing.GetDOMContentLoadedEvent().is_null()) |
| + return; |
| + |
| + Send(new PageLoadMetricsMsg_DOMContentLoadedEvent( |
| + routing_id(), |
| + timing.GetDOMContentLoadedEvent() - timing.GetNavigationStart())); |
| + did_send_dom_content_loaded_event_ = true; |
| +} |
| + |
| +void PageLoadMetricsRenderFrameObserver::PageMetricsSender::MaybeSendLoadEvent( |
| + const Timing& timing) { |
| + if (did_send_load_event_) |
| + return; |
| + |
| + if (timing.GetNavigationStart().is_null() || timing.GetLoadEvent().is_null()) |
| + return; |
| + |
| + Send(new PageLoadMetricsMsg_LoadEvent( |
| + routing_id(), |
| + timing.GetLoadEvent() - timing.GetNavigationStart())); |
| + did_send_load_event_ = true; |
| +} |
| + |
| +} // namespace page_load_metrics |