Changeset 277013 in webkit
- Timestamp:
- May 5, 2021 6:14:26 AM (3 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r277011 r277013 1 2021-05-05 Ryosuke Niwa <rniwa@webkit.org> 2 3 Use WeakHashSet instead of HashSet of raw pointes in Document and SVGDocumentExtensions 4 https://bugs.webkit.org/show_bug.cgi?id=225390 5 6 Reviewed by Antti Koivisto. 7 8 Replaced Document's m_documentSuspensionCallbackElements and m_articleElements as well as 9 SVGDocumentExtensions's m_timeContainers and m_svgFontFaceElements with WeakHashSet. 10 11 Also moved m_svgUseElements from Document to SVGDocumentExtensions as a WeakHashSet. 12 13 This patch also deletes Document::m_mediaStreamStateChangeElements which was never used, 14 m_mainArticleElement a WeakPtr instead of a raw pointer, replaces SVGDocumentExtensions's 15 m_rebuildElements, which is a temporary Vector used during tree mutations, with a Vector 16 of Refs instead of raw pointers. 17 18 No new tests since there should be no observable behavior differences. 19 20 * dom/Document.cpp: 21 (WebCore::Document::~Document): The release assert is moved to ~SVGDocumentExtensions. 22 (WebCore::Document::resolveStyle): 23 (WebCore::Document::suspend): 24 (WebCore::Document::resume): 25 (WebCore::Document::registerForDocumentSuspensionCallbacks): 26 (WebCore::Document::unregisterForDocumentSuspensionCallbacks): 27 (WebCore::Document::addSVGUseElement): Moved to SVGDocumentExtensions. 28 (WebCore::Document::removeSVGUseElement): Ditto. 29 (WebCore::Document::registerArticleElement): 30 (WebCore::Document::unregisterArticleElement): 31 (WebCore::Document::updateMainArticleElementAfterLayout): 32 (WebCore::Document::prepareCanvasesForDisplayIfNeeded): 33 (WebCore::Document::clearCanvasPreparation): 34 (WebCore::Document::canvasChanged): 35 (WebCore::Document::canvasDestroyed): 36 * dom/Document.h: 37 (WebCore::Document:: const const): Deleted. 38 * html/HTMLCanvasElement.cpp: 39 (WebCore::HTMLCanvasElement::~HTMLCanvasElement): 40 (WebCore::HTMLCanvasElement::didMoveToNewDocument): 41 (WebCore::HTMLCanvasElement::removedFromAncestor): 42 * style/StyleResolver.cpp: 43 (WebCore::Style::Resolver::addCurrentSVGFontFaceRules): 44 * svg/SVGDocumentExtensions.cpp: 45 (WebCore::SVGDocumentExtensions::~SVGDocumentExtensions): Moved the release assertion 46 from ~Document. 47 (WebCore::SVGDocumentExtensions::addTimeContainer): 48 (WebCore::SVGDocumentExtensions::removeTimeContainer): 49 (WebCore::SVGDocumentExtensions::addUseElementWithPendingShadowTreeUpdate): Moved here 50 Document::addSVGUseElement. 51 (WebCore::SVGDocumentExtensions::removeUseElementWithPendingShadowTreeUpdate): Ditto. 52 (WebCore::SVGDocumentExtensions::startAnimations): 53 (WebCore::SVGDocumentExtensions::pauseAnimations): 54 (WebCore::SVGDocumentExtensions::unpauseAnimations): 55 (WebCore::SVGDocumentExtensions::dispatchLoadEventToOutermostSVGElements): 56 (WebCore::SVGDocumentExtensions::rebuildElements): 57 (WebCore::SVGDocumentExtensions::clearTargetDependencies): 58 (WebCore::SVGDocumentExtensions::removeAllElementReferencesForTarget): 59 (WebCore::SVGDocumentExtensions::registerSVGFontFaceElement): 60 (WebCore::SVGDocumentExtensions::unregisterSVGFontFaceElement): 61 * svg/SVGDocumentExtensions.h: 62 (WebCore::SVGDocumentExtensions::useElementsWithPendingShadowTreeUpdate const): Added. 63 (WebCore::SVGDocumentExtensions::svgFontFaceElements const): 64 * svg/SVGUseElement.cpp: 65 (WebCore::SVGUseElement::insertedIntoAncestor): 66 (WebCore::SVGUseElement::removedFromAncestor): 67 (WebCore::SVGUseElement::updateShadowTree): 68 (WebCore::SVGUseElement::invalidateShadowTree): 69 1 70 2021-05-05 Chris Lord <clord@igalia.com> 2 71 -
trunk/Source/WebCore/dom/Document.cpp
r276952 r277013 780 780 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_listsInvalidatedAtDocument.isEmpty()); 781 781 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_collectionsInvalidatedAtDocument.isEmpty()); 782 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_svgUseElements.isEmpty());783 782 784 783 for (unsigned count : m_nodeListAndCollectionCounts) … … 2008 2007 2009 2008 // FIXME: Do this update per tree scope. 2010 {2011 auto elements = copyToVectorOf<Ref Ptr<SVGUseElement>>(m_svgUseElements);2009 if (auto* extensions = m_svgExtensions.get()) { 2010 auto elements = copyToVectorOf<Ref<SVGUseElement>>(extensions->useElementsWithPendingShadowTreeUpdate()); 2012 2011 // We can't clear m_svgUseElements here because updateShadowTree may end up executing arbitrary scripts 2013 2012 // which may insert new SVG use elements or remove existing ones inside sync IPC via ImageLoader::updateFromElement. … … 5649 5648 documentWillBecomeInactive(); 5650 5649 5651 for (auto *element : m_documentSuspensionCallbackElements)5652 element ->prepareForDocumentSuspension();5650 for (auto& element : m_documentSuspensionCallbackElements) 5651 element.prepareForDocumentSuspension(); 5653 5652 5654 5653 #if ASSERT_ENABLED … … 5697 5696 return; 5698 5697 5699 for (auto * element : copyToVector(m_documentSuspensionCallbackElements))5698 for (auto element : copyToVectorOf<Ref<Element>>(m_documentSuspensionCallbackElements)) 5700 5699 element->resumeFromDocumentSuspension(); 5701 5700 … … 5727 5726 void Document::registerForDocumentSuspensionCallbacks(Element& element) 5728 5727 { 5729 m_documentSuspensionCallbackElements.add( &element);5728 m_documentSuspensionCallbackElements.add(element); 5730 5729 } 5731 5730 5732 5731 void Document::unregisterForDocumentSuspensionCallbacks(Element& element) 5733 5732 { 5734 m_documentSuspensionCallbackElements.remove( &element);5733 m_documentSuspensionCallbackElements.remove(element); 5735 5734 } 5736 5735 … … 6053 6052 m_svgExtensions = makeUnique<SVGDocumentExtensions>(*this); 6054 6053 return *m_svgExtensions; 6055 }6056 6057 void Document::addSVGUseElement(SVGUseElement& element)6058 {6059 auto result = m_svgUseElements.add(&element);6060 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(result.isNewEntry);6061 }6062 6063 void Document::removeSVGUseElement(SVGUseElement& element)6064 {6065 m_svgUseElements.remove(&element);6066 // FIXME: Assert that element was in m_svgUseElements once re-entrancy to update style and layout have been removed.6067 6054 } 6068 6055 … … 8343 8330 void Document::registerArticleElement(Element& article) 8344 8331 { 8345 m_articleElements.add( &article);8332 m_articleElements.add(article); 8346 8333 } 8347 8334 8348 8335 void Document::unregisterArticleElement(Element& article) 8349 8336 { 8350 m_articleElements.remove( &article);8337 m_articleElements.remove(article); 8351 8338 if (m_mainArticleElement == &article) 8352 8339 m_mainArticleElement = nullptr; … … 8368 8355 m_mainArticleElement = nullptr; 8369 8356 8370 auto numberOfArticles = m_articleElements. size();8357 auto numberOfArticles = m_articleElements.computeSize(); 8371 8358 if (!numberOfArticles || numberOfArticles > maxNumberOfArticlesBeforeIgnoringMainContentArticle) 8372 8359 return; 8373 8360 8374 Element* tallestArticle = nullptr;8361 RefPtr<Element> tallestArticle; 8375 8362 float tallestArticleHeight = 0; 8376 8363 float tallestArticleWidth = 0; 8377 8364 float secondTallestArticleHeight = 0; 8378 8365 8379 for (auto *article : m_articleElements) {8380 auto* box = article ->renderBox();8366 for (auto& article : m_articleElements) { 8367 auto* box = article.renderBox(); 8381 8368 float height = box ? box->height().toFloat() : 0; 8382 8369 if (height >= tallestArticleHeight) { … … 8384 8371 tallestArticleHeight = height; 8385 8372 tallestArticleWidth = box ? box->width().toFloat() : 0; 8386 tallestArticle = article;8373 tallestArticle = &article; 8387 8374 } else if (height >= secondTallestArticleHeight) 8388 8375 secondTallestArticleHeight = height; … … 8390 8377 8391 8378 if (numberOfArticles == 1) { 8392 m_mainArticleElement = tallestArticle;8379 m_mainArticleElement = makeWeakPtr(tallestArticle.get()); 8393 8380 return; 8394 8381 } … … 8404 8391 return; 8405 8392 8406 m_mainArticleElement = tallestArticle;8393 m_mainArticleElement = makeWeakPtr(tallestArticle.get()); 8407 8394 } 8408 8395 … … 8958 8945 // would be nice if this could be enforced to remove the copyToVector. 8959 8946 8960 for (auto* canvas : copyToVector(m_canvasesNeedingDisplayPreparation)) { 8947 auto canvases = copyToVectorOf<Ref<HTMLCanvasElement>>(m_canvasesNeedingDisplayPreparation); 8948 m_canvasesNeedingDisplayPreparation.clear(); 8949 for (auto& canvas : canvases) { 8961 8950 // However, if they are not in the document body, then they won't 8962 8951 // be composited and thus don't need preparation. Unfortunately they … … 8965 8954 if (!canvas->isInTreeScope()) 8966 8955 continue; 8967 8968 auto protectedCanvas = makeRef(*canvas); 8969 protectedCanvas->prepareForDisplay(); 8970 } 8971 m_canvasesNeedingDisplayPreparation.clear(); 8972 } 8973 8974 void Document::clearCanvasPreparation(HTMLCanvasElement* canvas) 8956 canvas->prepareForDisplay(); 8957 } 8958 } 8959 8960 void Document::clearCanvasPreparation(HTMLCanvasElement& canvas) 8975 8961 { 8976 8962 m_canvasesNeedingDisplayPreparation.remove(canvas); … … 8979 8965 void Document::canvasChanged(CanvasBase& canvasBase, const Optional<FloatRect>&) 8980 8966 { 8981 if ( is<HTMLCanvasElement>(canvasBase)) {8982 auto* canvas = downcast<HTMLCanvasElement>(&canvasBase);8983 if (canvas->needsPreparationForDisplay())8984 m_canvasesNeedingDisplayPreparation.add(canvas);8985 }8967 if (!is<HTMLCanvasElement>(canvasBase)) 8968 return; 8969 auto& canvas = downcast<HTMLCanvasElement>(canvasBase); 8970 if (canvas.needsPreparationForDisplay()) 8971 m_canvasesNeedingDisplayPreparation.add(canvas); 8986 8972 } 8987 8973 8988 8974 void Document::canvasDestroyed(CanvasBase& canvasBase) 8989 8975 { 8990 if ( is<HTMLCanvasElement>(canvasBase)) {8991 auto* canvas = downcast<HTMLCanvasElement>(&canvasBase);8992 m_canvasesNeedingDisplayPreparation.remove(canvas);8993 }8976 if (!is<HTMLCanvasElement>(canvasBase)) 8977 return; 8978 auto& canvas = downcast<HTMLCanvasElement>(canvasBase); 8979 m_canvasesNeedingDisplayPreparation.remove(canvas); 8994 8980 } 8995 8981 -
trunk/Source/WebCore/dom/Document.h
r276952 r277013 1151 1151 WEBCORE_EXPORT SVGDocumentExtensions& accessSVGExtensions(); 1152 1152 1153 void addSVGUseElement(SVGUseElement&);1154 void removeSVGUseElement(SVGUseElement&);1155 HashSet<SVGUseElement*> const svgUseElements() const { return m_svgUseElements; }1156 1157 1153 void initSecurityContext(); 1158 1154 void initContentSecurityPolicy(); … … 1612 1608 1613 1609 void prepareCanvasesForDisplayIfNeeded(); 1614 void clearCanvasPreparation(HTMLCanvasElement *);1610 void clearCanvasPreparation(HTMLCanvasElement&); 1615 1611 void canvasChanged(CanvasBase&, const Optional<FloatRect>&) final; 1616 1612 void canvasResized(CanvasBase&) final { }; … … 1860 1856 1861 1857 std::unique_ptr<SVGDocumentExtensions> m_svgExtensions; 1862 HashSet<SVGUseElement*> m_svgUseElements;1863 1858 1864 1859 // Collection of canvas objects that need to do work after they've 1865 1860 // rendered but before compositing, for the next frame. The set is 1866 1861 // cleared after they've been called. 1867 HashSet<HTMLCanvasElement*> m_canvasesNeedingDisplayPreparation;1862 WeakHashSet<HTMLCanvasElement> m_canvasesNeedingDisplayPreparation; 1868 1863 1869 1864 #if ENABLE(DARK_MODE_CSS) … … 1874 1869 HashMap<String, RefPtr<HTMLCanvasElement>> m_cssCanvasElements; 1875 1870 1876 HashSet<Element*> m_documentSuspensionCallbackElements;1871 WeakHashSet<Element> m_documentSuspensionCallbackElements; 1877 1872 1878 1873 #if ENABLE(VIDEO) … … 1885 1880 #endif 1886 1881 1887 Element* m_mainArticleElement { nullptr };1888 HashSet<Element*> m_articleElements;1882 WeakPtr<Element> m_mainArticleElement; 1883 WeakHashSet<Element> m_articleElements; 1889 1884 1890 1885 HashSet<VisibilityChangeClient*> m_visibilityStateCallbackClients; … … 2132 2127 2133 2128 #if ENABLE(MEDIA_STREAM) 2134 HashSet<HTMLMediaElement*> m_mediaStreamStateChangeElements;2135 2129 String m_idHashSalt; 2136 2130 bool m_hasHadCaptureMediaStreamTrack { false }; -
trunk/Source/WebCore/html/HTMLCanvasElement.cpp
r276999 r277013 156 156 // avoided in destructors, but works as long as it's done before HTMLCanvasElement destructs completely. 157 157 notifyObserversCanvasDestroyed(); 158 document().clearCanvasPreparation( this);158 document().clearCanvasPreparation(*this); 159 159 160 160 m_context = nullptr; // Ensure this goes away before the ImageBuffer. … … 1056 1056 { 1057 1057 if (needsPreparationForDisplay()) { 1058 oldDocument.clearCanvasPreparation( this);1058 oldDocument.clearCanvasPreparation(*this); 1059 1059 removeObserver(oldDocument); 1060 1060 addObserver(newDocument); … … 1081 1081 { 1082 1082 if (needsPreparationForDisplay() && removalType.disconnectedFromDocument) { 1083 oldParentOfRemovedTree.document().clearCanvasPreparation( this);1083 oldParentOfRemovedTree.document().clearCanvasPreparation(*this); 1084 1084 removeObserver(oldParentOfRemovedTree.document()); 1085 1085 } -
trunk/Source/WebCore/style/StyleResolver.cpp
r276588 r277013 131 131 { 132 132 if (m_document.svgExtensions()) { 133 const HashSet<SVGFontFaceElement*>& svgFontFaceElements = m_document.svgExtensions()->svgFontFaceElements();134 for (auto *svgFontFaceElement : svgFontFaceElements)135 m_document.fontSelector().addFontFaceRule(svgFontFaceElement ->fontFaceRule(), svgFontFaceElement->isInUserAgentShadowTree());133 auto& svgFontFaceElements = m_document.svgExtensions()->svgFontFaceElements(); 134 for (auto& svgFontFaceElement : svgFontFaceElements) 135 m_document.fontSelector().addFontFaceRule(svgFontFaceElement.fontFaceRule(), svgFontFaceElement.isInUserAgentShadowTree()); 136 136 } 137 137 } -
trunk/Source/WebCore/svg/SVGDocumentExtensions.cpp
r267497 r277013 31 31 #include "SMILTimeContainer.h" 32 32 #include "SVGElement.h" 33 #include "SVGFontFaceElement.h" 33 34 #include "SVGResourcesCache.h" 34 35 #include "SVGSMILElement.h" … … 47 48 } 48 49 49 SVGDocumentExtensions::~SVGDocumentExtensions() = default; 50 SVGDocumentExtensions::~SVGDocumentExtensions() 51 { 52 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_useElementsWithPendingShadowTreeUpdate.computesEmpty()); 53 } 50 54 51 55 void SVGDocumentExtensions::addTimeContainer(SVGSVGElement& element) 52 56 { 53 m_timeContainers.add( &element);57 m_timeContainers.add(element); 54 58 if (m_areAnimationsPaused) 55 59 element.pauseAnimations(); … … 58 62 void SVGDocumentExtensions::removeTimeContainer(SVGSVGElement& element) 59 63 { 60 m_timeContainers.remove( &element);64 m_timeContainers.remove(element); 61 65 } 62 66 … … 85 89 return m_resources.get(id); 86 90 } 91 92 93 void SVGDocumentExtensions::addUseElementWithPendingShadowTreeUpdate(SVGUseElement& element) 94 { 95 auto result = m_useElementsWithPendingShadowTreeUpdate.add(element); 96 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(result.isNewEntry); 97 } 98 99 void SVGDocumentExtensions::removeUseElementWithPendingShadowTreeUpdate(SVGUseElement& element) 100 { 101 m_useElementsWithPendingShadowTreeUpdate.remove(element); 102 // FIXME: Assert that element was in m_svgUseElements once re-entrancy to update style and layout have been removed. 103 } 104 87 105 88 106 void SVGDocumentExtensions::startAnimations() … … 92 110 // FIXME: We hold a ref pointers to prevent a shadow tree from getting removed out from underneath us. 93 111 // In the future we should refactor the use-element to avoid this. See https://webkit.org/b/53704 94 Vector<RefPtr<SVGSVGElement>> timeContainers; 95 timeContainers.appendRange(m_timeContainers.begin(), m_timeContainers.end()); 112 auto timeContainers = copyToVectorOf<Ref<SVGSVGElement>>(m_timeContainers); 96 113 for (auto& element : timeContainers) 97 114 element->timeContainer().begin(); … … 101 118 { 102 119 for (auto& container : m_timeContainers) 103 container ->pauseAnimations();120 container.pauseAnimations(); 104 121 m_areAnimationsPaused = true; 105 122 } … … 108 125 { 109 126 for (auto& container : m_timeContainers) 110 container ->unpauseAnimations();127 container.unpauseAnimations(); 111 128 m_areAnimationsPaused = false; 112 129 } … … 114 131 void SVGDocumentExtensions::dispatchLoadEventToOutermostSVGElements() 115 132 { 116 Vector<RefPtr<SVGSVGElement>> timeContainers; 117 timeContainers.appendRange(m_timeContainers.begin(), m_timeContainers.end()); 118 133 auto timeContainers = copyToVectorOf<Ref<SVGSVGElement>>(m_timeContainers); 119 134 for (auto& container : timeContainers) { 120 135 if (!container->isOutermostSVGSVGElement()) … … 303 318 void SVGDocumentExtensions::rebuildElements() 304 319 { 305 Vector<SVGElement*> shadowRebuildElements = WTFMove(m_rebuildElements);306 for (auto *element : shadowRebuildElements)320 auto shadowRebuildElements = std::exchange(m_rebuildElements, { }); 321 for (auto& element : shadowRebuildElements) 307 322 element->svgAttributeChanged(SVGNames::hrefAttr); 308 323 } … … 314 329 return; 315 330 for (auto* element : *referencingElements) { 316 m_rebuildElements.append( element);331 m_rebuildElements.append(*element); 317 332 element->callClearTarget(); 318 333 } … … 339 354 { 340 355 m_elementDependencies.remove(&referencedElement); 341 m_rebuildElements.removeFirst( &referencedElement);356 m_rebuildElements.removeFirst(referencedElement); 342 357 } 343 358 344 359 void SVGDocumentExtensions::registerSVGFontFaceElement(SVGFontFaceElement& element) 345 360 { 346 m_svgFontFaceElements.add( &element);361 m_svgFontFaceElements.add(element); 347 362 } 348 363 349 364 void SVGDocumentExtensions::unregisterSVGFontFaceElement(SVGFontFaceElement& element) 350 365 { 351 ASSERT(m_svgFontFaceElements.contains( &element));352 m_svgFontFaceElements.remove( &element);353 } 354 355 } 366 ASSERT(m_svgFontFaceElements.contains(element)); 367 m_svgFontFaceElements.remove(element); 368 } 369 370 } -
trunk/Source/WebCore/svg/SVGDocumentExtensions.h
r267497 r277013 51 51 RenderSVGResourceContainer* resourceById(const AtomString& id) const; 52 52 53 void addUseElementWithPendingShadowTreeUpdate(SVGUseElement&); 54 void removeUseElementWithPendingShadowTreeUpdate(SVGUseElement&); 55 const WeakHashSet<SVGUseElement>& useElementsWithPendingShadowTreeUpdate() const { return m_useElementsWithPendingShadowTreeUpdate; } 56 53 57 void startAnimations(); 54 58 void pauseAnimations(); … … 71 75 void rebuildElements(); 72 76 73 const HashSet<SVGFontFaceElement*>& svgFontFaceElements() const { return m_svgFontFaceElements; }77 const WeakHashSet<SVGFontFaceElement>& svgFontFaceElements() const { return m_svgFontFaceElements; } 74 78 void registerSVGFontFaceElement(SVGFontFaceElement&); 75 79 void unregisterSVGFontFaceElement(SVGFontFaceElement&); … … 77 81 private: 78 82 Document& m_document; 79 HashSet<SVGSVGElement*> m_timeContainers; // For SVG 1.2 support this will need to be made more general.80 HashSet<SVGFontFaceElement*> m_svgFontFaceElements;83 WeakHashSet<SVGSVGElement> m_timeContainers; // For SVG 1.2 support this will need to be made more general. 84 WeakHashSet<SVGFontFaceElement> m_svgFontFaceElements; 81 85 HashMap<AtomString, RenderSVGResourceContainer*> m_resources; 82 86 HashMap<AtomString, std::unique_ptr<PendingElements>> m_pendingResources; // Resources that are pending. … … 85 89 std::unique_ptr<SVGResourcesCache> m_resourcesCache; 86 90 87 Vector<SVGElement*> m_rebuildElements; 91 Vector<Ref<SVGElement>> m_rebuildElements; 92 WeakHashSet<SVGUseElement> m_useElementsWithPendingShadowTreeUpdate; 88 93 bool m_areAnimationsPaused; 89 94 -
trunk/Source/WebCore/svg/SVGUseElement.cpp
r275410 r277013 100 100 if (insertionType.connectedToDocument) { 101 101 if (m_shadowTreeNeedsUpdate) 102 document().a ddSVGUseElement(*this);102 document().accessSVGExtensions().addUseElementWithPendingShadowTreeUpdate(*this); 103 103 invalidateShadowTree(); 104 104 // FIXME: Move back the call to updateExternalDocument() here once notifyFinished is made always async. … … 119 119 if (removalType.disconnectedFromDocument) { 120 120 if (m_shadowTreeNeedsUpdate) 121 document(). removeSVGUseElement(*this);121 document().accessSVGExtensions().removeUseElementWithPendingShadowTreeUpdate(*this); 122 122 } 123 123 SVGGraphicsElement::removedFromAncestor(removalType, oldParentOfRemovedTree); … … 226 226 if (!isConnected()) 227 227 return; 228 document(). removeSVGUseElement(*this);228 document().accessSVGExtensions().removeUseElementWithPendingShadowTreeUpdate(*this); 229 229 230 230 String targetID; … … 530 530 invalidateDependentShadowTrees(); 531 531 if (isConnected()) 532 document().a ddSVGUseElement(*this);532 document().accessSVGExtensions().addUseElementWithPendingShadowTreeUpdate(*this); 533 533 } 534 534
Note: See TracChangeset
for help on using the changeset viewer.