blob: ab5b83e17d4a58f031bcd78c31746e6376b69fc9 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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/modules/netinfo/network_information.h"
#include <algorithm>
#include "base/time/time.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/execution_context/navigator_base.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/supplementable.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
String ConnectionTypeToString(WebConnectionType type) {
switch (type) {
case kWebConnectionTypeCellular2G:
case kWebConnectionTypeCellular3G:
case kWebConnectionTypeCellular4G:
return "cellular";
case kWebConnectionTypeBluetooth:
return "bluetooth";
case kWebConnectionTypeEthernet:
return "ethernet";
case kWebConnectionTypeWifi:
return "wifi";
case kWebConnectionTypeWimax:
return "wimax";
case kWebConnectionTypeOther:
return "other";
case kWebConnectionTypeNone:
return "none";
case kWebConnectionTypeUnknown:
return "unknown";
}
NOTREACHED();
return "none";
}
String GetConsoleLogStringForWebHoldback() {
return "Network quality values are overridden using a holdback experiment, "
"and so may be inaccurate";
}
} // namespace
NetworkInformation::~NetworkInformation() {
DCHECK(!IsObserving());
}
bool NetworkInformation::IsObserving() const {
return !!connection_observer_handle_;
}
String NetworkInformation::type() const {
if (RuntimeEnabledFeatures::NetInfoConstantTypeEnabled()) {
return ConnectionTypeToString(kWebConnectionTypeUnknown);
}
// type_ is only updated when listening for events, so ask
// networkStateNotifier if not listening (crbug.com/379841).
if (!IsObserving())
return ConnectionTypeToString(GetNetworkStateNotifier().ConnectionType());
// If observing, return m_type which changes when the event fires, per spec.
return ConnectionTypeToString(type_);
}
double NetworkInformation::downlinkMax() const {
if (RuntimeEnabledFeatures::NetInfoConstantTypeEnabled()) {
return std::numeric_limits<double>::infinity();
}
if (!IsObserving())
return GetNetworkStateNotifier().MaxBandwidth();
return downlink_max_mbps_;
}
String NetworkInformation::effectiveType() {
MaybeShowWebHoldbackConsoleMsg();
std::optional<WebEffectiveConnectionType> override_ect =
GetNetworkStateNotifier().GetWebHoldbackEffectiveType();
if (override_ect) {
return NetworkStateNotifier::EffectiveConnectionTypeToString(
override_ect.value());
}
// effective_type_ is only updated when listening for events, so ask
// networkStateNotifier if not listening (crbug.com/379841).
if (!IsObserving()) {
return NetworkStateNotifier::EffectiveConnectionTypeToString(
GetNetworkStateNotifier().EffectiveType());
}
// If observing, return m_type which changes when the event fires, per spec.
return NetworkStateNotifier::EffectiveConnectionTypeToString(effective_type_);
}
uint32_t NetworkInformation::rtt() {
MaybeShowWebHoldbackConsoleMsg();
std::optional<base::TimeDelta> override_rtt =
GetNetworkStateNotifier().GetWebHoldbackHttpRtt();
if (override_rtt) {
return GetNetworkStateNotifier().RoundRtt(Host(), override_rtt.value());
}
if (!IsObserving()) {
return GetNetworkStateNotifier().RoundRtt(
Host(), GetNetworkStateNotifier().HttpRtt());
}
return http_rtt_msec_;
}
double NetworkInformation::downlink() {
MaybeShowWebHoldbackConsoleMsg();
std::optional<double> override_downlink_mbps =
GetNetworkStateNotifier().GetWebHoldbackDownlinkThroughputMbps();
if (override_downlink_mbps) {
return GetNetworkStateNotifier().RoundMbps(Host(),
override_downlink_mbps.value());
}
if (!IsObserving()) {
return GetNetworkStateNotifier().RoundMbps(
Host(), GetNetworkStateNotifier().DownlinkThroughputMbps());
}
return downlink_mbps_;
}
bool NetworkInformation::saveData() const {
return IsObserving() ? save_data_
: GetNetworkStateNotifier().SaveDataEnabled();
}
void NetworkInformation::ConnectionChange(
WebConnectionType type,
double downlink_max_mbps,
WebEffectiveConnectionType effective_type,
const std::optional<base::TimeDelta>& http_rtt,
const std::optional<base::TimeDelta>& transport_rtt,
const std::optional<double>& downlink_mbps,
bool save_data) {
DCHECK(GetExecutionContext()->IsContextThread());
const String host = Host();
uint32_t new_http_rtt_msec =
GetNetworkStateNotifier().RoundRtt(host, http_rtt);
double new_downlink_mbps =
GetNetworkStateNotifier().RoundMbps(host, downlink_mbps);
bool network_quality_estimate_changed = false;
// Allow setting |network_quality_estimate_changed| to true only if the
// network quality holdback experiment is not enabled.
if (!GetNetworkStateNotifier().GetWebHoldbackEffectiveType()) {
network_quality_estimate_changed = effective_type_ != effective_type ||
http_rtt_msec_ != new_http_rtt_msec ||
downlink_mbps_ != new_downlink_mbps;
}
// This can happen if the observer removes and then adds itself again
// during notification, or if |transport_rtt| was the only metric that
// changed.
if (type_ == type && downlink_max_mbps_ == downlink_max_mbps &&
!network_quality_estimate_changed && save_data_ == save_data) {
return;
}
// If the NetInfoDownlinkMaxEnabled is not enabled, then |type| and
// |downlink_max_mbps| should not be checked for change.
if (!RuntimeEnabledFeatures::NetInfoDownlinkMaxEnabled() &&
!network_quality_estimate_changed && save_data_ == save_data) {
return;
}
bool type_changed =
RuntimeEnabledFeatures::NetInfoDownlinkMaxEnabled() &&
(type_ != type || downlink_max_mbps_ != downlink_max_mbps);
type_ = type;
downlink_max_mbps_ = downlink_max_mbps;
if (network_quality_estimate_changed) {
effective_type_ = effective_type;
http_rtt_msec_ = new_http_rtt_msec;
downlink_mbps_ = new_downlink_mbps;
}
save_data_ = save_data;
if (type_changed)
DispatchEvent(*Event::Create(event_type_names::kTypechange));
DispatchEvent(*Event::Create(event_type_names::kChange));
}
const AtomicString& NetworkInformation::InterfaceName() const {
return event_target_names::kNetworkInformation;
}
ExecutionContext* NetworkInformation::GetExecutionContext() const {
return ExecutionContextLifecycleObserver::GetExecutionContext();
}
void NetworkInformation::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTarget::AddedEventListener(event_type, registered_listener);
MaybeShowWebHoldbackConsoleMsg();
StartObserving();
}
void NetworkInformation::RemovedEventListener(
const AtomicString& event_type,
const RegisteredEventListener& registered_listener) {
EventTarget::RemovedEventListener(event_type, registered_listener);
if (!HasEventListeners())
StopObserving();
}
void NetworkInformation::RemoveAllEventListeners() {
EventTarget::RemoveAllEventListeners();
DCHECK(!HasEventListeners());
StopObserving();
}
bool NetworkInformation::HasPendingActivity() const {
DCHECK(context_stopped_ || IsObserving() == HasEventListeners());
// Prevent collection of this object when there are active listeners.
return IsObserving();
}
void NetworkInformation::ContextDestroyed() {
context_stopped_ = true;
StopObserving();
}
void NetworkInformation::StartObserving() {
if (!IsObserving() && !context_stopped_) {
type_ = GetNetworkStateNotifier().ConnectionType();
DCHECK(!connection_observer_handle_);
connection_observer_handle_ =
GetNetworkStateNotifier().AddConnectionObserver(
this, GetExecutionContext()->GetTaskRunner(TaskType::kNetworking));
}
}
void NetworkInformation::StopObserving() {
if (IsObserving()) {
DCHECK(connection_observer_handle_);
connection_observer_handle_ = nullptr;
}
}
const char NetworkInformation::kSupplementName[] = "NetworkInformation";
NetworkInformation* NetworkInformation::connection(NavigatorBase& navigator) {
if (!navigator.GetExecutionContext())
return nullptr;
NetworkInformation* supplement =
Supplement<NavigatorBase>::From<NetworkInformation>(navigator);
if (!supplement) {
supplement = MakeGarbageCollected<NetworkInformation>(navigator);
ProvideTo(navigator, supplement);
}
return supplement;
}
NetworkInformation::NetworkInformation(NavigatorBase& navigator)
: ActiveScriptWrappable<NetworkInformation>({}),
Supplement<NavigatorBase>(navigator),
ExecutionContextLifecycleObserver(navigator.GetExecutionContext()),
web_holdback_console_message_shown_(false),
context_stopped_(false) {
std::optional<base::TimeDelta> http_rtt;
std::optional<double> downlink_mbps;
GetNetworkStateNotifier().GetMetricsWithWebHoldback(
&type_, &downlink_max_mbps_, &effective_type_, &http_rtt, &downlink_mbps,
&save_data_);
http_rtt_msec_ = GetNetworkStateNotifier().RoundRtt(Host(), http_rtt);
downlink_mbps_ = GetNetworkStateNotifier().RoundMbps(Host(), downlink_mbps);
DCHECK_LE(1u, GetNetworkStateNotifier().RandomizationSalt());
DCHECK_GE(20u, GetNetworkStateNotifier().RandomizationSalt());
}
void NetworkInformation::Trace(Visitor* visitor) const {
EventTarget::Trace(visitor);
Supplement<NavigatorBase>::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
const String NetworkInformation::Host() const {
return GetExecutionContext() ? GetExecutionContext()->Url().Host() : String();
}
void NetworkInformation::MaybeShowWebHoldbackConsoleMsg() {
if (web_holdback_console_message_shown_)
return;
web_holdback_console_message_shown_ = true;
if (!GetNetworkStateNotifier().GetWebHoldbackEffectiveType())
return;
GetExecutionContext()->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kOther, mojom::ConsoleMessageLevel::kWarning,
GetConsoleLogStringForWebHoldback()));
}
} // namespace blink