diff options
Diffstat (limited to 'dom/html/HTMLLabelElement.cpp')
-rw-r--r-- | dom/html/HTMLLabelElement.cpp | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/dom/html/HTMLLabelElement.cpp b/dom/html/HTMLLabelElement.cpp new file mode 100644 index 000000000..c1d22b0a6 --- /dev/null +++ b/dom/html/HTMLLabelElement.cpp @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Implementation of HTML <label> elements. + */ +#include "HTMLLabelElement.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/dom/HTMLLabelElementBinding.h" +#include "nsFocusManager.h" +#include "nsIDOMMouseEvent.h" +#include "nsQueryObject.h" + +// construction, destruction + +NS_IMPL_NS_NEW_HTML_ELEMENT(Label) + +namespace mozilla { +namespace dom { + +HTMLLabelElement::~HTMLLabelElement() +{ +} + +JSObject* +HTMLLabelElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) +{ + return HTMLLabelElementBinding::Wrap(aCx, this, aGivenProto); +} + +// nsISupports + +NS_IMPL_ISUPPORTS_INHERITED(HTMLLabelElement, nsGenericHTMLElement, + nsIDOMHTMLLabelElement) + +// nsIDOMHTMLLabelElement + +NS_IMPL_ELEMENT_CLONE(HTMLLabelElement) + +NS_IMETHODIMP +HTMLLabelElement::GetForm(nsIDOMHTMLFormElement** aForm) +{ + RefPtr<nsIDOMHTMLFormElement> form = GetForm(); + form.forget(aForm); + return NS_OK; +} + +NS_IMETHODIMP +HTMLLabelElement::GetControl(nsIDOMHTMLElement** aElement) +{ + nsCOMPtr<nsIDOMHTMLElement> element = do_QueryObject(GetLabeledElement()); + element.forget(aElement); + return NS_OK; +} + +NS_IMETHODIMP +HTMLLabelElement::SetHtmlFor(const nsAString& aHtmlFor) +{ + ErrorResult rv; + SetHtmlFor(aHtmlFor, rv); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +HTMLLabelElement::GetHtmlFor(nsAString& aHtmlFor) +{ + nsString htmlFor; + GetHtmlFor(htmlFor); + aHtmlFor = htmlFor; + return NS_OK; +} + +HTMLFormElement* +HTMLLabelElement::GetForm() const +{ + nsGenericHTMLElement* control = GetControl(); + if (!control) { + return nullptr; + } + + // Not all labeled things have a form association. Stick to the ones that do. + nsCOMPtr<nsIFormControl> formControl = do_QueryObject(control); + if (!formControl) { + return nullptr; + } + + return static_cast<HTMLFormElement*>(formControl->GetFormElement()); +} + +void +HTMLLabelElement::Focus(ErrorResult& aError) +{ + // retarget the focus method at the for content + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> elem = do_QueryObject(GetLabeledElement()); + if (elem) + fm->SetFocus(elem, 0); + } +} + +static bool +InInteractiveHTMLContent(nsIContent* aContent, nsIContent* aStop) +{ + nsIContent* content = aContent; + while (content && content != aStop) { + if (content->IsElement() && + content->AsElement()->IsInteractiveHTMLContent(true)) { + return true; + } + content = content->GetParent(); + } + return false; +} + +nsresult +HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent(); + if (mHandlingEvent || + (!(mouseEvent && mouseEvent->IsLeftClickEvent()) && + aVisitor.mEvent->mMessage != eMouseDown) || + aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault || + !aVisitor.mPresContext || + // Don't handle the event if it's already been handled by another label + aVisitor.mEvent->mFlags.mMultipleActionsPrevented) { + return NS_OK; + } + + nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->mTarget); + if (InInteractiveHTMLContent(target, this)) { + return NS_OK; + } + + // Strong ref because event dispatch is going to happen. + RefPtr<Element> content = GetLabeledElement(); + + if (content) { + mHandlingEvent = true; + switch (aVisitor.mEvent->mMessage) { + case eMouseDown: + if (mouseEvent->button == WidgetMouseEvent::eLeftButton) { + // We reset the mouse-down point on every event because there is + // no guarantee we will reach the eMouseClick code below. + LayoutDeviceIntPoint* curPoint = + new LayoutDeviceIntPoint(mouseEvent->mRefPoint); + SetProperty(nsGkAtoms::labelMouseDownPtProperty, + static_cast<void*>(curPoint), + nsINode::DeleteProperty<LayoutDeviceIntPoint>); + } + break; + + case eMouseClick: + if (mouseEvent->IsLeftClickEvent()) { + LayoutDeviceIntPoint* mouseDownPoint = + static_cast<LayoutDeviceIntPoint*>( + GetProperty(nsGkAtoms::labelMouseDownPtProperty)); + + bool dragSelect = false; + if (mouseDownPoint) { + LayoutDeviceIntPoint dragDistance = *mouseDownPoint; + DeleteProperty(nsGkAtoms::labelMouseDownPtProperty); + + dragDistance -= mouseEvent->mRefPoint; + const int CLICK_DISTANCE = 2; + dragSelect = dragDistance.x > CLICK_DISTANCE || + dragDistance.x < -CLICK_DISTANCE || + dragDistance.y > CLICK_DISTANCE || + dragDistance.y < -CLICK_DISTANCE; + } + // Don't click the for-content if we did drag-select text or if we + // have a kbd modifier (which adjusts a selection). + if (dragSelect || mouseEvent->IsShift() || mouseEvent->IsControl() || + mouseEvent->IsAlt() || mouseEvent->IsMeta()) { + break; + } + // Only set focus on the first click of multiple clicks to prevent + // to prevent immediate de-focus. + if (mouseEvent->mClickCount <= 1) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to. + // Also, within HTMLInputElement::PostHandleEvent, inputs will + // be selected only when focused via a key or when the navigation + // flag is used and we want to select the text on label clicks as + // well. + // If the label has been clicked by the user, we also want to + // pass FLAG_BYMOUSE so that we get correct focus ring behavior, + // but we don't want to pass FLAG_BYMOUSE if this click event was + // caused by the user pressing an accesskey. + nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(content); + bool byMouse = (mouseEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD); + bool byTouch = (mouseEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH); + fm->SetFocus(elem, nsIFocusManager::FLAG_BYMOVEFOCUS | + (byMouse ? nsIFocusManager::FLAG_BYMOUSE : 0) | + (byTouch ? nsIFocusManager::FLAG_BYTOUCH : 0)); + } + } + // Dispatch a new click event to |content| + // (For compatibility with IE, we do only left click. If + // we wanted to interpret the HTML spec very narrowly, we + // would do nothing. If we wanted to do something + // sensible, we might send more events through like + // this.) See bug 7554, bug 49897, and bug 96813. + nsEventStatus status = aVisitor.mEventStatus; + // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent + // will actually create a new event. + EventFlags eventFlags; + eventFlags.mMultipleActionsPrevented = true; + DispatchClickEvent(aVisitor.mPresContext, mouseEvent, + content, false, &eventFlags, &status); + // Do we care about the status this returned? I don't think we do... + // Don't run another <label> off of this click + mouseEvent->mFlags.mMultipleActionsPrevented = true; + } + break; + + default: + break; + } + mHandlingEvent = false; + } + return NS_OK; +} + +bool +HTMLLabelElement::PerformAccesskey(bool aKeyCausesActivation, + bool aIsTrustedEvent) +{ + if (!aKeyCausesActivation) { + RefPtr<Element> element = GetLabeledElement(); + if (element) { + return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent); + } + } else { + nsPresContext *presContext = GetPresContext(eForUncomposedDoc); + if (!presContext) { + return false; + } + + // Click on it if the users prefs indicate to do so. + WidgetMouseEvent event(aIsTrustedEvent, eMouseClick, + nullptr, WidgetMouseEvent::eReal); + event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD; + + nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ? + openAllowed : openAbused); + + EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext, + &event); + } + + return aKeyCausesActivation; +} + +nsGenericHTMLElement* +HTMLLabelElement::GetLabeledElement() const +{ + nsAutoString elementId; + + if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_for, elementId)) { + // No @for, so we are a label for our first form control element. + // Do a depth-first traversal to look for the first form control element. + return GetFirstLabelableDescendant(); + } + + // We have a @for. The id has to be linked to an element in the same document + // and this element should be a labelable form control. + //XXXsmaug It is unclear how this should work in case the element is in + // Shadow DOM. + // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26365. + nsIDocument* doc = GetUncomposedDoc(); + if (!doc) { + return nullptr; + } + + Element* element = doc->GetElementById(elementId); + if (element && element->IsLabelable()) { + return static_cast<nsGenericHTMLElement*>(element); + } + + return nullptr; +} + +nsGenericHTMLElement* +HTMLLabelElement::GetFirstLabelableDescendant() const +{ + for (nsIContent* cur = nsINode::GetFirstChild(); cur; + cur = cur->GetNextNode(this)) { + Element* element = cur->IsElement() ? cur->AsElement() : nullptr; + if (element && element->IsLabelable()) { + return static_cast<nsGenericHTMLElement*>(element); + } + } + + return nullptr; +} + +} // namespace dom +} // namespace mozilla |