Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp

Issue 2692093003: Rewrite DocumentMarkerController to use SynchronousMutationObserver (Closed)
Patch Set: Improve removal of zero-length markers, add PaintInvalidationReason Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org) 4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * (C) 2006 Alexey Proskuryakov (ap@webkit.org) 5 * (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
7 * reserved. 7 * reserved.
8 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. 8 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
9 * (http://www.torchmobile.com/) 9 * (http://www.torchmobile.com/)
10 * Copyright (C) Research In Motion Limited 2010. All rights reserved. 10 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
77 return DocumentMarker::SpellingMarkerIndex; 77 return DocumentMarker::SpellingMarkerIndex;
78 } 78 }
79 79
80 } // namespace 80 } // namespace
81 81
82 inline bool DocumentMarkerController::possiblyHasMarkers( 82 inline bool DocumentMarkerController::possiblyHasMarkers(
83 DocumentMarker::MarkerTypes types) { 83 DocumentMarker::MarkerTypes types) {
84 return m_possiblyExistingMarkerTypes.intersects(types); 84 return m_possiblyExistingMarkerTypes.intersects(types);
85 } 85 }
86 86
87 DocumentMarkerController::DocumentMarkerController(const Document& document) 87 DocumentMarkerController::DocumentMarkerController(Document& document)
88 : m_possiblyExistingMarkerTypes(0), m_document(&document) {} 88 : m_possiblyExistingMarkerTypes(0), m_document(&document) {
89 setContext(&document);
90 }
89 91
90 void DocumentMarkerController::clear() { 92 void DocumentMarkerController::clear() {
91 m_markers.clear(); 93 m_markers.clear();
92 m_possiblyExistingMarkerTypes = 0; 94 m_possiblyExistingMarkerTypes = 0;
93 } 95 }
94 96
95 void DocumentMarkerController::addMarker(const Position& start, 97 void DocumentMarkerController::addMarker(const Position& start,
96 const Position& end, 98 const Position& end,
97 DocumentMarker::MarkerType type, 99 DocumentMarker::MarkerType type,
98 const String& description, 100 const String& description,
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 TextIterator markedText(range.startPosition(), range.endPosition()); 173 TextIterator markedText(range.startPosition(), range.endPosition());
172 DocumentMarkerController::removeMarkers( 174 DocumentMarkerController::removeMarkers(
173 markedText, markerTypes, shouldRemovePartiallyOverlappingMarker); 175 markedText, markerTypes, shouldRemovePartiallyOverlappingMarker);
174 } 176 }
175 177
176 static bool startsFurther(const Member<RenderedDocumentMarker>& lhv, 178 static bool startsFurther(const Member<RenderedDocumentMarker>& lhv,
177 const DocumentMarker* rhv) { 179 const DocumentMarker* rhv) {
178 return lhv->startOffset() < rhv->startOffset(); 180 return lhv->startOffset() < rhv->startOffset();
179 } 181 }
180 182
181 static bool startsAfter(const Member<RenderedDocumentMarker>& marker,
182 size_t startOffset) {
183 return marker->startOffset() < startOffset;
184 }
185
186 static bool endsBefore(size_t startOffset, 183 static bool endsBefore(size_t startOffset,
187 const Member<RenderedDocumentMarker>& rhv) { 184 const Member<RenderedDocumentMarker>& rhv) {
188 return startOffset < rhv->endOffset(); 185 return startOffset < rhv->endOffset();
189 } 186 }
190 187
191 static bool compareByStart(const Member<DocumentMarker>& lhv, 188 static bool compareByStart(const Member<DocumentMarker>& lhv,
192 const Member<DocumentMarker>& rhv) { 189 const Member<DocumentMarker>& rhv) {
193 return lhv->startOffset() < rhv->startOffset(); 190 return lhv->startOffset() < rhv->startOffset();
194 } 191 }
195 192
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
258 newMarker.type() != DocumentMarker::Composition) { 255 newMarker.type() != DocumentMarker::Composition) {
259 mergeOverlapping(list.get(), newRenderedMarker); 256 mergeOverlapping(list.get(), newRenderedMarker);
260 } else { 257 } else {
261 MarkerList::iterator pos = std::lower_bound(list->begin(), list->end(), 258 MarkerList::iterator pos = std::lower_bound(list->begin(), list->end(),
262 &newMarker, startsFurther); 259 &newMarker, startsFurther);
263 list->insert(pos - list->begin(), newRenderedMarker); 260 list->insert(pos - list->begin(), newRenderedMarker);
264 } 261 }
265 } 262 }
266 263
267 // repaint the affected node 264 // repaint the affected node
268 if (node->layoutObject()) 265 if (node->layoutObject()) {
269 node->layoutObject()->setShouldDoFullPaintInvalidation(); 266 node->layoutObject()->setShouldDoFullPaintInvalidation(
267 PaintInvalidationDocumentMarkerChange);
268 }
270 } 269 }
271 270
272 void DocumentMarkerController::mergeOverlapping( 271 void DocumentMarkerController::mergeOverlapping(
273 MarkerList* list, 272 MarkerList* list,
274 RenderedDocumentMarker* toInsert) { 273 RenderedDocumentMarker* toInsert) {
275 MarkerList::iterator firstOverlapping = 274 MarkerList::iterator firstOverlapping =
276 std::lower_bound(list->begin(), list->end(), toInsert, doesNotOverlap); 275 std::lower_bound(list->begin(), list->end(), toInsert, doesNotOverlap);
277 size_t index = firstOverlapping - list->begin(); 276 size_t index = firstOverlapping - list->begin();
278 list->insert(index, toInsert); 277 list->insert(index, toInsert);
279 MarkerList::iterator inserted = list->begin() + index; 278 MarkerList::iterator inserted = list->begin() + index;
(...skipping 21 matching lines...) Expand all
301 300
302 if (!possiblyHasMarkers(DocumentMarker::AllMarkers())) 301 if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
303 return; 302 return;
304 DCHECK(!m_markers.isEmpty()); 303 DCHECK(!m_markers.isEmpty());
305 304
306 MarkerLists* markers = m_markers.get(srcNode); 305 MarkerLists* markers = m_markers.get(srcNode);
307 if (!markers) 306 if (!markers)
308 return; 307 return;
309 308
310 bool docDirty = false; 309 bool docDirty = false;
311 for (size_t markerListIndex = 0; 310 for (Member<MarkerList> list : *markers) {
312 markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
313 ++markerListIndex) {
314 Member<MarkerList>& list = (*markers)[markerListIndex];
315 if (!list) 311 if (!list)
316 continue; 312 continue;
317 313
318 unsigned endOffset = startOffset + length - 1; 314 unsigned endOffset = startOffset + length - 1;
319 MarkerList::iterator startPos = std::lower_bound( 315 MarkerList::iterator startPos = std::lower_bound(
320 list->begin(), list->end(), startOffset, doesNotInclude); 316 list->begin(), list->end(), startOffset, doesNotInclude);
321 for (MarkerList::iterator i = startPos; i != list->end(); ++i) { 317 for (MarkerList::iterator i = startPos; i != list->end(); ++i) {
322 DocumentMarker* marker = i->get(); 318 DocumentMarker* marker = i->get();
323 319
324 // stop if we are now past the specified range 320 // stop if we are now past the specified range
325 if (marker->startOffset() > endOffset) 321 if (marker->startOffset() > endOffset)
326 break; 322 break;
327 323
328 // pin the marker to the specified range and apply the shift delta 324 // pin the marker to the specified range and apply the shift delta
329 docDirty = true; 325 docDirty = true;
330 if (marker->startOffset() < startOffset) 326 if (marker->startOffset() < startOffset)
331 marker->setStartOffset(startOffset); 327 marker->setStartOffset(startOffset);
332 if (marker->endOffset() > endOffset) 328 if (marker->endOffset() > endOffset)
333 marker->setEndOffset(endOffset); 329 marker->setEndOffset(endOffset);
334 marker->shiftOffsets(delta); 330 marker->shiftOffsets(delta);
335 331
336 addMarker(dstNode, *marker); 332 addMarker(dstNode, *marker);
337 } 333 }
338 } 334 }
339 335
340 // repaint the affected node 336 // repaint the affected node
341 if (docDirty && dstNode->layoutObject()) 337 if (docDirty && dstNode->layoutObject()) {
342 dstNode->layoutObject()->setShouldDoFullPaintInvalidation(); 338 dstNode->layoutObject()->setShouldDoFullPaintInvalidation(
339 PaintInvalidationDocumentMarkerChange);
340 }
343 } 341 }
344 342
345 void DocumentMarkerController::removeMarkers( 343 void DocumentMarkerController::removeMarkers(
346 Node* node, 344 Node* node,
347 unsigned startOffset, 345 unsigned startOffset,
348 int length, 346 int length,
349 DocumentMarker::MarkerTypes markerTypes, 347 DocumentMarker::MarkerTypes markerTypes,
350 RemovePartiallyOverlappingMarkerOrNot 348 RemovePartiallyOverlappingMarkerOrNot
351 shouldRemovePartiallyOverlappingMarker) { 349 shouldRemovePartiallyOverlappingMarker) {
352 if (length <= 0) 350 if (length <= 0)
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
421 } 419 }
422 } 420 }
423 421
424 if (emptyListsCount == DocumentMarker::MarkerTypeIndexesCount) { 422 if (emptyListsCount == DocumentMarker::MarkerTypeIndexesCount) {
425 m_markers.erase(node); 423 m_markers.erase(node);
426 if (m_markers.isEmpty()) 424 if (m_markers.isEmpty())
427 m_possiblyExistingMarkerTypes = 0; 425 m_possiblyExistingMarkerTypes = 0;
428 } 426 }
429 427
430 // repaint the affected node 428 // repaint the affected node
431 if (docDirty && node->layoutObject()) 429 if (docDirty && node->layoutObject()) {
432 node->layoutObject()->setShouldDoFullPaintInvalidation(); 430 node->layoutObject()->setShouldDoFullPaintInvalidation(
431 PaintInvalidationDocumentMarkerChange);
432 }
433 } 433 }
434 434
435 DocumentMarkerVector DocumentMarkerController::markersFor( 435 DocumentMarkerVector DocumentMarkerController::markersFor(
436 Node* node, 436 Node* node,
437 DocumentMarker::MarkerTypes markerTypes) { 437 DocumentMarker::MarkerTypes markerTypes) {
438 DocumentMarkerVector result; 438 DocumentMarkerVector result;
439 439
440 MarkerLists* markers = m_markers.get(node); 440 MarkerLists* markers = m_markers.get(node);
441 if (!markers) 441 if (!markers)
442 return result; 442 return result;
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after
581 581
582 if (markerList->front()->type() == DocumentMarker::TextMatch) 582 if (markerList->front()->type() == DocumentMarker::TextMatch)
583 invalidatePaintForTickmarks(node); 583 invalidatePaintForTickmarks(node);
584 } 584 }
585 } 585 }
586 } 586 }
587 587
588 DEFINE_TRACE(DocumentMarkerController) { 588 DEFINE_TRACE(DocumentMarkerController) {
589 visitor->trace(m_markers); 589 visitor->trace(m_markers);
590 visitor->trace(m_document); 590 visitor->trace(m_document);
591 SynchronousMutationObserver::trace(visitor);
591 } 592 }
592 593
593 void DocumentMarkerController::removeMarkers( 594 void DocumentMarkerController::removeMarkers(
594 Node* node, 595 Node* node,
595 DocumentMarker::MarkerTypes markerTypes) { 596 DocumentMarker::MarkerTypes markerTypes) {
596 if (!possiblyHasMarkers(markerTypes)) 597 if (!possiblyHasMarkers(markerTypes))
597 return; 598 return;
598 DCHECK(!m_markers.isEmpty()); 599 DCHECK(!m_markers.isEmpty());
599 600
600 MarkerMap::iterator iterator = m_markers.find(node); 601 MarkerMap::iterator iterator = m_markers.find(node);
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
678 needsRepainting = true; 679 needsRepainting = true;
679 } 680 }
680 } 681 }
681 682
682 nodeCanBeRemoved = 683 nodeCanBeRemoved =
683 emptyListsCount == DocumentMarker::MarkerTypeIndexesCount; 684 emptyListsCount == DocumentMarker::MarkerTypeIndexesCount;
684 } 685 }
685 686
686 if (needsRepainting) { 687 if (needsRepainting) {
687 const Node& node = *iterator->key; 688 const Node& node = *iterator->key;
688 if (LayoutObject* layoutObject = node.layoutObject()) 689 if (LayoutObject* layoutObject = node.layoutObject()) {
689 layoutObject->setShouldDoFullPaintInvalidation(); 690 layoutObject->setShouldDoFullPaintInvalidation(
691 PaintInvalidationDocumentMarkerChange);
692 }
690 invalidatePaintForTickmarks(node); 693 invalidatePaintForTickmarks(node);
691 } 694 }
692 695
693 if (nodeCanBeRemoved) { 696 if (nodeCanBeRemoved) {
694 m_markers.remove(iterator); 697 m_markers.remove(iterator);
695 if (m_markers.isEmpty()) 698 if (m_markers.isEmpty())
696 m_possiblyExistingMarkerTypes = 0; 699 m_possiblyExistingMarkerTypes = 0;
697 } 700 }
698 } 701 }
699 702
(...skipping 13 matching lines...) Expand all
713 for (size_t markerListIndex = 0; 716 for (size_t markerListIndex = 0;
714 markerListIndex < DocumentMarker::MarkerTypeIndexesCount; 717 markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
715 ++markerListIndex) { 718 ++markerListIndex) {
716 Member<MarkerList>& list = (*markers)[markerListIndex]; 719 Member<MarkerList>& list = (*markers)[markerListIndex];
717 if (!list || list->isEmpty() || 720 if (!list || list->isEmpty() ||
718 !markerTypes.contains((*list->begin())->type())) 721 !markerTypes.contains((*list->begin())->type()))
719 continue; 722 continue;
720 723
721 // cause the node to be redrawn 724 // cause the node to be redrawn
722 if (LayoutObject* layoutObject = node->layoutObject()) { 725 if (LayoutObject* layoutObject = node->layoutObject()) {
723 layoutObject->setShouldDoFullPaintInvalidation(); 726 layoutObject->setShouldDoFullPaintInvalidation(
727 PaintInvalidationDocumentMarkerChange);
724 break; 728 break;
725 } 729 }
726 } 730 }
727 } 731 }
728 } 732 }
729 733
730 void DocumentMarkerController::shiftMarkers(Node* node,
731 unsigned startOffset,
732 int delta) {
733 if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
734 return;
735 DCHECK(!m_markers.isEmpty());
736
737 MarkerLists* markers = m_markers.get(node);
738 if (!markers)
739 return;
740
741 bool didShiftMarker = false;
742 for (size_t markerListIndex = 0;
743 markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
744 ++markerListIndex) {
745 Member<MarkerList>& list = (*markers)[markerListIndex];
746 if (!list)
747 continue;
748 MarkerList::iterator startPos =
749 std::lower_bound(list->begin(), list->end(), startOffset, startsAfter);
750 for (MarkerList::iterator marker = startPos; marker != list->end();
751 ++marker) {
752 #if DCHECK_IS_ON()
753 int startOffset = (*marker)->startOffset();
754 DCHECK_GE(startOffset + delta, 0);
755 #endif
756 (*marker)->shiftOffsets(delta);
757 didShiftMarker = true;
758 }
759 }
760
761 if (didShiftMarker) {
762 invalidateRectsForMarkersInNode(*node);
763 // repaint the affected node
764 if (node->layoutObject())
765 node->layoutObject()->setShouldDoFullPaintInvalidation();
766 }
767 }
768
769 bool DocumentMarkerController::setMarkersActive(const EphemeralRange& range, 734 bool DocumentMarkerController::setMarkersActive(const EphemeralRange& range,
770 bool active) { 735 bool active) {
771 if (!possiblyHasMarkers(DocumentMarker::AllMarkers())) 736 if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
772 return false; 737 return false;
773 738
774 DCHECK(!m_markers.isEmpty()); 739 DCHECK(!m_markers.isEmpty());
775 740
776 Node* const startContainer = range.startPosition().computeContainerNode(); 741 Node* const startContainer = range.startPosition().computeContainerNode();
777 DCHECK(startContainer); 742 DCHECK(startContainer);
778 Node* const endContainer = range.endPosition().computeContainerNode(); 743 Node* const endContainer = range.endPosition().computeContainerNode();
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
812 // Markers are returned in order, so stop if we are now past the specified 777 // Markers are returned in order, so stop if we are now past the specified
813 // range. 778 // range.
814 if ((*marker)->startOffset() >= endOffset) 779 if ((*marker)->startOffset() >= endOffset)
815 break; 780 break;
816 781
817 (*marker)->setActiveMatch(active); 782 (*marker)->setActiveMatch(active);
818 docDirty = true; 783 docDirty = true;
819 } 784 }
820 785
821 // repaint the affected node 786 // repaint the affected node
822 if (docDirty && node->layoutObject()) 787 if (docDirty && node->layoutObject()) {
823 node->layoutObject()->setShouldDoFullPaintInvalidation(); 788 node->layoutObject()->setShouldDoFullPaintInvalidation(
789 PaintInvalidationDocumentMarkerChange);
790 }
824 return docDirty; 791 return docDirty;
825 } 792 }
826 793
827 #ifndef NDEBUG 794 #ifndef NDEBUG
828 void DocumentMarkerController::showMarkers() const { 795 void DocumentMarkerController::showMarkers() const {
829 StringBuilder builder; 796 StringBuilder builder;
830 MarkerMap::const_iterator end = m_markers.end(); 797 MarkerMap::const_iterator end = m_markers.end();
831 for (MarkerMap::const_iterator nodeIterator = m_markers.begin(); 798 for (MarkerMap::const_iterator nodeIterator = m_markers.begin();
832 nodeIterator != end; ++nodeIterator) { 799 nodeIterator != end; ++nodeIterator) {
833 const Node* node = nodeIterator->key; 800 const Node* node = nodeIterator->key;
(...skipping 17 matching lines...) Expand all
851 builder.append(")"); 818 builder.append(")");
852 } 819 }
853 } 820 }
854 builder.append("\n"); 821 builder.append("\n");
855 } 822 }
856 LOG(INFO) << m_markers.size() << " nodes have markers:\n" 823 LOG(INFO) << m_markers.size() << " nodes have markers:\n"
857 << builder.toString().utf8().data(); 824 << builder.toString().utf8().data();
858 } 825 }
859 #endif 826 #endif
860 827
828 // SynchronousMutationObserver
829 void DocumentMarkerController::didUpdateCharacterData(CharacterData* node,
830 unsigned offset,
831 unsigned oldLength,
832 unsigned newLength) {
833 // If we're doing a pure remove operation, remove the markers in the range
834 // being removed (markers containing, but larger than, the range, will be
835 // split)
836 if (newLength == 0)
837 removeMarkers(node, offset, oldLength);
838
839 if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
840 return;
841 DCHECK(!m_markers.isEmpty());
842
843 MarkerLists* markers = m_markers.get(node);
844 if (!markers)
845 return;
846
847 bool didShiftMarker = false;
848 for (size_t markerListIndex = 0;
Xiaocheng 2017/02/22 23:39:28 Can we use for (MarkerList* list : markers) It s
rlanday 2017/02/23 01:27:28 Oops...thought I did this
849 markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
850 ++markerListIndex) {
851 Member<MarkerList> list = (*markers)[markerListIndex];
852 if (!list)
853 continue;
854
855 for (MarkerList::iterator marker = list->begin(); marker != list->end();
856 ++marker) {
857 // if marker is contained by but not equal to the replaced range, remove
858 // the marker
859 if ((offset <= (*marker)->startOffset() &&
860 (*marker)->endOffset() < offset + oldLength) ||
861 (offset < (*marker)->startOffset() &&
862 (*marker)->endOffset() <= offset + oldLength)) {
863 list->remove(marker - list->begin());
864 --marker;
865 continue;
866 }
867
868 // algorithm inspired by https://dom.spec.whatwg.org/#concept-cd-replace
869 // but with some changes
870 if ((*marker)->startOffset() > offset) {
871 // Deviation from the concept-cd-replace algorithm: < instead of <= in
872 // the next line
873 if ((*marker)->startOffset() < offset + oldLength) {
874 // Marker start was in the replaced text. Move to end of new text
875 // (Deviation from the concept-cd-replace algorithm: that algorithm
876 // would move to the beginning of the new text here)
877 (*marker)->setStartOffset(offset + newLength);
878 } else {
879 // Marker start was after the replaced text. Shift by length
880 // difference
881 unsigned oldStartOffset = (*marker)->startOffset();
882 (*marker)->setStartOffset(oldStartOffset + newLength - oldLength);
883 }
884 }
885
886 if ((*marker)->endOffset() > offset) {
887 // Deviation from the concept-cd-replace algorithm: < instead of <= in
888 // the next line
889 if ((*marker)->endOffset() < offset + oldLength) {
890 // Marker end was in the replaced text. Move to beginning of new text
891 (*marker)->setEndOffset(offset);
892 } else {
893 // Marker end was after the replaced text. Shift by length difference
894 unsigned oldEndOffset = (*marker)->endOffset();
895 (*marker)->setEndOffset(oldEndOffset + newLength - oldLength);
896 }
897 }
898
899 if ((*marker)->startOffset() == (*marker)->endOffset()) {
Xiaocheng 2017/02/22 23:39:28 The removal condition should be start >= end inste
rlanday 2017/02/23 01:27:28 Ah, good catch
rlanday 2017/02/23 01:55:52 Actually I think the case where start > end can on
900 list->remove(marker - list->begin());
901 --marker;
902 continue;
903 }
904
905 didShiftMarker = true;
906 }
907 }
908
909 if (didShiftMarker) {
910 invalidateRectsForMarkersInNode(*node);
911 // repaint the affected node
912 if (node->layoutObject()) {
913 node->layoutObject()->setShouldDoFullPaintInvalidation(
914 PaintInvalidationDocumentMarkerChange);
915 }
916 }
917 }
918
861 } // namespace blink 919 } // namespace blink
862 920
863 #ifndef NDEBUG 921 #ifndef NDEBUG
864 void showDocumentMarkers(const blink::DocumentMarkerController* controller) { 922 void showDocumentMarkers(const blink::DocumentMarkerController* controller) {
865 if (controller) 923 if (controller)
866 controller->showMarkers(); 924 controller->showMarkers();
867 } 925 }
868 #endif 926 #endif
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698