blob: 160178e4dc22ab7b753a1df8cbe0853f4743edab [file] [log] [blame]
// Copyright 2021 The Cobalt Authors. 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/performance_observer.h"
#include <unordered_set>
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/dom/performance.h"
#include "cobalt/dom/performance_entry.h"
#include "cobalt/dom/window.h"
namespace cobalt {
namespace dom {
// Abstract base class for a PerformancObserverCallback.
class PerformanceObserver::CallbackInternal {
public:
virtual bool RunCallback(const scoped_refptr<PerformanceObserverEntryList>& entries,
const scoped_refptr<PerformanceObserver>& observer) = 0;
virtual ~CallbackInternal() {}
};
namespace {
// Implement the CallbackInternal interface for a JavaScript callback.
class ScriptCallback : public PerformanceObserver::CallbackInternal {
public:
ScriptCallback(const PerformanceObserver::PerformanceObserverCallbackArg& callback,
PerformanceObserver* observer)
: callback_(observer, callback) {}
bool RunCallback(const scoped_refptr<PerformanceObserverEntryList>& entries,
const scoped_refptr<PerformanceObserver>& observer) override {
script::CallbackResult<void> result = callback_.value().Run(entries, observer);
return !result.exception;
}
private:
PerformanceObserver::PerformanceObserverCallbackArg::Reference callback_;
};
// Implement the CallbackInternal interface for a native callback.
class NativeCallback : public PerformanceObserver::CallbackInternal {
public:
explicit NativeCallback(
const PerformanceObserver::NativePerformanceObserverCallback& callback)
: callback_(callback) {}
bool RunCallback(const scoped_refptr<PerformanceObserverEntryList>& entries,
const scoped_refptr<PerformanceObserver>& observer) override {
callback_.Run(entries, observer);
return true;
}
private:
PerformanceObserver::NativePerformanceObserverCallback callback_;
};
} // namespace
PerformanceObserver::PerformanceObserver(
const NativePerformanceObserverCallback& native_callback,
const scoped_refptr<Performance>& performance)
: performance_(base::AsWeakPtr(performance.get())),
observer_type_(PerformanceObserverType::kUndefined),
is_registered_(false) {
callback_.reset(new NativeCallback(native_callback));
}
PerformanceObserver::PerformanceObserver(
script::EnvironmentSettings* env_settings,
const PerformanceObserverCallbackArg& callback)
: performance_(
base::AsWeakPtr(base::polymorphic_downcast<DOMSettings*>(
env_settings)
->window()
->performance().get())),
observer_type_(PerformanceObserverType::kUndefined),
is_registered_(false) {
callback_.reset(new ScriptCallback(callback, this));
}
PerformanceObserver::~PerformanceObserver() {}
void PerformanceObserver::Observe(const PerformanceObserverInit& options,
script::ExceptionState* exception_state) {
// The algorithm for registering the observer.
// https://www.w3.org/TR/2019/WD-performance-timeline-2-20191024/#observe-method
// 1. Let observer be the context object.
// 2. Let relevantGlobal be observer's relevant global object.
if (!performance_) {
return;
}
// 3. If options's entryTypes and type members are both omitted, then throw
// a SyntaxError.
bool has_entry_types = options.has_entry_types();
bool has_type = options.has_type();
if (!has_entry_types && !has_type) {
DOMException::Raise(DOMException::kSyntaxErr, exception_state);
}
// 4. If options's entryTypes is present and any other member is also
// present, then throw a SyntaxError.
bool entry_types_present = has_entry_types &&
!options.entry_types().empty();
bool type_present = has_type && !options.type().empty();
if (entry_types_present && type_present) {
DOMException::Raise(DOMException::kSyntaxErr, exception_state);
}
// 5. Update or check observer's observer type by running these steps:
// 5.1 If observer's observer type is "undefined":
if (observer_type_ == PerformanceObserverType::kUndefined) {
// 5.1.1 If options's entryTypes member is present, then set observer's
// observer type to "multiple".
if (entry_types_present) {
observer_type_ = PerformanceObserverType::kMultiple;
}
// 5.1.2 If options's type member is present, then set observer's
// observer type to "single".
if (type_present) {
observer_type_ = PerformanceObserverType::kSingle;
}
}
// 5.2 If observer's observer type is "single" and options's entryTypes
// member is present, then throw an InvalidModificationError.
if (observer_type_ == PerformanceObserverType::kSingle &&
entry_types_present) {
DOMException::Raise(DOMException::kInvalidModificationErr, exception_state);
}
// 5.3 If observer's observer type is "multiple" and options's type member
// is present, then throw an InvalidModificationError.
if (observer_type_ == PerformanceObserverType::kMultiple &&
type_present) {
DOMException::Raise(DOMException::kInvalidModificationErr, exception_state);
}
// 6 If observer's observer type is "multiple", run the following steps:
if (observer_type_ == PerformanceObserverType::kMultiple) {
// 6.1 Let entry types be options's entryTypes sequence.
script::Sequence<std::string> entry_types;
// 6.2 Remove all types from entry types that are not contained in
// relevantGlobal's frozen array of supported entry types. The user agent
// SHOULD notify developers if entry types is modified. For example, a
// console warning listing removed types might be appropriate.
for (const auto& entry_type_string : options.entry_types()) {
PerformanceEntryType entry_type =
PerformanceEntry::ToEntryTypeEnum(entry_type_string);
if (entry_type != PerformanceEntry::kInvalid) {
entry_types.push_back(entry_type_string);
} else {
DLOG(WARNING) << "The entry type " + entry_type_string +
" does not exist or isn't supported.";
}
}
// 6.3 If the resulting entry types sequence is an empty sequence,
// abort these steps. The user agent SHOULD notify developers when the
// steps are aborted to notify that registration has been aborted.
// For example, a console warning might be appropriate.
if (entry_types.empty()) {
DLOG(WARNING) << "An observe() call must include either entryTypes.";
return;
}
// 6.4 If the list of registered performance observer objects of
// relevantGlobal contains a registered performance observer whose
// observer is the context object, replace its options list with a list
// containing options as its only item.
if (is_registered_) {
performance_->ReplaceRegisteredPerformanceObserverOptionsList(this, options);
// 6.5 Otherwise, create and append a registered performance observer
// object to the list of registered performance observer objects of
// relevantGlobal, with observer set to the context object and options list
// set to a list containing options as its only item.
} else {
performance_->RegisterPerformanceObserver(this, options);
is_registered_ = true;
}
// 7. Otherwise, run the following steps:
} else {
// 7.1 Assert that observer's observer type is "single".
DCHECK(observer_type_ == PerformanceObserverType::kSingle);
// 7.2 If options's type is not contained in the relevantGlobal's frozen
// array of supported entry types, abort these steps. The user agent SHOULD
// notify developers when this happens, for instance via a console warning.
PerformanceEntryType options_type =
PerformanceEntry::ToEntryTypeEnum(options.type());
if (options_type == PerformanceEntry::kInvalid) {
DLOG(WARNING) << "The type " + options.type() +
" does not exist or isn't supported.";
return;
}
// 7.3 If the list of registered performance observer objects of
// relevantGlobal contains a registered performance observer obs whose
// observer is the context object:
if (is_registered_) {
// 7.3.1 If obs's options list contains a PerformanceObserverInit item
// currentOptions whose type is equal to options's type, replace
// currentOptions with options in obs's options list.
// 7.3.2 Otherwise, append options to obs's options list.
performance_->UpdateRegisteredPerformanceObserverOptionsList(this, options);
// 7.4 Otherwise, create and append a registered performance observer
// object to the list of registered performance observer objects of
// relevantGlobal, with observer set to the context object and options
// list set to a list containing options as its only item.
} else {
performance_->RegisterPerformanceObserver(this, options);
is_registered_ = true;
}
}
}
void PerformanceObserver::Observe(script::ExceptionState* exception_state) {
PerformanceObserverInit options;
Observe(options, exception_state);
}
void PerformanceObserver::Disconnect() {
// The disconnect() method must remove the context object from the list of
// registered performance observer objects of relevant global object, and
// also empty context object's observer buffer.
// https://www.w3.org/TR/2019/WD-performance-timeline-2-20191024/#disconnect-method
if (performance_) {
performance_->UnregisterPerformanceObserver(this);
}
observer_buffer_.clear();
is_registered_ = false;
}
PerformanceEntryList PerformanceObserver::TakeRecords() {
// The takeRecords() method must return a copy of the context object's
// observer buffer, and also empty context object's observer buffer.
// https://www.w3.org/TR/2019/WD-performance-timeline-2-20191024/#takerecords-method
PerformanceEntryList performance_entries;
performance_entries.swap(observer_buffer_);
return performance_entries;
}
script::Sequence<std::string> PerformanceObserver::supported_entry_types() {
// Each global object has an associated frozen array of supported entry
// types, which is initialized to the FrozenArray created from the
// sequence of strings among the registry that are supported for the
// global object, in alphabetical order.
// https://www.w3.org/TR/2019/WD-performance-timeline-2-20191024/#supportedentrytypes-attribute
// TODO: Implement the 'Time Entry Names Registry'.
// // https://w3c.github.io/timing-entrytypes-registry/#registry
// When supportedEntryTypes's attribute getter is called, run the
// following steps:
// 1. Let globalObject be the environment settings object's global object.
script::Sequence<std::string> supported_entry_type_list;
for (size_t i = 0; i < arraysize(PerformanceEntry::kEntryTypeString); ++i) {
const char* entry_type = PerformanceEntry::kEntryTypeString[i];
if (!base::LowerCaseEqualsASCII("invalid", entry_type)) {
std::string entry_type_string(entry_type);
supported_entry_type_list.push_back(entry_type_string);
}
}
// 2. Return globalObject's frozen array of supported entry types.
return supported_entry_type_list;
}
void PerformanceObserver::EnqueuePerformanceEntry(
const scoped_refptr<PerformanceEntry>& entry) {
observer_buffer_.push_back(entry);
}
void PerformanceObserver::TraceMembers(script::Tracer* tracer) {
tracer->TraceItems(observer_buffer_);
}
} // namespace dom
} // namespace cobalt