blob: ac89aff262b4b03f06b0540faefe8a59aee56b59 [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/array_buffer.h"
#include <algorithm>
#include "base/logging.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/script/javascript_engine.h"
#include "nb/memory_scope.h"
namespace cobalt {
namespace dom {
namespace {
int ClampIndex(int index, int length) {
if (index < 0) {
index = length + index;
}
index = std::max(index, 0);
return std::min(index, length);
}
} // namespace
ArrayBuffer::Data::Data(script::EnvironmentSettings* settings, size_t size)
: allocator_(NULL), cache_(NULL), offloaded_(false), data_(NULL), size_(0) {
Initialize(settings, size);
if (data_) {
memset(data_, 0, size);
}
if (cache_) {
cache_->Register(this);
}
}
ArrayBuffer::Data::Data(script::EnvironmentSettings* settings,
const uint8* data, size_t size)
: allocator_(NULL), cache_(NULL), offloaded_(false), data_(NULL), size_(0) {
Initialize(settings, size);
DCHECK(data_);
memcpy(data_, data, size);
// Register() has to be called after copying the data as Register() will call
// TryToOffload() which may delete the |data_| passed in that belongs to
// another ArrayBuffer.
if (cache_) {
cache_->Register(this);
}
}
ArrayBuffer::Data::Data(scoped_array<uint8> data, size_t size)
: allocator_(NULL),
cache_(NULL),
offloaded_(false),
data_(data.release()),
size_(size) {
DCHECK(data_);
}
ArrayBuffer::Data::~Data() {
if (offloaded_) {
allocator_->Free(data_);
} else {
delete[] data_;
}
if (cache_) {
cache_->Unregister(this);
}
}
uint8* ArrayBuffer::Data::data() const {
if (cache_) {
cache_->ReportUsage(this);
}
return data_;
}
bool ArrayBuffer::Data::Offload() {
if (offloaded_) {
return true;
}
if (!allocator_) {
return false;
}
uint8* data = reinterpret_cast<uint8*>(allocator_->Allocate(size()));
if (data) {
memcpy(data, data_, size());
delete[] data_;
data_ = data;
offloaded_ = true;
}
return offloaded_;
}
void ArrayBuffer::Data::Initialize(script::EnvironmentSettings* settings,
size_t size) {
TRACK_MEMORY_SCOPE("DOM");
if (settings) {
DOMSettings* dom_settings =
base::polymorphic_downcast<dom::DOMSettings*>(settings);
allocator_ = dom_settings->array_buffer_allocator();
cache_ = dom_settings->array_buffer_cache();
if (allocator_) {
DCHECK(cache_);
} else {
DCHECK(!cache_);
}
}
data_ = new uint8[size];
size_ = size;
}
ArrayBuffer::Cache::Cache(size_t maximum_size_in_main_memory)
: total_size_in_main_memory_(0),
maximum_size_in_main_memory_(maximum_size_in_main_memory) {}
void ArrayBuffer::Cache::Register(Data* data) {
total_size_in_main_memory_ += data->size();
// Offload before push_back to ensure that the last one is always not
// offloaded immediately.
TryToOffload();
datas_.push_back(data);
}
void ArrayBuffer::Cache::Unregister(Data* data) {
DCHECK(std::find(datas_.begin(), datas_.end(), data) != datas_.end());
datas_.erase(std::find(datas_.begin(), datas_.end(), data));
if (!data->offloaded()) {
DCHECK_GE(total_size_in_main_memory_, data->size());
total_size_in_main_memory_ -= data->size();
}
}
void ArrayBuffer::Cache::ReportUsage(const Data* data) {
DCHECK(data);
DCHECK(std::find(datas_.begin(), datas_.end(), data) != datas_.end());
if (data->offloaded() || datas_.back() == data) {
return;
}
// Move |data| to the end.
datas_.erase(std::find(datas_.begin(), datas_.end(), data));
datas_.push_back(const_cast<Data*>(data));
}
void ArrayBuffer::Cache::TryToOffload() {
TRACK_MEMORY_SCOPE("DOM");
if (total_size_in_main_memory_ <= maximum_size_in_main_memory_) {
return;
}
std::vector<Data*>::iterator iter = datas_.begin();
while (iter != datas_.end() &&
total_size_in_main_memory_ > maximum_size_in_main_memory_) {
if (!(*iter)->offloaded() && (*iter)->Offload()) {
total_size_in_main_memory_ -= (*iter)->size();
}
++iter;
}
if (total_size_in_main_memory_ > maximum_size_in_main_memory_) {
LOG(WARNING) << "ArrayBuffer takes " << total_size_in_main_memory_
<< " of main memory and cannot be offloaded";
}
}
ArrayBuffer::ArrayBuffer(script::EnvironmentSettings* settings, uint32 length)
: data_(settings, length) {
// TODO: Once we can have a reliable way to pass the
// EnvironmentSettings to HTMLMediaElement, we should make EnvironmentSettings
// mandatory for creating ArrayBuffer in non-testing code.
if (settings) {
DOMSettings* dom_settings =
base::polymorphic_downcast<dom::DOMSettings*>(settings);
dom_settings->javascript_engine()->ReportExtraMemoryCost(data_.size());
}
}
ArrayBuffer::ArrayBuffer(script::EnvironmentSettings* settings,
const uint8* data, uint32 length)
: data_(settings, data, length) {
// TODO: Make EnvironmentSettings mandatory for creating
// ArrayBuffer in non-testing code.
if (settings) {
DOMSettings* dom_settings =
base::polymorphic_downcast<dom::DOMSettings*>(settings);
dom_settings->javascript_engine()->ReportExtraMemoryCost(data_.size());
}
}
ArrayBuffer::ArrayBuffer(script::EnvironmentSettings* settings,
AllocationType allocation_type,
scoped_array<uint8> data, uint32 length)
: data_(data.Pass(), length) {
DCHECK_EQ(allocation_type, kFromHeap);
// TODO: Make EnvironmentSettings mandatory for creating
// ArrayBuffer in non-testing code.
if (settings) {
DOMSettings* dom_settings =
base::polymorphic_downcast<dom::DOMSettings*>(settings);
dom_settings->javascript_engine()->ReportExtraMemoryCost(data_.size());
}
}
scoped_refptr<ArrayBuffer> ArrayBuffer::Slice(
script::EnvironmentSettings* settings, int start, int end) const {
TRACK_MEMORY_SCOPE("DOM");
int clamped_start;
int clamped_end;
ClampRange(start, end, static_cast<int>(byte_length()), &clamped_start,
&clamped_end);
DCHECK_GE(clamped_end, clamped_start);
size_t slice_size = static_cast<size_t>(clamped_end - clamped_start);
return new ArrayBuffer(settings, data() + clamped_start,
static_cast<uint32>(slice_size));
}
void ArrayBuffer::ClampRange(int start, int end, int source_length,
int* clamped_start, int* clamped_end) {
start = ClampIndex(start, source_length);
end = ClampIndex(end, source_length);
// Clamp the length of the new array to non-negative.
if (end - start < 0) {
end = start;
}
*clamped_start = start;
*clamped_end = end;
}
ArrayBuffer::~ArrayBuffer() {}
} // namespace dom
} // namespace cobalt