HTMLLabelElement.cpp (9041B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * Implementation of HTML <label> elements. 9 */ 10 #include "HTMLLabelElement.h" 11 12 #include "mozilla/EventDispatcher.h" 13 #include "mozilla/MouseEvents.h" 14 #include "mozilla/dom/HTMLLabelElementBinding.h" 15 #include "mozilla/dom/MouseEventBinding.h" 16 #include "mozilla/dom/ShadowRoot.h" 17 #include "nsContentUtils.h" 18 #include "nsFocusManager.h" 19 #include "nsIFrame.h" 20 #include "nsQueryObject.h" 21 22 // construction, destruction 23 24 NS_IMPL_NS_NEW_HTML_ELEMENT(Label) 25 26 namespace mozilla::dom { 27 28 HTMLLabelElement::~HTMLLabelElement() = default; 29 30 JSObject* HTMLLabelElement::WrapNode(JSContext* aCx, 31 JS::Handle<JSObject*> aGivenProto) { 32 return HTMLLabelElement_Binding::Wrap(aCx, this, aGivenProto); 33 } 34 35 // nsIDOMHTMLLabelElement 36 37 NS_IMPL_ELEMENT_CLONE(HTMLLabelElement) 38 39 HTMLFormElement* HTMLLabelElement::GetForm() const { 40 // Not all labeled things have a form association. Stick to the ones that do. 41 const auto* formControl = nsIFormControl::FromNodeOrNull(GetControl()); 42 if (!formControl) { 43 return nullptr; 44 } 45 46 return formControl->GetForm(); 47 } 48 49 void HTMLLabelElement::Focus(const FocusOptions& aOptions, 50 const CallerType aCallerType, 51 ErrorResult& aError) { 52 { 53 nsIFrame* frame = GetPrimaryFrame(FlushType::Frames); 54 if (frame && frame->IsFocusable()) { 55 return nsGenericHTMLElement::Focus(aOptions, aCallerType, aError); 56 } 57 } 58 59 if (RefPtr<Element> elem = GetLabeledElement()) { 60 return elem->Focus(aOptions, aCallerType, aError); 61 } 62 } 63 64 nsresult HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { 65 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent(); 66 if (mHandlingEvent || 67 (!(mouseEvent && mouseEvent->IsLeftClickEvent()) && 68 aVisitor.mEvent->mMessage != eMouseDown) || 69 aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault || 70 !aVisitor.mPresContext || 71 // Don't handle the event if it's already been handled by another label 72 aVisitor.mEvent->mFlags.mMultipleActionsPrevented) { 73 return NS_OK; 74 } 75 76 nsCOMPtr<Element> target = 77 do_QueryInterface(aVisitor.mEvent->GetOriginalDOMEventTarget()); 78 if (nsContentUtils::IsInInteractiveHTMLContent(target, this)) { 79 return NS_OK; 80 } 81 82 // Strong ref because event dispatch is going to happen. 83 RefPtr<Element> content = GetLabeledElement(); 84 85 if (!content || content->IsDisabled()) { 86 return NS_OK; 87 } 88 89 mHandlingEvent = true; 90 switch (aVisitor.mEvent->mMessage) { 91 case eMouseDown: 92 if (mouseEvent->mButton == MouseButton::ePrimary) { 93 // We reset the mouse-down point on every event because there is 94 // no guarantee we will reach the ePointerClick code below. 95 LayoutDeviceIntPoint* curPoint = 96 new LayoutDeviceIntPoint(mouseEvent->mRefPoint); 97 SetProperty(nsGkAtoms::labelMouseDownPtProperty, 98 static_cast<void*>(curPoint), 99 nsINode::DeleteProperty<LayoutDeviceIntPoint>); 100 } 101 break; 102 103 case ePointerClick: 104 if (mouseEvent->IsLeftClickEvent()) { 105 LayoutDeviceIntPoint* mouseDownPoint = 106 static_cast<LayoutDeviceIntPoint*>( 107 GetProperty(nsGkAtoms::labelMouseDownPtProperty)); 108 109 bool dragSelect = false; 110 if (mouseDownPoint) { 111 LayoutDeviceIntPoint dragDistance = *mouseDownPoint; 112 RemoveProperty(nsGkAtoms::labelMouseDownPtProperty); 113 114 dragDistance -= mouseEvent->mRefPoint; 115 const int CLICK_DISTANCE = 2; 116 dragSelect = dragDistance.x > CLICK_DISTANCE || 117 dragDistance.x < -CLICK_DISTANCE || 118 dragDistance.y > CLICK_DISTANCE || 119 dragDistance.y < -CLICK_DISTANCE; 120 } 121 // Don't click the for-content if we did drag-select text or if we 122 // have a kbd modifier (which adjusts a selection). 123 if (dragSelect || mouseEvent->IsShift() || mouseEvent->IsControl() || 124 mouseEvent->IsAlt() || mouseEvent->IsMeta()) { 125 break; 126 } 127 // Only set focus on the first click of multiple clicks to prevent 128 // to prevent immediate de-focus. 129 if (mouseEvent->mClickCount <= 1) { 130 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 131 // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to. 132 // Also, within HTMLInputElement::PostHandleEvent, inputs will 133 // be selected only when focused via a key or when the navigation 134 // flag is used and we want to select the text on label clicks as 135 // well. 136 // If the label has been clicked by the user, we also want to 137 // pass FLAG_BYMOUSE so that we get correct focus ring behavior, 138 // but we don't want to pass FLAG_BYMOUSE if this click event was 139 // caused by the user pressing an accesskey. 140 bool byMouse = (mouseEvent->mInputSource != 141 MouseEvent_Binding::MOZ_SOURCE_KEYBOARD); 142 bool byTouch = (mouseEvent->mInputSource == 143 MouseEvent_Binding::MOZ_SOURCE_TOUCH); 144 fm->SetFocus(content, 145 nsIFocusManager::FLAG_BYMOVEFOCUS | 146 (byMouse ? nsIFocusManager::FLAG_BYMOUSE : 0) | 147 (byTouch ? nsIFocusManager::FLAG_BYTOUCH : 0)); 148 } 149 } 150 // Dispatch a new click event to |content| 151 // (For compatibility with IE, we do only left click. If 152 // we wanted to interpret the HTML spec very narrowly, we 153 // would do nothing. If we wanted to do something 154 // sensible, we might send more events through like 155 // this.) See bug 7554, bug 49897, and bug 96813. 156 nsEventStatus status = aVisitor.mEventStatus; 157 // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent 158 // will actually create a new event. 159 EventFlags eventFlags; 160 eventFlags.mMultipleActionsPrevented = true; 161 DispatchClickEvent(aVisitor.mPresContext, mouseEvent, content, false, 162 &eventFlags, &status); 163 // Do we care about the status this returned? I don't think we do... 164 // Don't run another <label> off of this click 165 mouseEvent->mFlags.mMultipleActionsPrevented = true; 166 } 167 break; 168 169 default: 170 break; 171 } 172 mHandlingEvent = false; 173 return NS_OK; 174 } 175 176 Result<bool, nsresult> HTMLLabelElement::PerformAccesskey( 177 bool aKeyCausesActivation, bool aIsTrustedEvent) { 178 if (!aKeyCausesActivation) { 179 RefPtr<Element> element = GetLabeledElement(); 180 if (element) { 181 return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent); 182 } 183 return Err(NS_ERROR_ABORT); 184 } 185 186 RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc); 187 if (!presContext) { 188 return Err(NS_ERROR_UNEXPECTED); 189 } 190 191 // Click on it if the users prefs indicate to do so. 192 AutoHandlingUserInputStatePusher userInputStatePusher(aIsTrustedEvent); 193 AutoPopupStatePusher popupStatePusher( 194 aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused); 195 DispatchSimulatedClick(this, aIsTrustedEvent, presContext); 196 197 // XXXedgar, do we need to check whether the focus is really changed? 198 return true; 199 } 200 201 nsGenericHTMLElement* HTMLLabelElement::GetLabeledElement() const { 202 nsAutoString elementId; 203 204 if (!GetAttr(nsGkAtoms::_for, elementId)) { 205 // No @for, so we are a label for our first form control element. 206 // Do a depth-first traversal to look for the first form control element. 207 return GetFirstLabelableDescendant(); 208 } 209 210 // We have a @for. The id has to be linked to an element in the same tree 211 // and this element should be a labelable form control. 212 Element* element = nullptr; 213 214 if (ShadowRoot* shadowRoot = GetContainingShadow()) { 215 element = shadowRoot->GetElementById(elementId); 216 } else if (Document* doc = GetUncomposedDoc()) { 217 element = doc->GetElementById(elementId); 218 } else { 219 element = 220 nsContentUtils::MatchElementId(SubtreeRoot()->AsContent(), elementId); 221 } 222 223 if (element && element->IsLabelable()) { 224 return static_cast<nsGenericHTMLElement*>(element); 225 } 226 227 return nullptr; 228 } 229 230 nsGenericHTMLElement* HTMLLabelElement::GetFirstLabelableDescendant() const { 231 for (nsIContent* cur = nsINode::GetFirstChild(); cur; 232 cur = cur->GetNextNode(this)) { 233 Element* element = Element::FromNode(cur); 234 if (element && element->IsLabelable()) { 235 return static_cast<nsGenericHTMLElement*>(element); 236 } 237 } 238 239 return nullptr; 240 } 241 242 } // namespace mozilla::dom