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..75cd9939700991ae6352b30a4454d4cab8993b6c |
--- /dev/null |
+++ b/Source/core/dom/EventHandlerRegistry.cpp |
@@ -0,0 +1,364 @@ |
+// 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::WindowObserver::WindowObserver(EventHandlerRegistry& registry, DOMWindow& window) |
+ : DOMWindowLifecycleObserver(&window) |
+ , m_registry(registry) |
+{ |
+} |
+ |
+EventHandlerRegistry::WindowObserver::~WindowObserver() |
+{ |
+} |
+ |
+void EventHandlerRegistry::WindowObserver::didAddEventListener(DOMWindow* window, const AtomicString& eventType) |
+{ |
+ m_registry.didAddEventHandler(*window, eventType); |
+} |
+ |
+void EventHandlerRegistry::WindowObserver::didRemoveEventListener(DOMWindow* window, const AtomicString& eventType) |
+{ |
+ m_registry.didRemoveEventHandler(*window, eventType); |
+} |
+ |
+void EventHandlerRegistry::WindowObserver::didRemoveAllEventListeners(DOMWindow* window) |
+{ |
+ m_registry.didRemoveAllEventHandlers(*window); |
+} |
+ |
+EventHandlerRegistry::DocumentObserver::DocumentObserver(Document& document) |
+ : DocumentLifecycleObserver(&document) |
+ , ActiveDOMObject(&document) |
+{ |
+ suspendIfNeeded(); |
+} |
+ |
+EventHandlerRegistry::DocumentObserver::~DocumentObserver() |
+{ |
+} |
+ |
+void EventHandlerRegistry::DocumentObserver::documentWasAttached() |
+{ |
+ Document* document = DocumentLifecycleObserver::lifecycleContext(); |
+ if (document->parentDocument()) |
+ return; |
+ EventHandlerRegistry* registry = EventHandlerRegistry::from(*document); |
abarth-chromium
2014/04/11 19:08:01
So, we're hanging the EventHandlerRegistry off the
Sami
2014/04/15 18:31:52
Sorry, this was my bug; actually EventHandlerRegis
|
+ DOMWindow* window = document->domWindow(); |
+ |
+ // Register any existing handlers on the window and start tracking new ones. |
+ if (!registry->m_windowObserver && window) { |
+ Vector<AtomicString> eventTypes = window->eventTypes(); |
+ for (size_t i = 0; i < eventTypes.size(); ++i) { |
+ const EventListenerVector& listeners = window->getEventListeners(eventTypes[i]); |
+ for (size_t count = 0; count < listeners.size(); count++) { |
+ registry->didAddEventHandler(*window, eventTypes[i]); |
+ } |
+ } |
abarth-chromium
2014/04/11 19:08:01
Do you have a test that demonstrates this code bei
Sami
2014/04/15 18:31:52
No, but I'll try to make one based on the comment
|
+ registry->m_windowObserver = adoptPtr(new WindowObserver(*registry, *document->domWindow())); |
abarth-chromium
2014/04/11 19:08:01
Why do we only observe the top-level DOMWindow?
Sami
2014/04/15 18:31:52
That's not intended -- fixed as explained under li
|
+ } |
+} |
+ |
+void EventHandlerRegistry::DocumentObserver::stop() |
+{ |
+ Document* document = DocumentLifecycleObserver::lifecycleContext(); |
+ |
+ // The detached document should no longer track handlers on its window. |
+ EventHandlerRegistry* registry = EventHandlerRegistry::from(*document); |
abarth-chromium
2014/04/11 19:08:01
On line 59, we skip creating the EventHandlerRegis
Sami
2014/04/15 18:31:52
In fact we do need an EventHandlerRegistry on all
|
+ if (document->domWindow()) |
+ registry->didRemoveAllEventHandlers(*document->domWindow()); |
+ registry->m_windowObserver.clear(); |
+ |
+ // Unregister our Document node from the parent for all event classes that |
+ // have handlers. |
+ Document* parentDocument = document->parentDocument(); |
+ if (!parentDocument) |
+ return; |
+ EventHandlerRegistry* parentRegistry = EventHandlerRegistry::from(*parentDocument); |
+ parentRegistry->didRemoveAllEventHandlers(*document); |
abarth-chromium
2014/04/11 19:08:01
What happens if someone adds an event handler afte
Sami
2014/04/15 18:31:52
Good point. I was assuming the document would be c
|
+} |
+ |
+EventHandlerRegistry::HandlerState::HandlerState() |
+ : externalHandlerCount(0) |
+{ |
+} |
+ |
+EventHandlerRegistry::HandlerState::~HandlerState() |
+{ |
+} |
+ |
+EventHandlerRegistry::EventHandlerRegistry(Document& document) |
+ : m_document(document) |
+ , m_documentObserver(DocumentObserver(document)) |
+ , m_windowObserver(adoptPtr(new WindowObserver(*this, *document.domWindow()))) |
abarth-chromium
2014/04/11 19:08:01
Why is the DocumentObserver held by value but the
Sami
2014/04/15 18:31:52
This was so that we could stop tracking handlers o
|
+{ |
+} |
+ |
+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; |
+} |
+ |
+const EventTargetSet* EventHandlerRegistry::eventHandlerTargets(EventHandlerClass handlerClass) const |
+{ |
+ return m_eventHandlers[handlerClass].targets.get(); |
+} |
+ |
+bool EventHandlerRegistry::hasEventHandlers(EventHandlerClass handlerClass) const |
+{ |
+ EventTargetSet* targets = m_eventHandlers[handlerClass].targets.get(); |
+ return m_eventHandlers[handlerClass].externalHandlerCount || (targets && targets->size()); |
+} |
+ |
+unsigned EventHandlerRegistry::externalEventHandlerCount(EventHandlerClass handlerClass) const |
+{ |
+ return m_eventHandlers[handlerClass].externalHandlerCount; |
+} |
+ |
+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, EventTarget* target) |
+{ |
+ EventTargetSet* targets = m_eventHandlers[handlerClass].targets.get(); |
+ if (op == Add) { |
+#ifndef NDEBUG |
+ if (Node* node = target->toNode()) { |
+ // The node should either be in the document, or be the Document node of a child |
+ // of the document. |
+ ASSERT(&node->document() == &m_document |
+ || (node->isDocumentNode() && toDocument(node)->parentDocument() == &m_document)); |
+ } else if (DOMWindow* window = target->toDOMWindow()) { |
+ // The window should belong to this document. |
+ ASSERT(window->document() == &m_document); |
+ } |
+#endif // NDEBUG |
+ |
+ if (!targets) { |
+ m_eventHandlers[handlerClass].targets = adoptPtr(new EventTargetSet); |
+ targets = m_eventHandlers[handlerClass].targets.get(); |
+ } |
+ |
+ 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. |
+#ifndef NDEBUG |
+ if (Node* node = target->toNode()) |
+ ASSERT(!node->isDocumentNode() || &node->document() == &m_document); |
+#endif // NDEBUG |
+ 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; |
+ targets->removeAll(target); |
+ } else { |
+ if (!targets->remove(target)) { |
+ // Just decremented refcount, no real update. |
+ return false; |
+ } |
+ } |
+ } |
+ return true; |
+} |
+ |
+void EventHandlerRegistry::updateEventHandlerInternal(ChangeOperation op, EventHandlerClass handlerClass, EventTarget* target) |
+{ |
+ bool hadHandlers = hasEventHandlers(handlerClass); |
+ bool targetSetChanged; |
+ if (target) { |
+ targetSetChanged = updateEventHandlerTargets(op, handlerClass, target); |
+ } else { |
+ updateExternalHandlerCount(op, handlerClass); |
+ targetSetChanged = true; |
+ } |
+ bool hasHandlers = hasEventHandlers(handlerClass); |
+ |
+ // Notify the parent document's registry if we added the first or removed |
+ // the last handler. |
+ if (hadHandlers != hasHandlers) { |
+ if (Document* parent = m_document.parentDocument()) { |
+ // Report change to parent with our Document as the target. |
+ EventHandlerRegistry::from(*parent)->updateEventHandlerInternal(op, handlerClass, &m_document); |
+ } else { |
+ // This is the root registry; notify clients accordingly. |
+ notifyHasHandlersChanged(handlerClass, hasHandlers); |
+ } |
+ } |
+ |
+ // 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() : 0; |
+ if (!node || !node->isDocumentNode() || node == &m_document) |
+ notifyDidAddOrRemoveEventHandlerTarget(handlerClass); |
+} |
+ |
+void EventHandlerRegistry::updateEventHandlerOfType(ChangeOperation op, const AtomicString& eventType, EventTarget* target) |
+{ |
+ EventHandlerClass handlerClass; |
+ if (!eventTypeToClass(eventType, &handlerClass)) |
+ return; |
+ updateEventHandlerInternal(op, handlerClass, target); |
+} |
+ |
+void EventHandlerRegistry::didAddExternalEventHandler(const AtomicString& eventType) |
+{ |
+ updateEventHandlerOfType(Add, eventType, 0); |
+} |
+ |
+void EventHandlerRegistry::didRemoveExternalEventHandler(const AtomicString& eventType) |
+{ |
+ updateEventHandlerOfType(Remove, eventType, 0); |
+} |
+ |
+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::didAddEventHandler(EventTarget& target, EventHandlerClass handlerClass) |
+{ |
+ updateEventHandlerInternal(Add, handlerClass, &target); |
+} |
+ |
+void EventHandlerRegistry::didRemoveEventHandler(EventTarget& target, EventHandlerClass handlerClass) |
+{ |
+ updateEventHandlerInternal(Remove, handlerClass, &target); |
+} |
+ |
+void EventHandlerRegistry::didMoveFromOtherDocument(EventTarget& target, Document& oldDocument) |
+{ |
+ EventHandlerRegistry* oldRegistry = EventHandlerRegistry::from(oldDocument); |
+ for (size_t i = 0; i < EventHandlerClassCount; ++i) { |
+ EventHandlerClass handlerClass = static_cast<EventHandlerClass>(i); |
+ const EventTargetSet* targets = oldRegistry->eventHandlerTargets(handlerClass); |
+ if (!targets) |
+ continue; |
+ for (unsigned count = targets->count(&target); count > 0; --count) { |
+ oldRegistry->updateEventHandlerInternal(Remove, handlerClass, &target); |
+ updateEventHandlerInternal(Add, handlerClass, &target); |
+ } |
+ } |
+} |
+ |
+void EventHandlerRegistry::didRemoveAllEventHandlers(EventTarget& target) |
+{ |
+ for (size_t i = 0; i < EventHandlerClassCount; ++i) { |
+ EventHandlerClass handlerClass = static_cast<EventHandlerClass>(i); |
+ const EventTargetSet* targets = eventHandlerTargets(handlerClass); |
+ if (!targets) |
+ continue; |
+ updateEventHandlerInternal(RemoveAll, handlerClass, &target); |
+ } |
+} |
+ |
+void EventHandlerRegistry::notifyHasHandlersChanged(EventHandlerClass handlerClass, bool hasActiveHandlers) |
+{ |
+ Page* page = m_document.page(); |
+ LocalFrame* mainFrame = page ? page->mainFrame() : 0; |
+ ScrollingCoordinator* scrollingCoordinator = page ? page->scrollingCoordinator() : 0; |
+ |
+ switch (handlerClass) { |
+ case WheelEvent: |
+ if (mainFrame) { |
+ // TODO(skyostil): This notification is wired up to a black hole, so remove it. |
+ mainFrame->notifyChromeClientWheelEventHandlerCountChanged(); |
+ } |
+ if (scrollingCoordinator) |
+ scrollingCoordinator->haveWheelEventHandlersChangedForPage(); |
+ break; |
+ case ScrollEvent: |
+ if (scrollingCoordinator) |
+ scrollingCoordinator->haveScrollEventHandlersChangedForPage(); |
+ break; |
+ case TouchEvent: |
+ if (FrameHost* frameHost = m_document.frameHost()) |
+ frameHost->chrome().client().needTouchEvents(hasActiveHandlers); |
+ break; |
+ default: |
+ ASSERT_NOT_REACHED(); |
+ break; |
+ } |
+} |
+ |
+void EventHandlerRegistry::notifyDidAddOrRemoveEventHandlerTarget(EventHandlerClass handlerClass) |
+{ |
+ Page* page = m_document.page(); |
+ if (!page) |
+ return; |
+ |
+ ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator(); |
+ if (!scrollingCoordinator) |
+ return; |
+ |
+ if (handlerClass == TouchEvent) |
+ scrollingCoordinator->touchEventTargetRectsDidChange(); |
+} |
+ |
+} // namespace WebCore |