blob: f46147bb32e3e8fef86161f89463b7c1b25f5065 [file] [log] [blame]
// Copyright 2019 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/timezone/timezone_controller.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/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/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/mojo/mojo_helper.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/icu/source/common/unicode/char16ptr.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
// 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(icu::toUCharPtr(unicode_timezone_id.getBuffer()),
static_cast<unsigned>(unicode_timezone_id.length()));
}
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()->DispatchEvent(
*Event::Create(event_type_names::kTimezonechange));
}
}
}
}
bool SetIcuTimeZoneAndNotifyV8(const String& timezone_id) {
DCHECK(!timezone_id.IsEmpty());
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());
NotifyTimezoneChangeToV8(V8PerIsolateData::MainThreadIsolate());
WorkerThread::CallOnAllWorkerThreads(&NotifyTimezoneChangeOnWorkerThread,
TaskType::kInternalDefault);
DispatchTimeZoneChangeEventToFrames();
return true;
}
} // namespace
TimeZoneController::TimeZoneController() {
DCHECK(IsMainThread());
host_timezone_id_ = GetCurrentTimezoneId();
}
TimeZoneController::~TimeZoneController() = default;
// static
void TimeZoneController::Init() {
// TODO(crbug.com/660274): The unit tests should not require this exception.
// Currently some unit tests have no message loop ready, so we can't
// initialize the mojo stuff here. They can initialize those mojo stuff
// they're interested in later after they got a message loop ready.
if (!CanInitializeMojo())
return;
// 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_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
std::unique_ptr<TimeZoneController::TimeZoneOverride>
TimeZoneController::SetTimeZoneOverride(const String& timezone_id) {
DCHECK(!timezone_id.IsEmpty());
if (HasTimeZoneOverride()) {
VLOG(1) << "Cannot override existing timezone override.";
return nullptr;
}
// Only notify if the override and the host are different.
if (!CanonicalEquals(timezone_id, instance().host_timezone_id_)) {
if (!SetIcuTimeZoneAndNotifyV8(timezone_id)) {
VLOG(1) << "Invalid override timezone id: " << timezone_id;
return nullptr;
}
}
instance().override_timezone_id_ = timezone_id;
return std::unique_ptr<TimeZoneOverride>(new TimeZoneOverride());
}
// static
bool TimeZoneController::HasTimeZoneOverride() {
return !instance().override_timezone_id_.IsEmpty();
}
// static
void TimeZoneController::ClearTimeZoneOverride() {
DCHECK(HasTimeZoneOverride());
if (!CanonicalEquals(instance().host_timezone_id_,
instance().override_timezone_id_)) {
// Restore remembered timezone request.
// Only do so if the host timezone is now different.
SetIcuTimeZoneAndNotifyV8(instance().host_timezone_id_);
}
instance().override_timezone_id_ = String();
}
// static
void TimeZoneController::ChangeTimeZoneOverride(const String& timezone_id) {
DCHECK(!timezone_id.IsEmpty());
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;
}
void TimeZoneController::OnTimeZoneChange(const String& timezone_id) {
DCHECK(IsMainThread());
// 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);
}
// static
void TimeZoneController::ChangeTimeZoneForTesting(const String& timezone) {
instance().OnTimeZoneChange(timezone);
}
} // namespace blink