summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/dom/Node.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/dom/Node.cpp')
-rw-r--r--Source/WebCore/dom/Node.cpp482
1 files changed, 74 insertions, 408 deletions
diff --git a/Source/WebCore/dom/Node.cpp b/Source/WebCore/dom/Node.cpp
index c125d16..4def034 100644
--- a/Source/WebCore/dom/Node.cpp
+++ b/Source/WebCore/dom/Node.cpp
@@ -51,6 +51,7 @@
#include "Element.h"
#include "Event.h"
#include "EventContext.h"
+#include "EventDispatcher.h"
#include "EventException.h"
#include "EventHandler.h"
#include "EventListener.h"
@@ -106,10 +107,13 @@
#if ENABLE(SVG)
#include "SVGElementInstance.h"
-#include "SVGNames.h"
#include "SVGUseElement.h"
#endif
+#if ENABLE(WML)
+#include "WMLNames.h"
+#endif
+
#if ENABLE(XHTMLMP)
#include "HTMLNoScriptElement.h"
#endif
@@ -126,8 +130,6 @@ namespace WebCore {
using namespace HTMLNames;
-static HashSet<Node*>* gNodesDispatchingSimulatedClicks = 0;
-
bool Node::isSupported(const String& feature, const String& version)
{
return DOMImplementation::hasFeature(feature, version);
@@ -347,6 +349,12 @@ Node::StyleChange Node::diff(const RenderStyle* s1, const RenderStyle* s2)
}
}
+ // When text-combine property has been changed, we need to prepare a separate renderer object.
+ // When text-combine is on, we use RenderCombineText, otherwise RenderText.
+ // https://bugs.webkit.org/show_bug.cgi?id=55069
+ if ((s1 && s2) && (s1->hasTextCombine() != s2->hasTextCombine()))
+ ch = Detach;
+
return ch;
}
@@ -506,6 +514,18 @@ void Node::setShadowHost(Element* host)
setParent(host);
}
+InputElement* Node::toInputElement()
+{
+ // If one of the below ASSERTs trigger, you are calling this function
+ // directly or indirectly from a constructor or destructor of this object.
+ // Don't do this!
+ ASSERT(!(isHTMLElement() && hasTagName(inputTag)));
+#if ENABLE(WML)
+ ASSERT(!(isWMLElement() && hasTagName(WMLNames::inputTag)));
+#endif
+ return 0;
+}
+
short Node::tabIndex() const
{
return hasRareData() ? rareData()->tabIndex() : 0;
@@ -694,19 +714,36 @@ void Node::deprecatedParserAddChild(PassRefPtr<Node>)
{
}
-bool Node::isContentEditable() const
+bool Node::rendererIsEditable(EditableLevel editableLevel) const
{
- return parentOrHostNode() && parentOrHostNode()->isContentEditable();
-}
+ if (document()->inDesignMode() || (document()->frame() && document()->frame()->page() && document()->frame()->page()->isEditable()))
+ return true;
-bool Node::isContentRichlyEditable() const
-{
- return parentOrHostNode() && parentOrHostNode()->isContentRichlyEditable();
+ // Ideally we'd call ASSERT(!needsStyleRecalc()) here, but
+ // ContainerNode::setFocus() calls setNeedsStyleRecalc(), so the assertion
+ // would fire in the middle of Document::setFocusedNode().
+
+ for (const Node* node = this; node; node = node->parentNode()) {
+ if ((node->isHTMLElement() || node->isDocumentNode()) && node->renderer()) {
+ switch (node->renderer()->style()->userModify()) {
+ case READ_ONLY:
+ return false;
+ case READ_WRITE:
+ return true;
+ case READ_WRITE_PLAINTEXT_ONLY:
+ return editableLevel != RichlyEditable;
+ }
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+ }
+
+ return false;
}
bool Node::shouldUseInputMethod() const
{
- return isContentEditable();
+ return rendererIsEditable();
}
RenderBox* Node::renderBox() const
@@ -764,10 +801,16 @@ bool Node::hasNonEmptyBoundingBox() const
void Node::setDocumentRecursively(Document* document)
{
- // FIXME: To match Gecko, we should do this for nodes that are already in the document as well.
- if (this->document() == document || this->inDocument())
+ if (this->document() == document)
return;
+ // If an element is moved from a document and then eventually back again the collection cache for
+ // that element may contain stale data as changes made to it will have updated the DOMTreeVersion
+ // of the document it was moved to. By increasing the DOMTreeVersion of the donating document here
+ // we ensure that the collection cache will be invalidated as needed when the element is moved back.
+ if (this->document())
+ this->document()->incDOMTreeVersion();
+
for (Node* node = this; node; node = node->traverseNextNode(this)) {
node->setDocument(document);
if (!node->isElementNode())
@@ -1147,37 +1190,25 @@ bool Node::canReplaceChild(Node* newChild, Node*)
static void checkAcceptChild(Node* newParent, Node* newChild, ExceptionCode& ec)
{
- // Perform error checking as required by spec for adding a new child. Used by replaceChild().
-
// Not mentioned in spec: throw NOT_FOUND_ERR if newChild is null
if (!newChild) {
ec = NOT_FOUND_ERR;
return;
}
- // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly
if (newParent->isReadOnlyNode()) {
ec = NO_MODIFICATION_ALLOWED_ERR;
return;
}
-
- // WRONG_DOCUMENT_ERR: Raised if newChild was created from a different document than the one that
- // created this node.
- // We assume that if newChild is a DocumentFragment, all children are created from the same document
- // as the fragment itself (otherwise they could not have been added as children)
- if (newChild->document() != newParent->document() && newChild->inDocument()) {
- // but if the child is not in a document yet then loosen the
- // restriction, so that e.g. creating an element with the Option()
- // constructor and then adding it to a different document works,
- // as it does in Mozilla and Mac IE.
- ec = WRONG_DOCUMENT_ERR;
+
+ if (newChild->inDocument() && newChild->nodeType() == Node::DOCUMENT_TYPE_NODE) {
+ ec = HIERARCHY_REQUEST_ERR;
return;
}
-
+
// HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not allow children of the type of the
// newChild node, or if the node to append is one of this node's ancestors.
- // check for ancestor/same node
if (newChild == newParent || newParent->isDescendantOf(newChild)) {
ec = HIERARCHY_REQUEST_ERR;
return;
@@ -1186,6 +1217,11 @@ static void checkAcceptChild(Node* newParent, Node* newChild, ExceptionCode& ec)
void Node::checkReplaceChild(Node* newChild, Node* oldChild, ExceptionCode& ec)
{
+ if (!oldChild) {
+ ec = NOT_FOUND_ERR;
+ return;
+ }
+
checkAcceptChild(this, newChild, ec);
if (ec)
return;
@@ -1481,7 +1517,7 @@ int Node::maxCharacterOffset() const
// is obviously misplaced.
bool Node::canStartSelection() const
{
- if (isContentEditable())
+ if (rendererIsEditable())
return true;
if (renderer()) {
@@ -1559,7 +1595,7 @@ Element *Node::enclosingBlockFlowElement() const
Element* Node::rootEditableElement() const
{
Element* result = 0;
- for (Node* n = const_cast<Node*>(this); n && n->isContentEditable(); n = n->parentNode()) {
+ for (Node* n = const_cast<Node*>(this); n && n->rendererIsEditable(); n = n->parentNode()) {
if (n->isElementNode())
result = static_cast<Element*>(n);
if (n->hasTagName(bodyTag))
@@ -2638,200 +2674,14 @@ void Node::handleLocalEvents(Event* event)
fireEventListeners(event);
}
-static inline EventTarget* eventTargetRespectingSVGTargetRules(Node* referenceNode)
-{
- ASSERT(referenceNode);
-
-#if ENABLE(SVG)
- if (!referenceNode->isSVGElement())
- return referenceNode;
-
- // Spec: The event handling for the non-exposed tree works as if the referenced element had been textually included
- // as a deeply cloned child of the 'use' element, except that events are dispatched to the SVGElementInstance objects
- for (Node* n = referenceNode; n; n = n->parentNode()) {
- if (!n->isShadowRoot() || !n->isSVGElement())
- continue;
-
- Element* shadowTreeParentElement = n->shadowHost();
- ASSERT(shadowTreeParentElement->hasTagName(SVGNames::useTag));
-
- if (SVGElementInstance* instance = static_cast<SVGUseElement*>(shadowTreeParentElement)->instanceForShadowTreeElement(referenceNode))
- return instance;
- }
-#endif
-
- return referenceNode;
-}
-
-void Node::getEventAncestors(Vector<EventContext>& ancestors, EventTarget* originalTarget, EventDispatchBehavior behavior)
-{
- if (!inDocument())
- return;
-
- EventTarget* target = originalTarget;
- Node* ancestor = this;
- bool shouldSkipNextAncestor = false;
- while (true) {
- if (ancestor->isShadowRoot()) {
- if (behavior == StayInsideShadowDOM)
- return;
- ancestor = ancestor->shadowHost();
- if (!shouldSkipNextAncestor)
- target = ancestor;
- } else
- ancestor = ancestor->parentNodeGuaranteedHostFree();
-
- if (!ancestor)
- return;
-
-#if ENABLE(SVG)
- // Skip SVGShadowTreeRootElement.
- shouldSkipNextAncestor = ancestor->isSVGElement() && ancestor->isShadowRoot();
- if (shouldSkipNextAncestor)
- continue;
-#endif
- // FIXME: Unroll the extra loop inside eventTargetRespectingSVGTargetRules into this loop.
- ancestors.append(EventContext(ancestor, eventTargetRespectingSVGTargetRules(ancestor), target));
-
- }
-}
-
-bool Node::dispatchEvent(PassRefPtr<Event> prpEvent)
-{
- RefPtr<EventTarget> protect = this;
- RefPtr<Event> event = prpEvent;
-
- event->setTarget(eventTargetRespectingSVGTargetRules(this));
-
- RefPtr<FrameView> view = document()->view();
- return dispatchGenericEvent(event.release());
-}
-
void Node::dispatchScopedEvent(PassRefPtr<Event> event)
{
- // We need to set the target here because it can go away by the time we actually fire the event.
- event->setTarget(eventTargetRespectingSVGTargetRules(this));
-
- ScopedEventQueue::instance()->enqueueEvent(event);
-}
-
-static const EventContext* topEventContext(const Vector<EventContext>& ancestors)
-{
- return ancestors.isEmpty() ? 0 : &ancestors.last();
+ EventDispatcher::dispatchScopedEvent(this, event);
}
-static EventDispatchBehavior determineDispatchBehavior(Event* event)
+bool Node::dispatchEvent(PassRefPtr<Event> event)
{
- // Per XBL 2.0 spec, mutation events should never cross shadow DOM boundary:
- // http://dev.w3.org/2006/xbl2/#event-flow-and-targeting-across-shadow-s
- if (event->isMutationEvent())
- return StayInsideShadowDOM;
-
- // WebKit never allowed selectstart event to cross the the shadow DOM boundary.
- // Changing this breaks existing sites.
- // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details.
- if (event->type() == eventNames().selectstartEvent)
- return StayInsideShadowDOM;
-
- return RetargetEvent;
-}
-
-bool Node::dispatchGenericEvent(PassRefPtr<Event> prpEvent)
-{
- RefPtr<Event> event(prpEvent);
-
- ASSERT(!eventDispatchForbidden());
- ASSERT(event->target());
- ASSERT(!event->type().isNull()); // JavaScript code can create an event with an empty name, but not null.
-
- // Make a vector of ancestors to send the event to.
- // If the node is not in a document just send the event to it.
- // Be sure to ref all of nodes since event handlers could result in the last reference going away.
- RefPtr<Node> thisNode(this);
- RefPtr<EventTarget> originalTarget = event->target();
- Vector<EventContext> ancestors;
- getEventAncestors(ancestors, originalTarget.get(), determineDispatchBehavior(event.get()));
-
- WindowEventContext windowContext(event.get(), this, topEventContext(ancestors));
-
- InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchEvent(document(), *event, windowContext.window(), this, ancestors);
-
- // Give the target node a chance to do some work before DOM event handlers get a crack.
- void* data = preDispatchEventHandler(event.get());
- if (event->propagationStopped())
- goto doneDispatching;
-
- // Trigger capturing event handlers, starting at the top and working our way down.
- event->setEventPhase(Event::CAPTURING_PHASE);
-
- if (windowContext.handleLocalEvents(event.get()) && event->propagationStopped())
- goto doneDispatching;
-
- for (size_t i = ancestors.size(); i; --i) {
- ancestors[i - 1].handleLocalEvents(event.get());
- if (event->propagationStopped())
- goto doneDispatching;
- }
-
- event->setEventPhase(Event::AT_TARGET);
- event->setTarget(originalTarget.get());
- event->setCurrentTarget(eventTargetRespectingSVGTargetRules(this));
- handleLocalEvents(event.get());
- if (event->propagationStopped())
- goto doneDispatching;
-
- if (event->bubbles() && !event->cancelBubble()) {
- // Trigger bubbling event handlers, starting at the bottom and working our way up.
- event->setEventPhase(Event::BUBBLING_PHASE);
-
- size_t size = ancestors.size();
- for (size_t i = 0; i < size; ++i) {
- ancestors[i].handleLocalEvents(event.get());
- if (event->propagationStopped() || event->cancelBubble())
- goto doneDispatching;
- }
- windowContext.handleLocalEvents(event.get());
- }
-
-doneDispatching:
- event->setTarget(originalTarget.get());
- event->setCurrentTarget(0);
- event->setEventPhase(0);
-
- // Pass the data from the preDispatchEventHandler to the postDispatchEventHandler.
- postDispatchEventHandler(event.get(), data);
-
- // Call default event handlers. While the DOM does have a concept of preventing
- // default handling, the detail of which handlers are called is an internal
- // implementation detail and not part of the DOM.
- if (!event->defaultPrevented() && !event->defaultHandled()) {
- // Non-bubbling events call only one default event handler, the one for the target.
- defaultEventHandler(event.get());
- ASSERT(!event->defaultPrevented());
- if (event->defaultHandled())
- goto doneWithDefault;
- // For bubbling events, call default event handlers on the same targets in the
- // same order as the bubbling phase.
- if (event->bubbles()) {
- size_t size = ancestors.size();
- for (size_t i = 0; i < size; ++i) {
- ancestors[i].node()->defaultEventHandler(event.get());
- ASSERT(!event->defaultPrevented());
- if (event->defaultHandled())
- goto doneWithDefault;
- }
- }
- }
-
-doneWithDefault:
-
- // Ensure that after event dispatch, the event's target object is the
- // outermost shadow DOM boundary.
- event->setTarget(windowContext.target());
- event->setCurrentTarget(0);
- InspectorInstrumentation::didDispatchEvent(cookie);
-
- return !event->defaultPrevented();
+ return EventDispatcher::dispatchEvent(this, event);
}
void Node::dispatchSubtreeModifiedEvent()
@@ -2861,209 +2711,25 @@ void Node::dispatchUIEvent(const AtomicString& eventType, int detail, PassRefPtr
dispatchScopedEvent(event.release());
}
-bool Node::dispatchKeyEvent(const PlatformKeyboardEvent& key)
+bool Node::dispatchKeyEvent(const PlatformKeyboardEvent& event)
{
- RefPtr<KeyboardEvent> keyboardEvent = KeyboardEvent::create(key, document()->defaultView());
- bool r = dispatchEvent(keyboardEvent);
-
- // we want to return false if default is prevented (already taken care of)
- // or if the element is default-handled by the DOM. Otherwise we let it just
- // let it get handled by AppKit
- if (keyboardEvent->defaultHandled())
- r = false;
-
- return r;
+ return EventDispatcher::dispatchEvent(this, KeyboardEvent::create(event, document()->defaultView()));
}
bool Node::dispatchMouseEvent(const PlatformMouseEvent& event, const AtomicString& eventType,
int detail, Node* relatedTarget)
{
- ASSERT(!eventDispatchForbidden());
-
- IntPoint contentsPos;
- if (FrameView* view = document()->view())
- contentsPos = view->windowToContents(event.pos());
-
- short button = event.button();
-
- ASSERT(event.eventType() == MouseEventMoved || button != NoButton);
-
- return dispatchMouseEvent(eventType, button, detail,
- contentsPos.x(), contentsPos.y(), event.globalX(), event.globalY(),
- event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(),
- false, relatedTarget, 0);
-}
-
-void Node::dispatchSimulatedMouseEvent(const AtomicString& eventType,
- PassRefPtr<Event> underlyingEvent)
-{
- ASSERT(!eventDispatchForbidden());
-
- bool ctrlKey = false;
- bool altKey = false;
- bool shiftKey = false;
- bool metaKey = false;
- if (UIEventWithKeyState* keyStateEvent = findEventWithKeyState(underlyingEvent.get())) {
- ctrlKey = keyStateEvent->ctrlKey();
- altKey = keyStateEvent->altKey();
- shiftKey = keyStateEvent->shiftKey();
- metaKey = keyStateEvent->metaKey();
- }
-
- // Like Gecko, we just pass 0 for everything when we make a fake mouse event.
- // Internet Explorer instead gives the current mouse position and state.
- dispatchMouseEvent(eventType, 0, 0, 0, 0, 0, 0,
- ctrlKey, altKey, shiftKey, metaKey, true, 0, underlyingEvent);
+ return EventDispatcher::dispatchMouseEvent(this, event, eventType, detail, relatedTarget);
}
void Node::dispatchSimulatedClick(PassRefPtr<Event> event, bool sendMouseEvents, bool showPressedLook)
{
- if (!gNodesDispatchingSimulatedClicks)
- gNodesDispatchingSimulatedClicks = new HashSet<Node*>;
- else if (gNodesDispatchingSimulatedClicks->contains(this))
- return;
-
- gNodesDispatchingSimulatedClicks->add(this);
-
- // send mousedown and mouseup before the click, if requested
- if (sendMouseEvents)
- dispatchSimulatedMouseEvent(eventNames().mousedownEvent, event.get());
- setActive(true, showPressedLook);
- if (sendMouseEvents)
- dispatchSimulatedMouseEvent(eventNames().mouseupEvent, event.get());
- setActive(false);
-
- // always send click
- dispatchSimulatedMouseEvent(eventNames().clickEvent, event);
-
- gNodesDispatchingSimulatedClicks->remove(this);
-}
-
-// FIXME: Once https://bugs.webkit.org/show_bug.cgi?id=52963 lands, this should
-// be greatly improved. See https://bugs.webkit.org/show_bug.cgi?id=54025.
-static Node* pullOutOfShadow(Node* node)
-{
- Node* outermostShadowBoundary = node;
- for (Node* n = node; n; n = n->parentOrHostNode()) {
- if (n->isShadowRoot())
- outermostShadowBoundary = n->parentOrHostNode();
- }
- return outermostShadowBoundary;
-}
-
-bool Node::dispatchMouseEvent(const AtomicString& eventType, int button, int detail,
- int pageX, int pageY, int screenX, int screenY,
- bool ctrlKey, bool altKey, bool shiftKey, bool metaKey,
- bool isSimulated, Node* relatedTargetArg, PassRefPtr<Event> underlyingEvent)
-{
- ASSERT(!eventDispatchForbidden());
- if (disabled()) // Don't even send DOM events for disabled controls..
- return true;
-
- if (eventType.isEmpty())
- return false; // Shouldn't happen.
-
- // Dispatching the first event can easily result in this node being destroyed.
- // Since we dispatch up to three events here, we need to make sure we're referenced
- // so the pointer will be good for the two subsequent ones.
- RefPtr<Node> protect(this);
-
- bool cancelable = eventType != eventNames().mousemoveEvent;
-
- bool swallowEvent = false;
-
- // Attempting to dispatch with a non-EventTarget relatedTarget causes the relatedTarget to be silently ignored.
- RefPtr<Node> relatedTarget = pullOutOfShadow(relatedTargetArg);
-
- int adjustedPageX = pageX;
- int adjustedPageY = pageY;
- if (Frame* frame = document()->frame()) {
- float pageZoom = frame->pageZoomFactor();
- if (pageZoom != 1.0f) {
- // Adjust our pageX and pageY to account for the page zoom.
- adjustedPageX = lroundf(pageX / pageZoom);
- adjustedPageY = lroundf(pageY / pageZoom);
- }
- }
-
- RefPtr<MouseEvent> mouseEvent = MouseEvent::create(eventType,
- true, cancelable, document()->defaultView(),
- detail, screenX, screenY, adjustedPageX, adjustedPageY,
- ctrlKey, altKey, shiftKey, metaKey, button,
- relatedTarget, 0, isSimulated);
- mouseEvent->setUnderlyingEvent(underlyingEvent.get());
- mouseEvent->setAbsoluteLocation(IntPoint(pageX, pageY));
-
- dispatchEvent(mouseEvent);
- bool defaultHandled = mouseEvent->defaultHandled();
- bool defaultPrevented = mouseEvent->defaultPrevented();
- if (defaultHandled || defaultPrevented)
- swallowEvent = true;
-
- // Special case: If it's a double click event, we also send the dblclick event. This is not part
- // of the DOM specs, but is used for compatibility with the ondblclick="" attribute. This is treated
- // as a separate event in other DOM-compliant browsers like Firefox, and so we do the same.
- if (eventType == eventNames().clickEvent && detail == 2) {
- RefPtr<Event> doubleClickEvent = MouseEvent::create(eventNames().dblclickEvent,
- true, cancelable, document()->defaultView(),
- detail, screenX, screenY, adjustedPageX, adjustedPageY,
- ctrlKey, altKey, shiftKey, metaKey, button,
- relatedTarget, 0, isSimulated);
- doubleClickEvent->setUnderlyingEvent(underlyingEvent.get());
- if (defaultHandled)
- doubleClickEvent->setDefaultHandled();
- dispatchEvent(doubleClickEvent);
- if (doubleClickEvent->defaultHandled() || doubleClickEvent->defaultPrevented())
- swallowEvent = true;
- }
-
- return swallowEvent;
+ EventDispatcher::dispatchSimulatedClick(this, event, sendMouseEvents, showPressedLook);
}
void Node::dispatchWheelEvent(PlatformWheelEvent& e)
{
- ASSERT(!eventDispatchForbidden());
- if (e.deltaX() == 0 && e.deltaY() == 0)
- return;
-
- FrameView* view = document()->view();
- if (!view)
- return;
-
- IntPoint pos = view->windowToContents(e.pos());
-
- int adjustedPageX = pos.x();
- int adjustedPageY = pos.y();
- if (Frame* frame = document()->frame()) {
- float pageZoom = frame->pageZoomFactor();
- if (pageZoom != 1.0f) {
- // Adjust our pageX and pageY to account for the page zoom.
- adjustedPageX = lroundf(pos.x() / pageZoom);
- adjustedPageY = lroundf(pos.y() / pageZoom);
- }
- }
-
- WheelEvent::Granularity granularity;
- switch (e.granularity()) {
- case ScrollByPageWheelEvent:
- granularity = WheelEvent::Page;
- break;
- case ScrollByPixelWheelEvent:
- default:
- granularity = WheelEvent::Pixel;
- break;
- }
-
- RefPtr<WheelEvent> we = WheelEvent::create(e.wheelTicksX(), e.wheelTicksY(), e.deltaX(), e.deltaY(), granularity,
- document()->defaultView(), e.globalX(), e.globalY(), adjustedPageX, adjustedPageY,
- e.ctrlKey(), e.altKey(), e.shiftKey(), e.metaKey());
-
- we->setAbsoluteLocation(IntPoint(pos.x(), pos.y()));
-
- if (!dispatchEvent(we) || we->defaultHandled())
- e.accept();
-
- we.release();
+ EventDispatcher::dispatchWheelEvent(this, e);
}
void Node::dispatchFocusEvent()