Issue #2282 - - Properly implement Performance Timeline Level 2 w3c spec. https://bugzilla.mozilla.org/show_bug.cgi?id=1539006 Do not throw from PerformanceObserver.observe when none of the entryTypes are known. https://bugzilla.mozilla.org/show_bug.cgi?id=1403027 Implement PerformanceObserver::takeRecords(). https://bugzilla.mozilla.org/show_bug.cgi?id=1436692 "server" is not a valid PerformanceEntry type. https://bugzilla.mozilla.org/show_bug.cgi?id=1463065 Fix a null ptr crash in PerformanceObserver::Observe. https://bugzilla.mozilla.org/show_bug.cgi?id=1631346

This commit is contained in:
Brian Smith 2023-08-02 02:41:40 -05:00 committed by roytam1
commit dd4c0a44b6
8 changed files with 284 additions and 49 deletions

View file

@ -9,6 +9,7 @@
#include "mozilla/dom/PerformanceBinding.h"
#include "mozilla/dom/PerformanceEntryBinding.h"
#include "mozilla/dom/PerformanceObserverBinding.h"
#include "nsIScriptError.h"
#include "nsPIDOMWindow.h"
#include "nsQueryObject.h"
#include "nsString.h"
@ -27,12 +28,14 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PerformanceObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PerformanceObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(PerformanceObserver)
@ -43,10 +46,14 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
const char UnsupportedEntryTypesIgnoredMsgId[] = "UnsupportedEntryTypesIgnored";
const char AllEntryTypesIgnoredMsgId[] = "AllEntryTypesIgnored";
PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner,
PerformanceObserverCallback& aCb)
: mOwner(aOwner)
, mCallback(&aCb)
, mObserverType(ObserverTypeUndefined)
, mConnected(false)
{
MOZ_ASSERT(mOwner);
@ -56,6 +63,7 @@ PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner,
PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate,
PerformanceObserverCallback& aCb)
: mCallback(&aCb)
, mObserverType(ObserverTypeUndefined)
, mConnected(false)
{
MOZ_ASSERT(aWorkerPrivate);
@ -115,7 +123,8 @@ PerformanceObserver::Notify()
mQueuedEntries.Clear();
ErrorResult rv;
mCallback->Call(this, *list, *this, rv);
RefPtr<PerformanceObserverCallback> callback(mCallback);
callback->Call(this, *list, *this, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
}
@ -126,63 +135,227 @@ PerformanceObserver::QueueEntry(PerformanceEntry* aEntry)
{
MOZ_ASSERT(aEntry);
nsAutoString entryType;
aEntry->GetEntryType(entryType);
if (!mEntryTypes.Contains<nsString>(entryType)) {
if (!ObservesTypeOfEntry(aEntry)) {
return;
}
mQueuedEntries.AppendElement(aEntry);
}
/*
* Keep this list in alphabetical order.
* https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
*/
static const char16_t *const sValidTypeNames[4] = {
u"mark",
u"measure",
u"resource",
u"server"
u"navigation",
u"resource"
};
void
PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
ErrorResult& aRv)
{
if (aOptions.mEntryTypes.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
PerformanceObserver::ReportUnsupportedTypesErrorToConsole(
bool aIsMainThread, const char* msgId, const nsString& aInvalidTypes) {
if (!aIsMainThread) {
nsTArray<nsString> params;
params.AppendElement(aInvalidTypes);
WorkerPrivate::ReportErrorToConsole(msgId, params);
} else {
nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(mOwner);
nsIDocument* document = ownerWindow->GetExtantDoc();
const char16_t* params[] = {aInvalidTypes.get()};
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), document,
nsContentUtils::eDOM_PROPERTIES, msgId, params, 1);
}
return;
}
void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
ErrorResult& aRv) {
const Optional<Sequence<nsString>>& maybeEntryTypes = aOptions.mEntryTypes;
const Optional<nsString>& maybeType = aOptions.mType;
const Optional<bool>& maybeBuffered = aOptions.mBuffered;
if (!mPerformance) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
nsTArray<nsString> validEntryTypes;
if (!maybeEntryTypes.WasPassed() && !maybeType.WasPassed()) {
/* Per spec (3.3.1.2), this should be a syntax error. */
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
for (const char16_t* name : sValidTypeNames) {
nsDependentString validTypeName(name);
if (aOptions.mEntryTypes.Contains<nsString>(validTypeName) &&
!validEntryTypes.Contains<nsString>(validTypeName)) {
validEntryTypes.AppendElement(validTypeName);
if (maybeEntryTypes.WasPassed() &&
(maybeType.WasPassed() || maybeBuffered.WasPassed())) {
/* Per spec (3.3.1.3), this, too, should be a syntax error. */
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
/* 3.3.1.4.1 */
if (mObserverType == ObserverTypeUndefined) {
if (maybeEntryTypes.WasPassed()) {
mObserverType = ObserverTypeMultiple;
} else {
mObserverType = ObserverTypeSingle;
}
}
if (validEntryTypes.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
/* 3.3.1.4.2 */
if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) {
aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
return;
}
/* 3.3.1.4.3 */
if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) {
aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
return;
}
mEntryTypes.SwapElements(validEntryTypes);
/* 3.3.1.5 */
if (mObserverType == ObserverTypeMultiple) {
const Sequence<nsString>& entryTypes = maybeEntryTypes.Value();
mPerformance->AddObserver(this);
if (entryTypes.IsEmpty()) {
return;
}
if (aOptions.mBuffered) {
for (auto entryType : mEntryTypes) {
/* 3.3.1.5.2 */
nsTArray<nsString> validEntryTypes;
for (const char16_t* name : sValidTypeNames) {
nsDependentString validTypeName(name);
if (entryTypes.Contains<nsString>(validTypeName) &&
!validEntryTypes.Contains<nsString>(validTypeName)) {
validEntryTypes.AppendElement(validTypeName);
}
}
nsAutoString invalidTypesJoined;
bool addComma = false;
for (const auto& type : entryTypes) {
if (!validEntryTypes.Contains<nsString>(type)) {
if (addComma) {
invalidTypesJoined.AppendLiteral(", ");
}
addComma = true;
invalidTypesJoined.Append(type);
}
}
if (!invalidTypesJoined.IsEmpty()) {
ReportUnsupportedTypesErrorToConsole(NS_IsMainThread(),
UnsupportedEntryTypesIgnoredMsgId,
invalidTypesJoined);
}
/* 3.3.1.5.3 */
if (validEntryTypes.IsEmpty()) {
nsString errorString;
ReportUnsupportedTypesErrorToConsole(
NS_IsMainThread(), AllEntryTypesIgnoredMsgId, errorString);
return;
}
/*
* Registered or not, we clear out the list of options, and start fresh
* with the one that we are using here. (3.3.1.5.4,5)
*/
mOptions.Clear();
mOptions.AppendElement(aOptions);
} else {
MOZ_ASSERT(mObserverType == ObserverTypeSingle);
bool typeValid = false;
nsString type = maybeType.Value();
/* 3.3.1.6.2 */
for (const char16_t* name : sValidTypeNames) {
nsDependentString validTypeName(name);
if (type == validTypeName) {
typeValid = true;
break;
}
}
if (!typeValid) {
ReportUnsupportedTypesErrorToConsole(
NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId, type);
return;
}
/* 3.3.1.6.4, 3.3.1.6.4 */
bool didUpdateOptionsList = false;
nsTArray<PerformanceObserverInit> updatedOptionsList;
for (auto& option : mOptions) {
if (option.mType.WasPassed() && option.mType.Value() == type) {
updatedOptionsList.AppendElement(aOptions);
didUpdateOptionsList = true;
} else {
updatedOptionsList.AppendElement(option);
}
}
if (!didUpdateOptionsList) {
updatedOptionsList.AppendElement(aOptions);
}
mOptions.SwapElements(updatedOptionsList);
/* 3.3.1.6.5 */
if (maybeBuffered.WasPassed() && maybeBuffered.Value()) {
nsTArray<RefPtr<PerformanceEntry>> existingEntries;
mPerformance->GetEntriesByType(entryType, existingEntries);
mPerformance->GetEntriesByType(type, existingEntries);
if (!existingEntries.IsEmpty()) {
mQueuedEntries.AppendElements(existingEntries);
}
}
}
/* Add ourselves to the list of registered performance
* observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4)
*/
mPerformance->AddObserver(this);
mConnected = true;
}
void
PerformanceObserver::GetSupportedEntryTypes(const GlobalObject& aGlobal, JS::MutableHandle<JSObject*> aObject)
{
nsTArray<nsString> validTypes;
JS::Rooted<JS::Value> val(aGlobal.Context());
for (const char16_t* name : sValidTypeNames) {
nsString validTypeName(name);
validTypes.AppendElement(validTypeName);
}
if (!ToJSValue(aGlobal.Context(), validTypes, &val)) {
/*
* If this conversion fails, we don't set a result.
* The spec does not allow us to throw an exception.
*/
return;
}
aObject.set(&val.toObject());
}
bool
PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry)
{
for (auto& option : mOptions) {
if (option.mType.WasPassed()) {
if (option.mType.Value() == aEntry->GetEntryType()) {
return true;
}
} else {
if (option.mEntryTypes.Value().Contains(aEntry->GetEntryType())) {
return true;
}
}
}
return false;
}
void
PerformanceObserver::Disconnect()
{
@ -192,3 +365,10 @@ PerformanceObserver::Disconnect()
mConnected = false;
}
}
void
PerformanceObserver::TakeRecords(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
{
MOZ_ASSERT(aRetval.IsEmpty());
aRetval.SwapElements(mQueuedEntries);
}