1/* 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> 3 * Copyright (C) 2004, 2005, 2006, 2008 Rob Buis <buis@kde.org> 4 * Copyright (C) 2008, 2014 Apple Inc. All rights reserved. 5 * Copyright (C) 2008 Alp Toker <alp@atoker.com> 6 * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au> 7 * Copyright (C) 2013 Samsung Electronics. All rights reserved. 8 * Copyright (C) 2014 Adobe Systems Incorporated. All rights reserved. 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Library General Public 12 * License as published by the Free Software Foundation; either 13 * version 2 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Library General Public License for more details. 19 * 20 * You should have received a copy of the GNU Library General Public License 21 * along with this library; see the file COPYING.LIB. If not, write to 22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 23 * Boston, MA 02110-1301, USA. 24 */ 25 26#include "config.h" 27#include "SVGElement.h" 28 29#include "Attr.h" 30#include "CSSCursorImageValue.h" 31#include "CSSParser.h" 32#include "DOMImplementation.h" 33#include "Document.h" 34#include "ElementIterator.h" 35#include "Event.h" 36#include "EventNames.h" 37#include "HTMLElement.h" 38#include "HTMLNames.h" 39#include "HTMLParserIdioms.h" 40#include "RenderObject.h" 41#include "RenderSVGResource.h" 42#include "RenderSVGResourceClipper.h" 43#include "RenderSVGResourceFilter.h" 44#include "RenderSVGResourceMasker.h" 45#include "SVGCursorElement.h" 46#include "SVGDocumentExtensions.h" 47#include "SVGElementInstance.h" 48#include "SVGElementRareData.h" 49#include "SVGGraphicsElement.h" 50#include "SVGImageElement.h" 51#include "SVGNames.h" 52#include "SVGRenderStyle.h" 53#include "SVGRenderSupport.h" 54#include "SVGSVGElement.h" 55#include "SVGTitleElement.h" 56#include "SVGUseElement.h" 57#include "ShadowRoot.h" 58#include "XLinkNames.h" 59#include "XMLNames.h" 60#include <wtf/Assertions.h> 61#include <wtf/HashMap.h> 62#include <wtf/NeverDestroyed.h> 63#include <wtf/StdLibExtras.h> 64#include <wtf/text/WTFString.h> 65 66 67namespace WebCore { 68 69// Animated property definitions 70DEFINE_ANIMATED_STRING(SVGElement, HTMLNames::classAttr, ClassName, className) 71 72BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGElement) 73 REGISTER_LOCAL_ANIMATED_PROPERTY(className) 74END_REGISTER_ANIMATED_PROPERTIES 75 76using namespace HTMLNames; 77using namespace SVGNames; 78 79static NEVER_INLINE void populateAttributeNameToCSSPropertyIDMap(HashMap<AtomicStringImpl*, CSSPropertyID>& map) 80{ 81 // This list should include all base CSS and SVG CSS properties which are exposed as SVG XML attributes. 82 static const QualifiedName* const attributeNames[] = { 83 &alignment_baselineAttr, 84 &baseline_shiftAttr, 85 &buffered_renderingAttr, 86 &clipAttr, 87 &clip_pathAttr, 88 &clip_ruleAttr, 89 &SVGNames::colorAttr, 90 &color_interpolationAttr, 91 &color_interpolation_filtersAttr, 92 &color_profileAttr, 93 &color_renderingAttr, 94 &cursorAttr, 95 &SVGNames::directionAttr, 96 &displayAttr, 97 &dominant_baselineAttr, 98 &enable_backgroundAttr, 99 &fillAttr, 100 &fill_opacityAttr, 101 &fill_ruleAttr, 102 &filterAttr, 103 &flood_colorAttr, 104 &flood_opacityAttr, 105 &font_familyAttr, 106 &font_sizeAttr, 107 &font_stretchAttr, 108 &font_styleAttr, 109 &font_variantAttr, 110 &font_weightAttr, 111 &glyph_orientation_horizontalAttr, 112 &glyph_orientation_verticalAttr, 113 &image_renderingAttr, 114 &kerningAttr, 115 &letter_spacingAttr, 116 &lighting_colorAttr, 117 &marker_endAttr, 118 &marker_midAttr, 119 &marker_startAttr, 120 &maskAttr, 121 &mask_typeAttr, 122 &opacityAttr, 123 &overflowAttr, 124 &paint_orderAttr, 125 &pointer_eventsAttr, 126 &shape_renderingAttr, 127 &stop_colorAttr, 128 &stop_opacityAttr, 129 &strokeAttr, 130 &stroke_dasharrayAttr, 131 &stroke_dashoffsetAttr, 132 &stroke_linecapAttr, 133 &stroke_linejoinAttr, 134 &stroke_miterlimitAttr, 135 &stroke_opacityAttr, 136 &stroke_widthAttr, 137 &text_anchorAttr, 138 &text_decorationAttr, 139 &text_renderingAttr, 140 &unicode_bidiAttr, 141 &vector_effectAttr, 142 &visibilityAttr, 143 &word_spacingAttr, 144 &writing_modeAttr, 145 }; 146 147 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(attributeNames); ++i) { 148 const AtomicString& localName = attributeNames[i]->localName(); 149 map.add(localName.impl(), cssPropertyID(localName)); 150 } 151 152 // FIXME: When CSS supports "transform-origin" this special case can be removed, 153 // and we can add transform_originAttr to the table above instead. 154 map.add(transform_originAttr.localName().impl(), CSSPropertyWebkitTransformOrigin); 155} 156 157static NEVER_INLINE void populateAttributeNameToAnimatedPropertyTypeMap(HashMap<QualifiedName::QualifiedNameImpl*, AnimatedPropertyType>& map) 158{ 159 struct TableEntry { 160 const QualifiedName& attributeName; 161 AnimatedPropertyType type; 162 }; 163 164 static const TableEntry table[] = { 165 { alignment_baselineAttr, AnimatedString }, 166 { baseline_shiftAttr, AnimatedString }, 167 { buffered_renderingAttr, AnimatedString }, 168 { clipAttr, AnimatedRect }, 169 { clip_pathAttr, AnimatedString }, 170 { clip_ruleAttr, AnimatedString }, 171 { SVGNames::colorAttr, AnimatedColor }, 172 { color_interpolationAttr, AnimatedString }, 173 { color_interpolation_filtersAttr, AnimatedString }, 174 { color_profileAttr, AnimatedString }, 175 { color_renderingAttr, AnimatedString }, 176 { cursorAttr, AnimatedString }, 177 { displayAttr, AnimatedString }, 178 { dominant_baselineAttr, AnimatedString }, 179 { fillAttr, AnimatedColor }, 180 { fill_opacityAttr, AnimatedNumber }, 181 { fill_ruleAttr, AnimatedString }, 182 { filterAttr, AnimatedString }, 183 { flood_colorAttr, AnimatedColor }, 184 { flood_opacityAttr, AnimatedNumber }, 185 { font_familyAttr, AnimatedString }, 186 { font_sizeAttr, AnimatedLength }, 187 { font_stretchAttr, AnimatedString }, 188 { font_styleAttr, AnimatedString }, 189 { font_variantAttr, AnimatedString }, 190 { font_weightAttr, AnimatedString }, 191 { image_renderingAttr, AnimatedString }, 192 { kerningAttr, AnimatedLength }, 193 { letter_spacingAttr, AnimatedLength }, 194 { lighting_colorAttr, AnimatedColor }, 195 { marker_endAttr, AnimatedString }, 196 { marker_midAttr, AnimatedString }, 197 { marker_startAttr, AnimatedString }, 198 { maskAttr, AnimatedString }, 199 { mask_typeAttr, AnimatedString }, 200 { opacityAttr, AnimatedNumber }, 201 { overflowAttr, AnimatedString }, 202 { paint_orderAttr, AnimatedString }, 203 { pointer_eventsAttr, AnimatedString }, 204 { shape_renderingAttr, AnimatedString }, 205 { stop_colorAttr, AnimatedColor }, 206 { stop_opacityAttr, AnimatedNumber }, 207 { strokeAttr, AnimatedColor }, 208 { stroke_dasharrayAttr, AnimatedLengthList }, 209 { stroke_dashoffsetAttr, AnimatedLength }, 210 { stroke_linecapAttr, AnimatedString }, 211 { stroke_linejoinAttr, AnimatedString }, 212 { stroke_miterlimitAttr, AnimatedNumber }, 213 { stroke_opacityAttr, AnimatedNumber }, 214 { stroke_widthAttr, AnimatedLength }, 215 { text_anchorAttr, AnimatedString }, 216 { text_decorationAttr, AnimatedString }, 217 { text_renderingAttr, AnimatedString }, 218 { vector_effectAttr, AnimatedString }, 219 { visibilityAttr, AnimatedString }, 220 { word_spacingAttr, AnimatedLength }, 221 }; 222 223 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) 224 map.add(table[i].attributeName.impl(), table[i].type); 225} 226 227static inline HashMap<QualifiedName::QualifiedNameImpl*, AnimatedPropertyType>& attributeNameToAnimatedPropertyTypeMap() 228{ 229 static NeverDestroyed<HashMap<QualifiedName::QualifiedNameImpl*, AnimatedPropertyType>> map; 230 if (map.get().isEmpty()) 231 populateAttributeNameToAnimatedPropertyTypeMap(map); 232 return map; 233} 234 235SVGElement::SVGElement(const QualifiedName& tagName, Document& document) 236 : StyledElement(tagName, document, CreateSVGElement) 237{ 238 registerAnimatedPropertiesForSVGElement(); 239} 240 241SVGElement::~SVGElement() 242{ 243 if (m_svgRareData) { 244 m_svgRareData->destroyAnimatedSMILStyleProperties(); 245 if (SVGCursorElement* cursorElement = m_svgRareData->cursorElement()) 246 cursorElement->removeClient(this); 247 if (CSSCursorImageValue* cursorImageValue = m_svgRareData->cursorImageValue()) 248 cursorImageValue->removeReferencedElement(this); 249 250 m_svgRareData = nullptr; 251 } 252 document().accessSVGExtensions()->rebuildAllElementReferencesForTarget(*this); 253 document().accessSVGExtensions()->removeAllElementReferencesForTarget(this); 254} 255 256short SVGElement::tabIndex() const 257{ 258 if (supportsFocus()) 259 return Element::tabIndex(); 260 return -1; 261} 262 263bool SVGElement::willRecalcStyle(Style::Change change) 264{ 265 if (!m_svgRareData || styleChangeType() == SyntheticStyleChange) 266 return true; 267 // If the style changes because of a regular property change (not induced by SMIL animations themselves) 268 // reset the "computed style without SMIL style properties", so the base value change gets reflected. 269 if (change > Style::NoChange || needsStyleRecalc()) 270 m_svgRareData->setNeedsOverrideComputedStyleUpdate(); 271 return true; 272} 273 274SVGElementRareData& SVGElement::ensureSVGRareData() 275{ 276 if (!m_svgRareData) 277 m_svgRareData = std::make_unique<SVGElementRareData>(); 278 return *m_svgRareData; 279} 280 281bool SVGElement::isOutermostSVGSVGElement() const 282{ 283 if (!isSVGSVGElement(this)) 284 return false; 285 286 // If we're living in a shadow tree, we're a <svg> element that got created as replacement 287 // for a <symbol> element or a cloned <svg> element in the referenced tree. In that case 288 // we're always an inner <svg> element. 289 if (isInShadowTree() && parentOrShadowHostElement() && parentOrShadowHostElement()->isSVGElement()) 290 return false; 291 292 // Element may not be in the document, pretend we're outermost for viewport(), getCTM(), etc. 293 if (!parentNode()) 294 return true; 295 296 // We act like an outermost SVG element, if we're a direct child of a <foreignObject> element. 297 if (parentNode()->hasTagName(SVGNames::foreignObjectTag)) 298 return true; 299 300 // This is true whenever this is the outermost SVG, even if there are HTML elements outside it 301 return !parentNode()->isSVGElement(); 302} 303 304void SVGElement::reportAttributeParsingError(SVGParsingError error, const QualifiedName& name, const AtomicString& value) 305{ 306 if (error == NoError) 307 return; 308 309 String errorString = "<" + tagName() + "> attribute " + name.toString() + "=\"" + value + "\""; 310 SVGDocumentExtensions* extensions = document().accessSVGExtensions(); 311 312 if (error == NegativeValueForbiddenError) { 313 extensions->reportError("Invalid negative value for " + errorString); 314 return; 315 } 316 317 if (error == ParsingAttributeFailedError) { 318 extensions->reportError("Invalid value for " + errorString); 319 return; 320 } 321 322 ASSERT_NOT_REACHED(); 323} 324 325bool SVGElement::isSupported(StringImpl* feature, StringImpl* version) const 326{ 327 return DOMImplementation::hasFeature(feature, version); 328} 329 330String SVGElement::xmlbase() const 331{ 332 return fastGetAttribute(XMLNames::baseAttr); 333} 334 335void SVGElement::setXmlbase(const String& value, ExceptionCode&) 336{ 337 setAttribute(XMLNames::baseAttr, value); 338} 339 340void SVGElement::removedFrom(ContainerNode& rootParent) 341{ 342 bool wasInDocument = rootParent.inDocument(); 343 if (wasInDocument) 344 updateRelativeLengthsInformation(false, this); 345 346 StyledElement::removedFrom(rootParent); 347 348 if (wasInDocument) { 349 document().accessSVGExtensions()->clearTargetDependencies(*this); 350 document().accessSVGExtensions()->removeAllElementReferencesForTarget(this); 351 } 352 SVGElementInstance::invalidateAllInstancesOfElement(this); 353} 354 355SVGSVGElement* SVGElement::ownerSVGElement() const 356{ 357 ContainerNode* n = parentOrShadowHostNode(); 358 while (n) { 359 if (n->hasTagName(SVGNames::svgTag)) 360 return toSVGSVGElement(n); 361 362 n = n->parentOrShadowHostNode(); 363 } 364 365 return 0; 366} 367 368SVGElement* SVGElement::viewportElement() const 369{ 370 // This function needs shadow tree support - as RenderSVGContainer uses this function 371 // to determine the "overflow" property. <use> on <symbol> wouldn't work otherwhise. 372 ContainerNode* n = parentOrShadowHostNode(); 373 while (n) { 374 if (n->hasTagName(SVGNames::svgTag) || isSVGImageElement(n) || n->hasTagName(SVGNames::symbolTag)) 375 return toSVGElement(n); 376 377 n = n->parentOrShadowHostNode(); 378 } 379 380 return 0; 381} 382 383SVGDocumentExtensions* SVGElement::accessDocumentSVGExtensions() 384{ 385 // This function is provided for use by SVGAnimatedProperty to avoid 386 // global inclusion of Document.h in SVG code. 387 return document().accessSVGExtensions(); 388} 389 390void SVGElement::mapInstanceToElement(SVGElementInstance* instance) 391{ 392 ASSERT(instance); 393 394 HashSet<SVGElementInstance*>& instances = ensureSVGRareData().elementInstances(); 395 ASSERT(!instances.contains(instance)); 396 397 instances.add(instance); 398} 399 400void SVGElement::removeInstanceMapping(SVGElementInstance* instance) 401{ 402 ASSERT(instance); 403 ASSERT(m_svgRareData); 404 405 HashSet<SVGElementInstance*>& instances = m_svgRareData->elementInstances(); 406 ASSERT(instances.contains(instance)); 407 408 instances.remove(instance); 409} 410 411const HashSet<SVGElementInstance*>& SVGElement::instancesForElement() const 412{ 413 if (!m_svgRareData) { 414 static NeverDestroyed<HashSet<SVGElementInstance*>> emptyInstances; 415 return emptyInstances; 416 } 417 return m_svgRareData->elementInstances(); 418} 419 420bool SVGElement::getBoundingBox(FloatRect& rect, SVGLocatable::StyleUpdateStrategy styleUpdateStrategy) 421{ 422 if (isSVGGraphicsElement()) { 423 rect = toSVGGraphicsElement(this)->getBBox(styleUpdateStrategy); 424 return true; 425 } 426 return false; 427} 428 429void SVGElement::setCursorElement(SVGCursorElement* cursorElement) 430{ 431 SVGElementRareData& rareData = ensureSVGRareData(); 432 if (SVGCursorElement* oldCursorElement = rareData.cursorElement()) { 433 if (cursorElement == oldCursorElement) 434 return; 435 oldCursorElement->removeReferencedElement(this); 436 } 437 rareData.setCursorElement(cursorElement); 438} 439 440void SVGElement::cursorElementRemoved() 441{ 442 ASSERT(m_svgRareData); 443 m_svgRareData->setCursorElement(0); 444} 445 446void SVGElement::setCursorImageValue(CSSCursorImageValue* cursorImageValue) 447{ 448 SVGElementRareData& rareData = ensureSVGRareData(); 449 if (CSSCursorImageValue* oldCursorImageValue = rareData.cursorImageValue()) { 450 if (cursorImageValue == oldCursorImageValue) 451 return; 452 oldCursorImageValue->removeReferencedElement(this); 453 } 454 rareData.setCursorImageValue(cursorImageValue); 455} 456 457void SVGElement::cursorImageValueRemoved() 458{ 459 ASSERT(m_svgRareData); 460 m_svgRareData->setCursorImageValue(0); 461} 462 463SVGElement* SVGElement::correspondingElement() 464{ 465 ASSERT(!m_svgRareData || !m_svgRareData->correspondingElement() || containingShadowRoot()); 466 return m_svgRareData ? m_svgRareData->correspondingElement() : 0; 467} 468 469void SVGElement::setCorrespondingElement(SVGElement* correspondingElement) 470{ 471 ensureSVGRareData().setCorrespondingElement(correspondingElement); 472} 473 474void SVGElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 475{ 476 if (name == HTMLNames::classAttr) 477 setClassNameBaseValue(value); 478 else if (name == tabindexAttr) { 479 int tabindex = 0; 480 if (value.isEmpty()) 481 clearTabIndexExplicitlyIfNeeded(); 482 else if (parseHTMLInteger(value, tabindex)) { 483 // Clamp tabindex to the range of 'short' to match Firefox's behavior. 484 setTabIndexExplicitly(std::max(static_cast<int>(std::numeric_limits<short>::min()), std::min(tabindex, static_cast<int>(std::numeric_limits<short>::max())))); 485 } 486 } else if (SVGLangSpace::parseAttribute(name, value)) 487 return; 488 else { 489 // FIXME: Can we do this even faster by checking the local name "on" prefix before we do anything with the map? 490 // See HTMLElement::parseAttribute(). 491 static NeverDestroyed<HashMap<AtomicStringImpl*, AtomicString>> eventNamesGlobal; 492 auto& eventNames = eventNamesGlobal.get(); 493 if (eventNames.isEmpty()) 494 HTMLElement::populateEventNameForAttributeLocalNameMap(eventNames); 495 const AtomicString& eventName = eventNames.get(name.localName().impl()); 496 if (!eventName.isNull()) 497 setAttributeEventListener(eventName, name, value); 498 else 499 StyledElement::parseAttribute(name, value); 500 } 501} 502 503void SVGElement::animatedPropertyTypeForAttribute(const QualifiedName& attributeName, Vector<AnimatedPropertyType>& propertyTypes) 504{ 505 localAttributeToPropertyMap().animatedPropertyTypeForAttribute(attributeName, propertyTypes); 506 if (!propertyTypes.isEmpty()) 507 return; 508 509 auto& map = attributeNameToAnimatedPropertyTypeMap(); 510 auto it = map.find(attributeName.impl()); 511 if (it != map.end()) 512 propertyTypes.append(it->value); 513} 514 515bool SVGElement::haveLoadedRequiredResources() 516{ 517 for (auto& child : childrenOfType<SVGElement>(*this)) { 518 if (!child.haveLoadedRequiredResources()) 519 return false; 520 } 521 return true; 522} 523 524bool SVGElement::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> prpListener, bool useCapture) 525{ 526 RefPtr<EventListener> listener = prpListener; 527 528 // Add event listener to regular DOM element 529 if (!Node::addEventListener(eventType, listener, useCapture)) 530 return false; 531 532 if (containingShadowRoot()) 533 return true; 534 535 // Add event listener to all shadow tree DOM element instances 536 ASSERT(!instanceUpdatesBlocked()); 537 for (auto& instance : instancesForElement()) { 538 ASSERT(instance->shadowTreeElement()); 539 ASSERT(instance->correspondingElement() == this); 540 541 bool result = instance->shadowTreeElement()->Node::addEventListener(eventType, listener, useCapture); 542 ASSERT_UNUSED(result, result); 543 } 544 545 return true; 546} 547 548bool SVGElement::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture) 549{ 550 if (containingShadowRoot()) 551 return Node::removeEventListener(eventType, listener, useCapture); 552 553 // EventTarget::removeEventListener creates a PassRefPtr around the given EventListener 554 // object when creating a temporary RegisteredEventListener object used to look up the 555 // event listener in a cache. If we want to be able to call removeEventListener() multiple 556 // times on different nodes, we have to delay its immediate destruction, which would happen 557 // after the first call below. 558 RefPtr<EventListener> protector(listener); 559 560 // Remove event listener from regular DOM element 561 if (!Node::removeEventListener(eventType, listener, useCapture)) 562 return false; 563 564 // Remove event listener from all shadow tree DOM element instances 565 ASSERT(!instanceUpdatesBlocked()); 566 for (auto& instance : instancesForElement()) { 567 ASSERT(instance->correspondingElement() == this); 568 569 SVGElement* shadowTreeElement = instance->shadowTreeElement(); 570 ASSERT(shadowTreeElement); 571 572 if (shadowTreeElement->Node::removeEventListener(eventType, listener, useCapture)) 573 continue; 574 575 // This case can only be hit for event listeners created from markup 576 ASSERT(listener->wasCreatedFromMarkup()); 577 578 // If the event listener 'listener' has been created from markup and has been fired before 579 // then JSLazyEventListener::parseCode() has been called and m_jsFunction of that listener 580 // has been created (read: it's not 0 anymore). During shadow tree creation, the event 581 // listener DOM attribute has been cloned, and another event listener has been setup in 582 // the shadow tree. If that event listener has not been used yet, m_jsFunction is still 0, 583 // and tryRemoveEventListener() above will fail. Work around that very seldom problem. 584 EventTargetData* data = shadowTreeElement->eventTargetData(); 585 ASSERT(data); 586 587 data->eventListenerMap.removeFirstEventListenerCreatedFromMarkup(eventType); 588 } 589 590 return true; 591} 592 593static bool hasLoadListener(Element* element) 594{ 595 if (element->hasEventListeners(eventNames().loadEvent)) 596 return true; 597 598 for (element = element->parentOrShadowHostElement(); element; element = element->parentOrShadowHostElement()) { 599 const EventListenerVector& entry = element->getEventListeners(eventNames().loadEvent); 600 for (size_t i = 0; i < entry.size(); ++i) { 601 if (entry[i].useCapture) 602 return true; 603 } 604 } 605 606 return false; 607} 608 609#if ENABLE(CSS_REGIONS) 610bool SVGElement::shouldMoveToFlowThread(const RenderStyle& styleToUse) const 611{ 612 // Allow only svg root elements to be directly collected by a render flow thread. 613 return parentNode() && !parentNode()->isSVGElement() && hasTagName(SVGNames::svgTag) && Element::shouldMoveToFlowThread(styleToUse); 614} 615#endif 616 617void SVGElement::sendSVGLoadEventIfPossible(bool sendParentLoadEvents) 618{ 619 RefPtr<SVGElement> currentTarget = this; 620 while (currentTarget && currentTarget->haveLoadedRequiredResources()) { 621 RefPtr<Element> parent; 622 if (sendParentLoadEvents) 623 parent = currentTarget->parentOrShadowHostElement(); // save the next parent to dispatch too incase dispatching the event changes the tree 624 if (hasLoadListener(currentTarget.get())) 625 currentTarget->dispatchEvent(Event::create(eventNames().loadEvent, false, false)); 626 currentTarget = (parent && parent->isSVGElement()) ? static_pointer_cast<SVGElement>(parent) : RefPtr<SVGElement>(); 627 SVGElement* element = currentTarget.get(); 628 if (!element || !element->isOutermostSVGSVGElement()) 629 continue; 630 631 // Consider <svg onload="foo()"><image xlink:href="foo.png" externalResourcesRequired="true"/></svg>. 632 // If foo.png is not yet loaded, the first SVGLoad event will go to the <svg> element, sent through 633 // Document::implicitClose(). Then the SVGLoad event will fire for <image>, once its loaded. 634 ASSERT(sendParentLoadEvents); 635 636 // If the load event was not sent yet by Document::implicitClose(), but the <image> from the example 637 // above, just appeared, don't send the SVGLoad event to the outermost <svg>, but wait for the document 638 // to be "ready to render", first. 639 if (!document().loadEventFinished()) 640 break; 641 } 642} 643 644void SVGElement::sendSVGLoadEventIfPossibleAsynchronously() 645{ 646 svgLoadEventTimer()->startOneShot(0); 647} 648 649void SVGElement::svgLoadEventTimerFired(Timer<SVGElement>*) 650{ 651 sendSVGLoadEventIfPossible(); 652} 653 654Timer<SVGElement>* SVGElement::svgLoadEventTimer() 655{ 656 ASSERT_NOT_REACHED(); 657 return 0; 658} 659 660void SVGElement::finishParsingChildren() 661{ 662 StyledElement::finishParsingChildren(); 663 664 // The outermost SVGSVGElement SVGLoad event is fired through Document::dispatchWindowLoadEvent. 665 if (isOutermostSVGSVGElement()) 666 return; 667 668 // finishParsingChildren() is called when the close tag is reached for an element (e.g. </svg>) 669 // we send SVGLoad events here if we can, otherwise they'll be sent when any required loads finish 670 sendSVGLoadEventIfPossible(); 671} 672 673bool SVGElement::childShouldCreateRenderer(const Node& child) const 674{ 675 static NeverDestroyed<HashSet<QualifiedName>> invalidTextContent; 676 677 if (invalidTextContent.get().isEmpty()) { 678 invalidTextContent.get().add(SVGNames::textPathTag); 679#if ENABLE(SVG_FONTS) 680 invalidTextContent.get().add(SVGNames::altGlyphTag); 681#endif 682 invalidTextContent.get().add(SVGNames::trefTag); 683 invalidTextContent.get().add(SVGNames::tspanTag); 684 } 685 if (child.isSVGElement()) { 686 const SVGElement& svgChild = toSVGElement(child); 687 if (invalidTextContent.get().contains(svgChild.tagQName())) 688 return false; 689 690 return svgChild.isValid(); 691 } 692 return false; 693} 694 695void SVGElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason) 696{ 697 StyledElement::attributeChanged(name, oldValue, newValue); 698 699 if (name == HTMLNames::idAttr) 700 document().accessSVGExtensions()->rebuildAllElementReferencesForTarget(*this); 701 702 // Changes to the style attribute are processed lazily (see Element::getAttribute() and related methods), 703 // so we don't want changes to the style attribute to result in extra work here. 704 if (name != HTMLNames::styleAttr) 705 svgAttributeChanged(name); 706} 707 708void SVGElement::synchronizeAllAnimatedSVGAttribute(SVGElement* svgElement) 709{ 710 ASSERT(svgElement->elementData()); 711 ASSERT(svgElement->elementData()->animatedSVGAttributesAreDirty()); 712 713 svgElement->localAttributeToPropertyMap().synchronizeProperties(svgElement); 714 svgElement->elementData()->setAnimatedSVGAttributesAreDirty(false); 715} 716 717void SVGElement::synchronizeAnimatedSVGAttribute(const QualifiedName& name) const 718{ 719 if (!elementData() || !elementData()->animatedSVGAttributesAreDirty()) 720 return; 721 722 SVGElement* nonConstThis = const_cast<SVGElement*>(this); 723 if (name == anyQName()) 724 synchronizeAllAnimatedSVGAttribute(nonConstThis); 725 else 726 nonConstThis->localAttributeToPropertyMap().synchronizeProperty(nonConstThis, name); 727} 728 729void SVGElement::synchronizeRequiredFeatures(SVGElement* contextElement) 730{ 731 ASSERT(contextElement); 732 contextElement->synchronizeRequiredFeatures(); 733} 734 735void SVGElement::synchronizeRequiredExtensions(SVGElement* contextElement) 736{ 737 ASSERT(contextElement); 738 contextElement->synchronizeRequiredExtensions(); 739} 740 741void SVGElement::synchronizeSystemLanguage(SVGElement* contextElement) 742{ 743 ASSERT(contextElement); 744 contextElement->synchronizeSystemLanguage(); 745} 746 747PassRefPtr<RenderStyle> SVGElement::customStyleForRenderer(RenderStyle& parentStyle) 748{ 749 if (!correspondingElement()) 750 return document().ensureStyleResolver().styleForElement(this, &parentStyle); 751 752 return document().ensureStyleResolver().styleForElement(correspondingElement(), &parentStyle, DisallowStyleSharing); 753} 754 755MutableStyleProperties* SVGElement::animatedSMILStyleProperties() const 756{ 757 if (m_svgRareData) 758 return m_svgRareData->animatedSMILStyleProperties(); 759 return 0; 760} 761 762MutableStyleProperties& SVGElement::ensureAnimatedSMILStyleProperties() 763{ 764 return ensureSVGRareData().ensureAnimatedSMILStyleProperties(); 765} 766 767void SVGElement::setUseOverrideComputedStyle(bool value) 768{ 769 if (m_svgRareData) 770 m_svgRareData->setUseOverrideComputedStyle(value); 771} 772 773RenderStyle* SVGElement::computedStyle(PseudoId pseudoElementSpecifier) 774{ 775 if (!m_svgRareData || !m_svgRareData->useOverrideComputedStyle()) 776 return Element::computedStyle(pseudoElementSpecifier); 777 778 RenderStyle* parentStyle = nullptr; 779 if (Element* parent = parentOrShadowHostElement()) { 780 if (auto renderer = parent->renderer()) 781 parentStyle = &renderer->style(); 782 } 783 784 return m_svgRareData->overrideComputedStyle(this, parentStyle); 785} 786 787static void addQualifiedName(HashMap<AtomicString, QualifiedName>& map, const QualifiedName& name) 788{ 789 HashMap<AtomicString, QualifiedName>::AddResult addResult = map.add(name.localName(), name); 790 ASSERT_UNUSED(addResult, addResult.isNewEntry); 791} 792 793QualifiedName SVGElement::animatableAttributeForName(const AtomicString& localName) 794{ 795 static NeverDestroyed<HashMap<AtomicString, QualifiedName>> neverDestroyedAnimatableAttributes; 796 HashMap<AtomicString, QualifiedName>& animatableAttributes = neverDestroyedAnimatableAttributes; 797 798 if (animatableAttributes.isEmpty()) { 799 addQualifiedName(animatableAttributes, HTMLNames::classAttr); 800 addQualifiedName(animatableAttributes, SVGNames::amplitudeAttr); 801 addQualifiedName(animatableAttributes, SVGNames::azimuthAttr); 802 addQualifiedName(animatableAttributes, SVGNames::baseFrequencyAttr); 803 addQualifiedName(animatableAttributes, SVGNames::biasAttr); 804 addQualifiedName(animatableAttributes, SVGNames::clipPathUnitsAttr); 805 addQualifiedName(animatableAttributes, SVGNames::cxAttr); 806 addQualifiedName(animatableAttributes, SVGNames::cyAttr); 807 addQualifiedName(animatableAttributes, SVGNames::diffuseConstantAttr); 808 addQualifiedName(animatableAttributes, SVGNames::divisorAttr); 809 addQualifiedName(animatableAttributes, SVGNames::dxAttr); 810 addQualifiedName(animatableAttributes, SVGNames::dyAttr); 811 addQualifiedName(animatableAttributes, SVGNames::edgeModeAttr); 812 addQualifiedName(animatableAttributes, SVGNames::elevationAttr); 813 addQualifiedName(animatableAttributes, SVGNames::exponentAttr); 814 addQualifiedName(animatableAttributes, SVGNames::externalResourcesRequiredAttr); 815 addQualifiedName(animatableAttributes, SVGNames::filterResAttr); 816 addQualifiedName(animatableAttributes, SVGNames::filterUnitsAttr); 817 addQualifiedName(animatableAttributes, SVGNames::fxAttr); 818 addQualifiedName(animatableAttributes, SVGNames::fyAttr); 819 addQualifiedName(animatableAttributes, SVGNames::gradientTransformAttr); 820 addQualifiedName(animatableAttributes, SVGNames::gradientUnitsAttr); 821 addQualifiedName(animatableAttributes, SVGNames::heightAttr); 822 addQualifiedName(animatableAttributes, SVGNames::in2Attr); 823 addQualifiedName(animatableAttributes, SVGNames::inAttr); 824 addQualifiedName(animatableAttributes, SVGNames::interceptAttr); 825 addQualifiedName(animatableAttributes, SVGNames::k1Attr); 826 addQualifiedName(animatableAttributes, SVGNames::k2Attr); 827 addQualifiedName(animatableAttributes, SVGNames::k3Attr); 828 addQualifiedName(animatableAttributes, SVGNames::k4Attr); 829 addQualifiedName(animatableAttributes, SVGNames::kernelMatrixAttr); 830 addQualifiedName(animatableAttributes, SVGNames::kernelUnitLengthAttr); 831 addQualifiedName(animatableAttributes, SVGNames::lengthAdjustAttr); 832 addQualifiedName(animatableAttributes, SVGNames::limitingConeAngleAttr); 833 addQualifiedName(animatableAttributes, SVGNames::markerHeightAttr); 834 addQualifiedName(animatableAttributes, SVGNames::markerUnitsAttr); 835 addQualifiedName(animatableAttributes, SVGNames::markerWidthAttr); 836 addQualifiedName(animatableAttributes, SVGNames::maskContentUnitsAttr); 837 addQualifiedName(animatableAttributes, SVGNames::maskUnitsAttr); 838 addQualifiedName(animatableAttributes, SVGNames::methodAttr); 839 addQualifiedName(animatableAttributes, SVGNames::modeAttr); 840 addQualifiedName(animatableAttributes, SVGNames::numOctavesAttr); 841 addQualifiedName(animatableAttributes, SVGNames::offsetAttr); 842 addQualifiedName(animatableAttributes, SVGNames::operatorAttr); 843 addQualifiedName(animatableAttributes, SVGNames::orderAttr); 844 addQualifiedName(animatableAttributes, SVGNames::orientAttr); 845 addQualifiedName(animatableAttributes, SVGNames::pathLengthAttr); 846 addQualifiedName(animatableAttributes, SVGNames::patternContentUnitsAttr); 847 addQualifiedName(animatableAttributes, SVGNames::patternTransformAttr); 848 addQualifiedName(animatableAttributes, SVGNames::patternUnitsAttr); 849 addQualifiedName(animatableAttributes, SVGNames::pointsAtXAttr); 850 addQualifiedName(animatableAttributes, SVGNames::pointsAtYAttr); 851 addQualifiedName(animatableAttributes, SVGNames::pointsAtZAttr); 852 addQualifiedName(animatableAttributes, SVGNames::preserveAlphaAttr); 853 addQualifiedName(animatableAttributes, SVGNames::preserveAspectRatioAttr); 854 addQualifiedName(animatableAttributes, SVGNames::primitiveUnitsAttr); 855 addQualifiedName(animatableAttributes, SVGNames::radiusAttr); 856 addQualifiedName(animatableAttributes, SVGNames::rAttr); 857 addQualifiedName(animatableAttributes, SVGNames::refXAttr); 858 addQualifiedName(animatableAttributes, SVGNames::refYAttr); 859 addQualifiedName(animatableAttributes, SVGNames::resultAttr); 860 addQualifiedName(animatableAttributes, SVGNames::rotateAttr); 861 addQualifiedName(animatableAttributes, SVGNames::rxAttr); 862 addQualifiedName(animatableAttributes, SVGNames::ryAttr); 863 addQualifiedName(animatableAttributes, SVGNames::scaleAttr); 864 addQualifiedName(animatableAttributes, SVGNames::seedAttr); 865 addQualifiedName(animatableAttributes, SVGNames::slopeAttr); 866 addQualifiedName(animatableAttributes, SVGNames::spacingAttr); 867 addQualifiedName(animatableAttributes, SVGNames::specularConstantAttr); 868 addQualifiedName(animatableAttributes, SVGNames::specularExponentAttr); 869 addQualifiedName(animatableAttributes, SVGNames::spreadMethodAttr); 870 addQualifiedName(animatableAttributes, SVGNames::startOffsetAttr); 871 addQualifiedName(animatableAttributes, SVGNames::stdDeviationAttr); 872 addQualifiedName(animatableAttributes, SVGNames::stitchTilesAttr); 873 addQualifiedName(animatableAttributes, SVGNames::surfaceScaleAttr); 874 addQualifiedName(animatableAttributes, SVGNames::tableValuesAttr); 875 addQualifiedName(animatableAttributes, SVGNames::targetAttr); 876 addQualifiedName(animatableAttributes, SVGNames::targetXAttr); 877 addQualifiedName(animatableAttributes, SVGNames::targetYAttr); 878 addQualifiedName(animatableAttributes, SVGNames::transformAttr); 879 addQualifiedName(animatableAttributes, SVGNames::typeAttr); 880 addQualifiedName(animatableAttributes, SVGNames::valuesAttr); 881 addQualifiedName(animatableAttributes, SVGNames::viewBoxAttr); 882 addQualifiedName(animatableAttributes, SVGNames::widthAttr); 883 addQualifiedName(animatableAttributes, SVGNames::x1Attr); 884 addQualifiedName(animatableAttributes, SVGNames::x2Attr); 885 addQualifiedName(animatableAttributes, SVGNames::xAttr); 886 addQualifiedName(animatableAttributes, SVGNames::xChannelSelectorAttr); 887 addQualifiedName(animatableAttributes, SVGNames::y1Attr); 888 addQualifiedName(animatableAttributes, SVGNames::y2Attr); 889 addQualifiedName(animatableAttributes, SVGNames::yAttr); 890 addQualifiedName(animatableAttributes, SVGNames::yChannelSelectorAttr); 891 addQualifiedName(animatableAttributes, SVGNames::zAttr); 892 addQualifiedName(animatableAttributes, XLinkNames::hrefAttr); 893 } 894 return animatableAttributes.get(localName); 895} 896 897#ifndef NDEBUG 898bool SVGElement::isAnimatableAttribute(const QualifiedName& name) const 899{ 900 if (SVGElement::animatableAttributeForName(name.localName()) == name) 901 return !filterOutAnimatableAttribute(name); 902 return false; 903} 904 905bool SVGElement::filterOutAnimatableAttribute(const QualifiedName&) const 906{ 907 return false; 908} 909#endif 910 911String SVGElement::title() const 912{ 913 // According to spec, we should not return titles when hovering over root <svg> elements (those 914 // <title> elements are the title of the document, not a tooltip) so we instantly return. 915 if (isOutermostSVGSVGElement()) 916 return String(); 917 918 // Walk up the tree, to find out whether we're inside a <use> shadow tree, to find the right title. 919 if (isInShadowTree()) { 920 Element* shadowHostElement = toShadowRoot(treeScope().rootNode()).hostElement(); 921 // At this time, SVG nodes are not allowed in non-<use> shadow trees, so any shadow root we do 922 // have should be a use. The assert and following test is here to catch future shadow DOM changes 923 // that do enable SVG in a shadow tree. 924 ASSERT(!shadowHostElement || shadowHostElement->hasTagName(SVGNames::useTag)); 925 if (shadowHostElement && shadowHostElement->hasTagName(SVGNames::useTag)) { 926 SVGUseElement* useElement = toSVGUseElement(shadowHostElement); 927 928 // If the <use> title is not empty we found the title to use. 929 String useTitle(useElement->title()); 930 if (!useTitle.isEmpty()) 931 return useTitle; 932 } 933 } 934 935 // If we aren't an instance in a <use> or the <use> title was not found, then find the first 936 // <title> child of this element. 937 auto firstTitle = descendantsOfType<SVGTitleElement>(*this).first(); 938 return firstTitle ? const_cast<SVGTitleElement*>(firstTitle)->innerText() : String(); 939} 940 941bool SVGElement::rendererIsNeeded(const RenderStyle& style) 942{ 943 // http://www.w3.org/TR/SVG/extend.html#PrivateData 944 // Prevent anything other than SVG renderers from appearing in our render tree 945 // Spec: SVG allows inclusion of elements from foreign namespaces anywhere 946 // with the SVG content. In general, the SVG user agent will include the unknown 947 // elements in the DOM but will otherwise ignore unknown elements. 948 if (!parentOrShadowHostElement() || parentOrShadowHostElement()->isSVGElement()) 949 return StyledElement::rendererIsNeeded(style); 950 951 return false; 952} 953 954CSSPropertyID SVGElement::cssPropertyIdForSVGAttributeName(const QualifiedName& attrName) 955{ 956 if (!attrName.namespaceURI().isNull()) 957 return CSSPropertyInvalid; 958 959 static NeverDestroyed<HashMap<AtomicStringImpl*, CSSPropertyID>> properties; 960 if (properties.get().isEmpty()) 961 populateAttributeNameToCSSPropertyIDMap(properties.get()); 962 963 return properties.get().get(attrName.localName().impl()); 964} 965 966bool SVGElement::isAnimatableCSSProperty(const QualifiedName& attributeName) 967{ 968 return attributeNameToAnimatedPropertyTypeMap().contains(attributeName.impl()); 969} 970 971bool SVGElement::isPresentationAttribute(const QualifiedName& name) const 972{ 973 if (cssPropertyIdForSVGAttributeName(name) > 0) 974 return true; 975 return StyledElement::isPresentationAttribute(name); 976} 977 978void SVGElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style) 979{ 980 CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(name); 981 if (propertyID > 0) 982 addPropertyToPresentationAttributeStyle(style, propertyID, value); 983} 984 985bool SVGElement::isKnownAttribute(const QualifiedName& attrName) 986{ 987 return attrName == HTMLNames::idAttr; 988} 989 990void SVGElement::svgAttributeChanged(const QualifiedName& attrName) 991{ 992 CSSPropertyID propId = cssPropertyIdForSVGAttributeName(attrName); 993 if (propId > 0) { 994 SVGElementInstance::invalidateAllInstancesOfElement(this); 995 return; 996 } 997 998 if (attrName == HTMLNames::classAttr) { 999 classAttributeChanged(className()); 1000 SVGElementInstance::invalidateAllInstancesOfElement(this); 1001 return; 1002 } 1003 1004 if (attrName == HTMLNames::idAttr) { 1005 auto renderer = this->renderer(); 1006 // Notify resources about id changes, this is important as we cache resources by id in SVGDocumentExtensions 1007 if (renderer && renderer->isSVGResourceContainer()) 1008 toRenderSVGResourceContainer(*renderer).idChanged(); 1009 if (inDocument()) 1010 buildPendingResourcesIfNeeded(); 1011 SVGElementInstance::invalidateAllInstancesOfElement(this); 1012 return; 1013 } 1014} 1015 1016Node::InsertionNotificationRequest SVGElement::insertedInto(ContainerNode& rootParent) 1017{ 1018 StyledElement::insertedInto(rootParent); 1019 updateRelativeLengthsInformation(); 1020 buildPendingResourcesIfNeeded(); 1021 return InsertionDone; 1022} 1023 1024void SVGElement::buildPendingResourcesIfNeeded() 1025{ 1026 if (!needsPendingResourceHandling() || !inDocument() || isInShadowTree()) 1027 return; 1028 1029 SVGDocumentExtensions* extensions = document().accessSVGExtensions(); 1030 String resourceId = getIdAttribute(); 1031 if (!extensions->isIdOfPendingResource(resourceId)) 1032 return; 1033 1034 // Mark pending resources as pending for removal. 1035 extensions->markPendingResourcesForRemoval(resourceId); 1036 1037 // Rebuild pending resources for each client of a pending resource that is being removed. 1038 while (Element* clientElement = extensions->removeElementFromPendingResourcesForRemovalMap(resourceId)) { 1039 ASSERT(clientElement->hasPendingResources()); 1040 if (clientElement->hasPendingResources()) { 1041 clientElement->buildPendingResource(); 1042 extensions->clearHasPendingResourcesIfPossible(clientElement); 1043 } 1044 } 1045} 1046 1047void SVGElement::childrenChanged(const ChildChange& change) 1048{ 1049 StyledElement::childrenChanged(change); 1050 1051 if (change.source == ChildChangeSourceParser) 1052 return; 1053 SVGElementInstance::invalidateAllInstancesOfElement(this); 1054} 1055 1056PassRefPtr<CSSValue> SVGElement::getPresentationAttribute(const String& name) 1057{ 1058 if (!hasAttributesWithoutUpdate()) 1059 return 0; 1060 1061 QualifiedName attributeName(nullAtom, name, nullAtom); 1062 const Attribute* attribute = findAttributeByName(attributeName); 1063 if (!attribute) 1064 return 0; 1065 1066 RefPtr<MutableStyleProperties> style = MutableStyleProperties::create(SVGAttributeMode); 1067 CSSPropertyID propertyID = cssPropertyIdForSVGAttributeName(attribute->name()); 1068 style->setProperty(propertyID, attribute->value()); 1069 RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(propertyID); 1070 return cssValue ? cssValue->cloneForCSSOM() : 0; 1071} 1072 1073bool SVGElement::instanceUpdatesBlocked() const 1074{ 1075 return m_svgRareData && m_svgRareData->instanceUpdatesBlocked(); 1076} 1077 1078void SVGElement::setInstanceUpdatesBlocked(bool value) 1079{ 1080 if (m_svgRareData) 1081 m_svgRareData->setInstanceUpdatesBlocked(value); 1082} 1083 1084AffineTransform SVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope) const 1085{ 1086 // To be overriden by SVGGraphicsElement (or as special case SVGTextElement and SVGPatternElement) 1087 return AffineTransform(); 1088} 1089 1090void SVGElement::updateRelativeLengthsInformation(bool hasRelativeLengths, SVGElement* element) 1091{ 1092 // If we're not yet in a document, this function will be called again from insertedInto(). Do nothing now. 1093 if (!inDocument()) 1094 return; 1095 1096 // An element wants to notify us that its own relative lengths state changed. 1097 // Register it in the relative length map, and register us in the parent relative length map. 1098 // Register the parent in the grandparents map, etc. Repeat procedure until the root of the SVG tree. 1099 1100 if (hasRelativeLengths) 1101 m_elementsWithRelativeLengths.add(element); 1102 else { 1103 if (!m_elementsWithRelativeLengths.contains(element)) { 1104 // We were never registered. Do nothing. 1105 return; 1106 } 1107 1108 m_elementsWithRelativeLengths.remove(element); 1109 } 1110 1111 if (!element->isSVGGraphicsElement()) 1112 return; 1113 1114 // Find first styled parent node, and notify it that we've changed our relative length state. 1115 ContainerNode* node = parentNode(); 1116 while (node) { 1117 if (!node->isSVGElement()) 1118 break; 1119 1120 // Register us in the parent element map. 1121 toSVGElement(node)->updateRelativeLengthsInformation(hasRelativeLengths, this); 1122 break; 1123 } 1124} 1125 1126bool SVGElement::hasFocusEventListeners() const 1127{ 1128 Element* eventTarget = const_cast<SVGElement*>(this); 1129 return eventTarget->hasEventListeners(eventNames().focusinEvent) 1130 || eventTarget->hasEventListeners(eventNames().focusoutEvent) 1131 || eventTarget->hasEventListeners(eventNames().focusEvent) 1132 || eventTarget->hasEventListeners(eventNames().blurEvent); 1133} 1134 1135bool SVGElement::isMouseFocusable() const 1136{ 1137 if (!isFocusable()) 1138 return false; 1139 Element* eventTarget = const_cast<SVGElement*>(this); 1140 return hasFocusEventListeners() 1141 || eventTarget->hasEventListeners(eventNames().keydownEvent) 1142 || eventTarget->hasEventListeners(eventNames().keyupEvent) 1143 || eventTarget->hasEventListeners(eventNames().keypressEvent); 1144} 1145 1146void SVGElement::accessKeyAction(bool sendMouseEvents) 1147{ 1148 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 1149} 1150 1151} 1152