blob: 48eb8d2fbdcf5917401ccf803ac8f4d3df917507 [file]
// Copyright 2019 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/core/timezone/timezone_controller.h"
#include "base/feature_list.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/frame.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/workers/worker_backing_thread.h"
#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/main_thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/main_thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/text/unicode_string.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
// When enabled, the host timezone id is evaluated only when needed.
// TODO(crbug.com/40287434): Cleanup the feature after running the experiment,
// no later than January 2025.
BASE_FEATURE(kLazyBlinkTimezoneInit, base::FEATURE_DISABLED_BY_DEFAULT);
// Notify V8 that the date/time configuration of the system might have changed.
void NotifyTimezoneChangeToV8(v8::Isolate* isolate) {
DCHECK(isolate);
isolate->DateTimeConfigurationChangeNotification();
}
void NotifyTimezoneChangeOnWorkerThread(WorkerThread* worker_thread) {
DCHECK(worker_thread->IsCurrentThread());
NotifyTimezoneChangeToV8(worker_thread->GlobalScope()->GetIsolate());
if (RuntimeEnabledFeatures::TimeZoneChangeEventEnabled() &&
worker_thread->GlobalScope()->IsWorkerGlobalScope()) {
worker_thread->GlobalScope()->DispatchEvent(
*Event::Create(event_type_names::kTimezonechange));
}
}
String GetTimezoneId(const icu::TimeZone& timezone) {
icu::UnicodeString unicode_timezone_id;
timezone.getID(unicode_timezone_id);
return String(unicode::ToSpan(unicode_timezone_id));
}
String GetCurrentTimezoneId() {
std::unique_ptr<icu::TimeZone> timezone(icu::TimeZone::createDefault());
CHECK(timezone);
return GetTimezoneId(*timezone.get());
}
void DispatchTimeZoneChangeEventToFrames() {
if (!RuntimeEnabledFeatures::TimeZoneChangeEventEnabled())
return;
for (const Page* page : Page::OrdinaryPages()) {
for (Frame* frame = page->MainFrame(); frame;
frame = frame->Tree().TraverseNext()) {
if (auto* main_local_frame = DynamicTo<LocalFrame>(frame)) {
main_local_frame->DomWindow()->EnqueueWindowEvent(
*Event::Create(event_type_names::kTimezonechange),
TaskType::kMiscPlatformAPI);
}
}
}
}
void NotifyTimezoneChangeOnMainThread() {
Thread::MainThread()
->Scheduler()
->ToMainThreadScheduler()
->ForEachMainThreadIsolate(&NotifyTimezoneChangeToV8);
DispatchTimeZoneChangeEventToFrames();
}
} // namespace
TimeZoneController::TimeZoneController() {
DCHECK(IsMainThread());
if (!base::FeatureList::IsEnabled(kLazyBlinkTimezoneInit)) {
host_timezone_id_ = GetCurrentTimezoneId();
}
}
TimeZoneController::~TimeZoneController() = default;
// static
void TimeZoneController::Init() {
// monitor must not use HeapMojoRemote. TimeZoneController is not managed by
// Oilpan. monitor is only used to bind receiver_ here and never used
// again.
mojo::Remote<device::mojom::blink::TimeZoneMonitor> monitor;
Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
monitor.BindNewPipeAndPassReceiver());
monitor->AddClient(instance().receiver_.BindNewPipeAndPassRemote());
}
// static
TimeZoneController& TimeZoneController::instance() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(TimeZoneController, instance, ());
return instance;
}
bool CanonicalEquals(const String& time_zone_a, const String& time_zone_b) {
if (time_zone_a == time_zone_b) {
return true;
}
icu::UnicodeString canonical_a, canonical_b;
UErrorCode status = U_ZERO_ERROR;
UBool dummy;
icu::TimeZone::getCanonicalID(
icu::UnicodeString(time_zone_a.Ascii().data(), -1, US_INV), canonical_a,
dummy, status);
icu::TimeZone::getCanonicalID(
icu::UnicodeString(time_zone_b.Ascii().data(), -1, US_INV), canonical_b,
dummy, status);
if (U_FAILURE(status)) {
return false;
}
return canonical_a == canonical_b;
}
// static
TimeZoneController::TimeZoneOverrideResult
TimeZoneController::SetTimeZoneOverride(const String& timezone_id) {
DCHECK(!timezone_id.empty());
base::AutoLock locker(instance().lock_);
if (timezone_id == instance().TimeZoneIdOverride()) {
// Do nothing.
return {TimeZoneOverrideStatus::kSuccess, nullptr};
}
if (HasTimeZoneOverride()) {
VLOG(1) << "Cannot override existing timezone override.";
return {TimeZoneOverrideStatus::kAlreadyInEffect, nullptr};
}
// Only notify if the override and the host are different.
if (!CanonicalEquals(timezone_id, instance().GetHostTimezoneId())) {
if (!SetIcuTimeZoneAndNotifyV8(timezone_id)) {
VLOG(1) << "Invalid override timezone id: " << timezone_id;
return {TimeZoneOverrideStatus::kInvalidTimezone, nullptr};
}
}
instance().override_timezone_id_ = timezone_id;
return {TimeZoneOverrideStatus::kSuccess,
std::unique_ptr<TimeZoneOverride>(new TimeZoneOverride())};
}
// static
bool TimeZoneController::HasTimeZoneOverride() {
instance().lock_.AssertAcquired();
return !instance().override_timezone_id_.empty();
}
// static
const String& TimeZoneController::TimeZoneIdOverride() {
instance().lock_.AssertAcquired();
return instance().override_timezone_id_;
}
// static
void TimeZoneController::ClearTimeZoneOverride() {
base::AutoLock locker(instance().lock_);
DCHECK(HasTimeZoneOverride());
if (!CanonicalEquals(instance().GetHostTimezoneId(),
instance().override_timezone_id_)) {
// Restore remembered timezone request.
// Only do so if the host timezone is now different.
SetIcuTimeZoneAndNotifyV8(instance().GetHostTimezoneId());
}
instance().override_timezone_id_ = String();
}
// static
void TimeZoneController::ChangeTimeZoneOverride(const String& timezone_id) {
DCHECK(!timezone_id.empty());
base::AutoLock locker(instance().lock_);
if (!HasTimeZoneOverride()) {
VLOG(1) << "Cannot change if there are no existing timezone override.";
return;
}
if (CanonicalEquals(instance().override_timezone_id_, timezone_id)) {
return;
}
if (!SetIcuTimeZoneAndNotifyV8(timezone_id)) {
VLOG(1) << "Invalid override timezone id: " << timezone_id;
return;
}
instance().override_timezone_id_ = timezone_id;
}
bool TimeZoneController::SetIcuTimeZoneAndNotifyV8(const String& timezone_id) {
DCHECK(!timezone_id.empty());
std::unique_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone(
icu::UnicodeString(timezone_id.Ascii().data(), -1, US_INV)));
CHECK(timezone);
if (*timezone == icu::TimeZone::getUnknown()) {
return false;
}
icu::TimeZone::adoptDefault(timezone.release());
if (IsMainThread()) {
NotifyTimezoneChangeOnMainThread();
} else {
Thread::MainThread()
->GetTaskRunner(MainThreadTaskRunnerRestricted())
->PostTask(FROM_HERE, ConvertToBaseOnceCallback(CrossThreadBindOnce(
&NotifyTimezoneChangeOnMainThread)));
}
WorkerThread::CallOnAllWorkerThreads(&NotifyTimezoneChangeOnWorkerThread,
TaskType::kInternalDefault);
return true;
}
void TimeZoneController::OnTimeZoneChange(const String& timezone_id) {
DCHECK(IsMainThread());
base::AutoLock locker(instance().lock_);
// Remember requested timezone id so we can set it when timezone
// override is removed.
instance().host_timezone_id_ = timezone_id;
if (!HasTimeZoneOverride())
SetIcuTimeZoneAndNotifyV8(timezone_id);
}
const String& TimeZoneController::GetHostTimezoneId() {
lock_.AssertAcquired();
if (host_timezone_id_.IsNull()) {
CHECK(base::FeatureList::IsEnabled(kLazyBlinkTimezoneInit));
host_timezone_id_ = GetCurrentTimezoneId();
}
return host_timezone_id_;
}
// static
void TimeZoneController::ChangeTimeZoneForTesting(const String& timezone) {
instance().OnTimeZoneChange(timezone);
}
} // namespace blink