1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsCoreUtils.h"
8 #include "nsIAccessibleTypes.h"
10 #include "nsIBaseWindow.h"
11 #include "nsIDocShellTreeOwner.h"
12 #include "mozilla/dom/Document.h"
14 #include "nsXULElement.h"
15 #include "nsIDocShell.h"
16 #include "nsIObserverService.h"
17 #include "nsPresContext.h"
18 #include "nsIScrollableFrame.h"
19 #include "nsISelectionController.h"
20 #include "nsISimpleEnumerator.h"
21 #include "mozilla/dom/TouchEvent.h"
22 #include "mozilla/ErrorResult.h"
23 #include "mozilla/EventListenerManager.h"
24 #include "mozilla/EventStateManager.h"
25 #include "mozilla/MouseEvents.h"
26 #include "mozilla/PresShell.h"
27 #include "mozilla/TouchEvents.h"
29 #include "nsGkAtoms.h"
31 #include "nsComponentManagerUtils.h"
33 #include "XULTreeElement.h"
34 #include "nsIContentInlines.h"
35 #include "nsTreeColumns.h"
36 #include "mozilla/dom/DocumentInlines.h"
37 #include "mozilla/dom/Element.h"
38 #include "mozilla/dom/HTMLLabelElement.h"
39 #include "mozilla/dom/MouseEventBinding.h"
40 #include "mozilla/dom/Selection.h"
42 using namespace mozilla
;
44 using mozilla::dom::DOMRect
;
45 using mozilla::dom::Element
;
46 using mozilla::dom::Selection
;
47 using mozilla::dom::XULTreeElement
;
49 ////////////////////////////////////////////////////////////////////////////////
51 ////////////////////////////////////////////////////////////////////////////////
53 bool nsCoreUtils::IsLabelWithControl(nsIContent
* aContent
) {
54 dom::HTMLLabelElement
* label
= dom::HTMLLabelElement::FromNode(aContent
);
55 if (label
&& label
->GetControl()) return true;
60 bool nsCoreUtils::HasClickListener(nsIContent
* aContent
) {
61 NS_ENSURE_TRUE(aContent
, false);
62 EventListenerManager
* listenerManager
=
63 aContent
->GetExistingListenerManager();
65 return listenerManager
&&
66 (listenerManager
->HasListenersFor(nsGkAtoms::onclick
) ||
67 listenerManager
->HasListenersFor(nsGkAtoms::onmousedown
) ||
68 listenerManager
->HasListenersFor(nsGkAtoms::onmouseup
));
71 void nsCoreUtils::DispatchClickEvent(XULTreeElement
* aTree
, int32_t aRowIndex
,
72 nsTreeColumn
* aColumn
,
73 const nsAString
& aPseudoElt
) {
74 RefPtr
<dom::Element
> tcElm
= aTree
->GetTreeBody();
77 Document
* document
= tcElm
->GetUncomposedDoc();
78 if (!document
) return;
80 RefPtr
<PresShell
> presShell
= document
->GetPresShell();
85 // Ensure row is visible.
86 aTree
->EnsureRowIsVisible(aRowIndex
);
88 // Calculate x and y coordinates.
91 aTree
->GetCoordsForCellItem(aRowIndex
, aColumn
, aPseudoElt
, rv
);
96 RefPtr
<DOMRect
> treeBodyRect
= tcElm
->GetBoundingClientRect();
97 int32_t tcX
= (int32_t)treeBodyRect
->X();
98 int32_t tcY
= (int32_t)treeBodyRect
->Y();
100 // Dispatch mouse events.
101 AutoWeakFrame tcFrame
= tcElm
->GetPrimaryFrame();
102 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
105 nsCOMPtr
<nsIWidget
> rootWidget
=
106 rootFrame
->GetView()->GetNearestWidget(&offset
);
108 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
110 int32_t cnvdX
= presContext
->CSSPixelsToDevPixels(tcX
+ int32_t(rect
.x
) + 1) +
111 presContext
->AppUnitsToDevPixels(offset
.x
);
112 int32_t cnvdY
= presContext
->CSSPixelsToDevPixels(tcY
+ int32_t(rect
.y
) + 1) +
113 presContext
->AppUnitsToDevPixels(offset
.y
);
115 // XUL is just desktop, so there is no real reason for senfing touch events.
116 DispatchMouseEvent(eMouseDown
, cnvdX
, cnvdY
, tcElm
, tcFrame
, presShell
,
119 DispatchMouseEvent(eMouseUp
, cnvdX
, cnvdY
, tcElm
, tcFrame
, presShell
,
123 void nsCoreUtils::DispatchMouseEvent(EventMessage aMessage
, int32_t aX
,
124 int32_t aY
, nsIContent
* aContent
,
125 nsIFrame
* aFrame
, PresShell
* aPresShell
,
126 nsIWidget
* aRootWidget
) {
127 WidgetMouseEvent
event(true, aMessage
, aRootWidget
, WidgetMouseEvent::eReal
,
128 WidgetMouseEvent::eNormal
);
130 event
.mRefPoint
= LayoutDeviceIntPoint(aX
, aY
);
132 event
.mClickCount
= 1;
133 event
.mButton
= MouseButton::ePrimary
;
134 event
.mTime
= PR_IntervalNow();
135 event
.mInputSource
= dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN
;
137 nsEventStatus status
= nsEventStatus_eIgnore
;
138 aPresShell
->HandleEventWithTarget(&event
, aFrame
, aContent
, &status
);
141 void nsCoreUtils::DispatchTouchEvent(EventMessage aMessage
, int32_t aX
,
142 int32_t aY
, nsIContent
* aContent
,
143 nsIFrame
* aFrame
, PresShell
* aPresShell
,
144 nsIWidget
* aRootWidget
) {
145 nsIDocShell
* docShell
= nullptr;
146 if (aPresShell
->GetDocument()) {
147 docShell
= aPresShell
->GetDocument()->GetDocShell();
149 if (!dom::TouchEvent::PrefEnabled(docShell
)) {
153 WidgetTouchEvent
event(true, aMessage
, aRootWidget
);
155 event
.mTime
= PR_IntervalNow();
157 // XXX: Touch has an identifier of -1 to hint that it is synthesized.
158 RefPtr
<dom::Touch
> t
= new dom::Touch(-1, LayoutDeviceIntPoint(aX
, aY
),
159 LayoutDeviceIntPoint(1, 1), 0.0f
, 1.0f
);
160 t
->SetTouchTarget(aContent
);
161 event
.mTouches
.AppendElement(t
);
162 nsEventStatus status
= nsEventStatus_eIgnore
;
163 aPresShell
->HandleEventWithTarget(&event
, aFrame
, aContent
, &status
);
166 uint32_t nsCoreUtils::GetAccessKeyFor(nsIContent
* aContent
) {
167 // Accesskeys are registered by @accesskey attribute only. At first check
168 // whether it is presented on the given element to avoid the slow
169 // EventStateManager::GetRegisteredAccessKey() method.
170 if (!aContent
->IsElement() || !aContent
->AsElement()->HasAttr(
171 kNameSpaceID_None
, nsGkAtoms::accesskey
)) {
175 nsPresContext
* presContext
= aContent
->OwnerDoc()->GetPresContext();
176 if (!presContext
) return 0;
178 EventStateManager
* esm
= presContext
->EventStateManager();
181 return esm
->GetRegisteredAccessKey(aContent
->AsElement());
184 nsIContent
* nsCoreUtils::GetDOMElementFor(nsIContent
* aContent
) {
185 if (aContent
->IsElement()) return aContent
;
187 if (aContent
->IsText()) return aContent
->GetFlattenedTreeParent();
192 nsINode
* nsCoreUtils::GetDOMNodeFromDOMPoint(nsINode
* aNode
, uint32_t aOffset
) {
193 if (aNode
&& aNode
->IsElement()) {
194 uint32_t childCount
= aNode
->GetChildCount();
195 NS_ASSERTION(aOffset
<= childCount
, "Wrong offset of the DOM point!");
197 // The offset can be after last child of container node that means DOM point
198 // is placed immediately after the last child. In this case use the DOM node
199 // from the given DOM point is used as result node.
200 if (aOffset
!= childCount
) return aNode
->GetChildAt_Deprecated(aOffset
);
206 bool nsCoreUtils::IsAncestorOf(nsINode
* aPossibleAncestorNode
,
207 nsINode
* aPossibleDescendantNode
,
208 nsINode
* aRootNode
) {
209 NS_ENSURE_TRUE(aPossibleAncestorNode
&& aPossibleDescendantNode
, false);
211 nsINode
* parentNode
= aPossibleDescendantNode
;
212 while ((parentNode
= parentNode
->GetParentNode()) &&
213 parentNode
!= aRootNode
) {
214 if (parentNode
== aPossibleAncestorNode
) return true;
220 nsresult
nsCoreUtils::ScrollSubstringTo(nsIFrame
* aFrame
, nsRange
* aRange
,
221 uint32_t aScrollType
) {
222 ScrollAxis vertical
, horizontal
;
223 ConvertScrollTypeToPercents(aScrollType
, &vertical
, &horizontal
);
225 return ScrollSubstringTo(aFrame
, aRange
, vertical
, horizontal
);
228 nsresult
nsCoreUtils::ScrollSubstringTo(nsIFrame
* aFrame
, nsRange
* aRange
,
229 ScrollAxis aVertical
,
230 ScrollAxis aHorizontal
) {
231 if (!aFrame
|| !aRange
) {
232 return NS_ERROR_FAILURE
;
235 nsPresContext
* presContext
= aFrame
->PresContext();
237 nsCOMPtr
<nsISelectionController
> selCon
;
238 aFrame
->GetSelectionController(presContext
, getter_AddRefs(selCon
));
239 NS_ENSURE_TRUE(selCon
, NS_ERROR_FAILURE
);
241 RefPtr
<dom::Selection
> selection
=
242 selCon
->GetSelection(nsISelectionController::SELECTION_ACCESSIBILITY
);
244 selection
->RemoveAllRanges(IgnoreErrors());
245 selection
->AddRangeAndSelectFramesAndNotifyListeners(*aRange
, IgnoreErrors());
247 selection
->ScrollIntoView(nsISelectionController::SELECTION_ANCHOR_REGION
,
248 aVertical
, aHorizontal
,
249 Selection::SCROLL_SYNCHRONOUS
);
251 selection
->CollapseToStart(IgnoreErrors());
256 void nsCoreUtils::ScrollFrameToPoint(nsIFrame
* aScrollableFrame
,
258 const LayoutDeviceIntPoint
& aPoint
) {
259 nsIScrollableFrame
* scrollableFrame
= do_QueryFrame(aScrollableFrame
);
260 if (!scrollableFrame
) return;
262 nsPoint point
= LayoutDeviceIntPoint::ToAppUnits(
263 aPoint
, aFrame
->PresContext()->AppUnitsPerDevPixel());
264 nsRect frameRect
= aFrame
->GetScreenRectInAppUnits();
265 nsPoint deltaPoint
= point
- frameRect
.TopLeft();
267 nsPoint scrollPoint
= scrollableFrame
->GetScrollPosition();
268 scrollPoint
-= deltaPoint
;
270 scrollableFrame
->ScrollTo(scrollPoint
, ScrollMode::Instant
);
273 void nsCoreUtils::ConvertScrollTypeToPercents(uint32_t aScrollType
,
274 ScrollAxis
* aVertical
,
275 ScrollAxis
* aHorizontal
) {
276 WhereToScroll whereY
, whereX
;
277 WhenToScroll whenY
, whenX
;
278 switch (aScrollType
) {
279 case nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT
:
280 whereY
= kScrollToTop
;
281 whenY
= WhenToScroll::Always
;
282 whereX
= kScrollToLeft
;
283 whenX
= WhenToScroll::Always
;
285 case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT
:
286 whereY
= kScrollToBottom
;
287 whenY
= WhenToScroll::Always
;
288 whereX
= kScrollToRight
;
289 whenX
= WhenToScroll::Always
;
291 case nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE
:
292 whereY
= kScrollToTop
;
293 whenY
= WhenToScroll::Always
;
294 whereX
= kScrollMinimum
;
295 whenX
= WhenToScroll::IfNotFullyVisible
;
297 case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_EDGE
:
298 whereY
= kScrollToBottom
;
299 whenY
= WhenToScroll::Always
;
300 whereX
= kScrollMinimum
;
301 whenX
= WhenToScroll::IfNotFullyVisible
;
303 case nsIAccessibleScrollType::SCROLL_TYPE_LEFT_EDGE
:
304 whereY
= kScrollMinimum
;
305 whenY
= WhenToScroll::IfNotFullyVisible
;
306 whereX
= kScrollToLeft
;
307 whenX
= WhenToScroll::Always
;
309 case nsIAccessibleScrollType::SCROLL_TYPE_RIGHT_EDGE
:
310 whereY
= kScrollMinimum
;
311 whenY
= WhenToScroll::IfNotFullyVisible
;
312 whereX
= kScrollToRight
;
313 whenX
= WhenToScroll::Always
;
316 whereY
= kScrollMinimum
;
317 whenY
= WhenToScroll::IfNotFullyVisible
;
318 whereX
= kScrollMinimum
;
319 whenX
= WhenToScroll::IfNotFullyVisible
;
321 *aVertical
= ScrollAxis(whereY
, whenY
);
322 *aHorizontal
= ScrollAxis(whereX
, whenX
);
325 LayoutDeviceIntPoint
nsCoreUtils::GetScreenCoordsForWindow(nsINode
* aNode
) {
326 LayoutDeviceIntPoint
coords(0, 0);
327 nsCOMPtr
<nsIDocShellTreeItem
> treeItem(GetDocShellFor(aNode
));
328 if (!treeItem
) return coords
;
330 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
331 treeItem
->GetTreeOwner(getter_AddRefs(treeOwner
));
332 if (!treeOwner
) return coords
;
334 nsCOMPtr
<nsIBaseWindow
> baseWindow
= do_QueryInterface(treeOwner
);
336 baseWindow
->GetPosition(&coords
.x
, &coords
.y
); // in device pixels
342 already_AddRefed
<nsIDocShell
> nsCoreUtils::GetDocShellFor(nsINode
* aNode
) {
343 if (!aNode
) return nullptr;
345 nsCOMPtr
<nsIDocShell
> docShell
= aNode
->OwnerDoc()->GetDocShell();
346 return docShell
.forget();
349 bool nsCoreUtils::IsRootDocument(Document
* aDocument
) {
350 nsCOMPtr
<nsIDocShellTreeItem
> docShellTreeItem
= aDocument
->GetDocShell();
351 NS_ASSERTION(docShellTreeItem
, "No document shell for document!");
353 nsCOMPtr
<nsIDocShellTreeItem
> parentTreeItem
;
354 docShellTreeItem
->GetInProcessParent(getter_AddRefs(parentTreeItem
));
356 return !parentTreeItem
;
359 bool nsCoreUtils::IsTopLevelContentDocInProcess(Document
* aDocumentNode
) {
360 mozilla::dom::BrowsingContext
* bc
= aDocumentNode
->GetBrowsingContext();
361 return bc
->IsContent() && (
364 // Out-of-process iframe.
365 !bc
->GetParent()->IsInProcess());
368 bool nsCoreUtils::IsErrorPage(Document
* aDocument
) {
369 nsIURI
* uri
= aDocument
->GetDocumentURI();
370 if (!uri
->SchemeIs("about")) {
375 uri
->GetPathQueryRef(path
);
377 constexpr auto neterror
= "neterror"_ns
;
378 constexpr auto certerror
= "certerror"_ns
;
380 return StringBeginsWith(path
, neterror
) || StringBeginsWith(path
, certerror
);
383 PresShell
* nsCoreUtils::GetPresShellFor(nsINode
* aNode
) {
384 return aNode
->OwnerDoc()->GetPresShell();
387 bool nsCoreUtils::GetID(nsIContent
* aContent
, nsAString
& aID
) {
388 return aContent
->IsElement() &&
389 aContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::id
, aID
);
392 bool nsCoreUtils::GetUIntAttr(nsIContent
* aContent
, nsAtom
* aAttr
,
395 if (!aContent
->IsElement()) {
398 aContent
->AsElement()->GetAttr(kNameSpaceID_None
, aAttr
, value
);
399 if (!value
.IsEmpty()) {
400 nsresult error
= NS_OK
;
401 int32_t integer
= value
.ToInteger(&error
);
402 if (NS_SUCCEEDED(error
) && integer
> 0) {
411 void nsCoreUtils::GetLanguageFor(nsIContent
* aContent
, nsIContent
* aRootContent
,
412 nsAString
& aLanguage
) {
413 aLanguage
.Truncate();
415 nsIContent
* walkUp
= aContent
;
416 while (walkUp
&& walkUp
!= aRootContent
&&
417 (!walkUp
->IsElement() ||
418 !walkUp
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::lang
,
420 walkUp
= walkUp
->GetParent();
424 XULTreeElement
* nsCoreUtils::GetTree(nsIContent
* aContent
) {
425 // Find DOMNode's parents recursively until reach the <tree> tag
426 nsIContent
* currentContent
= aContent
;
427 while (currentContent
) {
428 if (currentContent
->NodeInfo()->Equals(nsGkAtoms::tree
, kNameSpaceID_XUL
)) {
429 return XULTreeElement::FromNode(currentContent
);
431 currentContent
= currentContent
->GetFlattenedTreeParent();
437 already_AddRefed
<nsTreeColumn
> nsCoreUtils::GetFirstSensibleColumn(
438 XULTreeElement
* aTree
, FlushType aFlushType
) {
443 RefPtr
<nsTreeColumns
> cols
= aTree
->GetColumns(aFlushType
);
448 RefPtr
<nsTreeColumn
> column
= cols
->GetFirstColumn();
449 if (column
&& IsColumnHidden(column
)) return GetNextSensibleColumn(column
);
451 return column
.forget();
454 uint32_t nsCoreUtils::GetSensibleColumnCount(XULTreeElement
* aTree
) {
460 RefPtr
<nsTreeColumns
> cols
= aTree
->GetColumns();
465 nsTreeColumn
* column
= cols
->GetFirstColumn();
468 if (!IsColumnHidden(column
)) count
++;
470 column
= column
->GetNext();
476 already_AddRefed
<nsTreeColumn
> nsCoreUtils::GetSensibleColumnAt(
477 XULTreeElement
* aTree
, uint32_t aIndex
) {
482 uint32_t idx
= aIndex
;
484 nsCOMPtr
<nsTreeColumn
> column
= GetFirstSensibleColumn(aTree
);
486 if (idx
== 0) return column
.forget();
489 column
= GetNextSensibleColumn(column
);
495 already_AddRefed
<nsTreeColumn
> nsCoreUtils::GetNextSensibleColumn(
496 nsTreeColumn
* aColumn
) {
501 RefPtr
<nsTreeColumn
> nextColumn
= aColumn
->GetNext();
503 while (nextColumn
&& IsColumnHidden(nextColumn
)) {
504 nextColumn
= nextColumn
->GetNext();
507 return nextColumn
.forget();
510 already_AddRefed
<nsTreeColumn
> nsCoreUtils::GetPreviousSensibleColumn(
511 nsTreeColumn
* aColumn
) {
516 RefPtr
<nsTreeColumn
> prevColumn
= aColumn
->GetPrevious();
518 while (prevColumn
&& IsColumnHidden(prevColumn
)) {
519 prevColumn
= prevColumn
->GetPrevious();
522 return prevColumn
.forget();
525 bool nsCoreUtils::IsColumnHidden(nsTreeColumn
* aColumn
) {
530 Element
* element
= aColumn
->Element();
531 return element
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::hidden
,
532 nsGkAtoms::_true
, eCaseMatters
);
535 void nsCoreUtils::ScrollTo(PresShell
* aPresShell
, nsIContent
* aContent
,
536 uint32_t aScrollType
) {
537 ScrollAxis vertical
, horizontal
;
538 ConvertScrollTypeToPercents(aScrollType
, &vertical
, &horizontal
);
539 aPresShell
->ScrollContentIntoView(aContent
, vertical
, horizontal
,
540 ScrollFlags::ScrollOverflowHidden
);
543 bool nsCoreUtils::IsHTMLTableHeader(nsIContent
* aContent
) {
544 return aContent
->NodeInfo()->Equals(nsGkAtoms::th
) ||
545 (aContent
->IsElement() &&
546 aContent
->AsElement()->HasAttr(kNameSpaceID_None
, nsGkAtoms::scope
));
549 bool nsCoreUtils::IsWhitespaceString(const nsAString
& aString
) {
550 nsAString::const_char_iterator iterBegin
, iterEnd
;
552 aString
.BeginReading(iterBegin
);
553 aString
.EndReading(iterEnd
);
555 while (iterBegin
!= iterEnd
&& IsWhitespace(*iterBegin
)) ++iterBegin
;
557 return iterBegin
== iterEnd
;
560 bool nsCoreUtils::AccEventObserversExist() {
561 nsCOMPtr
<nsIObserverService
> obsService
= services::GetObserverService();
562 NS_ENSURE_TRUE(obsService
, false);
564 nsCOMPtr
<nsISimpleEnumerator
> observers
;
565 obsService
->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC
,
566 getter_AddRefs(observers
));
567 NS_ENSURE_TRUE(observers
, false);
569 bool hasObservers
= false;
570 observers
->HasMoreElements(&hasObservers
);
575 void nsCoreUtils::DispatchAccEvent(RefPtr
<nsIAccessibleEvent
> event
) {
576 nsCOMPtr
<nsIObserverService
> obsService
= services::GetObserverService();
577 NS_ENSURE_TRUE_VOID(obsService
);
579 obsService
->NotifyObservers(event
, NS_ACCESSIBLE_EVENT_TOPIC
, nullptr);
582 bool nsCoreUtils::IsDisplayContents(nsIContent
* aContent
) {
583 auto* element
= Element::FromNodeOrNull(aContent
);
584 return element
&& element
->IsDisplayContents();
587 bool nsCoreUtils::CanCreateAccessibleWithoutFrame(nsIContent
* aContent
) {
588 auto* element
= Element::FromNodeOrNull(aContent
);
592 if (!element
->HasServoData() || Servo_Element_IsDisplayNone(element
)) {
593 // Out of the flat tree or in a display: none subtree.
596 if (element
->IsDisplayContents()) {
599 // We don't have a frame, but we're not display: contents either.
600 // For now, only create accessibles for <option>/<optgroup> as our combobox
601 // select code depends on it.
602 return element
->IsAnyOfHTMLElements(nsGkAtoms::option
, nsGkAtoms::optgroup
);
605 bool nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(
606 const Document
* aDocument
) {
607 const Document
* parent
= aDocument
;
609 if (!parent
->IsVisible()) {
612 } while ((parent
= parent
->GetInProcessParentDocument()));