1/* 2 * This file is part of the select element renderer in WebCore. 3 * 4 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 5 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 6 * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25#include "config.h" 26#include "RenderMenuList.h" 27 28#include "AXObjectCache.h" 29#include "AccessibilityMenuList.h" 30#include "CSSFontSelector.h" 31#include "Chrome.h" 32#include "FontCache.h" 33#include "Frame.h" 34#include "FrameView.h" 35#include "HTMLNames.h" 36#include "HTMLOptionElement.h" 37#include "HTMLOptGroupElement.h" 38#include "HTMLSelectElement.h" 39#include "NodeRenderStyle.h" 40#include "Page.h" 41#include "PopupMenu.h" 42#include "RenderBR.h" 43#include "RenderScrollbar.h" 44#include "RenderTheme.h" 45#include "Settings.h" 46#include "StyleResolver.h" 47#include "TextRun.h" 48#include <math.h> 49 50using namespace std; 51 52namespace WebCore { 53 54using namespace HTMLNames; 55 56RenderMenuList::RenderMenuList(Element* element) 57 : RenderFlexibleBox(element) 58 , m_buttonText(0) 59 , m_innerBlock(0) 60 , m_optionsChanged(true) 61 , m_optionsWidth(0) 62 , m_lastActiveIndex(-1) 63 , m_popupIsVisible(false) 64{ 65 ASSERT(element); 66 ASSERT(element->isHTMLElement()); 67 ASSERT(element->hasTagName(HTMLNames::selectTag)); 68} 69 70RenderMenuList::~RenderMenuList() 71{ 72 if (m_popup) 73 m_popup->disconnectClient(); 74 m_popup = 0; 75} 76 77bool RenderMenuList::canBeReplacedWithInlineRunIn() const 78{ 79 return false; 80} 81 82void RenderMenuList::createInnerBlock() 83{ 84 if (m_innerBlock) { 85 ASSERT(firstChild() == m_innerBlock); 86 ASSERT(!m_innerBlock->nextSibling()); 87 return; 88 } 89 90 // Create an anonymous block. 91 ASSERT(!firstChild()); 92 m_innerBlock = createAnonymousBlock(); 93 adjustInnerStyle(); 94 RenderFlexibleBox::addChild(m_innerBlock); 95} 96 97void RenderMenuList::adjustInnerStyle() 98{ 99 RenderStyle* innerStyle = m_innerBlock->style(); 100 innerStyle->setFlexGrow(1); 101 innerStyle->setFlexShrink(1); 102 // min-width: 0; is needed for correct shrinking. 103 // FIXME: Remove this line when https://bugs.webkit.org/show_bug.cgi?id=111790 is fixed. 104 innerStyle->setMinWidth(Length(0, Fixed)); 105 // Use margin:auto instead of align-items:center to get safe centering, i.e. 106 // when the content overflows, treat it the same as align-items: flex-start. 107 // But we only do that for the cases where html.css would otherwise use center. 108 if (style()->alignItems() == AlignCenter) { 109 innerStyle->setMarginTop(Length()); 110 innerStyle->setMarginBottom(Length()); 111 innerStyle->setAlignSelf(AlignFlexStart); 112 } 113 114 innerStyle->setPaddingLeft(Length(theme()->popupInternalPaddingLeft(style()), Fixed)); 115 innerStyle->setPaddingRight(Length(theme()->popupInternalPaddingRight(style()), Fixed)); 116 innerStyle->setPaddingTop(Length(theme()->popupInternalPaddingTop(style()), Fixed)); 117 innerStyle->setPaddingBottom(Length(theme()->popupInternalPaddingBottom(style()), Fixed)); 118 119 if (document()->page()->chrome().selectItemWritingDirectionIsNatural()) { 120 // Items in the popup will not respect the CSS text-align and direction properties, 121 // so we must adjust our own style to match. 122 innerStyle->setTextAlign(LEFT); 123 TextDirection direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == WTF::Unicode::RightToLeft) ? RTL : LTR; 124 innerStyle->setDirection(direction); 125 } else if (m_optionStyle && document()->page()->chrome().selectItemAlignmentFollowsMenuWritingDirection()) { 126 if ((m_optionStyle->direction() != innerStyle->direction() || m_optionStyle->unicodeBidi() != innerStyle->unicodeBidi())) 127 m_innerBlock->setNeedsLayoutAndPrefWidthsRecalc(); 128 innerStyle->setTextAlign(style()->isLeftToRightDirection() ? LEFT : RIGHT); 129 innerStyle->setDirection(m_optionStyle->direction()); 130 innerStyle->setUnicodeBidi(m_optionStyle->unicodeBidi()); 131 } 132} 133 134inline HTMLSelectElement* RenderMenuList::selectElement() const 135{ 136 return toHTMLSelectElement(node()); 137} 138 139void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild) 140{ 141 createInnerBlock(); 142 m_innerBlock->addChild(newChild, beforeChild); 143 ASSERT(m_innerBlock == firstChild()); 144 145 if (AXObjectCache* cache = document()->existingAXObjectCache()) 146 cache->childrenChanged(this); 147} 148 149void RenderMenuList::removeChild(RenderObject* oldChild) 150{ 151 if (oldChild == m_innerBlock || !m_innerBlock) { 152 RenderFlexibleBox::removeChild(oldChild); 153 m_innerBlock = 0; 154 } else 155 m_innerBlock->removeChild(oldChild); 156} 157 158void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 159{ 160 RenderBlock::styleDidChange(diff, oldStyle); 161 162 if (m_buttonText) 163 m_buttonText->setStyle(style()); 164 if (m_innerBlock) // RenderBlock handled updating the anonymous block's style. 165 adjustInnerStyle(); 166 167 bool fontChanged = !oldStyle || oldStyle->font() != style()->font(); 168 if (fontChanged) 169 updateOptionsWidth(); 170} 171 172void RenderMenuList::updateOptionsWidth() 173{ 174 float maxOptionWidth = 0; 175 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 176 int size = listItems.size(); 177 FontCachePurgePreventer fontCachePurgePreventer; 178 179 for (int i = 0; i < size; ++i) { 180 HTMLElement* element = listItems[i]; 181 if (!element->hasTagName(optionTag)) 182 continue; 183 184 String text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 185 applyTextTransform(style(), text, ' '); 186 if (theme()->popupOptionSupportsTextIndent()) { 187 // Add in the option's text indent. We can't calculate percentage values for now. 188 float optionWidth = 0; 189 if (RenderStyle* optionStyle = element->renderStyle()) 190 optionWidth += minimumValueForLength(optionStyle->textIndent(), 0, view()); 191 if (!text.isEmpty()) 192 optionWidth += style()->font().width(text); 193 maxOptionWidth = max(maxOptionWidth, optionWidth); 194 } else if (!text.isEmpty()) 195 maxOptionWidth = max(maxOptionWidth, style()->font().width(text)); 196 } 197 198 int width = static_cast<int>(ceilf(maxOptionWidth)); 199 if (m_optionsWidth == width) 200 return; 201 202 m_optionsWidth = width; 203 if (parent()) 204 setNeedsLayoutAndPrefWidthsRecalc(); 205} 206 207void RenderMenuList::updateFromElement() 208{ 209 if (m_optionsChanged) { 210 updateOptionsWidth(); 211 m_optionsChanged = false; 212 } 213 214 if (m_popupIsVisible) 215 m_popup->updateFromElement(); 216 else 217 setTextFromOption(selectElement()->selectedIndex()); 218} 219 220void RenderMenuList::setTextFromOption(int optionIndex) 221{ 222 HTMLSelectElement* select = selectElement(); 223 const Vector<HTMLElement*>& listItems = select->listItems(); 224 int size = listItems.size(); 225 226 int i = select->optionToListIndex(optionIndex); 227 String text = emptyString(); 228 if (i >= 0 && i < size) { 229 Element* element = listItems[i]; 230 if (element->hasTagName(optionTag)) { 231 text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 232 m_optionStyle = element->renderStyle(); 233 } 234 } 235 236 setText(text.stripWhiteSpace()); 237 didUpdateActiveOption(optionIndex); 238} 239 240void RenderMenuList::setText(const String& s) 241{ 242 if (s.isEmpty()) { 243 if (!m_buttonText || !m_buttonText->isBR()) { 244 if (m_buttonText) 245 m_buttonText->destroy(); 246 m_buttonText = new (renderArena()) RenderBR(document()); 247 m_buttonText->setStyle(style()); 248 addChild(m_buttonText); 249 } 250 } else { 251 if (m_buttonText && !m_buttonText->isBR()) 252 m_buttonText->setText(s.impl(), true); 253 else { 254 if (m_buttonText) 255 m_buttonText->destroy(); 256 m_buttonText = new (renderArena()) RenderText(document(), s.impl()); 257 m_buttonText->setStyle(style()); 258 addChild(m_buttonText); 259 } 260 adjustInnerStyle(); 261 } 262} 263 264String RenderMenuList::text() const 265{ 266 return m_buttonText ? m_buttonText->text() : 0; 267} 268 269LayoutRect RenderMenuList::controlClipRect(const LayoutPoint& additionalOffset) const 270{ 271 // Clip to the intersection of the content box and the content box for the inner box 272 // This will leave room for the arrows which sit in the inner box padding, 273 // and if the inner box ever spills out of the outer box, that will get clipped too. 274 LayoutRect outerBox(additionalOffset.x() + borderLeft() + paddingLeft(), 275 additionalOffset.y() + borderTop() + paddingTop(), 276 contentWidth(), 277 contentHeight()); 278 279 LayoutRect innerBox(additionalOffset.x() + m_innerBlock->x() + m_innerBlock->paddingLeft(), 280 additionalOffset.y() + m_innerBlock->y() + m_innerBlock->paddingTop(), 281 m_innerBlock->contentWidth(), 282 m_innerBlock->contentHeight()); 283 284 return intersection(outerBox, innerBox); 285} 286 287void RenderMenuList::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const 288{ 289 maxLogicalWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight(); 290 if (!style()->width().isPercent()) 291 minLogicalWidth = maxLogicalWidth; 292} 293 294void RenderMenuList::computePreferredLogicalWidths() 295{ 296 m_minPreferredLogicalWidth = 0; 297 m_maxPreferredLogicalWidth = 0; 298 299 if (style()->width().isFixed() && style()->width().value() > 0) 300 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value()); 301 else 302 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); 303 304 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { 305 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value())); 306 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value())); 307 } 308 309 if (style()->maxWidth().isFixed()) { 310 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value())); 311 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value())); 312 } 313 314 LayoutUnit toAdd = borderAndPaddingWidth(); 315 m_minPreferredLogicalWidth += toAdd; 316 m_maxPreferredLogicalWidth += toAdd; 317 318 setPreferredLogicalWidthsDirty(false); 319} 320 321void RenderMenuList::showPopup() 322{ 323 if (m_popupIsVisible) 324 return; 325 326 if (document()->page()->chrome().hasOpenedPopup()) 327 return; 328 329 // Create m_innerBlock here so it ends up as the first child. 330 // This is important because otherwise we might try to create m_innerBlock 331 // inside the showPopup call and it would fail. 332 createInnerBlock(); 333 if (!m_popup) 334 m_popup = document()->page()->chrome().createPopupMenu(this); 335 m_popupIsVisible = true; 336 337 // Compute the top left taking transforms into account, but use 338 // the actual width of the element to size the popup. 339 FloatPoint absTopLeft = localToAbsolute(FloatPoint(), UseTransforms); 340 IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms(); 341 absBounds.setLocation(roundedIntPoint(absTopLeft)); 342 HTMLSelectElement* select = selectElement(); 343 m_popup->show(absBounds, document()->view(), select->optionToListIndex(select->selectedIndex())); 344} 345 346void RenderMenuList::hidePopup() 347{ 348 if (m_popup) 349 m_popup->hide(); 350} 351 352void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange) 353{ 354 // Check to ensure a page navigation has not occurred while 355 // the popup was up. 356 Document* doc = toElement(node())->document(); 357 if (!doc || doc != doc->frame()->document()) 358 return; 359 360 HTMLSelectElement* select = selectElement(); 361 select->optionSelectedByUser(select->listToOptionIndex(listIndex), fireOnChange); 362} 363 364void RenderMenuList::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) 365{ 366 selectElement()->listBoxSelectItem(listIndex, allowMultiplySelections, shift, fireOnChangeNow); 367} 368 369bool RenderMenuList::multiple() const 370{ 371 return selectElement()->multiple(); 372} 373 374void RenderMenuList::didSetSelectedIndex(int listIndex) 375{ 376 didUpdateActiveOption(selectElement()->listToOptionIndex(listIndex)); 377} 378 379void RenderMenuList::didUpdateActiveOption(int optionIndex) 380{ 381 if (!AXObjectCache::accessibilityEnabled() || !document()->existingAXObjectCache()) 382 return; 383 384 if (m_lastActiveIndex == optionIndex) 385 return; 386 m_lastActiveIndex = optionIndex; 387 388 HTMLSelectElement* select = selectElement(); 389 int listIndex = select->optionToListIndex(optionIndex); 390 if (listIndex < 0 || listIndex >= static_cast<int>(select->listItems().size())) 391 return; 392 393 ASSERT(select->listItems()[listIndex]); 394 395 if (AccessibilityMenuList* menuList = static_cast<AccessibilityMenuList*>(document()->axObjectCache()->get(this))) 396 menuList->didUpdateActiveOption(optionIndex); 397} 398 399String RenderMenuList::itemText(unsigned listIndex) const 400{ 401 HTMLSelectElement* select = selectElement(); 402 const Vector<HTMLElement*>& listItems = select->listItems(); 403 if (listIndex >= listItems.size()) 404 return String(); 405 406 String itemString; 407 Element* element = listItems[listIndex]; 408 if (element->hasTagName(optgroupTag)) 409 itemString = static_cast<const HTMLOptGroupElement*>(element)->groupLabelText(); 410 else if (element->hasTagName(optionTag)) 411 itemString = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 412 413 applyTextTransform(style(), itemString, ' '); 414 return itemString; 415} 416 417String RenderMenuList::itemLabel(unsigned) const 418{ 419 return String(); 420} 421 422String RenderMenuList::itemIcon(unsigned) const 423{ 424 return String(); 425} 426 427String RenderMenuList::itemAccessibilityText(unsigned listIndex) const 428{ 429 // Allow the accessible name be changed if necessary. 430 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 431 if (listIndex >= listItems.size()) 432 return String(); 433 return listItems[listIndex]->fastGetAttribute(aria_labelAttr); 434} 435 436String RenderMenuList::itemToolTip(unsigned listIndex) const 437{ 438 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 439 if (listIndex >= listItems.size()) 440 return String(); 441 return listItems[listIndex]->title(); 442} 443 444bool RenderMenuList::itemIsEnabled(unsigned listIndex) const 445{ 446 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 447 if (listIndex >= listItems.size()) 448 return false; 449 HTMLElement* element = listItems[listIndex]; 450 if (!element->hasTagName(optionTag)) 451 return false; 452 453 bool groupEnabled = true; 454 if (Element* parentElement = element->parentElement()) { 455 if (parentElement->hasTagName(optgroupTag)) 456 groupEnabled = !parentElement->isDisabledFormControl(); 457 } 458 if (!groupEnabled) 459 return false; 460 461 return !element->isDisabledFormControl(); 462} 463 464PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const 465{ 466 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 467 if (listIndex >= listItems.size()) { 468 // If we are making an out of bounds access, then we want to use the style 469 // of a different option element (index 0). However, if there isn't an option element 470 // before at index 0, we fall back to the menu's style. 471 if (!listIndex) 472 return menuStyle(); 473 474 // Try to retrieve the style of an option element we know exists (index 0). 475 listIndex = 0; 476 } 477 HTMLElement* element = listItems[listIndex]; 478 479 Color itemBackgroundColor; 480 bool itemHasCustomBackgroundColor; 481 getItemBackgroundColor(listIndex, itemBackgroundColor, itemHasCustomBackgroundColor); 482 483 RenderStyle* style = element->renderStyle() ? element->renderStyle() : element->computedStyle(); 484 return style ? PopupMenuStyle(style->visitedDependentColor(CSSPropertyColor), itemBackgroundColor, style->font(), style->visibility() == VISIBLE, 485 style->display() == NONE, style->textIndent(), style->direction(), isOverride(style->unicodeBidi()), 486 itemHasCustomBackgroundColor ? PopupMenuStyle::CustomBackgroundColor : PopupMenuStyle::DefaultBackgroundColor) : menuStyle(); 487} 488 489void RenderMenuList::getItemBackgroundColor(unsigned listIndex, Color& itemBackgroundColor, bool& itemHasCustomBackgroundColor) const 490{ 491 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 492 if (listIndex >= listItems.size()) { 493 itemBackgroundColor = style()->visitedDependentColor(CSSPropertyBackgroundColor); 494 itemHasCustomBackgroundColor = false; 495 return; 496 } 497 HTMLElement* element = listItems[listIndex]; 498 499 Color backgroundColor; 500 if (element->renderStyle()) 501 backgroundColor = element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor); 502 itemHasCustomBackgroundColor = backgroundColor.isValid() && backgroundColor.alpha(); 503 // If the item has an opaque background color, return that. 504 if (!backgroundColor.hasAlpha()) { 505 itemBackgroundColor = backgroundColor; 506 return; 507 } 508 509 // Otherwise, the item's background is overlayed on top of the menu background. 510 backgroundColor = style()->visitedDependentColor(CSSPropertyBackgroundColor).blend(backgroundColor); 511 if (!backgroundColor.hasAlpha()) { 512 itemBackgroundColor = backgroundColor; 513 return; 514 } 515 516 // If the menu background is not opaque, then add an opaque white background behind. 517 itemBackgroundColor = Color(Color::white).blend(backgroundColor); 518} 519 520PopupMenuStyle RenderMenuList::menuStyle() const 521{ 522 RenderStyle* s = m_innerBlock ? m_innerBlock->style() : style(); 523 return PopupMenuStyle(s->visitedDependentColor(CSSPropertyColor), s->visitedDependentColor(CSSPropertyBackgroundColor), s->font(), s->visibility() == VISIBLE, 524 s->display() == NONE, s->textIndent(), style()->direction(), isOverride(style()->unicodeBidi())); 525} 526 527HostWindow* RenderMenuList::hostWindow() const 528{ 529 return document()->view()->hostWindow(); 530} 531 532PassRefPtr<Scrollbar> RenderMenuList::createScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) 533{ 534 RefPtr<Scrollbar> widget; 535 bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR); 536 if (hasCustomScrollbarStyle) 537 widget = RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, this->node()); 538 else 539 widget = Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize); 540 return widget.release(); 541} 542 543int RenderMenuList::clientInsetLeft() const 544{ 545 return 0; 546} 547 548int RenderMenuList::clientInsetRight() const 549{ 550 return 0; 551} 552 553LayoutUnit RenderMenuList::clientPaddingLeft() const 554{ 555 return paddingLeft() + m_innerBlock->paddingLeft(); 556} 557 558const int endOfLinePadding = 2; 559LayoutUnit RenderMenuList::clientPaddingRight() const 560{ 561 if (style()->appearance() == MenulistPart || style()->appearance() == MenulistButtonPart) { 562 // For these appearance values, the theme applies padding to leave room for the 563 // drop-down button. But leaving room for the button inside the popup menu itself 564 // looks strange, so we return a small default padding to avoid having a large empty 565 // space appear on the side of the popup menu. 566 return endOfLinePadding; 567 } 568 569 // If the appearance isn't MenulistPart, then the select is styled (non-native), so 570 // we want to return the user specified padding. 571 return paddingRight() + m_innerBlock->paddingRight(); 572} 573 574int RenderMenuList::listSize() const 575{ 576 return selectElement()->listItems().size(); 577} 578 579int RenderMenuList::selectedIndex() const 580{ 581 HTMLSelectElement* select = selectElement(); 582 return select->optionToListIndex(select->selectedIndex()); 583} 584 585void RenderMenuList::popupDidHide() 586{ 587 m_popupIsVisible = false; 588} 589 590bool RenderMenuList::itemIsSeparator(unsigned listIndex) const 591{ 592 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 593 return listIndex < listItems.size() && listItems[listIndex]->hasTagName(hrTag); 594} 595 596bool RenderMenuList::itemIsLabel(unsigned listIndex) const 597{ 598 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 599 return listIndex < listItems.size() && listItems[listIndex]->hasTagName(optgroupTag); 600} 601 602bool RenderMenuList::itemIsSelected(unsigned listIndex) const 603{ 604 const Vector<HTMLElement*>& listItems = selectElement()->listItems(); 605 if (listIndex >= listItems.size()) 606 return false; 607 HTMLElement* element = listItems[listIndex]; 608 return element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected(); 609} 610 611void RenderMenuList::setTextFromItem(unsigned listIndex) 612{ 613 setTextFromOption(selectElement()->listToOptionIndex(listIndex)); 614} 615 616FontSelector* RenderMenuList::fontSelector() const 617{ 618 return document()->ensureStyleResolver()->fontSelector(); 619} 620 621} 622