/* -*- 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/. */
Maybe<nscoord> nsListControlFrame::GetNaturalBaselineBOffset(
WritingMode aWM, BaselineSharingGroup aBaselineGroup,
BaselineExportContext) const { // Unlike scroll frames which we inherit from, we don't export a baseline. return Nothing{};
} // for Bug 47302 (remove this comment later) void nsListControlFrame::Destroy(DestroyContext& aContext) { // get the receiver interface from the browser button's content node
NS_ENSURE_TRUE_VOID(mContent);
// Clear the frame pointer on our event listener, just in case the // event listener can outlive the frame.
void nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { // We allow visibility:hidden <select>s to contain visible options.
// Don't allow painting of list controls when painting is suppressed. // XXX why do we need this here? we should never reach this. Maybe // because these can have widgets? Hmm if (aBuilder->IsBackgroundOnly()) { return;
}
/** * This is called by the SelectsAreaFrame, which is the same * as the frame returned by GetOptionsContainer. It's the frame which is * scrolled by us. * @param aPt the offset of this frame, relative to the rendering reference * frame
*/ void nsListControlFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt) { if (!IsFocused()) { return;
}
nsIFrame* containerFrame = GetOptionsContainer(); if (!containerFrame) { return;
}
// Set up back stop colors and then ask L&F service for the real colors
nscolor color =
LookAndFeel::Color(isSelected ? LookAndFeel::ColorID::Selecteditemtext
: LookAndFeel::ColorID::Selecteditem, this);
// Return true if we found at least one <option> or non-empty <optgroup> label // that has a frame. aResult will be the maximum BSize of those. staticbool GetMaxRowBSize(nsIFrame* aContainer, WritingMode aWM,
nscoord* aResult) { bool found = false; for (nsIFrame* child : aContainer->PrincipalChildList()) { if (child->GetContent()->IsHTMLElement(nsGkAtoms::optgroup)) { // An optgroup; drill through any scroll frame and recurse. |inner| might // be null here though if |inner| is an anonymous leaf frame of some sort. auto inner = child->GetContentInsertionFrame(); if (inner && GetMaxRowBSize(inner, aWM, aResult)) {
found = true;
}
} else { // an option or optgroup label bool isOptGroupLabel =
child->Style()->IsPseudoElement() &&
aContainer->GetContent()->IsHTMLElement(nsGkAtoms::optgroup);
nscoord childBSize = child->BSize(aWM); // XXX bug 1499176: skip empty <optgroup> labels (zero bsize) for now if (!isOptGroupLabel || childBSize > nscoord(0)) {
found = true;
*aResult = std::max(childBSize, *aResult);
}
}
} return found;
}
//----------------------------------------------------------------- // Main Reflow for ListBox/Dropdown //-----------------------------------------------------------------
nscoord nsListControlFrame::CalcBSizeOfARow() { // Calculate the block size in our writing mode of a single row in the // listbox or dropdown list by using the tallest thing in the subtree, // since there may be option groups in addition to option elements, // either of which may be visible or invisible, may use different // fonts, etc.
nscoord rowBSize(0); if (GetContainSizeAxes().mBContained ||
!GetMaxRowBSize(GetOptionsContainer(), GetWritingMode(), &rowBSize)) { // We don't have any <option>s or <optgroup> labels with a frame. // (Or we're size-contained in block axis, which has the same outcome for // our sizing.) float inflation = nsLayoutUtils::FontSizeInflationFor(this);
rowBSize = CalcFallbackRowBSize(inflation);
} return rowBSize;
}
nscoord nsListControlFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
IntrinsicISizeType aType) { // Always add scrollbar inline sizes to the intrinsic isize of the // scrolled content. Combobox frames depend on this happening in the // dropdown, and standalone listboxes are overflow:scroll so they need // it too.
WritingMode wm = GetWritingMode();
nscoord result; if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
result = *containISize;
} else {
result = GetScrolledFrame()->IntrinsicISize(aInput, aType);
}
LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes());
result = NSCoordSaturatingAdd(result, scrollbarSize.IStartEnd(wm)); return result;
}
void nsListControlFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE, "Must have a computed inline size");
// If all the content and frames are here // then initialize it before reflow if (mIsAllContentHere && !mHasBeenInitialized) { if (!mIsAllFramesHere) {
CheckIfAllFramesHere();
} if (mIsAllFramesHere && !mHasBeenInitialized) {
mHasBeenInitialized = true;
}
}
MarkInReflow(); // Due to the fact that our intrinsic block size depends on the block // sizes of our kids, we end up having to do two-pass reflow, in // general -- the first pass to find the intrinsic block size and a // second pass to reflow the scrollframe at that block size (which // will size the scrollbars correctly, etc). // // Naturally, we want to avoid doing the second reflow as much as // possible. We can skip it in the following cases (in all of which the first // reflow is already happening at the right block size): bool autoBSize = (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE);
Maybe<nscoord> containBSize = ContainIntrinsicBSize(NS_UNCONSTRAINEDSIZE); bool usingContainBSize =
autoBSize && containBSize && *containBSize != NS_UNCONSTRAINEDSIZE;
mMightNeedSecondPass = [&] { if (!autoBSize) { // We're reflowing with a constrained computed block size -- just use that // block size. returnfalse;
} if (!IsSubtreeDirty() && !aReflowInput.ShouldReflowAllKids()) { // We're not dirty and have no dirty kids and shouldn't be reflowing all // kids. In this case, our cached max block size of a child is not going // to change. returnfalse;
} if (usingContainBSize) { // We're size-contained in the block axis. In this case the size of a row // doesn't depend on our children (it's the "fallback" size). returnfalse;
} // We might need to do a second pass. If we do our first reflow using our // cached max block size of a child, then compute the new max block size, // and it's the same as the old one, we might still skip it (see the // IsScrollbarUpdateSuppressed() check). returntrue;
}();
if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && autoBSize) { // When not doing an initial reflow, and when the block size is // auto, start off with our computed block size set to what we'd // expect our block size to be.
nscoord computedBSize = CalcIntrinsicBSize(oldBSizeOfARow, length);
computedBSize = state.ApplyMinMaxBSize(computedBSize);
state.SetComputedBSize(computedBSize);
}
if (usingContainBSize) {
state.SetComputedBSize(*containBSize);
}
if (!mMightNeedSecondPass) {
NS_ASSERTION(!autoBSize || BSizeOfARow() == oldBSizeOfARow, "How did our BSize of a row change if nothing was dirty?");
NS_ASSERTION(!autoBSize || !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
usingContainBSize, "How do we not need a second pass during initial reflow at " "auto BSize?");
NS_ASSERTION(!IsScrollbarUpdateSuppressed(), "Shouldn't be suppressing if we don't need a second pass!"); if (!autoBSize || usingContainBSize) { // Update our mNumDisplayRows based on our new row block size now // that we know it. Note that if autoBSize and we landed in this // code then we already set mNumDisplayRows in CalcIntrinsicBSize. // Also note that we can't use BSizeOfARow() here because that // just uses a cached value that we didn't compute.
nscoord rowBSize = CalcBSizeOfARow(); if (rowBSize == 0) { // Just pick something
mNumDisplayRows = 1;
} else {
mNumDisplayRows = std::max(1, state.ComputedBSize() / rowBSize);
}
}
return;
}
mMightNeedSecondPass = false;
// Now see whether we need a second pass. If we do, our // nsSelectsAreaFrame will have suppressed the scrollbar update. if (!IsScrollbarUpdateSuppressed()) { // All done. No need to do more reflow. return;
}
SetSuppressScrollbarUpdate(false);
// Gotta reflow again. // XXXbz We're just changing the block size here; do we need to dirty // ourselves or anything like that? We might need to, per the letter // of the reflow protocol, but things seem to work fine without it... // Is that just an implementation detail of ScrollContainerFrame that // we're depending on?
ScrollContainerFrame::DidReflow(aPresContext, &state);
// Now compute the block size we want to have
nscoord computedBSize = CalcIntrinsicBSize(BSizeOfARow(), length);
computedBSize = state.ApplyMinMaxBSize(computedBSize);
state.SetComputedBSize(computedBSize);
// XXXbz to make the ascent really correct, we should add our // mComputedPadding.top to it (and subtract it from descent). Need that // because ScrollContainerFrame just adds in the border....
aStatus.Reset();
ScrollContainerFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
void nsListControlFrame::InitSelectionRange(int32_t aClickedIndex) { // // If nothing is selected, set the start selection depending on where // the user clicked and what the initial selection is: // - if the user clicked *before* selectedIndex, set the start index to // the end of the first contiguous selection. // - if the user clicked *after* the end of the first contiguous // selection, set the start index to selectedIndex. // - if the user clicked *within* the first contiguous selection, set the // start index to selectedIndex. // The last two rules, of course, boil down to the same thing: if the user // clicked >= selectedIndex, return selectedIndex. // // This makes it so that shift click works properly when you first click // in a multiple select. //
int32_t selectedIndex = GetSelectedIndex(); if (selectedIndex >= 0) { // Get the end of the contiguous selection
RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
NS_ASSERTION(options, "Collection of options is null!");
uint32_t numOptions = options->Length(); // Push i to one past the last selected index in the group.
uint32_t i; for (i = selectedIndex + 1; i < numOptions; i++) { if (!options->ItemAsOption(i)->Selected()) { break;
}
}
if (aClickedIndex < selectedIndex) { // User clicked before selection, so start selection at end of // contiguous selection
mStartSelectionIndex = i - 1;
mEndSelectionIndex = selectedIndex;
} else { // User clicked after selection, so start selection at start of // contiguous selection
mStartSelectionIndex = selectedIndex;
mEndSelectionIndex = i - 1;
}
}
}
//--------------------------------------------------------- bool nsListControlFrame::PerformSelection(int32_t aClickedIndex, bool aIsShift, bool aIsControl) { if (aClickedIndex == kNothingSelected) { // Ignore kNothingSelected. returnfalse;
} if (!GetMultiple()) { return SingleSelection(aClickedIndex, false);
} bool wasChanged = false; if (aIsShift) { // Make sure shift+click actually does something expected when // the user has never clicked on the select if (mStartSelectionIndex == kNothingSelected) {
InitSelectionRange(aClickedIndex);
}
// Get the range from beginning (low) to end (high) // Shift *always* works, even if the current option is disabled
int32_t startIndex;
int32_t endIndex; if (mStartSelectionIndex == kNothingSelected) {
startIndex = aClickedIndex;
endIndex = aClickedIndex;
} elseif (mStartSelectionIndex <= aClickedIndex) {
startIndex = mStartSelectionIndex;
endIndex = aClickedIndex;
} else {
startIndex = aClickedIndex;
endIndex = mStartSelectionIndex;
}
// Clear only if control was not pressed
wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
AutoWeakFrame weakFrame(this);
ScrollToIndex(aClickedIndex); if (!weakFrame.IsAlive()) { return wasChanged;
}
//--------------------------------------------------------- void nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents) { if (aGrabMouseEvents) {
PresShell::SetCapturingContent(mContent, CaptureFlags::IgnoreAllowedState);
} else {
nsIContent* capturingContent = PresShell::GetCapturingContent(); if (capturingContent == mContent) { // only clear the capturing content if *we* are the ones doing the // capturing (or if the dropdown is hidden, in which case NO-ONE should // be capturing anything - it could be a scrollbar inside this listbox // which is actually grabbing // This shouldn't be necessary. We should simply ensure that events // targeting scrollbars are never visible to DOM consumers.
PresShell::ReleaseCapturingContent();
}
}
}
if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { return NS_OK;
}
// disabled state affects how we're selected, but we don't want to go through // ScrollContainerFrame if we're disabled. if (IsContentDisabled()) { return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
}
//--------------------------------------------------------- void nsListControlFrame::SetInitialChildList(ChildListID aListID,
nsFrameList&& aChildList) { if (aListID == FrameChildListID::Principal) { // First check to see if all the content has been added
mIsAllContentHere = Select().IsDoneAddingChildren(); if (!mIsAllContentHere) {
mIsAllFramesHere = false;
mHasBeenInitialized = false;
}
}
ScrollContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
}
// we shouldn't have to unregister this listener because when // our frame goes away all these content node go away as well // because our frame is the only one who references them. // we need to hook up our listeners before the editor is initialized
mEventListener = new HTMLSelectEventListener(
Select(), HTMLSelectEventListener::SelectType::Listbox);
void nsListControlFrame::ResetList(bool aAllowScrolling) { // if all the frames aren't here // don't bother reseting if (!mIsAllFramesHere) { return;
}
if (aAllowScrolling) {
mPostChildrenLoadedReset = true;
// Scroll to the selected index
int32_t indexToSelect = kNothingSelected;
HTMLSelectElement* selectElement = HTMLSelectElement::FromNode(mContent); if (selectElement) {
indexToSelect = selectElement->SelectedIndex();
AutoWeakFrame weakFrame(this);
ScrollToIndex(indexToSelect); if (!weakFrame.IsAlive()) { return;
}
}
}
mStartSelectionIndex = kNothingSelected;
mEndSelectionIndex = kNothingSelected;
InvalidateFocus(); // Combobox will redisplay itself with the OnOptionSelected event
}
void nsListControlFrame::ElementStateChanged(ElementState aStates) { if (aStates.HasState(ElementState::FOCUS)) {
InvalidateFocus();
}
}
//---------------------------------------------------------------------- // nsISelectControlFrame //---------------------------------------------------------------------- bool nsListControlFrame::CheckIfAllFramesHere() { // XXX Need to find a fail proof way to determine that // all the frames are there
mIsAllFramesHere = true;
// now make sure we have a frame each piece of content
return mIsAllFramesHere;
}
NS_IMETHODIMP
nsListControlFrame::DoneAddingChildren(bool aIsDone) {
mIsAllContentHere = aIsDone; if (mIsAllContentHere) { // Here we check to see if all the frames have been created // for all the content. // If so, then we can initialize; if (!mIsAllFramesHere) { // if all the frames are now present we can initialize if (CheckIfAllFramesHere()) {
mHasBeenInitialized = true;
ResetList(true);
}
}
} return NS_OK;
}
// Need to reset if we're a dropdown if (mStartSelectionIndex != kNothingSelected) {
NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
int32_t numOptions = GetNumberOfOptions(); // NOTE: numOptions is the new number of options whereas aIndex is the // unadjusted index of the removed option (hence the <= below).
NS_ASSERTION(aIndex <= numOptions, "out-of-bounds );
//--------------------------------------------------------- // Set the option selected in the DOM. This method is named // as it is because it indicates that the frame is the source // of this event rather than the receiver. bool nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
int32_t aEndIndex, bool aValue, bool aClearAll) { using OptionFlag = HTMLSelectElement::OptionFlag;
RefPtr<HTMLSelectElement> selectElement =
HTMLSelectElement::FromNode(mContent);
HTMLSelectElement::OptionFlags mask = OptionFlag::Notify; if (aValue) {
mask += OptionFlag::IsSelected;
} if (aClearAll) {
mask += OptionFlag::ClearAll;
}
// Dispatch event and such bool nsListControlFrame::UpdateSelection() { if (mIsAllFramesHere) { // if it's a combobox, display the new text. Note that after // FireOnInputAndOnChange we might be dead, as that can run script.
AutoWeakFrame weakFrame(this); if (mIsAllContentHere) {
RefPtr listener = mEventListener;
listener->FireOnInputAndOnChange();
} return weakFrame.IsAlive();
} returntrue;
}
//---------------------------------------------------------------------- // End nsISelectControlFrame //----------------------------------------------------------------------
class AsyncReset final : public Runnable { public:
AsyncReset(nsListControlFrame* aFrame, bool aScroll)
: Runnable("AsyncReset"), mFrame(aFrame), mScroll(aScroll) {}
bool nsListControlFrame::ReflowFinished() { if (mNeedToReset && !mReflowWasInterrupted) {
mNeedToReset = false; // Suppress scrolling to the selected element if we restored scroll // history state AND the list contents have not changed since we loaded // all the children AND nothing else forced us to scroll by calling // ResetList(true). The latter two conditions are folded into // mPostChildrenLoadedReset. // // The idea is that we want scroll history restoration to trump ResetList // scrolling to the selected element, when the ResetList was probably only // caused by content loading normally. constbool scroll = !DidHistoryRestore() || mPostChildrenLoadedReset;
nsContentUtils::AddScriptRunner(new AsyncReset(this, scroll));
}
mReflowWasInterrupted = false; return ScrollContainerFrame::ReflowFinished();
}
nsIContent* optionContent = GetCurrentOption(); if (aPreviousOption == optionContent) { // No change return;
}
if (aPreviousOption) {
FireDOMEvent(u"DOMMenuItemInactive"_ns, aPreviousOption);
}
if (optionContent) {
FireDOMEvent(u"DOMMenuItemActive"_ns, optionContent);
}
} #endif
nsresult nsListControlFrame::GetIndexFromDOMEvent(dom::Event* aMouseEvent,
int32_t& aCurIndex) { if (PresShell::GetCapturingContent() != mContent) { // If we're not capturing, then ignore movement in the border
nsPoint pt =
nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
nsRect borderInnerEdge = GetScrollPortRect(); if (!borderInnerEdge.Contains(pt)) { return NS_ERROR_FAILURE;
}
}
nsresult nsListControlFrame::HandleLeftButtonMouseDown(
dom::Event* aMouseEvent) {
int32_t selectedIndex; if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) { // Handle Like List
CaptureMouseEvents(true);
AutoWeakFrame weakFrame(this); bool change =
HandleListSelection(aMouseEvent, selectedIndex); // might destroy us if (!weakFrame.IsAlive()) { return NS_OK;
}
mChangesSinceDragStart = change;
} return NS_OK;
}
nsresult nsListControlFrame::HandleLeftButtonMouseUp(dom::Event* aMouseEvent) { if (!StyleVisibility()->IsVisible()) { return NS_OK;
} // Notify if (mChangesSinceDragStart) { // reset this so that future MouseUps without a prior MouseDown // won't fire onchange
mChangesSinceDragStart = false;
RefPtr listener = mEventListener;
listener->FireOnInputAndOnChange(); // Note that `this` may be dead now, as the above call runs script.
} return NS_OK;
}
nsresult nsListControlFrame::DragMove(dom::Event* aMouseEvent) {
NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
int32_t selectedIndex; if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) { // Don't waste cycles if we already dragged over this item if (selectedIndex == mEndSelectionIndex) { return NS_OK;
}
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
NS_ASSERTION(mouseEvent, "aMouseEvent is not a MouseEvent!"); bool isControl; #ifdef XP_MACOSX
isControl = mouseEvent->MetaKey(); #else
isControl = mouseEvent->CtrlKey(); #endif
AutoWeakFrame weakFrame(this); // Turn SHIFT on when you are dragging, unless control is on. bool wasChanged = PerformSelection(selectedIndex, !isControl, isControl); if (!weakFrame.IsAlive()) { return NS_OK;
}
mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
} return NS_OK;
}
//---------------------------------------------------------------------- // Scroll helpers. //---------------------------------------------------------------------- void nsListControlFrame::ScrollToIndex(int32_t aIndex) { if (aIndex < 0) { // XXX shouldn't we just do nothing if we're asked to scroll to // kNothingSelected?
ScrollTo(nsPoint(0, 0), ScrollMode::Instant);
} else {
RefPtr<dom::HTMLOptionElement> option =
GetOption(AssertedCast<uint32_t>(aIndex)); if (option) {
ScrollToFrame(*option);
}
}
}
void nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement) { // otherwise we find the content's frame and scroll to it if (nsIFrame* childFrame = aOptElement.GetPrimaryFrame()) {
RefPtr<mozilla::PresShell> presShell = PresShell();
presShell->ScrollFrameIntoView(childFrame, Nothing(), ScrollAxis(),
ScrollAxis(),
ScrollFlags::ScrollOverflowHidden |
ScrollFlags::ScrollFirstAncestorOnly);
}
}
void nsListControlFrame::UpdateSelectionAfterKeyEvent(
int32_t aNewIndex, uint32_t aCharCode, bool aIsShift, bool aIsControlOrMeta, bool aIsControlSelectMode) { // If you hold control, but not shift, no key will actually do anything // except space.
AutoWeakFrame weakFrame(this); bool wasChanged = false; if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') { #ifdef ACCESSIBILITY
nsCOMPtr<nsIContent> prevOption = GetCurrentOption(); #endif
mStartSelectionIndex = aNewIndex;
mEndSelectionIndex = aNewIndex;
InvalidateFocus();
ScrollToIndex(aNewIndex); if (!weakFrame.IsAlive()) { return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.