Bug 1492908 [wpt PR 13122] - Check completeness of images with and without srcset...
[gecko.git] / widget / nsNativeTheme.cpp
blob61370abc601c87331212f28af313353cd5753004
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 "nsIDOMXULMenuListElement.h"
17 #include "nsStyleConsts.h"
18 #include "nsIComponentManager.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsProgressFrame.h"
21 #include "nsMeterFrame.h"
22 #include "nsMenuFrame.h"
23 #include "nsRangeFrame.h"
24 #include "nsCSSRendering.h"
25 #include "mozilla/EventStates.h"
26 #include "mozilla/dom/Element.h"
27 #include "mozilla/dom/HTMLBodyElement.h"
28 #include "mozilla/dom/HTMLInputElement.h"
29 #include "mozilla/dom/HTMLProgressElement.h"
30 #include "mozilla/StaticPrefs.h"
31 #include "nsIDocumentInlines.h"
32 #include <algorithm>
34 using namespace mozilla;
35 using namespace mozilla::dom;
37 nsNativeTheme::nsNativeTheme()
38 : mAnimatedContentTimeout(UINT32_MAX)
42 NS_IMPL_ISUPPORTS(nsNativeTheme, nsITimerCallback, nsINamed)
44 nsIPresShell *
45 nsNativeTheme::GetPresShell(nsIFrame* aFrame)
47 if (!aFrame)
48 return nullptr;
50 nsPresContext* context = aFrame->PresContext();
51 return context ? context->GetPresShell() : nullptr;
54 EventStates
55 nsNativeTheme::GetContentState(nsIFrame* aFrame, StyleAppearance aWidgetType)
57 if (!aFrame)
58 return EventStates();
60 bool isXULCheckboxRadio =
61 (aWidgetType == StyleAppearance::Checkbox ||
62 aWidgetType == StyleAppearance::Radio) &&
63 aFrame->GetContent()->IsXULElement();
64 if (isXULCheckboxRadio)
65 aFrame = aFrame->GetParent();
67 if (!aFrame->GetContent())
68 return EventStates();
70 nsIPresShell *shell = GetPresShell(aFrame);
71 if (!shell)
72 return EventStates();
74 nsIContent* frameContent = aFrame->GetContent();
75 EventStates flags;
76 if (frameContent->IsElement()) {
77 flags = frameContent->AsElement()->State();
79 // <input type=number> needs special handling since its nested native
80 // anonymous <input type=text> takes focus for it.
81 if (aWidgetType == StyleAppearance::NumberInput &&
82 frameContent->IsHTMLElement(nsGkAtoms::input)) {
83 nsNumberControlFrame *numberControlFrame = do_QueryFrame(aFrame);
84 if (numberControlFrame && numberControlFrame->IsFocused()) {
85 flags |= NS_EVENT_STATE_FOCUS;
89 nsNumberControlFrame* numberControlFrame =
90 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
91 if (numberControlFrame &&
92 numberControlFrame->GetContent()->AsElement()->State().
93 HasState(NS_EVENT_STATE_DISABLED)) {
94 flags |= NS_EVENT_STATE_DISABLED;
98 if (isXULCheckboxRadio && aWidgetType == StyleAppearance::Radio) {
99 if (IsFocused(aFrame))
100 flags |= NS_EVENT_STATE_FOCUS;
103 // On Windows and Mac, only draw focus rings if they should be shown. This
104 // means that focus rings are only shown once the keyboard has been used to
105 // focus something in the window.
106 #if defined(XP_MACOSX)
107 // Mac always draws focus rings for textboxes and lists.
108 if (aWidgetType == StyleAppearance::NumberInput ||
109 aWidgetType == StyleAppearance::Textfield ||
110 aWidgetType == StyleAppearance::TextfieldMultiline ||
111 aWidgetType == StyleAppearance::Searchfield ||
112 aWidgetType == StyleAppearance::Listbox) {
113 return flags;
115 #endif
116 #if defined(XP_WIN)
117 // On Windows, focused buttons are always drawn as such by the native theme.
118 if (aWidgetType == StyleAppearance::Button)
119 return flags;
120 #endif
121 #if defined(XP_MACOSX) || defined(XP_WIN)
122 nsIDocument* doc = aFrame->GetContent()->OwnerDoc();
123 nsPIDOMWindowOuter* window = doc->GetWindow();
124 if (window && !window->ShouldShowFocusRing())
125 flags &= ~NS_EVENT_STATE_FOCUS;
126 #endif
128 return flags;
131 /* static */
132 bool
133 nsNativeTheme::CheckBooleanAttr(nsIFrame* aFrame, nsAtom* aAtom)
135 if (!aFrame)
136 return false;
138 nsIContent* content = aFrame->GetContent();
139 if (!content || !content->IsElement())
140 return false;
142 if (content->IsHTMLElement())
143 return content->AsElement()->HasAttr(kNameSpaceID_None, aAtom);
145 // For XML/XUL elements, an attribute must be equal to the literal
146 // string "true" to be counted as true. An empty string should _not_
147 // be counted as true.
148 return content->AsElement()->AttrValueIs(kNameSpaceID_None, aAtom,
149 NS_LITERAL_STRING("true"),
150 eCaseMatters);
153 /* static */
154 int32_t
155 nsNativeTheme::CheckIntAttr(nsIFrame* aFrame, nsAtom* aAtom, int32_t defaultValue)
157 if (!aFrame)
158 return defaultValue;
160 nsIContent* content = aFrame->GetContent();
161 if (!content || !content->IsElement())
162 return defaultValue;
164 nsAutoString attr;
165 content->AsElement()->GetAttr(kNameSpaceID_None, aAtom, attr);
166 nsresult err;
167 int32_t value = attr.ToInteger(&err);
168 if (attr.IsEmpty() || NS_FAILED(err))
169 return defaultValue;
171 return value;
174 /* static */
175 double
176 nsNativeTheme::GetProgressValue(nsIFrame* aFrame)
178 // When we are using the HTML progress element,
179 // we can get the value from the IDL property.
180 if (aFrame && aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
181 return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Value();
184 return (double)nsNativeTheme::CheckIntAttr(aFrame, nsGkAtoms::value, 0);
187 /* static */
188 double
189 nsNativeTheme::GetProgressMaxValue(nsIFrame* aFrame)
191 // When we are using the HTML progress element,
192 // we can get the max from the IDL property.
193 if (aFrame && aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
194 return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Max();
197 return (double)std::max(nsNativeTheme::CheckIntAttr(aFrame, nsGkAtoms::max, 100), 1);
200 bool
201 nsNativeTheme::GetCheckedOrSelected(nsIFrame* aFrame, bool aCheckSelected)
203 if (!aFrame)
204 return false;
206 nsIContent* content = aFrame->GetContent();
208 if (content->IsXULElement()) {
209 // For a XUL checkbox or radio button, the state of the parent determines
210 // the checked state
211 aFrame = aFrame->GetParent();
212 } else {
213 // Check for an HTML input element
214 HTMLInputElement* inputElt = HTMLInputElement::FromNode(content);
215 if (inputElt) {
216 return inputElt->Checked();
220 return CheckBooleanAttr(aFrame, aCheckSelected ? nsGkAtoms::selected
221 : nsGkAtoms::checked);
224 bool
225 nsNativeTheme::IsButtonTypeMenu(nsIFrame* aFrame)
227 if (!aFrame)
228 return false;
230 nsIContent* content = aFrame->GetContent();
231 return content->IsXULElement(nsGkAtoms::button) &&
232 content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
233 NS_LITERAL_STRING("menu"),
234 eCaseMatters);
237 bool
238 nsNativeTheme::IsPressedButton(nsIFrame* aFrame)
240 EventStates eventState = GetContentState(aFrame, StyleAppearance::Toolbarbutton);
241 if (IsDisabled(aFrame, eventState))
242 return false;
244 return IsOpenButton(aFrame) ||
245 eventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
249 bool
250 nsNativeTheme::GetIndeterminate(nsIFrame* aFrame)
252 if (!aFrame)
253 return false;
255 nsIContent* content = aFrame->GetContent();
257 if (content->IsXULElement()) {
258 // For a XUL checkbox or radio button, the state of the parent determines
259 // the state
260 return CheckBooleanAttr(aFrame->GetParent(), nsGkAtoms::indeterminate);
263 // Check for an HTML input element
264 HTMLInputElement* inputElt = HTMLInputElement::FromNode(content);
265 if (inputElt) {
266 return inputElt->Indeterminate();
269 return false;
272 bool
273 nsNativeTheme::IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame,
274 StyleAppearance aWidgetType)
276 // Check for specific widgets to see if HTML has overridden the style.
277 if (!aFrame)
278 return false;
280 // Resizers have some special handling, dependent on whether in a scrollable
281 // container or not. If so, use the scrollable container's to determine
282 // whether the style is overriden instead of the resizer. This allows a
283 // non-native transparent resizer to be used instead. Otherwise, we just
284 // fall through and return false.
285 if (aWidgetType == StyleAppearance::Resizer) {
286 nsIFrame* parentFrame = aFrame->GetParent();
287 if (parentFrame && parentFrame->IsScrollFrame()) {
288 // if the parent is a scrollframe, the resizer should be native themed
289 // only if the scrollable area doesn't override the widget style.
290 parentFrame = parentFrame->GetParent();
291 if (parentFrame) {
292 return IsWidgetStyled(aPresContext, parentFrame,
293 parentFrame->StyleDisplay()->mAppearance);
299 * Progress bar appearance should be the same for the bar and the container
300 * frame. nsProgressFrame owns the logic and will tell us what we should do.
302 if (aWidgetType == StyleAppearance::Progresschunk ||
303 aWidgetType == StyleAppearance::Progressbar) {
304 nsProgressFrame* progressFrame = do_QueryFrame(aWidgetType == StyleAppearance::Progresschunk
305 ? aFrame->GetParent() : aFrame);
306 if (progressFrame) {
307 return !progressFrame->ShouldUseNativeStyle();
312 * Meter bar appearance should be the same for the bar and the container
313 * frame. nsMeterFrame owns the logic and will tell us what we should do.
315 if (aWidgetType == StyleAppearance::Meterchunk ||
316 aWidgetType == StyleAppearance::Meterbar) {
317 nsMeterFrame* meterFrame = do_QueryFrame(aWidgetType == StyleAppearance::Meterchunk
318 ? aFrame->GetParent() : aFrame);
319 if (meterFrame) {
320 return !meterFrame->ShouldUseNativeStyle();
325 * An nsRangeFrame and its children are treated atomically when it
326 * comes to native theming (either all parts, or no parts, are themed).
327 * nsRangeFrame owns the logic and will tell us what we should do.
329 if (aWidgetType == StyleAppearance::Range ||
330 aWidgetType == StyleAppearance::RangeThumb) {
331 nsRangeFrame* rangeFrame =
332 do_QueryFrame(aWidgetType == StyleAppearance::RangeThumb
333 ? aFrame->GetParent() : aFrame);
334 if (rangeFrame) {
335 return !rangeFrame->ShouldUseNativeStyle();
339 if (aWidgetType == StyleAppearance::SpinnerUpbutton ||
340 aWidgetType == StyleAppearance::SpinnerDownbutton) {
341 nsNumberControlFrame* numberControlFrame =
342 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
343 if (numberControlFrame) {
344 return !numberControlFrame->ShouldUseNativeStyleForSpinner();
348 return (aWidgetType == StyleAppearance::NumberInput ||
349 aWidgetType == StyleAppearance::Button ||
350 aWidgetType == StyleAppearance::Textfield ||
351 aWidgetType == StyleAppearance::TextfieldMultiline ||
352 aWidgetType == StyleAppearance::Listbox ||
353 aWidgetType == StyleAppearance::Menulist ||
354 (aWidgetType == StyleAppearance::MenulistButton &&
355 StaticPrefs::layout_css_webkit_appearance_enabled())) &&
356 aFrame->GetContent()->IsHTMLElement() &&
357 aPresContext->HasAuthorSpecifiedRules(aFrame,
358 NS_AUTHOR_SPECIFIED_BORDER |
359 NS_AUTHOR_SPECIFIED_BACKGROUND);
362 bool
363 nsNativeTheme::IsDisabled(nsIFrame* aFrame, EventStates aEventStates)
365 if (!aFrame) {
366 return false;
369 nsIContent* content = aFrame->GetContent();
370 if (!content || !content->IsElement()) {
371 return false;
374 if (content->IsHTMLElement()) {
375 return aEventStates.HasState(NS_EVENT_STATE_DISABLED);
378 // For XML/XUL elements, an attribute must be equal to the literal
379 // string "true" to be counted as true. An empty string should _not_
380 // be counted as true.
381 return content->AsElement()->AttrValueIs(kNameSpaceID_None,
382 nsGkAtoms::disabled,
383 NS_LITERAL_STRING("true"),
384 eCaseMatters);
387 /* static */ bool
388 nsNativeTheme::IsFrameRTL(nsIFrame* aFrame)
390 if (!aFrame) {
391 return false;
393 WritingMode wm = aFrame->GetWritingMode();
394 return !(wm.IsVertical() ? wm.IsVerticalLR() : wm.IsBidiLTR());
397 bool
398 nsNativeTheme::IsHTMLContent(nsIFrame *aFrame)
400 if (!aFrame) {
401 return false;
403 nsIContent* content = aFrame->GetContent();
404 return content && content->IsHTMLElement();
408 // scrollbar button:
409 int32_t
410 nsNativeTheme::GetScrollbarButtonType(nsIFrame* aFrame)
412 if (!aFrame)
413 return 0;
415 static Element::AttrValuesArray strings[] =
416 {&nsGkAtoms::scrollbarDownBottom, &nsGkAtoms::scrollbarDownTop,
417 &nsGkAtoms::scrollbarUpBottom, &nsGkAtoms::scrollbarUpTop,
418 nullptr};
420 nsIContent* content = aFrame->GetContent();
421 if (!content || !content->IsElement()) {
422 return 0;
425 switch (content->AsElement()->FindAttrValueIn(kNameSpaceID_None,
426 nsGkAtoms::sbattr,
427 strings, eCaseMatters)) {
428 case 0: return eScrollbarButton_Down | eScrollbarButton_Bottom;
429 case 1: return eScrollbarButton_Down;
430 case 2: return eScrollbarButton_Bottom;
431 case 3: return eScrollbarButton_UpTop;
434 return 0;
437 // treeheadercell:
438 nsNativeTheme::TreeSortDirection
439 nsNativeTheme::GetTreeSortDirection(nsIFrame* aFrame)
441 if (!aFrame || !aFrame->GetContent())
442 return eTreeSortDirection_Natural;
444 static Element::AttrValuesArray strings[] =
445 {&nsGkAtoms::descending, &nsGkAtoms::ascending, nullptr};
447 nsIContent* content = aFrame->GetContent();
448 if (content->IsElement()) {
449 switch (content->AsElement()->FindAttrValueIn(kNameSpaceID_None,
450 nsGkAtoms::sortDirection,
451 strings, eCaseMatters)) {
452 case 0: return eTreeSortDirection_Descending;
453 case 1: return eTreeSortDirection_Ascending;
457 return eTreeSortDirection_Natural;
460 bool
461 nsNativeTheme::IsLastTreeHeaderCell(nsIFrame* aFrame)
463 if (!aFrame)
464 return false;
466 // A tree column picker is always the last header cell.
467 if (aFrame->GetContent()->IsXULElement(nsGkAtoms::treecolpicker))
468 return true;
470 // Find the parent tree.
471 nsIContent* parent = aFrame->GetContent()->GetParent();
472 while (parent && !parent->IsXULElement(nsGkAtoms::tree)) {
473 parent = parent->GetParent();
476 // If the column picker is visible, this can't be the last column.
477 if (parent && !parent->AsElement()->AttrValueIs(kNameSpaceID_None,
478 nsGkAtoms::hidecolumnpicker,
479 NS_LITERAL_STRING("true"),
480 eCaseMatters))
481 return false;
483 while ((aFrame = aFrame->GetNextSibling())) {
484 if (aFrame->GetRect().Width() > 0)
485 return false;
487 return true;
490 // tab:
491 bool
492 nsNativeTheme::IsBottomTab(nsIFrame* aFrame)
494 if (!aFrame)
495 return false;
497 nsAutoString classStr;
498 if (aFrame->GetContent()->IsElement()) {
499 aFrame->GetContent()->AsElement()->GetAttr(kNameSpaceID_None,
500 nsGkAtoms::_class,
501 classStr);
503 // FIXME: This looks bogus, shouldn't this be looking at GetClasses()?
504 return !classStr.IsEmpty() && classStr.Find("tab-bottom") != kNotFound;
507 bool
508 nsNativeTheme::IsFirstTab(nsIFrame* aFrame)
510 if (!aFrame)
511 return false;
513 for (nsIFrame* first : aFrame->GetParent()->PrincipalChildList()) {
514 if (first->GetRect().Width() > 0 &&
515 first->GetContent()->IsXULElement(nsGkAtoms::tab))
516 return (first == aFrame);
518 return false;
521 bool
522 nsNativeTheme::IsHorizontal(nsIFrame* aFrame)
524 if (!aFrame)
525 return false;
527 if (!aFrame->GetContent()->IsElement())
528 return true;
530 return !aFrame->GetContent()->AsElement()->AttrValueIs(kNameSpaceID_None,
531 nsGkAtoms::orient,
532 nsGkAtoms::vertical,
533 eCaseMatters);
536 bool
537 nsNativeTheme::IsNextToSelectedTab(nsIFrame* aFrame, int32_t aOffset)
539 if (!aFrame)
540 return false;
542 if (aOffset == 0)
543 return IsSelectedTab(aFrame);
545 int32_t thisTabIndex = -1, selectedTabIndex = -1;
547 nsIFrame* currentTab = aFrame->GetParent()->PrincipalChildList().FirstChild();
548 for (int32_t i = 0; currentTab; currentTab = currentTab->GetNextSibling()) {
549 if (currentTab->GetRect().Width() == 0)
550 continue;
551 if (aFrame == currentTab)
552 thisTabIndex = i;
553 if (IsSelectedTab(currentTab))
554 selectedTabIndex = i;
555 ++i;
558 if (thisTabIndex == -1 || selectedTabIndex == -1)
559 return false;
561 return (thisTabIndex - selectedTabIndex == aOffset);
564 // progressbar:
565 bool
566 nsNativeTheme::IsIndeterminateProgress(nsIFrame* aFrame,
567 EventStates aEventStates)
569 if (!aFrame || !aFrame->GetContent()|| !aFrame->GetContent()->IsElement())
570 return false;
572 if (aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
573 return aEventStates.HasState(NS_EVENT_STATE_INDETERMINATE);
576 return aFrame->GetContent()->AsElement()->AttrValueIs(kNameSpaceID_None,
577 nsGkAtoms::mode,
578 NS_LITERAL_STRING("undetermined"),
579 eCaseMatters);
582 bool
583 nsNativeTheme::IsVerticalProgress(nsIFrame* aFrame)
585 if (!aFrame) {
586 return false;
588 return IsVerticalMeter(aFrame);
591 bool
592 nsNativeTheme::IsVerticalMeter(nsIFrame* aFrame)
594 MOZ_ASSERT(aFrame, "You have to pass a non-null aFrame");
595 switch (aFrame->StyleDisplay()->mOrient) {
596 case StyleOrient::Horizontal:
597 return false;
598 case StyleOrient::Vertical:
599 return true;
600 case StyleOrient::Inline:
601 return aFrame->GetWritingMode().IsVertical();
602 case StyleOrient::Block:
603 return !aFrame->GetWritingMode().IsVertical();
605 MOZ_ASSERT_UNREACHABLE("unexpected -moz-orient value");
606 return false;
609 // menupopup:
610 bool
611 nsNativeTheme::IsSubmenu(nsIFrame* aFrame, bool* aLeftOfParent)
613 if (!aFrame)
614 return false;
616 nsIContent* parentContent = aFrame->GetContent()->GetParent();
617 if (!parentContent || !parentContent->IsXULElement(nsGkAtoms::menu))
618 return false;
620 nsIFrame* parent = aFrame;
621 while ((parent = parent->GetParent())) {
622 if (parent->GetContent() == parentContent) {
623 if (aLeftOfParent) {
624 LayoutDeviceIntRect selfBounds, parentBounds;
625 selfBounds = aFrame->GetNearestWidget()->GetScreenBounds();
626 parentBounds = parent->GetNearestWidget()->GetScreenBounds();
627 *aLeftOfParent = selfBounds.X() < parentBounds.X();
629 return true;
633 return false;
636 bool
637 nsNativeTheme::IsRegularMenuItem(nsIFrame *aFrame)
639 nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
640 return !(menuFrame && (menuFrame->IsOnMenuBar() ||
641 menuFrame->GetParentMenuListType() != eNotMenuList));
644 bool
645 nsNativeTheme::QueueAnimatedContentForRefresh(nsIContent* aContent,
646 uint32_t aMinimumFrameRate)
648 NS_ASSERTION(aContent, "Null pointer!");
649 NS_ASSERTION(aMinimumFrameRate, "aMinimumFrameRate must be non-zero!");
650 NS_ASSERTION(aMinimumFrameRate <= 1000,
651 "aMinimumFrameRate must be less than 1000!");
653 uint32_t timeout = 1000 / aMinimumFrameRate;
654 timeout = std::min(mAnimatedContentTimeout, timeout);
656 if (!mAnimatedContentTimer) {
657 mAnimatedContentTimer = NS_NewTimer();
658 NS_ENSURE_TRUE(mAnimatedContentTimer, false);
661 if (mAnimatedContentList.IsEmpty() || timeout != mAnimatedContentTimeout) {
662 nsresult rv;
663 if (!mAnimatedContentList.IsEmpty()) {
664 rv = mAnimatedContentTimer->Cancel();
665 NS_ENSURE_SUCCESS(rv, false);
668 if (XRE_IsContentProcess() && NS_IsMainThread()) {
669 mAnimatedContentTimer->SetTarget(aContent->OwnerDoc()->EventTargetFor(TaskCategory::Other));
671 rv = mAnimatedContentTimer->InitWithCallback(this, timeout,
672 nsITimer::TYPE_ONE_SHOT);
673 NS_ENSURE_SUCCESS(rv, false);
675 mAnimatedContentTimeout = timeout;
678 if (!mAnimatedContentList.AppendElement(aContent)) {
679 NS_WARNING("Out of memory!");
680 return false;
683 return true;
686 NS_IMETHODIMP
687 nsNativeTheme::Notify(nsITimer* aTimer)
689 NS_ASSERTION(aTimer == mAnimatedContentTimer, "Wrong timer!");
691 // XXX Assumes that calling nsIFrame::Invalidate won't reenter
692 // QueueAnimatedContentForRefresh.
694 uint32_t count = mAnimatedContentList.Length();
695 for (uint32_t index = 0; index < count; index++) {
696 nsIFrame* frame = mAnimatedContentList[index]->GetPrimaryFrame();
697 if (frame) {
698 frame->InvalidateFrame();
702 mAnimatedContentList.Clear();
703 mAnimatedContentTimeout = UINT32_MAX;
704 return NS_OK;
707 NS_IMETHODIMP
708 nsNativeTheme::GetName(nsACString& aName)
710 aName.AssignLiteral("nsNativeTheme");
711 return NS_OK;
714 nsIFrame*
715 nsNativeTheme::GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame,
716 bool aNextSibling)
718 if (!aFrame)
719 return nullptr;
721 // Find the next visible sibling.
722 nsIFrame* sibling = aFrame;
723 do {
724 sibling = aNextSibling ? sibling->GetNextSibling() : sibling->GetPrevSibling();
725 } while (sibling && sibling->GetRect().Width() == 0);
727 // Check same appearance and adjacency.
728 if (!sibling ||
729 sibling->StyleDisplay()->mAppearance != aFrame->StyleDisplay()->mAppearance ||
730 (sibling->GetRect().XMost() != aFrame->GetRect().X() &&
731 aFrame->GetRect().XMost() != sibling->GetRect().X()))
732 return nullptr;
733 return sibling;
736 bool
737 nsNativeTheme::IsRangeHorizontal(nsIFrame* aFrame)
739 nsIFrame* rangeFrame = aFrame;
740 if (!rangeFrame->IsRangeFrame()) {
741 // If the thumb's frame is passed in, get its range parent:
742 rangeFrame = aFrame->GetParent();
744 if (rangeFrame->IsRangeFrame()) {
745 return static_cast<nsRangeFrame*>(rangeFrame)->IsHorizontal();
747 // Not actually a range frame - just use the ratio of the frame's size to
748 // decide:
749 return aFrame->GetSize().width >= aFrame->GetSize().height;
752 static nsIFrame*
753 GetBodyFrame(nsIFrame* aCanvasFrame)
755 nsIContent* content = aCanvasFrame->GetContent();
756 if (!content) {
757 return nullptr;
759 nsIDocument* document = content->OwnerDoc();
760 nsIContent* body = document->GetBodyElement();
761 if (!body) {
762 return nullptr;
764 return body->GetPrimaryFrame();
767 bool
768 nsNativeTheme::IsDarkBackground(nsIFrame* aFrame)
770 nsIScrollableFrame* scrollFrame = nullptr;
771 while (!scrollFrame && aFrame) {
772 scrollFrame = aFrame->GetScrollTargetFrame();
773 aFrame = aFrame->GetParent();
775 if (!scrollFrame)
776 return false;
778 nsIFrame* frame = scrollFrame->GetScrolledFrame();
779 if (nsCSSRendering::IsCanvasFrame(frame)) {
780 // For canvas frames, prefer to look at the body first, because the body
781 // background color is most likely what will be visible as the background
782 // color of the page, even if the html element has a different background
783 // color which prevents that of the body frame to propagate to the viewport.
784 nsIFrame* bodyFrame = GetBodyFrame(frame);
785 if (bodyFrame) {
786 frame = bodyFrame;
789 ComputedStyle* bgSC = nullptr;
790 if (!nsCSSRendering::FindBackground(frame, &bgSC) ||
791 bgSC->StyleBackground()->IsTransparent(bgSC)) {
792 nsIFrame* backgroundFrame = nsCSSRendering::FindNonTransparentBackgroundFrame(frame, true);
793 nsCSSRendering::FindBackground(backgroundFrame, &bgSC);
795 if (bgSC) {
796 nscolor bgColor = bgSC->StyleBackground()->BackgroundColor(bgSC);
797 // Consider the background color dark if the sum of the r, g and b values is
798 // less than 384 in a semi-transparent document. This heuristic matches what
799 // WebKit does, and we can improve it later if needed.
800 return NS_GET_A(bgColor) > 127 &&
801 NS_GET_R(bgColor) + NS_GET_G(bgColor) + NS_GET_B(bgColor) < 384;
803 return false;
806 bool
807 nsNativeTheme::IsWidgetScrollbarPart(StyleAppearance aWidgetType)
809 switch (aWidgetType) {
810 case StyleAppearance::Scrollbar:
811 case StyleAppearance::ScrollbarSmall:
812 case StyleAppearance::ScrollbarVertical:
813 case StyleAppearance::ScrollbarHorizontal:
814 case StyleAppearance::ScrollbarbuttonUp:
815 case StyleAppearance::ScrollbarbuttonDown:
816 case StyleAppearance::ScrollbarbuttonLeft:
817 case StyleAppearance::ScrollbarbuttonRight:
818 case StyleAppearance::ScrollbarthumbVertical:
819 case StyleAppearance::ScrollbarthumbHorizontal:
820 case StyleAppearance::Scrollcorner:
821 return true;
822 default:
823 return false;
827 static nscolor
828 GetOpaqueBackgroundColor(ComputedStyle* aStyle)
830 nscolor color = aStyle->StyleBackground()->BackgroundColor(aStyle);
831 if (NS_GET_A(color) == 255) {
832 return color;
834 // Compose white background with the background color.
835 return NS_ComposeColors(NS_RGB(255, 255, 255), color);
838 nscolor
839 nsNativeTheme::GetScrollbarFaceColor(ComputedStyle* aStyle,
840 AutoColorGetter aAutoGetter)
842 StyleComplexColor complexColor = aStyle->StyleUI()->mScrollbarFaceColor;
843 if (complexColor.IsAuto()) {
844 return aAutoGetter(aStyle);
846 nscolor color = complexColor.CalcColor(aStyle);
847 if (NS_GET_A(color) == 255) {
848 return color;
850 nscolor bgColor = GetOpaqueBackgroundColor(aStyle);
851 return NS_ComposeColors(bgColor, color);
854 nscolor
855 nsNativeTheme::GetScrollbarTrackColor(ComputedStyle* aStyle,
856 AutoColorGetter aAutoGetter)
858 StyleComplexColor complexColor = aStyle->StyleUI()->mScrollbarTrackColor;
859 nscolor color;
860 if (complexColor.IsAuto()) {
861 color = aAutoGetter(aStyle);
862 } else {
863 color = complexColor.CalcColor(aStyle);
865 if (NS_GET_A(color) == 255) {
866 return color;
868 nscolor bgColor = GetOpaqueBackgroundColor(aStyle);
869 return NS_ComposeColors(bgColor, color);