Bug 1744009 - Simplify combobox <select> code. r=mconley,dholbert

With this patch on its own we get some a11y tests failures, but those
are fixed on a later patch.

Combobox select no longer creates frames for its <options>, nor an
nsListControlFrame. Instead, it computes its right intrinsic size using
the largest size of the options. This is better, because we render the
option text using the select style so if the select and option styles
are mismatched it'd cause changes in the size of the select when text
changes. See the following in a build without the patch, for example:

  <select>
    <option>ABC</option>
    <option style="font-size: 1px">Something long</option>
  </select>

This seems like a rather obscure case, but it's important to get it
right, see bug 1741888.

With this patch we use the same setup in content and parent processes
(this needs bug 1596852 and bug 1744152). This means we can remove a
bunch of the native view and popup code in nsListControlFrame. A couple
browser_* tests are affected by this change and have been tweaked
appropriately (the changes there are trivial).

Not creating an nsListControlFrame for dropdown select means that we
need to move a bunch of the event handling code from nsListControlFrame
to a common place that nsComboboxControlFrame can also use. That place
is HTMLSelectEventListener, and I think the setup is much nicer than
having the code intertwined with nsListControlFrame. It should be
relatively straight-forward to review, mostly moving code from one part
to another.

Another thing that we need to do in HTMLSelectEventListener that we
didn't use to do is listening for DOM mutations on the dropdown. Before,
we were relying on changes like text mutations triggering a reflow of
the listcontrolframe, which also triggered a reflow of the
comboboxcontrolframe, which in turn updated the text of the anonymous
content. Now we need to trigger that reflow manually.

There are some further simplifications that can be done after this
lands (cleanup naming of openInParentProcess and so on, among others),
but I'd rather land this first (after the merge of course) and work on
them separately.

Differential Revision: https://phabricator.services.mozilla.com/D132719
This commit is contained in:
Emilio Cobos Álvarez 2022-01-16 23:31:22 +00:00
Родитель afbff3f3ae
Коммит a8d469a8d0
47 изменённых файлов: 1357 добавлений и 2648 удалений

Просмотреть файл

@ -308,14 +308,10 @@ HTMLComboboxAccessible::HTMLComboboxAccessible(nsIContent* aContent,
mGenericTypes |= eCombobox;
mStateFlags |= eNoKidsFromDOM;
nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
if (comboFrame) {
nsIFrame* listFrame = comboFrame->GetDropDown();
if (listFrame) {
mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
Document()->BindToDocument(mListAccessible, nullptr);
AppendChild(mListAccessible);
}
if ((nsComboboxControlFrame*)do_QueryFrame(GetFrame())) {
mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
Document()->BindToDocument(mListAccessible, nullptr);
AppendChild(mListAccessible);
}
}
@ -458,16 +454,6 @@ HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible* aParent,
////////////////////////////////////////////////////////////////////////////////
// HTMLComboboxAccessible: LocalAccessible
nsIFrame* HTMLComboboxListAccessible::GetFrame() const {
nsIFrame* frame = HTMLSelectListAccessible::GetFrame();
nsComboboxControlFrame* comboBox = do_QueryFrame(frame);
if (comboBox) {
return comboBox->GetDropDown();
}
return nullptr;
}
role HTMLComboboxListAccessible::NativeRole() const {
return roles::COMBOBOX_LIST;
}

Просмотреть файл

@ -205,7 +205,6 @@ class HTMLComboboxListAccessible : public HTMLSelectListAccessible {
virtual ~HTMLComboboxListAccessible() {}
// LocalAccessible
virtual nsIFrame* GetFrame() const override;
virtual a11y::role NativeRole() const override;
virtual uint64_t NativeState() const override;
virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override;

Просмотреть файл

@ -136,6 +136,7 @@
<hbox class="dialogBox">
<browser class="dialogFrame"
autoscroll="false"
selectmenulist="ContentSelectDropdown"
disablehistory="true"/>
</hbox>
</vbox>

Просмотреть файл

@ -283,6 +283,7 @@
<vbox class="dialogTemplate dialogOverlay" align="center" topmost="true" hidden="true">
<hbox class="dialogBox">
<browser class="dialogFrame"
selectmenulist="ContentSelectDropdown"
autoscroll="false"
disablehistory="true"/>
</hbox>

Просмотреть файл

@ -323,10 +323,7 @@ async function doSelectTests(contentType, content) {
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.select_popup_in_parent.enabled", true],
["dom.forms.select.customstyling", true],
],
set: [["dom.forms.select.customstyling", true]],
});
});

Просмотреть файл

@ -461,10 +461,7 @@ let kDefaultSelectStyles = {};
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.select_popup_in_parent.enabled", true],
["dom.forms.select.customstyling", true],
],
set: [["dom.forms.select.customstyling", true]],
});
kDefaultSelectStyles = await BrowserTestUtils.withNewTab(
`data:text/html,<select>`,

Просмотреть файл

@ -8,10 +8,7 @@ SELECT +=
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.select_popup_in_parent.enabled", true],
["dom.forms.selectSearch", true],
],
set: [["dom.forms.selectSearch", true]],
});
});

Просмотреть файл

@ -80,7 +80,7 @@ add_task(async function test_subdialog_esc_on_dropdown_does_not_close_dialog() {
// Open dropdown
let select = dialog._frame.contentDocument.getElementById("select");
let shownPromise = BrowserTestUtils.waitForEvent(
select,
document.getElementById("ContentSelectDropdown"),
"popupshowing",
true
);
@ -92,7 +92,7 @@ add_task(async function test_subdialog_esc_on_dropdown_does_not_close_dialog() {
await shownPromise;
let hiddenPromise = BrowserTestUtils.waitForEvent(
select,
document.getElementById("ContentSelectDropdown"),
"popuphiding",
true
);

Просмотреть файл

@ -420,6 +420,7 @@ function createDevToolsFrame(doc, className) {
frame.setAttribute("type", "content");
frame.flex = 1; // Required to be able to shrink when the window shrinks
frame.className = className;
frame.setAttribute("selectmenulist", "ContentSelectDropdown");
const inXULDocument = doc.documentElement.namespaceURI === XUL_NS;
if (inXULDocument) {

Просмотреть файл

@ -30,7 +30,7 @@ add_task(async function() {
info("Change the playback rate to x10 after selecting '.div2'");
await selectNode(".div2", inspector);
await waitUntil(() => panel.querySelectorAll(".animation-item").length === 1);
clickOnPlaybackRateSelector(animationInspector, panel, 10);
await changePlaybackRateSelector(animationInspector, panel, 10);
info("Check each adjusted result of animations after selecting 'body' again");
await selectNode("body", inspector);

Просмотреть файл

@ -40,7 +40,7 @@ add_task(async function() {
info("Make the current time of animation to be over its end time");
clickOnCurrentTimeScrubberController(animationInspector, panel, 1.1);
await waitUntilAnimationsPlayState(animationInspector, "paused");
clickOnPlaybackRateSelector(animationInspector, panel, 0.1);
await changePlaybackRateSelector(animationInspector, panel, 0.1);
info("Resume animations");
clickOnPauseResumeButton(animationInspector, panel);
await waitUntilAnimationsPlayState(animationInspector, "running");

Просмотреть файл

@ -31,13 +31,13 @@ add_task(async function() {
is(Number(selectEl.value), 1.5, "Selected option should be 1.5");
info("Checking playback rate of animations");
clickOnPlaybackRateSelector(animationInspector, panel, 0.5);
await changePlaybackRateSelector(animationInspector, panel, 0.5);
await assertPlaybackRate(animationInspector, 0.5);
info("Checking mixed playback rate");
await selectNode("div", inspector);
await waitUntil(() => panel.querySelectorAll(".animation-item").length === 1);
clickOnPlaybackRateSelector(animationInspector, panel, 2);
await changePlaybackRateSelector(animationInspector, panel, 2);
await assertPlaybackRate(animationInspector, 2);
await selectNode("body", inspector);
await waitUntil(() => panel.querySelectorAll(".animation-item").length === 2);
@ -45,7 +45,7 @@ add_task(async function() {
ok(true, "Selected option should be empty");
info("Checking playback rate after re-setting");
clickOnPlaybackRateSelector(animationInspector, panel, 1);
await changePlaybackRateSelector(animationInspector, panel, 1);
await assertPlaybackRate(animationInspector, 1);
info(

Просмотреть файл

@ -269,21 +269,23 @@ const clickOnInspectIcon = async function(animationInspector, panel, index) {
};
/**
* Click on playback rate selector to select given rate.
* Change playback rate selector to select given rate.
*
* @param {AnimationInspector} animationInspector
* @param {DOMElement} panel
* #animation-container element.
* @param {Number} rate
*/
const clickOnPlaybackRateSelector = function(animationInspector, panel, rate) {
const changePlaybackRateSelector = async function(
animationInspector,
panel,
rate
) {
info(`Click on playback rate selector to select ${rate}`);
const selectEl = panel.querySelector(".playback-rate-selector");
const optionEl = [...selectEl.options].filter(
o => Number(o.value) === rate
)[0];
const optionIndex = [...selectEl.options].findIndex(o => +o.value == rate);
if (!optionEl) {
if (optionIndex == -1) {
ok(
false,
`Could not find an option for rate ${rate} in the rate selector. ` +
@ -292,9 +294,13 @@ const clickOnPlaybackRateSelector = function(animationInspector, panel, rate) {
return;
}
selectEl.focus();
const win = selectEl.ownerGlobal;
EventUtils.synthesizeMouseAtCenter(selectEl, { type: "mousedown" }, win);
EventUtils.synthesizeMouseAtCenter(optionEl, { type: "mouseup" }, win);
while (selectEl.selectedIndex != optionIndex) {
const key = selectEl.selectedIndex > optionIndex ? "LEFT" : "RIGHT";
EventUtils.sendKey(key, win);
}
};
/**

Просмотреть файл

@ -2815,17 +2815,6 @@ nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
canScroll = true;
}
// Comboboxes need special care.
nsComboboxControlFrame* comboBox = do_QueryFrame(scrollFrame);
if (comboBox) {
if (comboBox->IsDroppedDown()) {
// Don't propagate to parent when drop down menu is active.
return canScroll ? frameToScroll : nullptr;
}
// Always propagate when not dropped down (even if focused).
continue;
}
if (canScroll) {
return frameToScroll;
}

Просмотреть файл

@ -1423,20 +1423,9 @@ HTMLSelectElement::SubmitNamesValues(FormData* aFormData) {
}
void HTMLSelectElement::DispatchContentReset() {
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
if (formControlFrame) {
// Only dispatch content reset notification if this is a list control
// frame or combo box control frame.
if (IsCombobox()) {
nsComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
if (comboFrame) {
comboFrame->OnContentReset();
}
} else {
nsListControlFrame* listFrame = do_QueryFrame(formControlFrame);
if (listFrame) {
listFrame->OnContentReset();
}
if (nsIFormControlFrame* formControlFrame = GetFormControlFrame(false)) {
if (nsListControlFrame* listFrame = do_QueryFrame(formControlFrame)) {
listFrame->OnContentReset();
}
}
}

Просмотреть файл

@ -437,8 +437,8 @@ class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState,
nsISelectControlFrame* GetSelectFrame();
/**
* Helper method for dispatching ContentReset notifications to list
* and combo box frames.
* Helper method for dispatching ContentReset notifications to list box
* frames.
*/
void DispatchContentReset();

Просмотреть файл

@ -2911,34 +2911,9 @@ nsIFrame* nsCSSFrameConstructor::ConstructSelectFrame(
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::dropDownList, computedStyle);
// Create a listbox
nsListControlFrame* listFrame =
NS_NewListControlFrame(mPresShell, listStyle);
// Notify the listbox that it is being used as a dropdown list.
listFrame->SetComboboxFrame(comboboxFrame);
// Notify combobox that it should use the listbox as it's popup
comboboxFrame->SetDropDown(listFrame);
NS_ASSERTION(!listFrame->IsAbsPosContainingBlock(),
"Ended up with positioned dropdown list somehow.");
NS_ASSERTION(!listFrame->IsFloating(),
"Ended up with floating dropdown list somehow.");
// child frames of combobox frame
nsFrameList childList;
// Initialize the scroll frame positioned. Note that it is NOT
// initialized as absolutely positioned.
nsContainerFrame* scrolledFrame =
NS_NewSelectsAreaFrame(mPresShell, computedStyle, flags);
InitializeSelectFrame(aState, listFrame, scrolledFrame, content,
comboboxFrame, listStyle, true, childList);
NS_ASSERTION(listFrame->GetView(), "ListFrame's view is nullptr");
// Create display and button frames from the combobox's anonymous content.
// The anonymous content is appended to existing anonymous content for this
// element (the scrollbars).
@ -2973,12 +2948,6 @@ nsIFrame* nsCSSFrameConstructor::ConstructSelectFrame(
comboboxFrame->SetInitialChildList(kPrincipalList, childList);
// Initialize the additional popup child list which contains the
// dropdown list frame.
nsFrameList popupList;
popupList.AppendFrame(nullptr, listFrame);
comboboxFrame->SetInitialChildList(nsIFrame::kSelectPopupList, popupList);
aState.mFrameState = historyState;
if (aState.mFrameState) {
// Restore frame state for the entire subtree of |comboboxFrame|.
@ -2997,22 +2966,17 @@ nsIFrame* nsCSSFrameConstructor::ConstructSelectFrame(
// ******* this code stolen from Initialze ScrollFrame ********
// please adjust this code to use BuildScrollFrame.
InitializeSelectFrame(aState, listFrame, scrolledFrame, content, aParentFrame,
computedStyle, false, aFrameList);
InitializeListboxSelect(aState, listFrame, scrolledFrame, content,
aParentFrame, computedStyle, aFrameList);
return listFrame;
}
/**
* Used to be InitializeScrollFrame but now it's only used for the select tag
* But the select tag should really be fixed to use GFX scrollbars that can
* be create with BuildScrollFrame.
*/
void nsCSSFrameConstructor::InitializeSelectFrame(
void nsCSSFrameConstructor::InitializeListboxSelect(
nsFrameConstructorState& aState, nsContainerFrame* scrollFrame,
nsContainerFrame* scrolledFrame, nsIContent* aContent,
nsContainerFrame* aParentFrame, ComputedStyle* aComputedStyle,
bool aBuildCombobox, nsFrameList& aFrameList) {
nsFrameList& aFrameList) {
// Initialize it
nsContainerFrame* geometricParent =
aState.GetGeometricParent(*aComputedStyle->StyleDisplay(), aParentFrame);
@ -3022,14 +2986,9 @@ void nsCSSFrameConstructor::InitializeSelectFrame(
// the scrollable view). So we have to split Init and Restore.
scrollFrame->Init(aContent, geometricParent, nullptr);
if (!aBuildCombobox) {
aState.AddChild(scrollFrame, aFrameList, aContent, aParentFrame);
}
aState.AddChild(scrollFrame, aFrameList, aContent, aParentFrame);
BuildScrollFrame(aState, aContent, aComputedStyle, scrolledFrame,
geometricParent, scrollFrame);
if (aState.mFrameState) {
// Restore frame state for the scroll frame
RestoreFrameStateFor(scrollFrame, aState.mFrameState);

Просмотреть файл

@ -1662,16 +1662,13 @@ class nsCSSFrameConstructor final : public nsFrameManager {
void FinishBuildingScrollFrame(nsContainerFrame* aScrollFrame,
nsIFrame* aScrolledFrame);
// InitializeSelectFrame puts scrollFrame in aFrameList if aBuildCombobox is
// false aBuildCombobox indicates if we are building a combobox that has a
// dropdown popup widget or not.
void InitializeSelectFrame(nsFrameConstructorState& aState,
nsContainerFrame* aScrollFrame,
nsContainerFrame* aScrolledFrame,
nsIContent* aContent,
nsContainerFrame* aParentFrame,
ComputedStyle* aComputedStyle, bool aBuildCombobox,
nsFrameList& aFrameList);
void InitializeListboxSelect(nsFrameConstructorState& aState,
nsContainerFrame* aScrollFrame,
nsContainerFrame* aScrolledFrame,
nsIContent* aContent,
nsContainerFrame* aParentFrame,
ComputedStyle* aComputedStyle,
nsFrameList& aFrameList);
/**
* Recreate frames for aContent.

Просмотреть файл

@ -6850,27 +6850,14 @@ nsTransparencyMode nsLayoutUtils::GetFrameTransparency(
return eTransparencyOpaque;
}
static bool IsPopupFrame(const nsIFrame* aFrame) {
// aFrame is a popup it's the list control frame dropdown for a combobox.
LayoutFrameType frameType = aFrame->Type();
if (frameType == LayoutFrameType::ListControl) {
const nsListControlFrame* lcf =
static_cast<const nsListControlFrame*>(aFrame);
return lcf->IsInDropDownMode();
}
// ... or if it's a XUL menupopup frame.
return frameType == LayoutFrameType::MenuPopup;
}
/* static */
bool nsLayoutUtils::IsPopup(const nsIFrame* aFrame) {
// Optimization: the frame can't possibly be a popup if it has no view.
if (!aFrame->HasView()) {
NS_ASSERTION(!IsPopupFrame(aFrame), "popup frame must have a view");
NS_ASSERTION(!aFrame->IsMenuPopupFrame(), "popup frame must have a view");
return false;
}
return IsPopupFrame(aFrame);
return aFrame->IsMenuPopupFrame();
}
/* static */

Просмотреть файл

@ -47,7 +47,6 @@
#include "nsHtml5Module.h"
#include "nsHTMLTags.h"
#include "nsFocusManager.h"
#include "nsListControlFrame.h"
#include "mozilla/dom/HTMLDNSPrefetch.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/SVGElementFactory.h"
@ -356,7 +355,6 @@ void nsLayoutStatics::Shutdown() {
ShutdownJSEnvironment();
nsGlobalWindowInner::ShutDown();
nsGlobalWindowOuter::ShutDown();
nsListControlFrame::Shutdown();
CubebUtils::ShutdownLibrary();
WebAudioUtils::Shutdown();

Просмотреть файл

@ -0,0 +1,804 @@
/* -*- 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/. */
#include "HTMLSelectEventListener.h"
#include "nsListControlFrame.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/MouseEvent.h"
#include "mozilla/Casting.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/TextEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLOptionElement.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/ClearOnShutdown.h"
using namespace mozilla;
using namespace mozilla::dom;
static bool IsOptionInteractivelySelectable(HTMLSelectElement& aSelect,
HTMLOptionElement& aOption,
bool aIsCombobox) {
if (aSelect.IsOptionDisabled(&aOption)) {
return false;
}
if (!aIsCombobox) {
return aOption.GetPrimaryFrame();
}
// In dropdown mode no options have frames, but we can check whether they
// are rendered / not in a display: none subtree.
if (!aOption.HasServoData() || Servo_Element_IsDisplayNone(&aOption)) {
return false;
}
// TODO(emilio): This is a bit silly and doesn't match the options that we
// show / don't show in the dropdown, but matches the frame construction we
// do for multiple selects. For backwards compat also don't allow selecting
// options in a display: contents subtree interactively.
// test_select_key_navigation_bug1498769.html tests for this and should
// probably be changed (and this loop removed) or alternatively
// SelectChild.jsm should be changed to match it.
for (Element* el = &aOption; el && el != &aSelect;
el = el->GetParentElement()) {
if (Servo_Element_IsDisplayContents(el)) {
return false;
}
}
return true;
}
namespace mozilla {
static StaticAutoPtr<nsString> sIncrementalString;
static DOMTimeStamp gLastKeyTime = 0;
static constexpr int32_t kNothingSelected = -1;
static nsString& GetIncrementalString() {
if (!sIncrementalString) {
sIncrementalString = new nsString();
ClearOnShutdown(&sIncrementalString);
}
return *sIncrementalString;
}
class MOZ_RAII AutoIncrementalSearchResetter {
public:
AutoIncrementalSearchResetter() = default;
~AutoIncrementalSearchResetter() {
if (!mCancelled) {
GetIncrementalString().Truncate();
}
}
void Cancel() { mCancelled = true; }
private:
bool mCancelled = false;
};
NS_IMPL_ISUPPORTS(HTMLSelectEventListener, nsIMutationObserver,
nsIDOMEventListener)
HTMLSelectEventListener::~HTMLSelectEventListener() = default;
nsListControlFrame* HTMLSelectEventListener::GetListControlFrame() const {
if (mIsCombobox) {
MOZ_ASSERT(!mElement->GetPrimaryFrame() ||
!mElement->GetPrimaryFrame()->IsListControlFrame());
return nullptr;
}
return do_QueryFrame(mElement->GetPrimaryFrame());
}
int32_t HTMLSelectEventListener::GetEndSelectionIndex() const {
if (auto* lf = GetListControlFrame()) {
return lf->GetEndSelectionIndex();
}
// Combobox selects only have one selected index, so the end and start is the
// same.
return mElement->SelectedIndex();
}
bool HTMLSelectEventListener::IsOptionInteractivelySelectable(
uint32_t aIndex) const {
HTMLOptionElement* option = mElement->Item(aIndex);
return option &&
::IsOptionInteractivelySelectable(*mElement, *option, mIsCombobox);
}
//---------------------------------------------------------------------
// Ok, the entire idea of this routine is to move to the next item that
// is suppose to be selected. If the item is disabled then we search in
// the same direction looking for the next item to select. If we run off
// the end of the list then we start at the end of the list and search
// backwards until we get back to the original item or an enabled option
//
// aStartIndex - the index to start searching from
// aNewIndex - will get set to the new index if it finds one
// aNumOptions - the total number of options in the list
// aDoAdjustInc - the initial index increment / decrement
// aDoAdjustIncNext - the subsequent index increment/decrement used to search
// for the next enabled option
//
// the aDoAdjustInc could be a "1" for a single item or
// any number greater representing a page of items
//
void HTMLSelectEventListener::AdjustIndexForDisabledOpt(
int32_t aStartIndex, int32_t& aNewIndex, int32_t aNumOptions,
int32_t aDoAdjustInc, int32_t aDoAdjustIncNext) {
// Cannot select anything if there is nothing to select
if (aNumOptions == 0) {
aNewIndex = kNothingSelected;
return;
}
// means we reached the end of the list and now we are searching backwards
bool doingReverse = false;
// lowest index in the search range
int32_t bottom = 0;
// highest index in the search range
int32_t top = aNumOptions;
// Start off keyboard options at selectedIndex if nothing else is defaulted to
//
// XXX Perhaps this should happen for mouse too, to start off shift click
// automatically in multiple ... to do this, we'd need to override
// OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
// sure of the effects, though, so I'm not doing it just yet.
int32_t startIndex = aStartIndex;
if (startIndex < bottom) {
startIndex = mElement->SelectedIndex();
}
int32_t newIndex = startIndex + aDoAdjustInc;
// make sure we start off in the range
if (newIndex < bottom) {
newIndex = 0;
} else if (newIndex >= top) {
newIndex = aNumOptions - 1;
}
while (true) {
// if the newIndex is selectable, we are golden, bail out
if (IsOptionInteractivelySelectable(newIndex)) {
break;
}
// it WAS disabled, so sart looking ahead for the next enabled option
newIndex += aDoAdjustIncNext;
// well, if we reach end reverse the search
if (newIndex < bottom) {
if (doingReverse) {
return; // if we are in reverse mode and reach the end bail out
}
// reset the newIndex to the end of the list we hit
// reverse the incrementer
// set the other end of the list to our original starting index
newIndex = bottom;
aDoAdjustIncNext = 1;
doingReverse = true;
top = startIndex;
} else if (newIndex >= top) {
if (doingReverse) {
return; // if we are in reverse mode and reach the end bail out
}
// reset the newIndex to the end of the list we hit
// reverse the incrementer
// set the other end of the list to our original starting index
newIndex = top - 1;
aDoAdjustIncNext = -1;
doingReverse = true;
bottom = startIndex;
}
}
// Looks like we found one
aNewIndex = newIndex;
}
NS_IMETHODIMP
HTMLSelectEventListener::HandleEvent(dom::Event* aEvent) {
nsAutoString eventType;
aEvent->GetType(eventType);
if (eventType.EqualsLiteral("keydown")) {
return KeyDown(aEvent);
}
if (eventType.EqualsLiteral("keypress")) {
return KeyPress(aEvent);
}
if (eventType.EqualsLiteral("mousedown")) {
if (aEvent->DefaultPrevented()) {
return NS_OK;
}
return MouseDown(aEvent);
}
if (eventType.EqualsLiteral("mouseup")) {
// Don't try to honor defaultPrevented here - it's not web compatible.
// (bug 1194733)
return MouseUp(aEvent);
}
if (eventType.EqualsLiteral("mousemove")) {
// I don't think we want to honor defaultPrevented on mousemove
// in general, and it would only prevent highlighting here.
return MouseMove(aEvent);
}
MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
return NS_OK;
}
void HTMLSelectEventListener::Attach() {
mElement->AddSystemEventListener(u"keydown"_ns, this, false, false);
mElement->AddSystemEventListener(u"keypress"_ns, this, false, false);
mElement->AddSystemEventListener(u"mousedown"_ns, this, false, false);
mElement->AddSystemEventListener(u"mouseup"_ns, this, false, false);
mElement->AddSystemEventListener(u"mousemove"_ns, this, false, false);
if (mIsCombobox) {
mElement->AddMutationObserver(this);
}
}
void HTMLSelectEventListener::Detach() {
mElement->RemoveSystemEventListener(u"keydown"_ns, this, false);
mElement->RemoveSystemEventListener(u"keypress"_ns, this, false);
mElement->RemoveSystemEventListener(u"mousedown"_ns, this, false);
mElement->RemoveSystemEventListener(u"mouseup"_ns, this, false);
mElement->RemoveSystemEventListener(u"mousemove"_ns, this, false);
if (mIsCombobox) {
mElement->RemoveMutationObserver(this);
nsContentUtils::AddScriptRunner(
new AsyncEventDispatcher(mElement, u"mozhidedropdown"_ns,
CanBubble::eYes, ChromeOnlyDispatch::eYes));
}
}
const uint32_t kMaxDropdownRows = 20; // matches the setting for 4.x browsers
int32_t HTMLSelectEventListener::ItemsPerPage() const {
uint32_t size = [&] {
if (mIsCombobox) {
return kMaxDropdownRows;
}
if (auto* lf = GetListControlFrame()) {
return lf->GetNumDisplayRows();
}
return mElement->Size();
}();
if (size <= 1) {
return 1;
}
if (MOZ_UNLIKELY(size > INT32_MAX)) {
return INT32_MAX - 1;
}
return AssertedCast<int32_t>(size - 1u);
}
void HTMLSelectEventListener::AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue) {
if (aElement->IsHTMLElement(nsGkAtoms::option) &&
aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::label) {
ComboboxMightHaveChanged();
}
}
void HTMLSelectEventListener::CharacterDataChanged(
nsIContent* aContent, const CharacterDataChangeInfo&) {
if (nsContentUtils::IsInSameAnonymousTree(mElement, aContent)) {
ComboboxMightHaveChanged();
}
}
void HTMLSelectEventListener::ContentRemoved(nsIContent* aChild,
nsIContent* aPreviousSibling) {
if (nsContentUtils::IsInSameAnonymousTree(mElement, aChild)) {
ComboboxMightHaveChanged();
}
}
void HTMLSelectEventListener::ContentAppended(nsIContent* aFirstNewContent) {
if (nsContentUtils::IsInSameAnonymousTree(mElement, aFirstNewContent)) {
ComboboxMightHaveChanged();
}
}
void HTMLSelectEventListener::ContentInserted(nsIContent* aChild) {
if (nsContentUtils::IsInSameAnonymousTree(mElement, aChild)) {
ComboboxMightHaveChanged();
}
}
void HTMLSelectEventListener::ComboboxMightHaveChanged() {
if (nsIFrame* f = mElement->GetPrimaryFrame()) {
// nsComoboxControlFrame::Reflow updates the selected text. AddOption /
// RemoveOption / etc takes care of keeping the displayed index up to date.
f->PresShell()->FrameNeedsReflow(f, IntrinsicDirty::StyleChange,
NS_FRAME_IS_DIRTY);
}
}
void HTMLSelectEventListener::FireOnInputAndOnChange() {
RefPtr<HTMLSelectElement> element = mElement;
// Dispatch the input event.
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(element);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Failed to dispatch input event");
// Dispatch the change event.
nsContentUtils::DispatchTrustedEvent(element->OwnerDoc(), ToSupports(element),
u"change"_ns, CanBubble::eYes,
Cancelable::eNo);
}
static void FireDropDownEvent(HTMLSelectElement* aElement, bool aShow,
bool aIsSourceTouchEvent) {
const auto eventName = [&] {
if (aShow) {
return aIsSourceTouchEvent ? u"mozshowdropdown-sourcetouch"_ns
: u"mozshowdropdown"_ns;
}
return u"mozhidedropdown"_ns;
}();
nsContentUtils::DispatchChromeEvent(aElement->OwnerDoc(),
ToSupports(aElement), eventName,
CanBubble::eYes, Cancelable::eNo);
}
nsresult HTMLSelectEventListener::MouseDown(dom::Event* aMouseEvent) {
NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
EventStates eventStates = mElement->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
return NS_OK;
}
// only allow selection with the left button
// if a right button click is on the combobox itself
// or on the select when in listbox mode, then let the click through
const bool isLeftButton = mouseEvent->Button() == 0;
if (!isLeftButton) {
return NS_OK;
}
if (mIsCombobox) {
uint16_t inputSource = mouseEvent->MozInputSource();
if (mElement->OpenInParentProcess()) {
nsCOMPtr<nsIContent> target = do_QueryInterface(aMouseEvent->GetTarget());
if (target && target->IsHTMLElement(nsGkAtoms::option)) {
return NS_OK;
}
}
const bool isSourceTouchEvent =
inputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH;
FireDropDownEvent(mElement, !mElement->OpenInParentProcess(),
isSourceTouchEvent);
return NS_OK;
}
if (nsListControlFrame* list = GetListControlFrame()) {
mButtonDown = true;
return list->HandleLeftButtonMouseDown(aMouseEvent);
}
return NS_OK;
}
nsresult HTMLSelectEventListener::MouseUp(dom::Event* aMouseEvent) {
NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
mButtonDown = false;
EventStates eventStates = mElement->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
return NS_OK;
}
if (nsListControlFrame* lf = GetListControlFrame()) {
lf->CaptureMouseEvents(false);
}
// only allow selection with the left button
// if a right button click is on the combobox itself
// or on the select when in listbox mode, then let the click through
const bool isLeftButton = mouseEvent->Button() == 0;
if (!isLeftButton) {
return NS_OK;
}
if (nsListControlFrame* lf = GetListControlFrame()) {
return lf->HandleLeftButtonMouseUp(aMouseEvent);
}
return NS_OK;
}
nsresult HTMLSelectEventListener::MouseMove(dom::Event* aMouseEvent) {
NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
if (!mButtonDown) {
return NS_OK;
}
if (nsListControlFrame* lf = GetListControlFrame()) {
return lf->DragMove(aMouseEvent);
}
return NS_OK;
}
nsresult HTMLSelectEventListener::KeyPress(dom::Event* aKeyEvent) {
MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
EventStates eventStates = mElement->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
return NS_OK;
}
AutoIncrementalSearchResetter incrementalSearchResetter;
const WidgetKeyboardEvent* keyEvent =
aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
MOZ_ASSERT(keyEvent,
"DOM event must have WidgetKeyboardEvent for its internal event");
// Select option with this as the first character
// XXX Not I18N compliant
// Don't do incremental search if the key event has already consumed.
if (keyEvent->DefaultPrevented()) {
return NS_OK;
}
if (keyEvent->IsAlt()) {
return NS_OK;
}
// With some keyboard layout, space key causes non-ASCII space.
// So, the check in keydown event handler isn't enough, we need to check it
// again with keypress event.
if (keyEvent->mCharCode != ' ') {
mControlSelectMode = false;
}
bool isControlOrMeta = keyEvent->IsControl() || keyEvent->IsMeta();
if (isControlOrMeta && keyEvent->mCharCode != ' ') {
return NS_OK;
}
// NOTE: If mKeyCode of keypress event is not 0, mCharCode is always 0.
// Therefore, all non-printable keys are not handled after this block.
if (!keyEvent->mCharCode) {
// Backspace key will delete the last char in the string. Otherwise,
// non-printable keypress should reset incremental search.
if (keyEvent->mKeyCode == NS_VK_BACK) {
incrementalSearchResetter.Cancel();
if (!GetIncrementalString().IsEmpty()) {
GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
}
aKeyEvent->PreventDefault();
} else {
// XXX When a select element has focus, even if the key causes nothing,
// it might be better to call preventDefault() here because nobody
// should expect one of other elements including chrome handles the
// key event.
}
return NS_OK;
}
incrementalSearchResetter.Cancel();
// We ate the key if we got this far.
aKeyEvent->PreventDefault();
// XXX Why don't we check/modify timestamp first?
// Incremental Search: if time elapsed is below
// ui.menu.incremental_search.timeout, append this keystroke to the search
// string we will use to find options and start searching at the current
// keystroke. Otherwise, Truncate the string if it's been a long time
// since our last keypress.
if (keyEvent->mTime - gLastKeyTime >
StaticPrefs::ui_menu_incremental_search_timeout()) {
// If this is ' ' and we are at the beginning of the string, treat it as
// "select this option" (bug 191543)
if (keyEvent->mCharCode == ' ') {
// Actually process the new index and let the selection code
// do the scrolling for us
PostHandleKeyEvent(GetEndSelectionIndex(), keyEvent->mCharCode,
keyEvent->IsShift(), isControlOrMeta);
return NS_OK;
}
GetIncrementalString().Truncate();
}
gLastKeyTime = keyEvent->mTime;
// Append this keystroke to the search string.
char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->mCharCode));
GetIncrementalString().Append(uniChar);
// See bug 188199, if all letters in incremental string are same, just try to
// match the first one
nsAutoString incrementalString(GetIncrementalString());
uint32_t charIndex = 1, stringLength = incrementalString.Length();
while (charIndex < stringLength &&
incrementalString[charIndex] == incrementalString[charIndex - 1]) {
charIndex++;
}
if (charIndex == stringLength) {
incrementalString.Truncate(1);
stringLength = 1;
}
// Determine where we're going to start reading the string
// If we have multiple characters to look for, we start looking *at* the
// current option. If we have only one character to look for, we start
// looking *after* the current option.
// Exception: if there is no option selected to start at, we always start
// *at* 0.
int32_t startIndex = mElement->SelectedIndex();
if (startIndex == kNothingSelected) {
startIndex = 0;
} else if (stringLength == 1) {
startIndex++;
}
// now make sure there are options or we are wasting our time
RefPtr<dom::HTMLOptionsCollection> options = mElement->Options();
uint32_t numOptions = options->Length();
for (uint32_t i = 0; i < numOptions; ++i) {
uint32_t index = (i + startIndex) % numOptions;
RefPtr<dom::HTMLOptionElement> optionElement = options->ItemAsOption(index);
if (!optionElement || !::IsOptionInteractivelySelectable(
*mElement, *optionElement, mIsCombobox)) {
continue;
}
nsAutoString text;
optionElement->GetRenderedLabel(text);
if (!StringBeginsWith(
nsContentUtils::TrimWhitespace<
nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
incrementalString, nsCaseInsensitiveStringComparator)) {
continue;
}
if (mIsCombobox) {
if (optionElement->Selected()) {
return NS_OK;
}
optionElement->SetSelected(true);
FireOnInputAndOnChange();
return NS_OK;
}
if (nsListControlFrame* lf = GetListControlFrame()) {
bool wasChanged =
lf->PerformSelection(index, keyEvent->IsShift(), isControlOrMeta);
if (!wasChanged) {
return NS_OK;
}
FireOnInputAndOnChange();
}
break;
}
return NS_OK;
}
nsresult HTMLSelectEventListener::KeyDown(dom::Event* aKeyEvent) {
MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
EventStates eventStates = mElement->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
return NS_OK;
}
AutoIncrementalSearchResetter incrementalSearchResetter;
if (aKeyEvent->DefaultPrevented()) {
return NS_OK;
}
const WidgetKeyboardEvent* keyEvent =
aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
MOZ_ASSERT(keyEvent,
"DOM event must have WidgetKeyboardEvent for its internal event");
bool dropDownMenuOnUpDown;
bool dropDownMenuOnSpace;
#ifdef XP_MACOSX
dropDownMenuOnUpDown = mIsCombobox && !mElement->OpenInParentProcess();
dropDownMenuOnSpace = mIsCombobox && !keyEvent->IsAlt() &&
!keyEvent->IsControl() && !keyEvent->IsMeta();
#else
dropDownMenuOnUpDown = mIsCombobox && keyEvent->IsAlt();
dropDownMenuOnSpace = mIsCombobox && !mElement->OpenInParentProcess();
#endif
bool withinIncrementalSearchTime =
keyEvent->mTime - gLastKeyTime <=
StaticPrefs::ui_menu_incremental_search_timeout();
if ((dropDownMenuOnUpDown &&
(keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN)) ||
(dropDownMenuOnSpace && keyEvent->mKeyCode == NS_VK_SPACE &&
!withinIncrementalSearchTime)) {
FireDropDownEvent(mElement, !mElement->OpenInParentProcess(), false);
aKeyEvent->PreventDefault();
return NS_OK;
}
if (keyEvent->IsAlt()) {
return NS_OK;
}
// We should not change the selection if the popup is "opened in the parent
// process" (even when we're in single-process mode).
const bool shouldSelect = !mIsCombobox || !mElement->OpenInParentProcess();
// now make sure there are options or we are wasting our time
RefPtr<dom::HTMLOptionsCollection> options = mElement->Options();
uint32_t numOptions = options->Length();
// this is the new index to set
int32_t newIndex = kNothingSelected;
bool isControlOrMeta = keyEvent->IsControl() || keyEvent->IsMeta();
// Don't try to handle multiple-select pgUp/pgDown in single-select lists.
if (isControlOrMeta && !mElement->Multiple() &&
(keyEvent->mKeyCode == NS_VK_PAGE_UP ||
keyEvent->mKeyCode == NS_VK_PAGE_DOWN)) {
return NS_OK;
}
if (isControlOrMeta &&
(keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_LEFT ||
keyEvent->mKeyCode == NS_VK_DOWN || keyEvent->mKeyCode == NS_VK_RIGHT ||
keyEvent->mKeyCode == NS_VK_HOME || keyEvent->mKeyCode == NS_VK_END)) {
// Don't go into multiple-select mode unless this list can handle it.
isControlOrMeta = mControlSelectMode = mElement->Multiple();
} else if (keyEvent->mKeyCode != NS_VK_SPACE) {
mControlSelectMode = false;
}
switch (keyEvent->mKeyCode) {
case NS_VK_UP:
case NS_VK_LEFT:
if (shouldSelect) {
AdjustIndexForDisabledOpt(GetEndSelectionIndex(), newIndex,
int32_t(numOptions), -1, -1);
}
break;
case NS_VK_DOWN:
case NS_VK_RIGHT:
if (shouldSelect) {
AdjustIndexForDisabledOpt(GetEndSelectionIndex(), newIndex,
int32_t(numOptions), 1, 1);
}
break;
case NS_VK_RETURN:
// If this is single select listbox, Enter key doesn't cause anything.
if (!mElement->Multiple()) {
return NS_OK;
}
newIndex = GetEndSelectionIndex();
break;
case NS_VK_PAGE_UP: {
if (shouldSelect) {
AdjustIndexForDisabledOpt(GetEndSelectionIndex(), newIndex,
int32_t(numOptions), -ItemsPerPage(), -1);
}
break;
}
case NS_VK_PAGE_DOWN: {
if (shouldSelect) {
AdjustIndexForDisabledOpt(GetEndSelectionIndex(), newIndex,
int32_t(numOptions), ItemsPerPage(), 1);
}
break;
}
case NS_VK_HOME:
if (shouldSelect) {
AdjustIndexForDisabledOpt(0, newIndex, int32_t(numOptions), 0, 1);
}
break;
case NS_VK_END:
if (shouldSelect) {
AdjustIndexForDisabledOpt(int32_t(numOptions) - 1, newIndex,
int32_t(numOptions), 0, -1);
}
break;
default: // printable key will be handled by keypress event.
incrementalSearchResetter.Cancel();
return NS_OK;
}
aKeyEvent->PreventDefault();
// Actually process the new index and let the selection code
// do the scrolling for us
PostHandleKeyEvent(newIndex, 0, keyEvent->IsShift(), isControlOrMeta);
return NS_OK;
}
HTMLOptionElement* HTMLSelectEventListener::GetCurrentOption() const {
// The mEndSelectionIndex is what is currently being selected. Use
// the selected index if this is kNothingSelected.
int32_t endIndex = GetEndSelectionIndex();
int32_t focusedIndex =
endIndex == kNothingSelected ? mElement->SelectedIndex() : endIndex;
if (focusedIndex != kNothingSelected) {
return mElement->Item(AssertedCast<uint32_t>(focusedIndex));
}
// There is no selected option. Return the first non-disabled option, if any.
return GetNonDisabledOptionFrom(0);
}
HTMLOptionElement* HTMLSelectEventListener::GetNonDisabledOptionFrom(
int32_t aFromIndex, int32_t* aFoundIndex) const {
const uint32_t length = mElement->Length();
for (uint32_t i = std::max(aFromIndex, 0); i < length; ++i) {
if (IsOptionInteractivelySelectable(i)) {
if (aFoundIndex) {
*aFoundIndex = i;
}
return mElement->Item(i);
}
}
return nullptr;
}
void HTMLSelectEventListener::PostHandleKeyEvent(int32_t aNewIndex,
uint32_t aCharCode,
bool aIsShift,
bool aIsControlOrMeta) {
if (aNewIndex == kNothingSelected) {
int32_t endIndex = GetEndSelectionIndex();
int32_t focusedIndex =
endIndex == kNothingSelected ? mElement->SelectedIndex() : endIndex;
if (focusedIndex != kNothingSelected) {
return;
}
// No options are selected. In this case the focus ring is on the first
// non-disabled option (if any), so we should behave as if that's the option
// the user acted on.
if (!GetNonDisabledOptionFrom(0, &aNewIndex)) {
return;
}
}
if (mIsCombobox) {
RefPtr<HTMLOptionElement> newOption = mElement->Item(aNewIndex);
MOZ_ASSERT(newOption);
if (newOption->Selected()) {
return;
}
newOption->SetSelected(true);
FireOnInputAndOnChange();
return;
}
if (nsListControlFrame* lf = GetListControlFrame()) {
lf->UpdateSelectionAfterKeyEvent(aNewIndex, aCharCode, aIsShift,
aIsControlOrMeta, mControlSelectMode);
}
}
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,102 @@
/* -*- 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/. */
#ifndef mozilla_HTMLSelectEventListener_h
#define mozilla_HTMLSelectEventListener_h
#include "nsIDOMEventListener.h"
#include "nsStubMutationObserver.h"
class nsIFrame;
class nsListControlFrame;
namespace mozilla {
namespace dom {
class HTMLSelectElement;
class HTMLOptionElement;
class Event;
} // namespace dom
/**
* HTMLSelectEventListener
* This class is responsible for propagating events to the select element while
* it has a frame.
* Frames are not refcounted so they can't be used as event listeners.
*/
class HTMLSelectEventListener final : public nsStubMutationObserver,
public nsIDOMEventListener {
public:
enum class SelectType : uint8_t { Listbox, Combobox };
HTMLSelectEventListener(dom::HTMLSelectElement& aElement,
SelectType aSelectType)
: mElement(&aElement), mIsCombobox(aSelectType == SelectType::Combobox) {
Attach();
}
NS_DECL_ISUPPORTS
// For comboboxes, we need to keep the list up to date when options change.
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
// nsIDOMEventListener
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD HandleEvent(dom::Event*) override;
void Attach();
void Detach();
dom::HTMLOptionElement* GetCurrentOption() const;
MOZ_CAN_RUN_SCRIPT void FireOnInputAndOnChange();
private:
// This is always guaranteed to be > 0, but callers want signed integers so we
// do the cast for them.
int32_t ItemsPerPage() const;
nsListControlFrame* GetListControlFrame() const;
MOZ_CAN_RUN_SCRIPT nsresult KeyDown(dom::Event*);
MOZ_CAN_RUN_SCRIPT nsresult KeyPress(dom::Event*);
MOZ_CAN_RUN_SCRIPT nsresult MouseDown(dom::Event*);
MOZ_CAN_RUN_SCRIPT nsresult MouseUp(dom::Event*);
MOZ_CAN_RUN_SCRIPT nsresult MouseMove(dom::Event*);
void AdjustIndexForDisabledOpt(int32_t aStartIndex, int32_t& aNewIndex,
int32_t aNumOptions, int32_t aDoAdjustInc,
int32_t aDoAdjustIncNext);
bool IsOptionInteractivelySelectable(uint32_t aIndex) const;
int32_t GetEndSelectionIndex() const;
MOZ_CAN_RUN_SCRIPT
void PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode, bool aIsShift,
bool aIsControlOrMeta);
/**
* Return the first non-disabled option starting at aFromIndex (inclusive).
* @param aFoundIndex if non-null, set to the index of the returned option
*/
dom::HTMLOptionElement* GetNonDisabledOptionFrom(
int32_t aFromIndex, int32_t* aFoundIndex = nullptr) const;
void ComboboxMightHaveChanged();
~HTMLSelectEventListener();
RefPtr<dom::HTMLSelectElement> mElement;
const bool mIsCombobox;
bool mButtonDown = false;
bool mControlSelectMode = false;
};
} // namespace mozilla
#endif // mozilla_HTMLSelectEventListener_h

Просмотреть файл

@ -17,6 +17,7 @@ EXPORTS += [
]
UNIFIED_SOURCES += [
"HTMLSelectEventListener.cpp",
"nsButtonFrameRenderer.cpp",
"nsCheckboxRadioFrame.cpp",
"nsColorControlFrame.cpp",

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -28,17 +28,18 @@
#include "nsIAnonymousContentCreator.h"
#include "nsISelectControlFrame.h"
#include "nsIRollupListener.h"
#include "nsIStatefulFrame.h"
#include "nsThreadUtils.h"
class nsListControlFrame;
class nsComboboxDisplayFrame;
class nsIDOMEventListener;
class nsIScrollableFrame;
class nsTextNode;
namespace mozilla {
class PresShell;
class HTMLSelectEventListener;
namespace dom {
class HTMLSelectElement;
}
namespace gfx {
class DrawTarget;
} // namespace gfx
@ -47,9 +48,7 @@ class DrawTarget;
class nsComboboxControlFrame final : public nsBlockFrame,
public nsIFormControlFrame,
public nsIAnonymousContentCreator,
public nsISelectControlFrame,
public nsIRollupListener,
public nsIStatefulFrame {
public nsISelectControlFrame {
using DrawTarget = mozilla::gfx::DrawTarget;
using Element = mozilla::dom::Element;
@ -100,7 +99,8 @@ class nsComboboxControlFrame final : public nsBlockFrame,
aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
}
nsIScrollableFrame* GetScrollTargetFrame() const final;
void Init(nsIContent* aContent, nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) final;
#ifdef DEBUG_FRAME_DUMP
nsresult GetFrameName(nsAString& aResult) const final;
@ -117,7 +117,10 @@ class nsComboboxControlFrame final : public nsBlockFrame,
void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) final;
// nsIFormControlFrame
nsresult SetFormProperty(nsAtom* aName, const nsAString& aValue) final;
nsresult SetFormProperty(nsAtom* aName, const nsAString& aValue) final {
return NS_OK;
}
/**
* Inform the control that it got (or lost) focus.
* If it lost focus, the dropdown menu will be rolled up if needed,
@ -130,12 +133,6 @@ class nsComboboxControlFrame final : public nsBlockFrame,
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void SetFocus(bool aOn, bool aRepaint) final;
bool IsDroppedDown() { return mDroppedDown; }
MOZ_CAN_RUN_SCRIPT void ShowDropDown(bool aDoDropDown);
nsIFrame* GetDropDown();
void SetDropDown(nsListControlFrame* aDropDownFrame);
MOZ_CAN_RUN_SCRIPT void RollupFromList();
/**
* Return the available space before and after this frame for
* placing the drop-down list, and the current 2D translation.
@ -151,15 +148,12 @@ class nsComboboxControlFrame final : public nsBlockFrame,
*/
nsresult RedisplaySelectedText();
int32_t UpdateRecentIndex(int32_t aIndex);
void OnContentReset();
bool IsOpenInParentProcess() { return mIsOpenInParentProcess; }
void SetOpenInParentProcess(bool aVal) { mIsOpenInParentProcess = aVal; }
bool IsDroppedDownOrHasParentPopup() {
return IsDroppedDown() || IsOpenInParentProcess();
}
bool IsDroppedDown() { return IsOpenInParentProcess(); }
// nsISelectControlFrame
NS_IMETHOD AddOption(int32_t index) final;
@ -169,55 +163,13 @@ class nsComboboxControlFrame final : public nsBlockFrame,
NS_IMETHOD_(void)
OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) final;
// nsIRollupListener
/**
* Hide the dropdown menu and stop capturing mouse events.
* @note This method might destroy |this|.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY
bool Rollup(uint32_t aCount, bool aFlush,
const mozilla::LayoutDeviceIntPoint* pos,
nsIContent** aLastRolledUp) final;
void NotifyGeometryChange() final;
/**
* A combobox should roll up if a mousewheel event happens outside of
* the popup area.
*/
bool ShouldRollupOnMouseWheelEvent() final { return true; }
bool ShouldConsumeOnMouseWheelEvent() final { return false; }
/**
* A combobox should not roll up if activated by a mouse activate message
* (eg. X-mouse).
*/
bool ShouldRollupOnMouseActivate() final { return false; }
uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*>* aWidgetChain) final {
return 0;
}
nsIWidget* GetRollupWidget() final;
// nsIStatefulFrame
mozilla::UniquePtr<mozilla::PresState> SaveState() final;
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHOD RestoreState(mozilla::PresState* aState) final;
void GenerateStateKey(nsIContent* aContent, mozilla::dom::Document* aDocument,
nsACString& aKey) final;
static bool ToolkitHasNativePopup();
int32_t CharCountOfLargestOptionForInflation() const;
protected:
friend class RedisplayTextEvent;
friend class nsAsyncResize;
friend class nsResizeDropdownAtFinalPosition;
// Utilities
void ReflowDropdown(nsPresContext* aPresContext,
const ReflowInput& aReflowInput);
// Return true if we should render a dropdown button.
bool HasDropDownButton() const;
nscoord DropDownButtonISize();
@ -248,38 +200,25 @@ class nsComboboxControlFrame final : public nsBlockFrame,
nsComboboxControlFrame* mControlFrame;
};
/**
* Show or hide the dropdown list.
* @note This method might destroy |this|.
*/
MOZ_CAN_RUN_SCRIPT void ShowPopup(bool aShowPopup);
/**
* Show or hide the dropdown list.
* @param aShowList true to show, false to hide the dropdown.
* @note This method might destroy |this|.
* @return false if this frame is destroyed, true if still alive.
*/
MOZ_CAN_RUN_SCRIPT bool ShowList(bool aShowList);
void CheckFireOnChange();
void FireValueChangeEvent();
nsresult RedisplayText();
void HandleRedisplayTextEvent();
void ActuallyDisplayText(bool aNotify);
private:
// If our total transform to the root frame of the root document is only a 2d
// translation then return that translation, otherwise returns (0,0).
nsPoint GetCSSTransformTranslation();
protected:
mozilla::dom::HTMLSelectElement& Select() const;
void GetOptionText(uint32_t aIndex, nsAString& aText) const;
nsFrameList mPopupFrames; // additional named child list
RefPtr<nsTextNode> mDisplayContent; // Anonymous content used to display the
// current selection
RefPtr<Element> mButtonContent; // Anonymous content for the button
nsContainerFrame* mDisplayFrame; // frame to display selection
nsIFrame* mButtonFrame; // button frame
nsListControlFrame* mDropdownFrame; // dropdown list frame
// The inline size of our display area. Used by that frame's reflow
// to size to the full inline size except the drop-marker.
@ -296,24 +235,10 @@ class nsComboboxControlFrame final : public nsBlockFrame,
int32_t mDisplayedIndex;
nsString mDisplayedOptionTextOrPreview;
// make someone to listen to the button. If its programmatically pressed by
// someone like Accessibility then open or close the combo box.
nsCOMPtr<nsIDOMEventListener> mButtonListener;
RefPtr<mozilla::HTMLSelectEventListener> mEventListener;
// The last y-positions used for estimating available space before and
// after for the dropdown list in GetAvailableDropdownSpace. These are
// reset to nscoord_MIN in AbsolutelyPositionDropDown when placing the
// dropdown at its actual position. The GetAvailableDropdownSpace call
// from nsListControlFrame::ReflowAsDropdown use the last position.
nscoord mLastDropDownBeforeScreenBCoord;
nscoord mLastDropDownAfterScreenBCoord;
// Current state of the dropdown list, true is dropped down.
bool mDroppedDown;
// See comment in HandleRedisplayTextEvent().
bool mInRedisplayText;
// Acting on ShowDropDown(true) is delayed until we're focused.
bool mDelayedShowDropDown;
bool mIsOpenInParentProcess;
// static class data member for Bug 32920

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -24,17 +24,13 @@
#include "nsISelectControlFrame.h"
#include "nsSelectsAreaFrame.h"
// X.h defines KeyPress
#ifdef KeyPress
# undef KeyPress
#endif
class nsComboboxControlFrame;
class nsPresContext;
class nsListEventListener;
namespace mozilla {
class PresShell;
class HTMLSelectEventListener;
namespace dom {
class Event;
class HTMLOptionElement;
@ -86,6 +82,10 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
nsContainerFrame* GetContentInsertionFrame() final;
int32_t GetEndSelectionIndex() const { return mEndSelectionIndex; }
mozilla::dom::HTMLOptionElement* GetCurrentOption() const;
bool IsFrameOfType(uint32_t aFlags) const final {
return nsHTMLScrollFrame::IsFrameOfType(
aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
@ -108,9 +108,7 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
mozilla::a11y::AccType AccessibleType() final;
#endif
void SetComboboxFrame(nsIFrame* aComboboxFrame);
int32_t GetSelectedIndex();
HTMLOptionElement* GetCurrentOption();
/**
* Gets the text of the currently selected item.
@ -122,25 +120,7 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
void CaptureMouseEvents(bool aGrabMouseEvents);
nscoord GetBSizeOfARow();
uint32_t GetNumberOfOptions();
MOZ_CAN_RUN_SCRIPT_BOUNDARY void AboutToDropDown();
/**
* @note This method might destroy the frame, pres shell and other objects.
*/
void AboutToRollup();
/**
* Dispatch a DOM oninput and onchange event synchroniously.
* @note This method might destroy the frame, pres shell and other objects.
*/
MOZ_CAN_RUN_SCRIPT
void FireOnInputAndOnChange();
/**
* Makes aIndex the selected option of a combobox list.
* @note This method might destroy the frame, pres shell and other objects.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY void ComboboxFinish(int32_t aIndex);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void OnContentReset();
// nsISelectControlFrame
@ -164,17 +144,19 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
* @note These methods might destroy the frame, pres shell and other objects.
*/
MOZ_CAN_RUN_SCRIPT
nsresult MouseDown(mozilla::dom::Event* aMouseEvent);
nsresult HandleLeftButtonMouseDown(mozilla::dom::Event* aMouseEvent);
MOZ_CAN_RUN_SCRIPT
nsresult MouseUp(mozilla::dom::Event* aMouseEvent);
MOZ_CAN_RUN_SCRIPT
nsresult MouseMove(mozilla::dom::Event* aMouseEvent);
nsresult HandleLeftButtonMouseUp(mozilla::dom::Event* aMouseEvent);
MOZ_CAN_RUN_SCRIPT
nsresult DragMove(mozilla::dom::Event* aMouseEvent);
MOZ_CAN_RUN_SCRIPT
nsresult KeyDown(mozilla::dom::Event* aKeyEvent);
MOZ_CAN_RUN_SCRIPT
nsresult KeyPress(mozilla::dom::Event* aKeyEvent);
bool PerformSelection(int32_t aClickedIndex, bool aIsShift, bool aIsControl);
MOZ_CAN_RUN_SCRIPT
void UpdateSelectionAfterKeyEvent(int32_t aNewIndex, uint32_t aCharCode,
bool aIsShift, bool aIsControlOrMeta,
bool aIsControlSelectMode);
/**
* Returns the options collection for mContent, if any.
@ -185,8 +167,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
*/
HTMLOptionElement* GetOption(uint32_t aIndex) const;
static void ComboboxFocusSet();
// Helper
bool IsFocused() { return this == mFocused; }
@ -222,27 +202,11 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
nsHTMLScrollFrame::SetSuppressScrollbarUpdate(aSuppress);
}
/**
* Return whether the list is in dropdown mode.
*/
bool IsInDropDownMode() const;
/**
* Return the number of displayed rows in the list.
*/
uint32_t GetNumDisplayRows() const { return mNumDisplayRows; }
/**
* Return true if the drop-down list can display more rows.
* (always false if not in drop-down mode)
*/
bool GetDropdownCanGrow() const { return mDropdownCanGrow; }
/**
* Frees statics owned by this class.
*/
static void Shutdown();
#ifdef ACCESSIBILITY
/**
* Post a custom DOM event for the change, so that accessibility can
@ -254,13 +218,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
#endif
protected:
/**
* Return the first non-disabled option starting at aFromIndex (inclusive).
* @param aFoundIndex if non-null, set to the index of the returned option
*/
HTMLOptionElement* GetNonDisabledOptionFrom(int32_t aFromIndex,
int32_t* aFoundIndex = nullptr);
/**
* Updates the selected text in a combobox and then calls FireOnChange().
* @note This method might destroy the frame, pres shell and other objects.
@ -277,12 +234,7 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
nsGkAtoms::multiple);
}
/**
* Toggles (show/hide) the combobox dropdown menu.
* @note This method might destroy the frame, pres shell and other objects.
*/
MOZ_CAN_RUN_SCRIPT
void DropDownToggleKey(mozilla::dom::Event* aKeyEvent);
mozilla::dom::HTMLSelectElement& Select() const;
/**
* @return true if the <option> at aIndex is selectable by the user.
@ -299,27 +251,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
MOZ_CAN_RUN_SCRIPT void ScrollToIndex(int32_t anIndex);
/**
* When the user clicks on the comboboxframe to show the dropdown
* listbox, they then have to move the mouse into the list. We don't
* want to process those mouse events as selection events (i.e., to
* scroll list items into view). So we ignore the events until
* the mouse moves below our border-inner-edge, when
* mItemSelectionStarted is set.
*
* @param aPoint relative to this frame
*/
bool IgnoreMouseEventForSelection(mozilla::dom::Event* aEvent);
/**
* If the dropdown is showing and the mouse has moved below our
* border-inner-edge, then set mItemSelectionStarted.
*/
void UpdateInListState(mozilla::dom::Event* aEvent);
void AdjustIndexForDisabledOpt(int32_t aStartIndex, int32_t& anNewIndex,
int32_t aNumOptions, int32_t aDoAdjustInc,
int32_t aDoAdjustIncNext);
/**
* Resets the select back to it's original default values;
* those values as determined by the original HTML
@ -340,7 +271,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
int32_t& aCurIndex);
bool CheckIfAllFramesHere();
bool IsLeftButton(mozilla::dom::Event* aMouseEvent);
// guess at a row block size based on our own style.
nscoord CalcFallbackRowBSize(float aFontSizeInflation);
@ -353,15 +283,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
// Dropped down stuff
void SetComboboxItem(int32_t aIndex);
/**
* Method to reflow ourselves as a dropdown list. This differs from
* reflow as a listbox because the criteria for needing a second
* pass are different. This will be called from Reflow() as needed.
*/
void ReflowAsDropdown(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus);
// Selection
bool SetOptionsSelectedFromFrame(int32_t aStartIndex, int32_t aEndIndex,
bool aValue, bool aClearAll);
@ -372,20 +293,17 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
bool ExtendedSelection(int32_t aStartIndex, int32_t aEndIndex,
bool aClearAll);
MOZ_CAN_RUN_SCRIPT
bool PerformSelection(int32_t aClickedIndex, bool aIsShift, bool aIsControl);
MOZ_CAN_RUN_SCRIPT
bool HandleListSelection(mozilla::dom::Event* aDOMEvent,
int32_t selectedIndex);
void InitSelectionRange(int32_t aClickedIndex);
MOZ_CAN_RUN_SCRIPT
void PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode, bool aIsShift,
bool aIsControlOrMeta);
public:
nsSelectsAreaFrame* GetOptionsContainer() const {
return static_cast<nsSelectsAreaFrame*>(GetScrolledFrame());
}
static constexpr int32_t kNothingSelected = -1;
protected:
nscoord BSizeOfARow() { return GetOptionsContainer()->BSizeOfARow(); }
@ -394,21 +312,12 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
*/
uint32_t GetNumberOfRows();
nsView* GetViewInternal() const final { return mView; }
void SetViewInternal(nsView* aView) final { mView = aView; }
// Data Members
int32_t mStartSelectionIndex;
int32_t mEndSelectionIndex;
nsComboboxControlFrame* mComboboxFrame;
// The view is only created (& non-null) if IsInDropDownMode() is true.
nsView* mView;
uint32_t mNumDisplayRows;
bool mChangesSinceDragStart : 1;
bool mButtonDown : 1;
// Has the user selected a visible item since we showed the dropdown?
bool mItemSelectionStarted : 1;
@ -419,9 +328,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
bool mNeedToReset : 1;
bool mPostChildrenLoadedReset : 1;
// bool value for multiple discontiguous selection
bool mControlSelectMode : 1;
// True if we're in the middle of a reflow and might need a second
// pass. This only happens for auto heights.
bool mMightNeedSecondPass : 1;
@ -432,10 +338,6 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
*/
bool mHasPendingInterruptAtStartOfReflow : 1;
// True if the drop-down can show more rows. Always false if this list
// is not in drop-down mode.
bool mDropdownCanGrow : 1;
// True if the selection can be set to nothing or disabled options.
bool mForceSelection : 1;
@ -449,33 +351,13 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
// are translucent.
nscolor mLastDropdownBackstopColor;
RefPtr<nsListEventListener> mEventListener;
RefPtr<mozilla::HTMLSelectEventListener> mEventListener;
static nsListControlFrame* mFocused;
static mozilla::StaticAutoPtr<nsString> sIncrementalString;
#ifdef DO_REFLOW_COUNTER
int32_t mReflowId;
#endif
private:
// for incremental typing navigation
static nsAString& GetIncrementalString();
static DOMTimeStamp gLastKeyTime;
class MOZ_RAII AutoIncrementalSearchResetter {
public:
AutoIncrementalSearchResetter() : mCancelled(false) {}
~AutoIncrementalSearchResetter() {
if (!mCancelled) {
nsListControlFrame::GetIncrementalString().Truncate();
}
}
void Cancel() { mCancelled = true; }
private:
bool mCancelled;
};
};
#endif /* nsListControlFrame_h___ */

Просмотреть файл

@ -49,22 +49,6 @@ void nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
"Must have an nsListControlFrame! Frame constructor is "
"broken");
bool isInDropdownMode = list->IsInDropDownMode();
// See similar logic in nsListControlFrame::Reflow and
// nsListControlFrame::ReflowAsDropdown. We need to match it here.
WritingMode wm = aReflowInput.GetWritingMode();
nscoord oldBSize;
if (isInDropdownMode) {
// Store the block size now in case it changes during
// nsBlockFrame::Reflow for some odd reason.
if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
oldBSize = BSize(wm);
} else {
oldBSize = NS_UNCONSTRAINEDSIZE;
}
}
nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
// Check whether we need to suppress scrollbar updates. We want to do
@ -76,9 +60,7 @@ void nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
// comboboxes, we'll also need it if our block size changed. If
// we're going to do a second pass, suppress scrollbar updates for
// this pass.
if (newBSizeOfARow != mBSizeOfARow ||
(isInDropdownMode &&
(oldBSize != aDesiredSize.BSize(wm) || oldBSize != BSize(wm)))) {
if (newBSizeOfARow != mBSizeOfARow) {
mBSizeOfARow = newBSizeOfARow;
list->SetSuppressScrollbarUpdate(true);
}

Просмотреть файл

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window title="Test Select Dropdown Positioning in Fullscreen Window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<body xmlns="http://www.w3.org/1999/xhtml">
<select id="select" style="-moz-appearance:none">
<option id="optiona">a</option>
<option>b</option>
</select>
</body>
</window>

Просмотреть файл

@ -3,9 +3,5 @@ skip-if = os == 'android'
support-files =
bug536567_iframe.html
bug536567_subframe.html
bug665540_window.xhtml
[test_bug536567_perwindowpb.html]
[test_bug665540.html]
tags = fullscreen
skip-if = (os == 'linux' && bits == 64) # Bug 888164

Просмотреть файл

@ -1,133 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=665540
-->
<head>
<title>Test for Bug 665540 Select dropdown position in fullscreen window</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body onload="openFullscreenWindow()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=665540">Mozilla Bug 665540</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 665540 **/
SimpleTest.waitForExplicitFinish();
var win;
var select;
var optiona;
var eventType = "mouseover";
var timeoutID;
var eventOffsetX = 2;
var eventOffsetY = 2;
let sizemodeChanged = false;
let fullscreenChanged = false;
let childHasFocused = false;
function openFullscreenWindow() {
win = window.browsingContext.topChromeWindow
.openDialog("bug665540_window.xhtml", "_blank", "resizable=yes,chrome");
win.addEventListener("sizemodechange", () => {
info("sizemodechange. windowState = " + win.windowState + " fullScreen = " + win.fullScreen);
sizemodeChanged = true;
tryStart();
}, { once: true });
win.addEventListener("fullscreen", () => {
info("fullscreen event. windowState = " + win.windowState + " fullScreen = " + win.fullScreen);
fullscreenChanged = true;
tryStart();
}, { once: true });
SimpleTest.waitForFocus(() => {
win.fullScreen = true;
}, win);
// Close our window if the test times out so that it doesn't interfere
// with later tests.
timeoutID = setTimeout(function () {
ok(false, "Test timed out.");
// Provide some time for a screenshot
setTimeout(finish, 1000);
}, 20000);
}
function tryStart() {
// wait until the window goes full screen and its size mode actually changes.
if (!sizemodeChanged || !fullscreenChanged) {
return;
}
SimpleTest.waitForFocus(start, win);
}
function start() {
// The select doesn't open if the mouse click is fired too soon
// (on X11 at least).
setTimeout(openSelect, 1000);
}
function openSelect() {
select = win.document.getElementById("select");
synthesizeMouseAtCenter(select, {}, win);
// A yield was required on X11 tinderbox machines.
// (Wasn't required on other platforms nor on an X11 system with kwin.)
setTimeout(checkPosition, 1000);
}
function checkPosition() {
optiona = win.document.getElementById("optiona");
optiona.addEventListener(eventType, eventReceived);
// If the select dropdown is opened in the position where
// getBoundingClientRect() predicts, then optiona will receive the event.
// The event is received asynchronously (I don't know why), so the handler
// is removed later.
synthesizeMouse(optiona, eventOffsetX, eventOffsetY,
{ type: eventType }, win);
}
function eventReceived(event) {
clearTimeout(timeoutID);
optiona.removeEventListener(eventType, eventReceived);
var rect = optiona.getBoundingClientRect();
// Note that fullscreen only fully covers one monitor, so win.screenX can
// be non-zero.
is(event.screenX, win.screenX + rect.left + eventOffsetX,
"event.screenX should match sent event");
is(event.screenY, win.screenY + rect.top + eventOffsetY,
"event.screenY should match sent event");
finish();
}
function finish() {
if (select && navigator.platform.includes("Win")) {
todo(false,
"Should not have to close select before closing its window");
// This avoids mochitest "Unable to restore focus" errors (bug 670053).
synthesizeMouseAtCenter(select, {}, win);
}
is(win.windowState, win.STATE_FULLSCREEN,
"window state should still be fullscreen");
win.close();
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -19,18 +19,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1498769
SimpleTest.waitForFocus(function() {
[...document.querySelectorAll('select')].forEach(function(e) {
e.focus();
is(e.selectedIndex, 1, "the 'selected' attribute is respected");
const description = ` (size ${e.size})`;
is(e.selectedIndex, 1, "the 'selected' attribute is respected" + description);
if (kIsMac && e.size == "1") {
// On OSX, UP/DOWN opens the dropdown menu rather than changing
// the value so we skip the rest of this test there in this case.
return;
}
synthesizeKey("VK_DOWN", {});
is(e.selectedIndex, 2, "VK_DOWN selected the first option below");
is(e.selectedIndex, 2, "VK_DOWN selected the first option below" + description);
synthesizeKey("VK_UP", {});
is(e.selectedIndex, 0, "VK_UP skips the display:none/contents option");
is(e.selectedIndex, 0, "VK_UP skips the display:none/contents option" + description);
synthesizeKey("VK_DOWN", {});
is(e.selectedIndex, 2, "VK_DOWN skips the display:none/contents option");
is(e.selectedIndex, 2, "VK_DOWN skips the display:none/contents option" + description);
});
SimpleTest.finish();
});

Просмотреть файл

@ -352,8 +352,8 @@ void nsFontInflationData::ScanTextIn(nsIFrame* aFrame) {
// Don't just recurse down to the list control inside, since we
// need to exclude the display frame.
nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
int32_t charCount = CharCountOfLargestOption(
static_cast<nsComboboxControlFrame*>(kid)->GetDropDown());
int32_t charCount = static_cast<nsComboboxControlFrame*>(kid)
->CharCountOfLargestOptionForInflation();
mTextAmount += charCount * fontSize;
} else if (fType == LayoutFrameType::ListControl) {
// See textInputFrame above (with s/amount of text/selected option/).

Просмотреть файл

@ -1616,26 +1616,15 @@ nsMargin ScrollFrameHelper::GetDesiredScrollbarSizes(nsBoxLayoutState* aState) {
return result;
}
nscoord ScrollFrameHelper::GetNondisappearingScrollbarWidth(
nsBoxLayoutState* aState, WritingMode aWM) {
NS_ASSERTION(aState && aState->GetRenderingContext(),
"Must have rendering context in layout state for size "
"computations");
bool verticalWM = aWM.IsVertical();
// We need to have the proper un-themed scrollbar size, regardless of whether
// we're using e.g. scrollbar-width: thin, or overlay scrollbars.
nsIFrame* box = verticalWM ? mHScrollbarBox : mVScrollbarBox;
if (box) {
auto sizes = aState->PresContext()->Theme()->GetScrollbarSizes(
aState->PresContext(), StyleScrollbarWidth::Auto,
nsITheme::Overlay::No);
return aState->PresContext()->DevPixelsToAppUnits(
verticalWM ? sizes.mHorizontal : sizes.mVertical);
}
nsMargin sizes(GetDesiredScrollbarSizes(aState));
return verticalWM ? sizes.TopBottom() : sizes.LeftRight();
nscoord nsIScrollableFrame::GetNondisappearingScrollbarWidth(nsPresContext* aPc,
WritingMode aWM) {
// We use this to size the combobox dropdown button. For that, we need to have
// the proper big, non-overlay scrollbar size, regardless of whether we're
// using e.g. scrollbar-width: thin, or overlay scrollbars.
auto sizes = aPc->Theme()->GetScrollbarSizes(aPc, StyleScrollbarWidth::Auto,
nsITheme::Overlay::No);
return aPc->DevPixelsToAppUnits(aWM.IsVertical() ? sizes.mHorizontal
: sizes.mVertical);
}
void ScrollFrameHelper::HandleScrollbarStyleSwitching() {

Просмотреть файл

@ -382,8 +382,6 @@ class ScrollFrameHelper : public nsIReflowCallback {
nsIScrollableFrame::ScrollbarSizesOptions aOptions =
nsIScrollableFrame::ScrollbarSizesOptions::NONE) const;
nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState);
nscoord GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState,
mozilla::WritingMode aVerticalWM);
bool IsPhysicalLTR() const {
return mOuter->GetWritingMode().IsPhysicalLTR();
}
@ -988,12 +986,6 @@ class nsHTMLScrollFrame : public nsContainerFrame,
nsBoxLayoutState bls(aPresContext, aRC, 0);
return GetDesiredScrollbarSizes(&bls);
}
nscoord GetNondisappearingScrollbarWidth(nsPresContext* aPresContext,
gfxContext* aRC,
mozilla::WritingMode aWM) final {
nsBoxLayoutState bls(aPresContext, aRC, 0);
return mHelper.GetNondisappearingScrollbarWidth(&bls, aWM);
}
nsSize GetLayoutSize() const final { return mHelper.GetLayoutSize(); }
nsRect GetScrolledRect() const final { return mHelper.GetScrolledRect(); }
nsRect GetScrollPortRect() const final { return mHelper.GetScrollPortRect(); }
@ -1462,12 +1454,6 @@ class nsXULScrollFrame final : public nsBoxFrame,
nsBoxLayoutState bls(aPresContext, aRC, 0);
return GetDesiredScrollbarSizes(&bls);
}
nscoord GetNondisappearingScrollbarWidth(nsPresContext* aPresContext,
gfxContext* aRC,
mozilla::WritingMode aWM) final {
nsBoxLayoutState bls(aPresContext, aRC, 0);
return mHelper.GetNondisappearingScrollbarWidth(&bls, aWM);
}
nsSize GetLayoutSize() const final { return mHelper.GetLayoutSize(); }
nsRect GetScrolledRect() const final { return mHelper.GetScrolledRect(); }
nsRect GetScrollPortRect() const final { return mHelper.GetScrollPortRect(); }

Просмотреть файл

@ -143,9 +143,8 @@ class nsIScrollableFrame : public nsIScrollbarMediator {
/**
* Return the width for non-disappearing scrollbars.
*/
virtual nscoord GetNondisappearingScrollbarWidth(
nsPresContext* aPresContext, gfxContext* aRC,
mozilla::WritingMode aWM) = 0;
static nscoord GetNondisappearingScrollbarWidth(nsPresContext*,
mozilla::WritingMode);
/**
* Get the layout size of this frame.
* Note that this is a value which is not expanded by the minimum scale size.

Просмотреть файл

@ -341,45 +341,51 @@ template bool nsTextFrameUtils::IsSkippableCharacterForTransformText(
template bool nsTextFrameUtils::IsSkippableCharacterForTransformText(
char16_t aChar);
uint32_t nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
Text* aText, const nsStyleText* aStyleText) {
const nsTextFragment* frag = &aText->TextFragment();
template <typename CharT>
static uint32_t DoComputeApproximateLengthWithWhitespaceCompression(
const CharT* aChars, uint32_t aLength, const nsStyleText* aStyleText) {
// This is an approximation so we don't really need anything
// too fancy here.
uint32_t len;
if (aStyleText->WhiteSpaceIsSignificant()) {
len = frag->GetLength();
} else {
bool is2b = frag->Is2b();
union {
const char* s1b;
const char16_t* s2b;
} u;
if (is2b) {
u.s2b = frag->Get2b();
} else {
u.s1b = frag->Get1b();
}
bool prevWS = true; // more important to ignore blocks with
// only whitespace than get inline boundaries
// exactly right
len = 0;
for (uint32_t i = 0, i_end = frag->GetLength(); i < i_end; ++i) {
char16_t c = is2b ? u.s2b[i] : u.s1b[i];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
if (!prevWS) {
++len;
}
prevWS = true;
} else {
return aLength;
}
bool prevWS = true; // more important to ignore blocks with
// only whitespace than get inline boundaries
// exactly right
len = 0;
for (uint32_t i = 0; i < aLength; ++i) {
CharT c = aChars[i];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
if (!prevWS) {
++len;
prevWS = false;
}
prevWS = true;
} else {
++len;
prevWS = false;
}
}
return len;
}
uint32_t nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
Text* aText, const nsStyleText* aStyleText) {
const nsTextFragment* frag = &aText->TextFragment();
if (frag->Is2b()) {
return DoComputeApproximateLengthWithWhitespaceCompression(
frag->Get2b(), frag->GetLength(), aStyleText);
}
return DoComputeApproximateLengthWithWhitespaceCompression(
frag->Get1b(), frag->GetLength(), aStyleText);
}
uint32_t nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
const nsAString& aString, const nsStyleText* aStyleText) {
return DoComputeApproximateLengthWithWhitespaceCompression(
aString.BeginReading(), aString.Length(), aStyleText);
}
bool nsSkipCharsRunIterator::NextRun() {
do {
if (mRunLength) {

Просмотреть файл

@ -146,7 +146,9 @@ class nsTextFrameUtils {
}
static uint32_t ComputeApproximateLengthWithWhitespaceCompression(
mozilla::dom::Text* aText, const nsStyleText* aStyleText);
mozilla::dom::Text*, const nsStyleText*);
static uint32_t ComputeApproximateLengthWithWhitespaceCompression(
const nsAString&, const nsStyleText*);
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrameUtils::Flags)

Просмотреть файл

@ -1034,7 +1034,6 @@ pref("dom.forms.selectSearch", false);
#else
pref("dom.forms.select.customstyling", true);
#endif
pref("dom.select_popup_in_parent.enabled", false);
pref("dom.cycle_collector.incremental", true);

Просмотреть файл

@ -0,0 +1,5 @@
<!doctype html>
<title>CSS Test Reference</title>
<select>
<option>ABC</option>
</select>

Просмотреть файл

@ -0,0 +1,6 @@
<!doctype html>
<title>Select should be as wide as needed to fit its options regardless of option styles</title>
<link rel=match href=select-intrinsic-option-font-size-ref.html>
<select>
<option style="font-size: 5px">ABC</option>
</select>

Просмотреть файл

@ -28,10 +28,15 @@ async function setupPrinters(helper) {
async function changeDestination(helper, dir) {
let picker = helper.get("printer-picker");
let changed = BrowserTestUtils.waitForEvent(picker, "change");
let pickerOpened = BrowserTestUtils.waitForEvent(
document.getElementById("ContentSelectDropdown"),
"popupshown"
);
picker.focus();
EventUtils.sendKey("space", helper.win);
EventUtils.sendKey(dir, helper.win);
EventUtils.sendKey("return", helper.win);
await pickerOpened;
EventUtils.sendKey(dir, window);
EventUtils.sendKey("return", window);
await changed;
}

Просмотреть файл

@ -4,24 +4,32 @@
"use strict";
function changeToOption(helper, index) {
return helper.waitForSettingsEvent(function() {
return helper.waitForSettingsEvent(async function() {
let select = helper.get("duplex-select");
select.focus();
select.scrollIntoView({ block: "center" });
let popupOpen = BrowserTestUtils.waitForEvent(
document.getElementById("ContentSelectDropdown"),
"popupshown"
);
EventUtils.sendKey("space", helper.win);
await popupOpen;
let selectedIndex = select.selectedIndex;
info(`Looking for ${index} from ${selectedIndex}`);
while (selectedIndex != index) {
if (index > selectedIndex) {
EventUtils.sendKey("down", helper.win);
EventUtils.sendKey("down", window);
selectedIndex++;
} else {
EventUtils.sendKey("up", helper.win);
EventUtils.sendKey("up", window);
selectedIndex--;
}
}
EventUtils.sendKey("return", helper.win);
EventUtils.sendKey("return", window);
});
}

Просмотреть файл

@ -3,33 +3,44 @@
"use strict";
function changeDefaultToCustom(helper) {
async function changeMargin(helper, scroll, value) {
let marginSelect = helper.get("margins-picker");
info(" current value is " + marginSelect.value);
marginSelect.focus();
marginSelect.scrollIntoView({ block: "center" });
EventUtils.sendKey("space", helper.win);
EventUtils.sendKey("down", helper.win);
EventUtils.sendKey("down", helper.win);
EventUtils.sendKey("down", helper.win);
EventUtils.sendKey("return", helper.win);
if (scroll) {
marginSelect.scrollIntoView({ block: "center" });
}
marginSelect.value = value;
marginSelect.dispatchEvent(
new marginSelect.ownerGlobal.Event("input", {
bubbles: true,
composed: true,
})
);
marginSelect.dispatchEvent(
new marginSelect.ownerGlobal.Event("change", {
bubbles: true,
})
);
}
function changeDefaultToCustom(helper) {
info("Trying to change margin from default -> custom");
return changeMargin(helper, true, "custom");
}
function changeCustomToDefault(helper) {
let marginSelect = helper.get("margins-picker");
marginSelect.focus();
EventUtils.sendKey("space", helper.win);
EventUtils.sendKey("up", helper.win);
EventUtils.sendKey("up", helper.win);
EventUtils.sendKey("up", helper.win);
EventUtils.sendKey("return", helper.win);
info("Trying to change margin from custom -> default");
return changeMargin(helper, false, "default");
}
function changeCustomToNone(helper) {
let marginSelect = helper.get("margins-picker");
marginSelect.focus();
EventUtils.sendKey("space", helper.win);
EventUtils.sendKey("up", helper.win);
EventUtils.sendKey("return", helper.win);
info("Trying to change margin from custom -> none");
return changeMargin(helper, false, "none");
}
function assertPendingMarginsUpdate(helper) {
@ -101,7 +112,7 @@ add_task(async function testCustomMarginMaxAttrsSet() {
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
let marginsSelect = helper.get("margins-select");
is(
@ -156,7 +167,7 @@ add_task(async function testPresetMargins() {
is(marginSelect.value, "default", "Default margins set");
helper.assertSettingsMatch({ honorPageRuleMargins: true });
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
is(marginSelect.value, "custom", "Custom margins are now set");
ok(!customMargins.hidden, "Custom margins are present");
@ -205,7 +216,7 @@ add_task(async function testHeightError() {
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.assertSettingsNotChanged(
{ marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
@ -228,7 +239,7 @@ add_task(async function testWidthError() {
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.assertSettingsNotChanged(
{ marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
@ -251,7 +262,7 @@ add_task(async function testInvalidMarginsReset() {
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
let marginError = helper.get("error-invalid-margin");
await helper.assertSettingsNotChanged(
@ -267,13 +278,13 @@ add_task(async function testInvalidMarginsReset() {
}
);
this.changeCustomToDefault(helper);
await changeCustomToDefault(helper);
assertNoPendingMarginsUpdate(helper);
await BrowserTestUtils.waitForCondition(
() => marginError.hidden,
"Wait for margin error to be hidden"
);
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
helper.assertSettingsMatch({
marginTop: 0.5,
marginRight: 0.5,
@ -319,7 +330,7 @@ add_task(async function testChangeInvalidToValidUpdate() {
await setupLetterPaper();
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
let marginError = helper.get("error-invalid-margin");
@ -367,7 +378,7 @@ add_task(async function testChangeInvalidCanRevalidate() {
await setupLetterPaper();
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
let marginError = helper.get("error-invalid-margin");
@ -423,7 +434,7 @@ add_task(async function testCustomMarginsPersist() {
{ marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
{ marginTop: 0.25, marginRight: 1, marginBottom: 2, marginLeft: 0 },
async () => {
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
await helper.text(helper.get("custom-margin-top"), "0.25");
@ -523,7 +534,7 @@ add_task(async function testChangingBetweenMargins() {
{ marginLeft: 0.5 },
async () => {
let settingsChanged = helper.waitForSettingsEvent();
changeCustomToDefault(helper);
await changeCustomToDefault(helper);
await settingsChanged;
}
);
@ -536,7 +547,7 @@ add_task(async function testChangingBetweenMargins() {
{ marginLeft: 1 },
async () => {
let settingsChanged = helper.waitForSettingsEvent();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await settingsChanged;
}
);
@ -549,7 +560,7 @@ add_task(async function testChangingBetweenMargins() {
{ marginLeft: 0.5 },
async () => {
let settingsChanged = helper.waitForSettingsEvent();
changeCustomToDefault(helper);
await changeCustomToDefault(helper);
await settingsChanged;
}
);
@ -569,7 +580,7 @@ add_task(async function testChangeHonoredInPrint() {
await helper.openMoreSettings();
helper.assertSettingsMatch({ marginRight: 0.5 });
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.withClosingFn(async () => {
await helper.text(helper.get("custom-margin-right"), "1");
@ -638,7 +649,7 @@ add_task(async function testRevalidateSwitchToNone() {
await setupLetterPaper();
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
await helper.text(helper.get("custom-margin-bottom"), "6");
@ -669,7 +680,7 @@ add_task(async function testRevalidateSwitchToNone() {
{ marginTop: 6, marginRight: 0.5, marginBottom: 3, marginLeft: 0.5 },
{ marginTop: 0, marginRight: 0, marginBottom: 0, marginLeft: 0 },
async () => {
this.changeCustomToNone(helper);
await changeCustomToNone(helper);
is(
helper.get("margins-picker").value,
"none",
@ -701,7 +712,7 @@ add_task(async function testInvalidMarginResetAfterDestinationChange() {
let destinationPicker = helper.get("printer-picker");
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
let marginError = helper.get("error-invalid-margin");
@ -743,7 +754,7 @@ add_task(async function testRevalidateCustomMarginsAfterPaperChanges() {
await helper.startPrint();
helper.dispatchSettingsChange({ paperId: "iso_a3" });
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
let marginError = helper.get("error-invalid-margin");
@ -784,7 +795,7 @@ add_task(async function testRevalidateCustomMarginsAfterOrientationChanges() {
await setupLetterPaper();
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
let marginError = helper.get("error-invalid-margin");
@ -821,7 +832,7 @@ add_task(async function testResetMarginPersists() {
await helper.startPrint();
await helper.openMoreSettings();
changeDefaultToCustom(helper);
await changeDefaultToCustom(helper);
await helper.awaitAnimationFrame();
let marginError = helper.get("error-invalid-margin");

Просмотреть файл

@ -3,20 +3,34 @@
"use strict";
function changeRangeTo(helper, destination) {
async function changeRangeTo(helper, destination) {
let rangeSelect = helper.get("range-picker");
let options = getRangeOptions(helper);
let numberMove =
options.indexOf(destination) - options.indexOf(rangeSelect.value);
let direction = numberMove > 0 ? "down" : "up";
if (!numberMove) {
return;
}
let input = BrowserTestUtils.waitForEvent(rangeSelect, "input");
let popupOpen = BrowserTestUtils.waitForEvent(
document.getElementById("ContentSelectDropdown"),
"popupshown"
);
rangeSelect.focus();
rangeSelect.scrollIntoView({ block: "center" });
EventUtils.sendKey("space", helper.win);
await popupOpen;
for (let i = Math.abs(numberMove); i > 0; i--) {
EventUtils.sendKey(direction, helper.win);
EventUtils.sendKey(direction, window);
}
EventUtils.sendKey("return", helper.win);
EventUtils.sendKey("return", window);
await input;
}
function getRangeOptions(helper) {
@ -43,7 +57,7 @@ add_task(async function testRangeResetAfterScale() {
await helper.setupMockPrint();
helper.mockFilePicker("changeRangeFromScale.pdf");
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
await helper.openMoreSettings();
let scaleRadio = helper.get("percent-scale-choice");
@ -83,7 +97,7 @@ add_task(async function testRangeResetAfterPaperSize() {
await helper.waitForPreview(() => helper.text(percentScale, "200"));
let customRange = helper.get("custom-range");
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
await BrowserTestUtils.waitForAttributeRemoval("hidden", customRange);
let rangeError = helper.get("error-invalid-range");
@ -116,7 +130,7 @@ add_task(async function testInvalidRangeResetAfterDestinationChange() {
let customPageRange = helper.get("custom-range");
await helper.assertSettingsNotChanged({ pageRanges: [] }, async () => {
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
});
let rangeError = helper.get("error-invalid-range");
@ -151,7 +165,7 @@ add_task(async function testPageRangeSets() {
ok(customRange.hidden, "Custom range input is hidden");
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
await BrowserTestUtils.waitForAttributeRemoval("hidden", customRange);
ok(!customRange.hidden, "Custom range is showing");
@ -216,11 +230,11 @@ add_task(async function testPageRangeSelect() {
let pageRangeInput = helper.get("page-range-input");
changeRangeTo(helper, "all");
await changeRangeTo(helper, "all");
let pageRanges = pageRangeInput.formatPageRange();
ok(!pageRanges.length, "Page range for all should be []");
changeRangeTo(helper, "odd");
await changeRangeTo(helper, "odd");
pageRanges = pageRangeInput.formatPageRange();
ok(
pageRanges.length == 4 &&
@ -228,7 +242,7 @@ add_task(async function testPageRangeSelect() {
"Page range for odd should be [1, 1, 3, 3]"
);
changeRangeTo(helper, "even");
await changeRangeTo(helper, "even");
pageRanges = pageRangeInput.formatPageRange();
ok(
pageRanges.length == 2 &&
@ -242,7 +256,7 @@ add_task(async function testRangeError() {
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
let invalidError = helper.get("error-invalid-range");
let invalidOverflowError = helper.get("error-invalid-start-range-overflow");
@ -265,7 +279,7 @@ add_task(async function testStartOverflowRangeError() {
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
await helper.openMoreSettings();
let scaleRadio = helper.get("percent-scale-choice");
@ -297,7 +311,7 @@ add_task(async function testErrorClearedAfterSwitchingToAll() {
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
let customRange = helper.get("custom-range");
let rangeError = helper.get("error-invalid-range");
@ -308,7 +322,7 @@ add_task(async function testErrorClearedAfterSwitchingToAll() {
await BrowserTestUtils.waitForAttributeRemoval("hidden", rangeError);
ok(!rangeError.hidden, "Generic error message is showing");
changeRangeTo(helper, "all");
await changeRangeTo(helper, "all");
await BrowserTestUtils.waitForCondition(
() => rangeError.hidden,
@ -403,7 +417,7 @@ add_task(async function testPageCountChangeRangeNoRerender() {
]);
await helper.waitForPreview(async () => {
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
helper.text(helper.get("custom-range"), "1");
});
}
@ -460,7 +474,7 @@ add_task(async function testPageCountChangeRangeRerender() {
]);
await helper.waitForPreview(async () => {
changeRangeTo(helper, "custom");
await changeRangeTo(helper, "custom");
helper.text(helper.get("custom-range"), "1-");
});
}

Просмотреть файл

@ -294,14 +294,28 @@ add_task(async function testPagesPerSheetCount() {
let pagesPerSheet = helper.get("pages-per-sheet-picker");
ok(BrowserTestUtils.is_visible(pagesPerSheet), "Pages per sheet is shown");
pagesPerSheet.focus();
let popupOpen = BrowserTestUtils.waitForEvent(
document.getElementById("ContentSelectDropdown"),
"popupshown"
);
EventUtils.sendKey("space", helper.win);
for (let i = 0; i < 7; i++) {
EventUtils.sendKey("down", helper.win);
if (pagesPerSheet.value == 16) {
await popupOpen;
let numberMove =
[...pagesPerSheet.options].map(o => o.value).indexOf("16") -
pagesPerSheet.selectedIndex;
for (let i = 0; i < numberMove; i++) {
EventUtils.sendKey("down", window);
if (document.activeElement.value == 16) {
break;
}
}
await helper.waitForPreview(() => EventUtils.sendKey("return", helper.win));
await helper.waitForPreview(() => EventUtils.sendKey("return", window));
sheets = helper.sheetCount;
is(sheets, 1, "There's only one sheet now");

Просмотреть файл

@ -1203,7 +1203,7 @@ nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
if (isHTML) {
nsComboboxControlFrame* ccf = do_QueryFrame(aFrame);
isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
isOpen = (ccf && ccf->IsDroppedDown());
} else
isOpen = IsOpenButton(aFrame);
@ -2981,7 +2981,7 @@ nsresult nsNativeThemeWin::ClassicGetThemePartAndState(
if (isHTML) {
nsComboboxControlFrame* ccf = do_QueryFrame(aFrame);
isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
isOpen = (ccf && ccf->IsDroppedDown());
} else
isOpen = IsOpenButton(aFrame);