Bumping manifests a=b2g-bump
[gecko.git] / widget / xpwidgets / nsNativeTheme.cpp
blobd647d85c28f00c26de23803120b4a64475079abb
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 "nsNativeTheme.h"
7 #include "nsIWidget.h"
8 #include "nsIDocument.h"
9 #include "nsIContent.h"
10 #include "nsIFrame.h"
11 #include "nsIPresShell.h"
12 #include "nsNumberControlFrame.h"
13 #include "nsPresContext.h"
14 #include "nsString.h"
15 #include "nsNameSpaceManager.h"
16 #include "nsIDOMHTMLInputElement.h"
17 #include "nsIDOMXULMenuListElement.h"
18 #include "nsThemeConstants.h"
19 #include "nsIComponentManager.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsProgressFrame.h"
22 #include "nsMeterFrame.h"
23 #include "nsMenuFrame.h"
24 #include "nsRangeFrame.h"
25 #include "nsCSSRendering.h"
26 #include "mozilla/EventStates.h"
27 #include "mozilla/dom/Element.h"
28 #include "mozilla/dom/HTMLBodyElement.h"
29 #include "mozilla/dom/HTMLProgressElement.h"
30 #include "nsIDocumentInlines.h"
31 #include <algorithm>
33 using namespace mozilla;
34 using namespace mozilla::dom;
36 nsNativeTheme::nsNativeTheme()
37 : mAnimatedContentTimeout(UINT32_MAX)
41 NS_IMPL_ISUPPORTS(nsNativeTheme, nsITimerCallback)
43 nsIPresShell *
44 nsNativeTheme::GetPresShell(nsIFrame* aFrame)
46 if (!aFrame)
47 return nullptr;
49 // this is a workaround for the egcs 1.1.2 not inlining
50 // aFrame->PresContext(), which causes an undefined symbol
51 nsPresContext *context = aFrame->StyleContext()->RuleNode()->PresContext();
52 return context ? context->GetPresShell() : nullptr;
55 EventStates
56 nsNativeTheme::GetContentState(nsIFrame* aFrame, uint8_t aWidgetType)
58 if (!aFrame)
59 return EventStates();
61 bool isXULCheckboxRadio =
62 (aWidgetType == NS_THEME_CHECKBOX ||
63 aWidgetType == NS_THEME_RADIO) &&
64 aFrame->GetContent()->IsXUL();
65 if (isXULCheckboxRadio)
66 aFrame = aFrame->GetParent();
68 if (!aFrame->GetContent())
69 return EventStates();
71 nsIPresShell *shell = GetPresShell(aFrame);
72 if (!shell)
73 return EventStates();
75 nsIContent* frameContent = aFrame->GetContent();
76 EventStates flags;
77 if (frameContent->IsElement()) {
78 flags = frameContent->AsElement()->State();
80 // <input type=number> needs special handling since its nested native
81 // anonymous <input type=text> takes focus for it.
82 if (aWidgetType == NS_THEME_NUMBER_INPUT &&
83 frameContent->IsHTML(nsGkAtoms::input)) {
84 nsNumberControlFrame *numberControlFrame = do_QueryFrame(aFrame);
85 if (numberControlFrame && numberControlFrame->IsFocused()) {
86 flags |= NS_EVENT_STATE_FOCUS;
90 nsNumberControlFrame* numberControlFrame =
91 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
92 if (numberControlFrame &&
93 numberControlFrame->GetContent()->AsElement()->State().
94 HasState(NS_EVENT_STATE_DISABLED)) {
95 flags |= NS_EVENT_STATE_DISABLED;
99 if (isXULCheckboxRadio && aWidgetType == NS_THEME_RADIO) {
100 if (IsFocused(aFrame))
101 flags |= NS_EVENT_STATE_FOCUS;
104 // On Windows and Mac, only draw focus rings if they should be shown. This
105 // means that focus rings are only shown once the keyboard has been used to
106 // focus something in the window.
107 #if defined(XP_MACOSX)
108 // Mac always draws focus rings for textboxes and lists.
109 if (aWidgetType == NS_THEME_NUMBER_INPUT ||
110 aWidgetType == NS_THEME_TEXTFIELD ||
111 aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
112 aWidgetType == NS_THEME_SEARCHFIELD ||
113 aWidgetType == NS_THEME_LISTBOX) {
114 return flags;
116 #endif
117 #if defined(XP_WIN)
118 // On Windows, focused buttons are always drawn as such by the native theme.
119 if (aWidgetType == NS_THEME_BUTTON)
120 return flags;
121 #endif
122 #if defined(XP_MACOSX) || defined(XP_WIN)
123 nsIDocument* doc = aFrame->GetContent()->OwnerDoc();
124 nsPIDOMWindow* window = doc->GetWindow();
125 if (window && !window->ShouldShowFocusRing())
126 flags &= ~NS_EVENT_STATE_FOCUS;
127 #endif
129 return flags;
132 /* static */
133 bool
134 nsNativeTheme::CheckBooleanAttr(nsIFrame* aFrame, nsIAtom* aAtom)
136 if (!aFrame)
137 return false;
139 nsIContent* content = aFrame->GetContent();
140 if (!content)
141 return false;
143 if (content->IsHTML())
144 return content->HasAttr(kNameSpaceID_None, aAtom);
146 // For XML/XUL elements, an attribute must be equal to the literal
147 // string "true" to be counted as true. An empty string should _not_
148 // be counted as true.
149 return content->AttrValueIs(kNameSpaceID_None, aAtom,
150 NS_LITERAL_STRING("true"), eCaseMatters);
153 /* static */
154 int32_t
155 nsNativeTheme::CheckIntAttr(nsIFrame* aFrame, nsIAtom* aAtom, int32_t defaultValue)
157 if (!aFrame)
158 return defaultValue;
160 nsAutoString attr;
161 aFrame->GetContent()->GetAttr(kNameSpaceID_None, aAtom, attr);
162 nsresult err;
163 int32_t value = attr.ToInteger(&err);
164 if (attr.IsEmpty() || NS_FAILED(err))
165 return defaultValue;
167 return value;
170 /* static */
171 double
172 nsNativeTheme::GetProgressValue(nsIFrame* aFrame)
174 // When we are using the HTML progress element,
175 // we can get the value from the IDL property.
176 if (aFrame && aFrame->GetContent()->IsHTML(nsGkAtoms::progress)) {
177 return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Value();
180 return (double)nsNativeTheme::CheckIntAttr(aFrame, nsGkAtoms::value, 0);
183 /* static */
184 double
185 nsNativeTheme::GetProgressMaxValue(nsIFrame* aFrame)
187 // When we are using the HTML progress element,
188 // we can get the max from the IDL property.
189 if (aFrame && aFrame->GetContent()->IsHTML(nsGkAtoms::progress)) {
190 return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Max();
193 return (double)std::max(nsNativeTheme::CheckIntAttr(aFrame, nsGkAtoms::max, 100), 1);
196 bool
197 nsNativeTheme::GetCheckedOrSelected(nsIFrame* aFrame, bool aCheckSelected)
199 if (!aFrame)
200 return false;
202 nsIContent* content = aFrame->GetContent();
204 if (content->IsXUL()) {
205 // For a XUL checkbox or radio button, the state of the parent determines
206 // the checked state
207 aFrame = aFrame->GetParent();
208 } else {
209 // Check for an HTML input element
210 nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(content);
211 if (inputElt) {
212 bool checked;
213 inputElt->GetChecked(&checked);
214 return checked;
218 return CheckBooleanAttr(aFrame, aCheckSelected ? nsGkAtoms::selected
219 : nsGkAtoms::checked);
222 bool
223 nsNativeTheme::IsButtonTypeMenu(nsIFrame* aFrame)
225 if (!aFrame)
226 return false;
228 nsIContent* content = aFrame->GetContent();
229 return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
230 NS_LITERAL_STRING("menu"), eCaseMatters);
233 bool
234 nsNativeTheme::IsPressedButton(nsIFrame* aFrame)
236 EventStates eventState = GetContentState(aFrame, NS_THEME_TOOLBAR_BUTTON);
237 if (IsDisabled(aFrame, eventState))
238 return false;
240 return IsOpenButton(aFrame) ||
241 eventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
245 bool
246 nsNativeTheme::GetIndeterminate(nsIFrame* aFrame)
248 if (!aFrame)
249 return false;
251 nsIContent* content = aFrame->GetContent();
253 if (content->IsXUL()) {
254 // For a XUL checkbox or radio button, the state of the parent determines
255 // the state
256 return CheckBooleanAttr(aFrame->GetParent(), nsGkAtoms::indeterminate);
259 // Check for an HTML input element
260 nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(content);
261 if (inputElt) {
262 bool indeterminate;
263 inputElt->GetIndeterminate(&indeterminate);
264 return indeterminate;
267 return false;
270 bool
271 nsNativeTheme::IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame,
272 uint8_t aWidgetType)
274 // Check for specific widgets to see if HTML has overridden the style.
275 if (!aFrame)
276 return false;
278 // Resizers have some special handling, dependent on whether in a scrollable
279 // container or not. If so, use the scrollable container's to determine
280 // whether the style is overriden instead of the resizer. This allows a
281 // non-native transparent resizer to be used instead. Otherwise, we just
282 // fall through and return false.
283 if (aWidgetType == NS_THEME_RESIZER) {
284 nsIFrame* parentFrame = aFrame->GetParent();
285 if (parentFrame && parentFrame->GetType() == nsGkAtoms::scrollFrame) {
286 // if the parent is a scrollframe, the resizer should be native themed
287 // only if the scrollable area doesn't override the widget style.
288 parentFrame = parentFrame->GetParent();
289 if (parentFrame) {
290 return IsWidgetStyled(aPresContext, parentFrame,
291 parentFrame->StyleDisplay()->mAppearance);
297 * Progress bar appearance should be the same for the bar and the container
298 * frame. nsProgressFrame owns the logic and will tell us what we should do.
300 if (aWidgetType == NS_THEME_PROGRESSBAR_CHUNK ||
301 aWidgetType == NS_THEME_PROGRESSBAR) {
302 nsProgressFrame* progressFrame = do_QueryFrame(aWidgetType == NS_THEME_PROGRESSBAR_CHUNK
303 ? aFrame->GetParent() : aFrame);
304 if (progressFrame) {
305 return !progressFrame->ShouldUseNativeStyle();
310 * Meter bar appearance should be the same for the bar and the container
311 * frame. nsMeterFrame owns the logic and will tell us what we should do.
313 if (aWidgetType == NS_THEME_METERBAR_CHUNK ||
314 aWidgetType == NS_THEME_METERBAR) {
315 nsMeterFrame* meterFrame = do_QueryFrame(aWidgetType == NS_THEME_METERBAR_CHUNK
316 ? aFrame->GetParent() : aFrame);
317 if (meterFrame) {
318 return !meterFrame->ShouldUseNativeStyle();
323 * An nsRangeFrame and its children are treated atomically when it
324 * comes to native theming (either all parts, or no parts, are themed).
325 * nsRangeFrame owns the logic and will tell us what we should do.
327 if (aWidgetType == NS_THEME_RANGE ||
328 aWidgetType == NS_THEME_RANGE_THUMB) {
329 nsRangeFrame* rangeFrame =
330 do_QueryFrame(aWidgetType == NS_THEME_RANGE_THUMB
331 ? aFrame->GetParent() : aFrame);
332 if (rangeFrame) {
333 return !rangeFrame->ShouldUseNativeStyle();
337 if (aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
338 aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
339 nsNumberControlFrame* numberControlFrame =
340 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
341 if (numberControlFrame) {
342 return !numberControlFrame->ShouldUseNativeStyleForSpinner();
346 return (aWidgetType == NS_THEME_NUMBER_INPUT ||
347 aWidgetType == NS_THEME_BUTTON ||
348 aWidgetType == NS_THEME_TEXTFIELD ||
349 aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
350 aWidgetType == NS_THEME_LISTBOX ||
351 aWidgetType == NS_THEME_DROPDOWN) &&
352 aFrame->GetContent()->IsHTML() &&
353 aPresContext->HasAuthorSpecifiedRules(aFrame,
354 NS_AUTHOR_SPECIFIED_BORDER |
355 NS_AUTHOR_SPECIFIED_BACKGROUND);
358 bool
359 nsNativeTheme::IsDisabled(nsIFrame* aFrame, EventStates aEventStates)
361 if (!aFrame) {
362 return false;
365 nsIContent* content = aFrame->GetContent();
366 if (!content) {
367 return false;
370 if (content->IsHTML()) {
371 return aEventStates.HasState(NS_EVENT_STATE_DISABLED);
374 // For XML/XUL elements, an attribute must be equal to the literal
375 // string "true" to be counted as true. An empty string should _not_
376 // be counted as true.
377 return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
378 NS_LITERAL_STRING("true"), eCaseMatters);
381 bool
382 nsNativeTheme::IsFrameRTL(nsIFrame* aFrame)
384 return aFrame && aFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
387 bool
388 nsNativeTheme::IsHTMLContent(nsIFrame *aFrame)
390 if (!aFrame) {
391 return false;
393 nsIContent* content = aFrame->GetContent();
394 return content && content->IsHTML();
398 // scrollbar button:
399 int32_t
400 nsNativeTheme::GetScrollbarButtonType(nsIFrame* aFrame)
402 if (!aFrame)
403 return 0;
405 static nsIContent::AttrValuesArray strings[] =
406 {&nsGkAtoms::scrollbarDownBottom, &nsGkAtoms::scrollbarDownTop,
407 &nsGkAtoms::scrollbarUpBottom, &nsGkAtoms::scrollbarUpTop,
408 nullptr};
410 switch (aFrame->GetContent()->FindAttrValueIn(kNameSpaceID_None,
411 nsGkAtoms::sbattr,
412 strings, eCaseMatters)) {
413 case 0: return eScrollbarButton_Down | eScrollbarButton_Bottom;
414 case 1: return eScrollbarButton_Down;
415 case 2: return eScrollbarButton_Bottom;
416 case 3: return eScrollbarButton_UpTop;
419 return 0;
422 // treeheadercell:
423 nsNativeTheme::TreeSortDirection
424 nsNativeTheme::GetTreeSortDirection(nsIFrame* aFrame)
426 if (!aFrame || !aFrame->GetContent())
427 return eTreeSortDirection_Natural;
429 static nsIContent::AttrValuesArray strings[] =
430 {&nsGkAtoms::descending, &nsGkAtoms::ascending, nullptr};
431 switch (aFrame->GetContent()->FindAttrValueIn(kNameSpaceID_None,
432 nsGkAtoms::sortDirection,
433 strings, eCaseMatters)) {
434 case 0: return eTreeSortDirection_Descending;
435 case 1: return eTreeSortDirection_Ascending;
438 return eTreeSortDirection_Natural;
441 bool
442 nsNativeTheme::IsLastTreeHeaderCell(nsIFrame* aFrame)
444 if (!aFrame)
445 return false;
447 // A tree column picker is always the last header cell.
448 if (aFrame->GetContent()->Tag() == nsGkAtoms::treecolpicker)
449 return true;
451 // Find the parent tree.
452 nsIContent* parent = aFrame->GetContent()->GetParent();
453 while (parent && parent->Tag() != nsGkAtoms::tree) {
454 parent = parent->GetParent();
457 // If the column picker is visible, this can't be the last column.
458 if (parent && !parent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidecolumnpicker,
459 NS_LITERAL_STRING("true"), eCaseMatters))
460 return false;
462 while ((aFrame = aFrame->GetNextSibling())) {
463 if (aFrame->GetRect().width > 0)
464 return false;
466 return true;
469 // tab:
470 bool
471 nsNativeTheme::IsBottomTab(nsIFrame* aFrame)
473 if (!aFrame)
474 return false;
476 nsAutoString classStr;
477 aFrame->GetContent()->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, classStr);
478 return !classStr.IsEmpty() && classStr.Find("tab-bottom") != kNotFound;
481 bool
482 nsNativeTheme::IsFirstTab(nsIFrame* aFrame)
484 if (!aFrame)
485 return false;
487 nsIFrame* first = aFrame->GetParent()->GetFirstPrincipalChild();
488 while (first) {
489 if (first->GetRect().width > 0 && first->GetContent()->Tag() == nsGkAtoms::tab)
490 return (first == aFrame);
491 first = first->GetNextSibling();
493 return false;
496 bool
497 nsNativeTheme::IsHorizontal(nsIFrame* aFrame)
499 if (!aFrame)
500 return false;
502 return !aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
503 nsGkAtoms::vertical,
504 eCaseMatters);
507 bool
508 nsNativeTheme::IsNextToSelectedTab(nsIFrame* aFrame, int32_t aOffset)
510 if (!aFrame)
511 return false;
513 if (aOffset == 0)
514 return IsSelectedTab(aFrame);
516 int32_t thisTabIndex = -1, selectedTabIndex = -1;
518 nsIFrame* currentTab = aFrame->GetParent()->GetFirstPrincipalChild();
519 for (int32_t i = 0; currentTab; currentTab = currentTab->GetNextSibling()) {
520 if (currentTab->GetRect().width == 0)
521 continue;
522 if (aFrame == currentTab)
523 thisTabIndex = i;
524 if (IsSelectedTab(currentTab))
525 selectedTabIndex = i;
526 ++i;
529 if (thisTabIndex == -1 || selectedTabIndex == -1)
530 return false;
532 return (thisTabIndex - selectedTabIndex == aOffset);
535 // progressbar:
536 bool
537 nsNativeTheme::IsIndeterminateProgress(nsIFrame* aFrame,
538 EventStates aEventStates)
540 if (!aFrame || !aFrame->GetContent())
541 return false;
543 if (aFrame->GetContent()->IsHTML(nsGkAtoms::progress)) {
544 return aEventStates.HasState(NS_EVENT_STATE_INDETERMINATE);
547 return aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mode,
548 NS_LITERAL_STRING("undetermined"),
549 eCaseMatters);
552 bool
553 nsNativeTheme::IsVerticalProgress(nsIFrame* aFrame)
555 return aFrame &&
556 aFrame->StyleDisplay()->mOrient == NS_STYLE_ORIENT_VERTICAL;
559 bool
560 nsNativeTheme::IsVerticalMeter(nsIFrame* aFrame)
562 NS_PRECONDITION(aFrame, "You have to pass a non-null aFrame");
563 return aFrame->StyleDisplay()->mOrient == NS_STYLE_ORIENT_VERTICAL;
566 // menupopup:
567 bool
568 nsNativeTheme::IsSubmenu(nsIFrame* aFrame, bool* aLeftOfParent)
570 if (!aFrame)
571 return false;
573 nsIContent* parentContent = aFrame->GetContent()->GetParent();
574 if (!parentContent || parentContent->Tag() != nsGkAtoms::menu)
575 return false;
577 nsIFrame* parent = aFrame;
578 while ((parent = parent->GetParent())) {
579 if (parent->GetContent() == parentContent) {
580 if (aLeftOfParent) {
581 nsIntRect selfBounds, parentBounds;
582 aFrame->GetNearestWidget()->GetScreenBounds(selfBounds);
583 parent->GetNearestWidget()->GetScreenBounds(parentBounds);
584 *aLeftOfParent = selfBounds.x < parentBounds.x;
586 return true;
590 return false;
593 bool
594 nsNativeTheme::IsRegularMenuItem(nsIFrame *aFrame)
596 nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
597 return !(menuFrame && (menuFrame->IsOnMenuBar() ||
598 menuFrame->GetParentMenuListType() != eNotMenuList));
601 bool
602 nsNativeTheme::IsMenuListEditable(nsIFrame *aFrame)
604 bool isEditable = false;
605 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aFrame->GetContent());
606 if (menulist)
607 menulist->GetEditable(&isEditable);
608 return isEditable;
611 bool
612 nsNativeTheme::QueueAnimatedContentForRefresh(nsIContent* aContent,
613 uint32_t aMinimumFrameRate)
615 NS_ASSERTION(aContent, "Null pointer!");
616 NS_ASSERTION(aMinimumFrameRate, "aMinimumFrameRate must be non-zero!");
617 NS_ASSERTION(aMinimumFrameRate <= 1000,
618 "aMinimumFrameRate must be less than 1000!");
620 uint32_t timeout = 1000 / aMinimumFrameRate;
621 timeout = std::min(mAnimatedContentTimeout, timeout);
623 if (!mAnimatedContentTimer) {
624 mAnimatedContentTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
625 NS_ENSURE_TRUE(mAnimatedContentTimer, false);
628 if (mAnimatedContentList.IsEmpty() || timeout != mAnimatedContentTimeout) {
629 nsresult rv;
630 if (!mAnimatedContentList.IsEmpty()) {
631 rv = mAnimatedContentTimer->Cancel();
632 NS_ENSURE_SUCCESS(rv, false);
635 rv = mAnimatedContentTimer->InitWithCallback(this, timeout,
636 nsITimer::TYPE_ONE_SHOT);
637 NS_ENSURE_SUCCESS(rv, false);
639 mAnimatedContentTimeout = timeout;
642 if (!mAnimatedContentList.AppendElement(aContent)) {
643 NS_WARNING("Out of memory!");
644 return false;
647 return true;
650 NS_IMETHODIMP
651 nsNativeTheme::Notify(nsITimer* aTimer)
653 NS_ASSERTION(aTimer == mAnimatedContentTimer, "Wrong timer!");
655 // XXX Assumes that calling nsIFrame::Invalidate won't reenter
656 // QueueAnimatedContentForRefresh.
658 uint32_t count = mAnimatedContentList.Length();
659 for (uint32_t index = 0; index < count; index++) {
660 nsIFrame* frame = mAnimatedContentList[index]->GetPrimaryFrame();
661 if (frame) {
662 frame->InvalidateFrame();
666 mAnimatedContentList.Clear();
667 mAnimatedContentTimeout = UINT32_MAX;
668 return NS_OK;
671 nsIFrame*
672 nsNativeTheme::GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame,
673 bool aNextSibling)
675 if (!aFrame)
676 return nullptr;
678 // Find the next visible sibling.
679 nsIFrame* sibling = aFrame;
680 do {
681 sibling = aNextSibling ? sibling->GetNextSibling() : sibling->GetPrevSibling();
682 } while (sibling && sibling->GetRect().width == 0);
684 // Check same appearance and adjacency.
685 if (!sibling ||
686 sibling->StyleDisplay()->mAppearance != aFrame->StyleDisplay()->mAppearance ||
687 (sibling->GetRect().XMost() != aFrame->GetRect().x &&
688 aFrame->GetRect().XMost() != sibling->GetRect().x))
689 return nullptr;
690 return sibling;
693 bool
694 nsNativeTheme::IsRangeHorizontal(nsIFrame* aFrame)
696 nsIFrame* rangeFrame = aFrame;
697 if (rangeFrame->GetType() != nsGkAtoms::rangeFrame) {
698 // If the thumb's frame is passed in, get its range parent:
699 rangeFrame = aFrame->GetParent();
701 if (rangeFrame->GetType() == nsGkAtoms::rangeFrame) {
702 return static_cast<nsRangeFrame*>(rangeFrame)->IsHorizontal();
704 // Not actually a range frame - just use the ratio of the frame's size to
705 // decide:
706 return aFrame->GetSize().width >= aFrame->GetSize().height;
709 static nsIFrame*
710 GetBodyFrame(nsIFrame* aCanvasFrame)
712 nsIContent* content = aCanvasFrame->GetContent();
713 if (!content) {
714 return nullptr;
716 nsIDocument* document = content->OwnerDoc();
717 nsIContent* body = document->GetBodyElement();
718 if (!body) {
719 return nullptr;
721 return body->GetPrimaryFrame();
724 bool
725 nsNativeTheme::IsDarkBackground(nsIFrame* aFrame)
727 nsIScrollableFrame* scrollFrame = nullptr;
728 while (!scrollFrame && aFrame) {
729 scrollFrame = aFrame->GetScrollTargetFrame();
730 aFrame = aFrame->GetParent();
732 if (!scrollFrame)
733 return false;
735 nsIFrame* frame = scrollFrame->GetScrolledFrame();
736 if (nsCSSRendering::IsCanvasFrame(frame)) {
737 // For canvas frames, prefer to look at the body first, because the body
738 // background color is most likely what will be visible as the background
739 // color of the page, even if the html element has a different background
740 // color which prevents that of the body frame to propagate to the viewport.
741 nsIFrame* bodyFrame = GetBodyFrame(frame);
742 if (bodyFrame) {
743 frame = bodyFrame;
746 nsStyleContext* bgSC = nullptr;
747 if (!nsCSSRendering::FindBackground(frame, &bgSC) ||
748 bgSC->StyleBackground()->IsTransparent()) {
749 nsIFrame* backgroundFrame = nsCSSRendering::FindNonTransparentBackgroundFrame(frame, true);
750 nsCSSRendering::FindBackground(backgroundFrame, &bgSC);
752 if (bgSC) {
753 nscolor bgColor = bgSC->StyleBackground()->mBackgroundColor;
754 // Consider the background color dark if the sum of the r, g and b values is
755 // less than 384 in a semi-transparent document. This heuristic matches what
756 // WebKit does, and we can improve it later if needed.
757 return NS_GET_A(bgColor) > 127 &&
758 NS_GET_R(bgColor) + NS_GET_G(bgColor) + NS_GET_B(bgColor) < 384;
760 return false;