Commit 0f5c20d3 authored by housseindjirdeh's avatar housseindjirdeh Committed by Commit Bot

Adds NextJS JavaScript framework UKM

First pass in adding JavaScript framework detections to UKM. This patch creates a UKM and adds a loading behavior flag for when Next.js is detected on a page.

UKM collection review doc: https://docs.google.com/document/d/1eatNgsHthqcSaocbevAslky0kb7WqGP4_DT9HorGAQc/edit?usp=sharing

Bug: 1006274
Change-Id: I8e645b80d0a2cb5f569c3c413f76345ca75c7995
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2294392Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Reviewed-by: default avatarBrian White <bcwhite@chromium.org>
Reviewed-by: default avatarAnnie Sullivan <sullivan@chromium.org>
Commit-Queue: Houssein  Djirdeh <houssein@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795915}
parent 3f4942f2
......@@ -997,6 +997,8 @@ static_library("browser") {
"page_load_metrics/observers/https_engagement_metrics/https_engagement_service_factory.h",
"page_load_metrics/observers/isolated_prerender_page_load_metrics_observer.cc",
"page_load_metrics/observers/isolated_prerender_page_load_metrics_observer.h",
"page_load_metrics/observers/javascript_frameworks_ukm_observer.cc",
"page_load_metrics/observers/javascript_frameworks_ukm_observer.h",
"page_load_metrics/observers/live_tab_count_page_load_metrics_observer.cc",
"page_load_metrics/observers/live_tab_count_page_load_metrics_observer.h",
"page_load_metrics/observers/loading_predictor_page_load_metrics_observer.cc",
......
// Copyright 2020 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 "chrome/browser/page_load_metrics/observers/javascript_frameworks_ukm_observer.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
JavascriptFrameworksUkmObserver::JavascriptFrameworksUkmObserver() = default;
JavascriptFrameworksUkmObserver::~JavascriptFrameworksUkmObserver() = default;
void JavascriptFrameworksUkmObserver::OnLoadingBehaviorObserved(
content::RenderFrameHost* rfh,
int behavior_flag) {
RecordNextJS();
}
void JavascriptFrameworksUkmObserver::RecordNextJS() {
if (nextjs_detected ||
(GetDelegate().GetMainFrameMetadata().behavior_flags &
blink::LoadingBehaviorFlag::kLoadingBehaviorNextJSFrameworkUsed) == 0) {
return;
}
ukm::builders::JavascriptFrameworkPageLoad builder(
GetDelegate().GetSourceId());
builder.SetNextJSPageLoad(true);
nextjs_detected = true;
builder.Record(ukm::UkmRecorder::Get());
}
// Copyright 2020 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.
#ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_JAVASCRIPT_FRAMEWORKS_UKM_OBSERVER_H_
#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_JAVASCRIPT_FRAMEWORKS_UKM_OBSERVER_H_
#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
// If URL-Keyed-Metrics (UKM) is enabled in the system, this is used to
// populate it with JavaScript framework-related page-load metrics.
class JavascriptFrameworksUkmObserver
: public page_load_metrics::PageLoadMetricsObserver {
public:
JavascriptFrameworksUkmObserver();
~JavascriptFrameworksUkmObserver() override;
void OnLoadingBehaviorObserved(content::RenderFrameHost* rfh,
int behavior_flag) override;
private:
bool nextjs_detected = false;
void RecordNextJS();
DISALLOW_COPY_AND_ASSIGN(JavascriptFrameworksUkmObserver);
};
#endif // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_JAVASCRIPT_FRAMEWORKS_UKM_OBSERVER_H_
// Copyright 2020 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 "chrome/browser/page_load_metrics/observers/foreground_duration_ukm_observer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/window_open_disposition.h"
#include "url/gurl.h"
using UkmEntry = ukm::builders::JavascriptFrameworkPageLoad;
class JavascriptFrameworksUkmObserverBrowserTest : public InProcessBrowserTest {
public:
JavascriptFrameworksUkmObserverBrowserTest() = default;
~JavascriptFrameworksUkmObserverBrowserTest() override = default;
void PreRunTestOnMainThread() override {
InProcessBrowserTest::PreRunTestOnMainThread();
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
protected:
void StartHttpsServer(net::EmbeddedTestServer::ServerCertificate cert) {
https_test_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_test_server_->SetSSLConfig(cert);
https_test_server_->ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_test_server_->Start());
}
void ExpectMetricValueForUrl(const GURL& url,
const char* metric_name,
const int expected_value) {
for (auto* entry :
test_ukm_recorder_->GetEntriesByName(UkmEntry::kEntryName)) {
auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
if (source && source->url() == url) {
test_ukm_recorder_->EntryHasMetric(entry, metric_name);
test_ukm_recorder_->ExpectEntryMetric(entry, metric_name,
expected_value);
}
}
}
void ExpectMetricCountForUrl(const GURL& url,
const char* metric_name,
const int expected_count) {
int count = 0;
for (auto* entry :
test_ukm_recorder_->GetEntriesByName(UkmEntry::kEntryName)) {
auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
if (source && source->url() == url &&
test_ukm_recorder_->EntryHasMetric(entry, metric_name)) {
count++;
}
}
EXPECT_EQ(count, expected_count);
}
void CloseAllTabs() {
TabStripModel* tab_strip_model = browser()->tab_strip_model();
content::WebContentsDestroyedWatcher destroyed_watcher(
tab_strip_model->GetActiveWebContents());
tab_strip_model->CloseAllTabs();
destroyed_watcher.Wait();
}
net::EmbeddedTestServer* https_test_server() {
return https_test_server_.get();
}
private:
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
std::unique_ptr<net::EmbeddedTestServer> https_test_server_;
DISALLOW_COPY_AND_ASSIGN(JavascriptFrameworksUkmObserverBrowserTest);
};
IN_PROC_BROWSER_TEST_F(JavascriptFrameworksUkmObserverBrowserTest,
NoNextjsFrameworkDetected) {
page_load_metrics::PageLoadMetricsTestWaiter waiter(
browser()->tab_strip_model()->GetActiveWebContents());
waiter.AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLoadEvent);
StartHttpsServer(net::EmbeddedTestServer::CERT_OK);
GURL url = https_test_server()->GetURL("/english_page.html");
ui_test_utils::NavigateToURL(browser(), url);
waiter.Wait();
CloseAllTabs();
ExpectMetricCountForUrl(url, "NextJSPageLoad", 0);
}
IN_PROC_BROWSER_TEST_F(JavascriptFrameworksUkmObserverBrowserTest,
NextjsFrameworkDetected) {
page_load_metrics::PageLoadMetricsTestWaiter waiter(
browser()->tab_strip_model()->GetActiveWebContents());
waiter.AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLoadEvent);
StartHttpsServer(net::EmbeddedTestServer::CERT_OK);
GURL url = https_test_server()->GetURL("/page_load_metrics/nextjs_page.html");
ui_test_utils::NavigateToURL(browser(), url);
waiter.Wait();
CloseAllTabs();
ExpectMetricCountForUrl(url, "NextJSPageLoad", 1);
}
......@@ -19,6 +19,7 @@
#include "chrome/browser/page_load_metrics/observers/from_gws_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/https_engagement_metrics/https_engagement_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/isolated_prerender_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/javascript_frameworks_ukm_observer.h"
#include "chrome/browser/page_load_metrics/observers/live_tab_count_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/loading_predictor_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/local_network_requests_page_load_metrics_observer.h"
......@@ -96,6 +97,7 @@ void PageLoadMetricsEmbedder::RegisterEmbedderObservers(
if (!IsPrerendering()) {
tracker->AddObserver(std::make_unique<AbortsPageLoadMetricsObserver>());
tracker->AddObserver(std::make_unique<AMPPageLoadMetricsObserver>());
tracker->AddObserver(std::make_unique<JavascriptFrameworksUkmObserver>());
tracker->AddObserver(std::make_unique<SchemePageLoadMetricsObserver>());
tracker->AddObserver(std::make_unique<FromGWSPageLoadMetricsObserver>());
tracker->AddObserver(std::make_unique<ForegroundDurationUKMObserver>());
......
......@@ -1060,6 +1060,7 @@ if (!is_android) {
"../browser/page_load_metrics/observers/foreground_duration_ukm_observer_browsertest.cc",
"../browser/page_load_metrics/observers/https_engagement_metrics/https_engagement_page_load_metrics_observer_browsertest.cc",
"../browser/page_load_metrics/observers/isolated_prerender_page_load_metrics_observer_browsertest.cc",
"../browser/page_load_metrics/observers/javascript_frameworks_ukm_observer_browsertest.cc",
"../browser/page_load_metrics/observers/live_tab_count_page_load_metrics_observer_browsertest.cc",
"../browser/page_load_metrics/observers/multi_tab_loading_page_load_metrics_observer_browsertest.cc",
"../browser/page_load_metrics/observers/resource_metrics_observer_browsertest.cc",
......
<!DOCTYPE html>
<script>
window.__NEXT_DATA__ = {
buildId: 'test',
};
</script>
<body>
<div id="__next">
This is a test to see if a page with a
<pre>window.__NEXT_DATA__</pre>
JavaScript object and has a node with id
<pre>__next</pre>
is using the Next.js Javascript framework.
</div>
</body>
......@@ -41,6 +41,9 @@ enum LoadingBehaviorFlag {
// occurred before the first rendering cycle begins. Used to study the
// effects of delaying the first rendering cycle for web font loading.
kLoadingBehaviorFontPreloadStartedBeforeRendering = 1 << 8,
// Indicates that the page uses the Next.js JavaScript framework (via a
// window variable)
kLoadingBehaviorNextJSFrameworkUsed = 1 << 9,
};
} // namespace blink
......
......@@ -283,6 +283,7 @@
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.h"
#include "third_party/blink/renderer/core/script/detect_javascript_frameworks.h"
#include "third_party/blink/renderer/core/script/script_runner.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
#include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
......@@ -3936,6 +3937,7 @@ bool Document::CheckCompletedInternal() {
GetFrame()->GetFrameScheduler()->OnLoad();
AnchorElementMetrics::NotifyOnLoad(*this);
DetectJavascriptFrameworksOnLoad(*this);
// If this is a document associated with a resource loading hints based
// preview, then record the resource loading hints UKM now that the load is
......
......@@ -10,6 +10,8 @@ blink_core_sources("script") {
"classic_pending_script.h",
"classic_script.cc",
"classic_script.h",
"detect_javascript_frameworks.cc",
"detect_javascript_frameworks.h",
"document_modulator_impl.cc",
"document_modulator_impl.h",
"document_write_intervention.cc",
......
// Copyright 2020 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 "third_party/blink/renderer/core/script/detect_javascript_frameworks.h"
#include "third_party/blink/public/common/loader/loading_behavior_flag.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
namespace blink {
namespace {
bool IsFrameworkVariableUsed(v8::Local<v8::Context> context,
const String& framework_variable_name) {
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> global = context->Global();
v8::TryCatch try_catch(isolate);
bool has_property;
bool succeeded =
global
->HasRealNamedProperty(
context, V8AtomicString(isolate, framework_variable_name))
.To(&has_property);
DCHECK(succeeded && !try_catch.HasCaught());
return has_property;
}
bool IsFrameworkIDUsed(Document& document, const AtomicString& framework_id) {
if (document.getElementById(framework_id)) {
return true;
}
return false;
}
void CheckForNextJS(Document& document, v8::Local<v8::Context> context) {
if (IsFrameworkIDUsed(document, "__next") &&
IsFrameworkVariableUsed(context, "__NEXT_DATA__")) {
document.Loader()->DidObserveLoadingBehavior(
LoadingBehaviorFlag::kLoadingBehaviorNextJSFrameworkUsed);
}
}
} // namespace
void DetectJavascriptFrameworksOnLoad(Document& document) {
// Only detect Javascript frameworks on the main frame and if URL and BaseURL
// is HTTP. Note: Without these checks, ToScriptStateForMainWorld will
// initialize WindowProxy and trigger a second DidClearWindowObject() earlier
// than expected for Android WebView. The Gin Java Bridge has a race condition
// that relies on a second DidClearWindowObject() firing immediately before
// executing JavaScript. See the document that explains this in more detail:
// https://docs.google.com/document/d/1R5170is5vY425OO2Ru-HJBEraEKu0HjQEakcYldcSzM/edit?usp=sharing
if (!document.GetFrame() || !document.GetFrame()->IsMainFrame() ||
!document.Url().ProtocolIsInHTTPFamily() ||
!document.BaseURL().ProtocolIsInHTTPFamily()) {
return;
}
ScriptState* script_state = ToScriptStateForMainWorld(document.GetFrame());
if (!script_state || !script_state->ContextIsValid()) {
return;
}
ScriptState::Scope scope(script_state);
v8::Local<v8::Context> context = script_state->GetContext();
CheckForNextJS(document, context);
}
} // namespace blink
// Copyright 2020 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_DETECT_JAVASCRIPT_FRAMEWORKS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_DETECT_JAVASCRIPT_FRAMEWORKS_H_
#include "third_party/blink/renderer/core/core_export.h"
namespace blink {
class Document;
CORE_EXPORT void DetectJavascriptFrameworksOnLoad(Document&);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_DETECT_JAVASCRIPT_FRAMEWORKS_H_
......@@ -5282,6 +5282,16 @@ be describing additional metrics about the same event.
</metric>
</event>
<event name="JavascriptFrameworkPageLoad" singular="True">
<owner>houssein@chromium.org</owner>
<metric name="NextJSPageLoad">
<summary>
True if the page loaded in the main frame uses the Next.js JavaScript
framework.
</summary>
</metric>
</event>
<event name="Layout.DisplayCutout.StateChanged">
<owner>beccahughes@chromium.org</owner>
<owner>media-dev@chromium.org</owner>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment