Chromium Code Reviews| Index: Source/core/dom/EventHandlerRegistry.cpp |
| diff --git a/Source/core/dom/EventHandlerRegistry.cpp b/Source/core/dom/EventHandlerRegistry.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ac04f991991cab82b9d623298f5d74727ab826bb |
| --- /dev/null |
| +++ b/Source/core/dom/EventHandlerRegistry.cpp |
| @@ -0,0 +1,343 @@ |
| +// Copyright 2014 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 "config.h" |
| +#include "core/dom/EventHandlerRegistry.h" |
| + |
| +#include "core/dom/Document.h" |
| +#include "core/events/ThreadLocalEventNames.h" |
| +#include "core/events/WheelEvent.h" |
| +#include "core/frame/FrameHost.h" |
| +#include "core/frame/LocalFrame.h" |
| +#include "core/page/Chrome.h" |
| +#include "core/page/ChromeClient.h" |
| +#include "core/page/Page.h" |
| +#include "core/page/scrolling/ScrollingCoordinator.h" |
| + |
| +namespace WebCore { |
| + |
| +EventHandlerRegistry::HandlerState::HandlerState() |
| + : handlerCount(0) |
| + , externalHandlerCount(0) |
| +{ |
| +} |
| + |
| +EventHandlerRegistry::HandlerState::~HandlerState() |
| +{ |
| +} |
| + |
| +EventHandlerRegistry::EventHandlerRegistry(Document& document) |
| + : DOMWindowLifecycleObserver(document.domWindow()) |
| + , DocumentLifecycleObserver(&document) |
| +{ |
| +} |
| + |
| +EventHandlerRegistry::~EventHandlerRegistry() |
| +{ |
| +} |
| + |
| +const char* EventHandlerRegistry::supplementName() |
| +{ |
| + return "EventHandlerRegistry"; |
| +} |
| + |
| +EventHandlerRegistry* EventHandlerRegistry::from(Document& document) |
| +{ |
| + EventHandlerRegistry* registry = static_cast<EventHandlerRegistry*>(DocumentSupplement::from(document, supplementName())); |
| + if (!registry) { |
| + registry = new EventHandlerRegistry(document); |
| + DocumentSupplement::provideTo(document, supplementName(), adoptPtr(registry)); |
| + } |
| + return registry; |
| +} |
| + |
| +bool EventHandlerRegistry::eventTypeToClass(const AtomicString& eventType, EventHandlerClass* result) |
| +{ |
| + if (eventType == EventTypeNames::wheel || eventType == EventTypeNames::mousewheel) { |
| + *result = WheelEvent; |
| + } else if (eventType == EventTypeNames::scroll) { |
| + *result = ScrollEvent; |
| + } else if (isTouchEventType(eventType)) { |
| + *result = TouchEvent; |
| + } else { |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +unsigned EventHandlerRegistry::eventHandlerCount(EventHandlerClass handlerClass) const |
| +{ |
| + return m_eventHandlers[handlerClass].handlerCount + m_eventHandlers[handlerClass].externalHandlerCount; |
| +} |
| + |
| +const EventTargetSet* EventHandlerRegistry::eventHandlerTargets(EventHandlerClass handlerClass) const |
| +{ |
| + return m_eventHandlers[handlerClass].targets.get(); |
| +} |
| + |
| +void EventHandlerRegistry::updateExternalHandlerCount(ChangeOperation op, EventHandlerClass handlerClass) |
| +{ |
| + if (op == Add) { |
| + m_eventHandlers[handlerClass].externalHandlerCount++; |
| + } else { |
| + ASSERT(op == Remove); |
| + ASSERT(m_eventHandlers[handlerClass].externalHandlerCount > 0); |
| + m_eventHandlers[handlerClass].externalHandlerCount--; |
| + } |
| +} |
| + |
| +bool EventHandlerRegistry::updateEventHandlerTargets(ChangeOperation op, EventHandlerClass handlerClass, Document& document, EventTarget* target) |
| +{ |
| + EventTargetSet* targets = m_eventHandlers[handlerClass].targets.get(); |
| + if (op == Add) { |
| + if (Node* node = target->toNode()) { |
| + // The node should either be in the document, or be the Document node of a child |
|
Rick Byers
2014/03/27 16:43:31
I like this assert, but again, but what if 'docume
Sami
2014/04/02 19:58:05
Agreed. This is now cleared up too.
|
| + // of the document. |
| + ASSERT(&node->document() == &document |
| + || (node->isDocumentNode() && toDocument(node)->parentDocument() == &document)); |
| + } else if (DOMWindow* window = target->toDOMWindow()) { |
| + // The window should belong to this document. |
| + ASSERT(window->document() == &document); |
| + } |
| + |
| + if (!targets) { |
| + m_eventHandlers[handlerClass].targets = adoptPtr(new EventTargetSet); |
| + targets = m_eventHandlers[handlerClass].targets.get(); |
| + } |
| + |
| + m_eventHandlers[handlerClass].handlerCount++; |
| + if (!targets->add(target).isNewEntry) { |
| + // Just incremented refcount, no real change. |
| + // If this is a child document node, then the count should never go above 1. |
| + if (Node* node = target->toNode()) |
| + ASSERT(!node->isDocumentNode() || &node->document() == &document); |
| + return false; |
| + } |
| + } else { |
| + // Note that we can't assert that |target| is in this document because |
| + // it might be in the process of moving out of it. |
| + ASSERT(op == Remove || op == RemoveAll); |
| + ASSERT(op == RemoveAll || targets->contains(target)); |
| + if (!targets) |
| + return false; |
| + |
| + if (op == RemoveAll) { |
| + if (!targets->contains(target)) |
| + return false; |
| + m_eventHandlers[handlerClass].handlerCount -= targets->count(target); |
| + targets->removeAll(target); |
| + } else { |
| + m_eventHandlers[handlerClass].handlerCount--; |
| + if (!targets->remove(target)) { |
| + // Just decremented refcount, no real update. |
| + return false; |
| + } |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +void EventHandlerRegistry::checkEventHandlerConsistency(EventHandlerClass handlerClass) const |
| +{ |
| +#ifndef NDEBUG |
| + EventTargetSet* targets = m_eventHandlers[handlerClass].targets.get(); |
| + if (!targets) { |
| + ASSERT(!m_eventHandlers[handlerClass].handlerCount); |
| + return; |
| + } |
| + |
| + unsigned count = 0; |
| + for (EventTargetSet::const_iterator iter = targets->begin(); iter != targets->end(); ++iter) |
| + count += iter->value; |
| + ASSERT(count == m_eventHandlers[handlerClass].handlerCount); |
| +#endif // NDEBUG |
| +} |
| + |
| +void EventHandlerRegistry::updateEventHandlerInternal(ChangeOperation op, Document& document, EventHandlerClass handlerClass, EventTarget* target) |
| +{ |
| + bool hadHandlers = eventHandlerCount(handlerClass); |
| + bool targetSetChanged; |
| + if (target) { |
| + targetSetChanged = updateEventHandlerTargets(op, handlerClass, document, target); |
| + checkEventHandlerConsistency(handlerClass); |
| + } else { |
| + updateExternalHandlerCount(op, handlerClass); |
| + targetSetChanged = true; |
| + } |
| + bool haveHandlers = eventHandlerCount(handlerClass); |
| + |
| + // Notify the parent document's registry if we added the first or removed |
| + // the last handler. |
| + bool addedFirstHandler = !hadHandlers && haveHandlers; |
| + bool removedLastHandler = hadHandlers && !haveHandlers; |
| + if (addedFirstHandler || removedLastHandler) { |
|
Rick Byers
2014/03/27 16:43:31
simpler just to check if(hadHandlers != haveHandle
Sami
2014/04/02 19:58:05
D'oh, done.
|
| + if (Document* parent = document.parentDocument()) { |
| + // Report change to parent with our Document as the target. |
| + EventHandlerRegistry::from(*parent)->updateEventHandlerInternal(op, *parent, handlerClass, &document); |
|
Rick Byers
2014/03/27 16:43:31
It's calls like this that make me think the Docume
Sami
2014/04/02 19:58:05
Done.
|
| + } else { |
| + // This is the root registry; notify clients accordingly. |
| + notifyDidAddFirstOrRemoveLastEventHandler(document, handlerClass, haveHandlers); |
| + } |
| + } |
| + |
| + // Only notify the client when something actually changed, and then only if |
| + // this wasn't a notification that was sent by a sub-registry. |
| + if (!targetSetChanged) |
| + return; |
| + Node* node = target ? target->toNode() : nullptr; |
| + if (!node || !node->isDocumentNode() || node == &document) |
| + notifyDidAddOrRemoveEventHandlerTarget(handlerClass, document); |
| +} |
| + |
| +void EventHandlerRegistry::updateEventHandlerOfClass(ChangeOperation op, EventHandlerClass handlerClass, EventTarget& target) |
| +{ |
|
Rick Byers
2014/03/27 16:43:31
this function should collapse to nothing if you ca
Sami
2014/04/02 19:58:05
Done.
|
| + if (Node* node = target.toNode()) |
| + updateEventHandlerInternal(op, node->document(), handlerClass, node); |
| + else if (DOMWindow* window = target.toDOMWindow()) |
| + updateEventHandlerInternal(op, *window->document(), handlerClass, window); |
| + else |
| + ASSERT_NOT_REACHED(); |
| +} |
| + |
| +void EventHandlerRegistry::updateEventHandlerOfType(ChangeOperation op, const AtomicString& eventType, EventTarget& target) |
| +{ |
| + EventHandlerClass handlerClass; |
| + if (!eventTypeToClass(eventType, &handlerClass)) |
| + return; |
| + updateEventHandlerOfClass(op, handlerClass, target); |
| +} |
| + |
| +void EventHandlerRegistry::didAddExternalEventHandler(Document& document, const AtomicString& eventType) |
| +{ |
| + EventHandlerClass handlerClass; |
| + if (!eventTypeToClass(eventType, &handlerClass)) |
| + return; |
| + updateEventHandlerInternal(Add, document, handlerClass, nullptr); |
| +} |
| + |
| +void EventHandlerRegistry::didRemoveExternalEventHandler(Document& document, const AtomicString& eventType) |
| +{ |
| + EventHandlerClass handlerClass; |
| + if (!eventTypeToClass(eventType, &handlerClass)) |
| + return; |
| + updateEventHandlerInternal(Remove, document, handlerClass, nullptr); |
| +} |
| + |
| +void EventHandlerRegistry::willDetachDocument() |
| +{ |
| + Document* document = DocumentLifecycleObserver::lifecycleContext(); |
| + Document* parentDocument = document->parentDocument(); |
| + if (!parentDocument) |
| + return; |
| + // Unregister our Document node from the parent for all event classes that |
| + // have handlers. |
| + EventHandlerRegistry* parentRegistry = EventHandlerRegistry::from(*parentDocument); |
| + updateAllEventHandlersForTarget(RemoveAll, *document, *parentRegistry); |
| +} |
| + |
| +void EventHandlerRegistry::documentWasAttached() |
| +{ |
| + Document* document = DocumentLifecycleObserver::lifecycleContext(); |
| + if (document->parentDocument()) |
| + return; |
| + for (size_t i = 0; i < EventHandlerClassCount; ++i) { |
| + EventHandlerClass handlerClass = static_cast<EventHandlerClass>(i); |
| + if (eventHandlerCount(handlerClass)) |
| + notifyDidAddFirstOrRemoveLastEventHandler(*document, handlerClass, true); |
| + } |
| +} |
| + |
| +void EventHandlerRegistry::didAddEventHandler(EventTarget& target, const AtomicString& eventType) |
| +{ |
| + updateEventHandlerOfType(Add, eventType, target); |
| +} |
| + |
| +void EventHandlerRegistry::didRemoveEventHandler(EventTarget& target, const AtomicString& eventType) |
| +{ |
| + updateEventHandlerOfType(Remove, eventType, target); |
| +} |
| + |
| +void EventHandlerRegistry::didAddEventListener(DOMWindow* window, const AtomicString& eventType) |
| +{ |
| + updateEventHandlerOfType(Add, eventType, *window); |
| +} |
| + |
| +void EventHandlerRegistry::didRemoveEventListener(DOMWindow* window, const AtomicString& eventType) |
| +{ |
| + updateEventHandlerOfType(Remove, eventType, *window); |
| +} |
| + |
| +void EventHandlerRegistry::didRemoveAllEventListeners(DOMWindow* window) |
| +{ |
| + updateAllEventHandlersForTarget(RemoveAll, *window, *this); |
| +} |
| + |
| +void EventHandlerRegistry::didMoveToNewDocument(EventTarget& target, Document& oldDocument) |
| +{ |
| + EventHandlerRegistry* oldRegistry = EventHandlerRegistry::from(oldDocument); |
| + oldRegistry->updateAllEventHandlersForTarget(Add, target, *this); |
| + oldRegistry->updateAllEventHandlersForTarget(Remove, target, *oldRegistry); |
| +} |
| + |
| +void EventHandlerRegistry::didRemoveAllEventHandlers(EventTarget& target) |
| +{ |
| + updateAllEventHandlersForTarget(RemoveAll, target, *this); |
| +} |
| + |
| +void EventHandlerRegistry::updateAllEventHandlersForTarget(ChangeOperation op, EventTarget& target, EventHandlerRegistry& targetRegistry) const |
|
Rick Byers
2014/03/27 16:43:31
I find this function confusing. Perhaps there's r
Sami
2014/04/02 19:58:05
Right, I tried a little too hard to reuse code and
|
| +{ |
| + for (size_t i = 0; i < EventHandlerClassCount; ++i) { |
| + EventHandlerClass handlerClass = static_cast<EventHandlerClass>(i); |
| + const EventTargetSet* targets = eventHandlerTargets(handlerClass); |
| + if (!targets) |
| + continue; |
| + for (unsigned count = targets->count(&target); count > 0; --count) |
|
Rick Byers
2014/03/27 16:43:31
This loop doesn't seem to make sense when the op i
Sami
2014/04/02 19:58:05
Done.
|
| + targetRegistry.updateEventHandlerOfClass(op, handlerClass, target); |
| + } |
| +} |
| + |
| +void EventHandlerRegistry::notifyDidAddFirstOrRemoveLastEventHandler(Document& document, EventHandlerClass handlerClass, bool hasActiveHandlers) |
| +{ |
| + Page* page = document.page(); |
| + LocalFrame* mainFrame = page ? page->mainFrame() : nullptr; |
| + ScrollingCoordinator* scrollingCoordinator = page ? page->scrollingCoordinator() : nullptr; |
| + FrameView* frameView = document.view(); |
| + |
| + switch (handlerClass) { |
| + case WheelEvent: |
| + if (mainFrame) { |
| + // TODO(skyostil): This notification is wired up to a black hole, so remove it. |
| + mainFrame->notifyChromeClientWheelEventHandlerCountChanged(); |
| + } |
| + if (scrollingCoordinator && frameView) |
| + scrollingCoordinator->frameViewWheelEventHandlerCountChanged(frameView); |
|
Rick Byers
2014/03/27 16:43:31
This function and the below say they're supposed t
Sami
2014/04/02 19:58:05
Yes, especially now that ScrollingCoordinator no l
|
| + break; |
| + case ScrollEvent: |
| + if (scrollingCoordinator && frameView) |
| + scrollingCoordinator->frameViewScrollEventHandlerCountChanged(frameView); |
| + break; |
| + case TouchEvent: |
| + if (FrameHost* frameHost = document.frameHost()) |
| + frameHost->chrome().client().needTouchEvents(hasActiveHandlers); |
| + break; |
| + default: |
| + ASSERT_NOT_REACHED(); |
| + break; |
| + } |
| +} |
| + |
| +void EventHandlerRegistry::notifyDidAddOrRemoveEventHandlerTarget(EventHandlerClass handlerClass, Document& document) |
| +{ |
| + Page* page = document.page(); |
| + if (!page) |
| + return; |
| + |
| + ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator(); |
| + if (!scrollingCoordinator) |
| + return; |
| + |
| + if (handlerClass == TouchEvent) |
| + scrollingCoordinator->touchEventTargetRectsDidChange(); |
| +} |
| + |
| +} // namespace WebCore |