blob: 7635ab4c3b649dc899edb8bc61ac4f8b2efc14ab [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/dom/mutation_observer.h"
#include <algorithm>
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_mutation_callback.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/mutation_observer_init.h"
#include "third_party/blink/renderer/core/dom/mutation_observer_registration.h"
#include "third_party/blink/renderer/core/dom/mutation_record.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/execution_context/window_agent.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
namespace blink {
class MutationObserver::V8DelegateImpl final
: public MutationObserver::Delegate,
public ContextClient {
USING_GARBAGE_COLLECTED_MIXIN(V8DelegateImpl);
public:
static V8DelegateImpl* Create(V8MutationCallback* callback,
ExecutionContext* execution_context) {
return MakeGarbageCollected<V8DelegateImpl>(callback, execution_context);
}
V8DelegateImpl(V8MutationCallback* callback,
ExecutionContext* execution_context)
: ContextClient(execution_context), callback_(callback) {}
ExecutionContext* GetExecutionContext() const override {
return ContextClient::GetExecutionContext();
}
void Deliver(const MutationRecordVector& records,
MutationObserver& observer) override {
// https://dom.spec.whatwg.org/#notify-mutation-observers
// step 5-4. specifies that the callback this value is a MutationObserver.
callback_->InvokeAndReportException(&observer, records, &observer);
}
void Trace(Visitor* visitor) override {
visitor->Trace(callback_);
MutationObserver::Delegate::Trace(visitor);
ContextClient::Trace(visitor);
}
private:
Member<V8MutationCallback> callback_;
};
static unsigned g_observer_priority = 0;
struct MutationObserver::ObserverLessThan {
bool operator()(const Member<MutationObserver>& lhs,
const Member<MutationObserver>& rhs) {
return lhs->priority_ < rhs->priority_;
}
};
MutationObserver* MutationObserver::Create(Delegate* delegate) {
DCHECK(IsMainThread());
return MakeGarbageCollected<MutationObserver>(delegate->GetExecutionContext(),
delegate);
}
MutationObserver* MutationObserver::Create(ScriptState* script_state,
V8MutationCallback* callback) {
DCHECK(IsMainThread());
ExecutionContext* execution_context = ExecutionContext::From(script_state);
return MakeGarbageCollected<MutationObserver>(
execution_context, V8DelegateImpl::Create(callback, execution_context));
}
MutationObserver::MutationObserver(ExecutionContext* execution_context,
Delegate* delegate)
: ContextClient(execution_context), delegate_(delegate) {
priority_ = g_observer_priority++;
}
MutationObserver::~MutationObserver() = default;
void MutationObserver::observe(Node* node,
const MutationObserverInit* observer_init,
ExceptionState& exception_state) {
DCHECK(node);
MutationObserverOptions options = 0;
if (observer_init->hasAttributeOldValue() &&
observer_init->attributeOldValue())
options |= kAttributeOldValue;
HashSet<AtomicString> attribute_filter;
if (observer_init->hasAttributeFilter()) {
for (const auto& name : observer_init->attributeFilter())
attribute_filter.insert(AtomicString(name));
options |= kAttributeFilter;
}
bool attributes =
observer_init->hasAttributes() && observer_init->attributes();
if (attributes || (!observer_init->hasAttributes() &&
(observer_init->hasAttributeOldValue() ||
observer_init->hasAttributeFilter())))
options |= kMutationTypeAttributes;
if (observer_init->hasCharacterDataOldValue() &&
observer_init->characterDataOldValue())
options |= kCharacterDataOldValue;
bool character_data =
observer_init->hasCharacterData() && observer_init->characterData();
if (character_data || (!observer_init->hasCharacterData() &&
observer_init->hasCharacterDataOldValue()))
options |= kMutationTypeCharacterData;
if (observer_init->childList())
options |= kMutationTypeChildList;
if (observer_init->subtree())
options |= kSubtree;
if (!(options & kMutationTypeAttributes)) {
if (options & kAttributeOldValue) {
exception_state.ThrowTypeError(
"The options object may only set 'attributeOldValue' to true when "
"'attributes' is true or not present.");
return;
}
if (options & kAttributeFilter) {
exception_state.ThrowTypeError(
"The options object may only set 'attributeFilter' when 'attributes' "
"is true or not present.");
return;
}
}
if (!((options & kMutationTypeCharacterData) ||
!(options & kCharacterDataOldValue))) {
exception_state.ThrowTypeError(
"The options object may only set 'characterDataOldValue' to true when "
"'characterData' is true or not present.");
return;
}
if (!(options & kMutationTypeAll)) {
exception_state.ThrowTypeError(
"The options object must set at least one of 'attributes', "
"'characterData', or 'childList' to true.");
return;
}
node->RegisterMutationObserver(*this, options, attribute_filter);
}
MutationRecordVector MutationObserver::takeRecords() {
MutationRecordVector records;
CancelInspectorAsyncTasks();
swap(records_, records);
return records;
}
void MutationObserver::disconnect() {
CancelInspectorAsyncTasks();
records_.clear();
MutationObserverRegistrationSet registrations(registrations_);
for (auto& registration : registrations) {
// The registration may be already unregistered while iteration.
// Only call unregister if it is still in the original set.
if (registrations_.Contains(registration))
registration->Unregister();
}
DCHECK(registrations_.IsEmpty());
}
void MutationObserver::ObservationStarted(
MutationObserverRegistration* registration) {
DCHECK(!registrations_.Contains(registration));
registrations_.insert(registration);
}
void MutationObserver::ObservationEnded(
MutationObserverRegistration* registration) {
DCHECK(registrations_.Contains(registration));
registrations_.erase(registration);
}
static MutationObserverSet& ActiveMutationObservers() {
DEFINE_STATIC_LOCAL(Persistent<MutationObserverSet>, active_observers,
(MakeGarbageCollected<MutationObserverSet>()));
return *active_observers;
}
using SlotChangeList = HeapVector<Member<HTMLSlotElement>>;
// TODO(hayato): We should have a SlotChangeList for each unit of related
// similar-origin browsing context.
// https://html.spec.whatwg.org/C/#unit-of-related-similar-origin-browsing-contexts
static SlotChangeList& ActiveSlotChangeList() {
DEFINE_STATIC_LOCAL(Persistent<SlotChangeList>, slot_change_list,
(MakeGarbageCollected<SlotChangeList>()));
return *slot_change_list;
}
static MutationObserverSet& SuspendedMutationObservers() {
DEFINE_STATIC_LOCAL(Persistent<MutationObserverSet>, suspended_observers,
(MakeGarbageCollected<MutationObserverSet>()));
return *suspended_observers;
}
static void EnsureEnqueueMicrotask() {
if (ActiveMutationObservers().IsEmpty() && ActiveSlotChangeList().IsEmpty())
Microtask::EnqueueMicrotask(WTF::Bind(&MutationObserver::DeliverMutations));
}
// static
void MutationObserver::EnqueueSlotChange(HTMLSlotElement& slot) {
DCHECK(IsMainThread());
EnsureEnqueueMicrotask();
ActiveSlotChangeList().push_back(&slot);
}
// static
void MutationObserver::CleanSlotChangeList(Document& document) {
SlotChangeList kept;
kept.ReserveCapacity(ActiveSlotChangeList().size());
for (auto& slot : ActiveSlotChangeList()) {
if (slot->GetDocument() != document)
kept.push_back(slot);
}
ActiveSlotChangeList().swap(kept);
}
static void ActivateObserver(MutationObserver* observer) {
EnsureEnqueueMicrotask();
ActiveMutationObservers().insert(observer);
}
void MutationObserver::EnqueueMutationRecord(MutationRecord* mutation) {
DCHECK(IsMainThread());
records_.push_back(mutation);
ActivateObserver(this);
probe::AsyncTaskScheduled(delegate_->GetExecutionContext(), mutation->type(),
mutation->async_task_id());
}
void MutationObserver::SetHasTransientRegistration() {
DCHECK(IsMainThread());
ActivateObserver(this);
}
HeapHashSet<Member<Node>> MutationObserver::GetObservedNodes() const {
HeapHashSet<Member<Node>> observed_nodes;
for (const auto& registration : registrations_)
registration->AddRegistrationNodesToSet(observed_nodes);
return observed_nodes;
}
bool MutationObserver::ShouldBeSuspended() const {
const ExecutionContext* execution_context = delegate_->GetExecutionContext();
return execution_context && execution_context->IsContextPaused();
}
void MutationObserver::CancelInspectorAsyncTasks() {
for (auto& record : records_) {
probe::AsyncTaskCanceled(delegate_->GetExecutionContext(),
record->async_task_id());
}
}
void MutationObserver::Deliver() {
DCHECK(!ShouldBeSuspended());
// Calling ClearTransientRegistrations() can modify registrations_, so it's
// necessary to make a copy of the transient registrations before operating on
// them.
HeapVector<Member<MutationObserverRegistration>, 1> transient_registrations;
for (auto& registration : registrations_) {
if (registration->HasTransientRegistrations())
transient_registrations.push_back(registration);
}
for (const auto& registration : transient_registrations)
registration->ClearTransientRegistrations();
if (records_.IsEmpty())
return;
MutationRecordVector records;
swap(records_, records);
// Report the first (earliest) stack as the async cause.
probe::AsyncTask async_task(delegate_->GetExecutionContext(),
records.front()->async_task_id());
delegate_->Deliver(records, *this);
}
// static
void MutationObserver::ResumeSuspendedObservers() {
DCHECK(IsMainThread());
if (SuspendedMutationObservers().IsEmpty())
return;
MutationObserverVector suspended;
CopyToVector(SuspendedMutationObservers(), suspended);
for (const auto& observer : suspended) {
if (!observer->ShouldBeSuspended()) {
SuspendedMutationObservers().erase(observer);
ActivateObserver(observer);
}
}
}
// static
void MutationObserver::DeliverMutations() {
// These steps are defined in DOM Standard's "notify mutation observers".
// https://dom.spec.whatwg.org/#notify-mutation-observers
DCHECK(IsMainThread());
MutationObserverVector observers;
CopyToVector(ActiveMutationObservers(), observers);
ActiveMutationObservers().clear();
SlotChangeList slots;
slots.swap(ActiveSlotChangeList());
for (const auto& slot : slots)
slot->ClearSlotChangeEventEnqueued();
std::sort(observers.begin(), observers.end(), ObserverLessThan());
for (const auto& observer : observers) {
if (!observer->GetExecutionContext()) {
// The observer's execution context is already gone, as active observers
// intentionally do not hold their execution context. Do nothing then.
continue;
}
if (observer->ShouldBeSuspended())
SuspendedMutationObservers().insert(observer);
else
observer->Deliver();
}
for (const auto& slot : slots)
slot->DispatchSlotChangeEvent();
}
void MutationObserver::Trace(Visitor* visitor) {
visitor->Trace(delegate_);
visitor->Trace(records_);
visitor->Trace(registrations_);
ScriptWrappable::Trace(visitor);
ContextClient::Trace(visitor);
}
} // namespace blink