blob: c193c423060fb51d125e6bb1d8173f0f5f9ace1d [file] [log] [blame]
// Copyright 2018 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/platform/wtf/text/movable_string.h"
#include <string.h>
#include "base/metrics/histogram_macros.h"
namespace WTF {
namespace {
template <typename T>
bool IsLargeEnough(const T* impl) {
// Don't attempt to park strings smaller than this size.
static constexpr unsigned int kSizeThreshold = 10000;
return impl && impl->length() > kSizeThreshold;
}
void RecordParkingAction(MovableStringImpl::ParkingAction action) {
UMA_HISTOGRAM_ENUMERATION("Memory.MovableStringParkingAction", action);
}
} // namespace
MovableStringImpl::MovableStringImpl() = default;
MovableStringImpl::MovableStringImpl(scoped_refptr<StringImpl>&& impl)
: is_parked_(false),
string_(std::move(impl))
#if DCHECK_IS_ON()
,
parked_string_()
#endif
{
is_null_ = string_.IsNull();
is_8bit_ = is_null_ ? false : string_.Is8Bit();
length_ = is_null_ ? 0 : string_.length();
}
MovableStringImpl::~MovableStringImpl() {
MovableStringTable::Instance().Remove(this, string_.Impl());
}
bool MovableStringImpl::Is8Bit() const {
return is_8bit_;
}
bool MovableStringImpl::IsNull() const {
return is_null_;
}
const String& MovableStringImpl::ToString() {
Unpark();
return string_;
}
unsigned MovableStringImpl::CharactersSizeInBytes() const {
return length() * (Is8Bit() ? sizeof(LChar) : sizeof(UChar));
}
bool MovableStringImpl::Park() {
// Cannot park strings with several references.
if (!is_parked_ && string_.Impl()->HasOneRef()) {
RecordParkingAction(ParkingAction::kParkedInBackground);
#if DCHECK_IS_ON()
// Since the copy is done before freeing the original data, the two strings
// are guaranteed to not have the same address.
parked_string_ = string_.IsolatedCopy();
// Poison the previous allocation.
// |string_| is kept as is to make sure the memory isn't reused.
const void* data = Is8Bit()
? static_cast<const void*>(string_.Characters8())
: static_cast<const void*>(string_.Characters16());
memset(const_cast<void*>(data), 0xcc, string_.CharactersSizeInBytes());
#endif
is_parked_ = true;
}
return is_parked_;
}
void MovableStringImpl::Unpark() {
if (!is_parked_)
return;
bool backgrounded = MovableStringTable::Instance().IsRendererBackgrounded();
RecordParkingAction(backgrounded ? ParkingAction::kUnparkedInBackground
: ParkingAction::kUnparkedInForeground);
is_parked_ = false;
#if DCHECK_IS_ON()
// string_'s data has the same address as parked_string_.
// This enforces MovableStringImpl.ToString().Characthers{8,16}() returning
// a different value after Park() / Unpark().
string_ = parked_string_;
parked_string_ = String();
#endif
MovableStringTable::Instance().OnUnpark(this, string_.Impl());
}
MovableString::MovableString(scoped_refptr<StringImpl>&& impl) {
impl_ = MovableStringTable::Instance().Add(std::move(impl));
if (!impl_)
impl_ = base::MakeRefCounted<MovableStringImpl>(std::move(impl));
}
MovableString::~MovableString() = default;
bool MovableString::Is8Bit() const {
return impl_->Is8Bit();
}
bool MovableString::IsNull() const {
return impl_->IsNull();
}
const String& MovableString::ToString() const {
return impl_->ToString();
}
unsigned MovableString::CharactersSizeInBytes() const {
return impl_->CharactersSizeInBytes();
}
MovableStringTable::MovableStringTable() = default;
MovableStringTable::~MovableStringTable() = default;
scoped_refptr<MovableStringImpl> MovableStringTable::Add(
scoped_refptr<StringImpl>&& string) {
StringImpl* impl = string.get();
if (!IsLargeEnough(impl))
return nullptr;
auto it = unparked_strings_.find(impl);
if (it != unparked_strings_.end()) {
return it->second;
}
auto new_movable_string =
base::MakeRefCounted<MovableStringImpl>(std::move(string));
unparked_strings_.emplace(impl, new_movable_string.get());
return new_movable_string;
}
void MovableStringTable::OnUnpark(MovableStringImpl* movable_impl,
StringImpl* impl) {
if (!IsLargeEnough(movable_impl))
return;
auto it = parked_strings_.find(movable_impl);
DCHECK(it != parked_strings_.end());
parked_strings_.erase(it);
unparked_strings_.emplace(impl, movable_impl);
}
void MovableStringTable::Remove(MovableStringImpl* movable_impl,
StringImpl* string) {
if (!IsLargeEnough(movable_impl))
return;
if (movable_impl->is_parked()) {
auto it = parked_strings_.find(movable_impl);
DCHECK(it != parked_strings_.end());
parked_strings_.erase(it);
} else {
auto it = unparked_strings_.find(string);
DCHECK(it != unparked_strings_.end());
unparked_strings_.erase(it);
}
}
void MovableStringTable::SetRendererBackgrounded(bool backgrounded) {
backgrounded_ = backgrounded;
}
bool MovableStringTable::IsRendererBackgrounded() const {
return backgrounded_;
}
void MovableStringTable::MaybeParkAll() {
if (!IsRendererBackgrounded())
return;
size_t total_size = 0, count = 0;
for (auto it = unparked_strings_.begin(); it != unparked_strings_.end();) {
MovableStringImpl* str = it->second;
bool parked = str->Park();
if (parked) {
parked_strings_.insert(str);
it = unparked_strings_.erase(it);
} else {
++it;
total_size += str->CharactersSizeInBytes();
count += 1;
}
}
for (const auto* str : parked_strings_) {
DCHECK(str);
total_size += str->CharactersSizeInBytes();
count += 1;
}
size_t total_size_kb = total_size / 1000;
UMA_HISTOGRAM_COUNTS_100000("Memory.MovableStringsTotalSizeKb",
total_size_kb);
UMA_HISTOGRAM_COUNTS_1000("Memory.MovableStringsCount", count);
}
size_t MovableStringTable::Size() const {
return parked_strings_.size() + unparked_strings_.size();
}
} // namespace WTF