diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp index 9c00ebe6ffe..45fcd5b61d7 100644 --- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -1635,6 +1635,87 @@ void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem) } } +void nsMenuPopupFrame::ChangeByPage(bool aIsUp) +{ + nsIFrame* parentMenu = GetParent(); + if (parentMenu) { + // Only scroll by page within menulists. + nsCOMPtr menulist = do_QueryInterface(parentMenu->GetContent()); + if (!menulist) { + return; + } + } + + nsMenuFrame* newMenu = nullptr; + nsIFrame* currentMenu = mCurrentMenu; + if (!currentMenu) { + // If there is no current menu item, get the first item. When moving up, + // just use this as the newMenu and leave currentMenu null so that no + // check for a later element is performed. When moving down, set currentMenu + // so that we look for one page down from the first item. + newMenu = nsXULPopupManager::GetNextMenuItem(this, nullptr, true); + if (!aIsUp) { + currentMenu = newMenu; + } + } + + if (currentMenu) { + nscoord scrollHeight = mRect.height; + nsIScrollableFrame *scrollframe = GetScrollFrame(this); + if (scrollframe) { + scrollHeight = scrollframe->GetScrollPortRect().height; + } + + // Get the position of the current item and add or subtract one popup's + // height to or from it. + nscoord targetPosition = aIsUp ? currentMenu->GetRect().YMost() - scrollHeight : + currentMenu->GetRect().y + scrollHeight; + + // Indicates that the last visible child was a valid menuitem. + bool lastWasValid = false; + + // Look for the next child which is just past the target position. This child + // will need to be selected. + while (currentMenu) { + // Only consider menu frames. + nsMenuFrame* menuFrame = do_QueryFrame(currentMenu); + if (menuFrame && + nsXULPopupManager::IsValidMenuItem(PresContext(), menuFrame->GetContent(), true)) { + + // If the right position was found, break out. Otherwise, look for another item. + if ((!aIsUp && currentMenu->GetRect().YMost() > targetPosition) || + (aIsUp && currentMenu->GetRect().y < targetPosition)) { + + // If the last visible child was not a valid menuitem or was disabled, + // use this as the menu to select, skipping over any non-valid items at + // the edge of the page. + if (!lastWasValid) { + newMenu = menuFrame; + } + + break; + } + + // Assign this item to newMenu. This item will be selected in case we + // don't find any more. + lastWasValid = true; + newMenu = menuFrame; + } + else { + lastWasValid = false; + } + + currentMenu = aIsUp ? currentMenu->GetPrevSibling() : + currentMenu->GetNextSibling(); + } + } + + // Select the new menuitem. + if (newMenu) { + ChangeMenuItem(newMenu, false); + } +} + NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) { if (mCurrentMenu == aMenuItem) diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h index d68ed46dff7..4133af10cb0 100644 --- a/layout/xul/nsMenuPopupFrame.h +++ b/layout/xul/nsMenuPopupFrame.h @@ -326,6 +326,8 @@ public: void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame); + void ChangeByPage(bool aIsUp); + // Move the popup to the screen coordinate (aLeft, aTop) in CSS pixels. // If aUpdateAttrs is true, and the popup already has left or top attributes, // then those attributes are updated to the new location. diff --git a/layout/xul/nsXULPopupManager.cpp b/layout/xul/nsXULPopupManager.cpp index b4e05a0804a..ff1b8365c10 100644 --- a/layout/xul/nsXULPopupManager.cpp +++ b/layout/xul/nsXULPopupManager.cpp @@ -2172,6 +2172,13 @@ nsXULPopupManager::HandleKeyboardEventWithKeyCode( HandleKeyboardNavigation(keyCode); break; + case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: + case nsIDOMKeyEvent::DOM_VK_PAGE_UP: + if (aTopVisibleMenuItem) { + aTopVisibleMenuItem->Frame()->ChangeByPage(keyCode == nsIDOMKeyEvent::DOM_VK_PAGE_UP); + } + break; + case nsIDOMKeyEvent::DOM_VK_ESCAPE: // Pressing Escape hides one level of menus only. If no menu is open, // check if a menubar is active and inform it that a menu closed. Even diff --git a/toolkit/content/tests/chrome/chrome.ini b/toolkit/content/tests/chrome/chrome.ini index 7d5a53dd31c..24e2f2a5977 100644 --- a/toolkit/content/tests/chrome/chrome.ini +++ b/toolkit/content/tests/chrome/chrome.ini @@ -116,6 +116,7 @@ skip-if = buildapp == 'mulet' skip-if = buildapp == 'mulet' [test_menulist_keynav.xul] [test_menulist_null_value.xul] +[test_menulist_paging.xul] [test_mousecapture.xul] skip-if = buildapp == 'mulet' [test_mousescroll.xul] diff --git a/toolkit/content/tests/chrome/test_menulist_paging.xul b/toolkit/content/tests/chrome/test_menulist_paging.xul new file mode 100644 index 00000000000..54cbadf1b96 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_paging.xul @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+

+ +
+
+ + +