Index: src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(revision 16009)
+++ src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(working copy)
@@ -17,6 +17,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -66,6 +67,7 @@
 import org.openstreetmap.josm.tools.I18n;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Stopwatch;
 import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -75,7 +77,6 @@
 public class MapCSSTagChecker extends Test.TagTest {
     private MapCSSStyleIndex indexData;
     final Map<MapCSSRule, MapCSSTagCheckerAndRule> ruleToCheckMap = new HashMap<>();
-    private final Set<OsmPrimitive> tested = new HashSet<>();
     private static final Map<IPrimitive, Area> mpAreaCache = new HashMap<>();
     static final boolean ALL_TESTS = true;
     static final boolean ONLY_SELECTED_TESTS = false;
@@ -203,6 +204,9 @@
 
     final MultiMap<String, TagCheck> checks = new MultiMap<>();
 
+    /** maps the source URL for a test to the title shown in the dialog where known */
+    private final Map<String, String> urlTitles = new HashMap<>();
+
     /**
      * Result of {@link TagCheck#readMapCSS}
      * @since 8936
@@ -617,7 +621,7 @@
 
         @Override
         public String getSource() {
-            return tr("URL / File: {0}", source);
+            return source;
         }
     }
 
@@ -661,7 +665,7 @@
                                 // rule.selectors might be different due to MapCSSStyleIndex, however, the declarations are the same object
                                 .filter(c -> c.rule.declaration == rule.declaration)
                                 .findFirst()
-                                .map(c -> new MapCSSTagCheckerAndRule(c, e.getKey()))
+                                .map(c -> new MapCSSTagCheckerAndRule(c, getTitle(e.getKey())))
                                 .orElse(null))
                         .filter(Objects::nonNull)
                         .findFirst()
@@ -680,6 +684,10 @@
         return res;
     }
 
+    private String getTitle(String url) {
+        return urlTitles.getOrDefault(url, tr("unknown"));
+    }
+
     /**
      * See #12627
      * Add error to given list if list doesn't already contain a similar error.
@@ -749,9 +757,6 @@
         for (TestError e : getErrorsForPrimitive(p, ValidatorPrefHelper.PREF_OTHER.get())) {
             addIfNotSimilar(e, errors);
         }
-        if (partialSelection) {
-            tested.add(p);
-        }
     }
 
     /**
@@ -787,6 +792,12 @@
             result = TagCheck.readMapCSS(reader, assertionConsumer);
             checks.remove(url);
             checks.putAll(url, result.parseChecks);
+            for (SourceEntry source : new ValidatorPrefHelper().get()) {
+                if (url.equals(source.url)) {
+                    urlTitles.put(url, source.title);
+                    break;
+                }
+            }
             indexData = null;
         }
         return result;
@@ -795,6 +806,7 @@
     @Override
     public synchronized void initialize() throws Exception {
         checks.clear();
+        urlTitles.clear();
         indexData = null;
         for (SourceEntry source : new ValidatorPrefHelper().get()) {
             if (!source.active) {
@@ -842,15 +854,76 @@
     public synchronized void startTest(ProgressMonitor progressMonitor) {
         super.startTest(progressMonitor);
         super.setShowElements(true);
-        if (indexData == null) {
-            indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), ALL_TESTS);
+    }
+
+    @Override
+    public synchronized void endTest() {
+        // no need to keep the index, it is quickly build and doubles the memory needs
+        indexData = null;
+        // always clear the cache to make sure that we catch changes in geometry
+        mpAreaCache.clear();
+        super.endTest();
+    }
+
+    @Override
+    public void visit(Collection<OsmPrimitive> selection) {
+        if (progressMonitor != null) {
+            progressMonitor.setTicksCount(selection.size() * checks.size());
         }
-        tested.clear();
+
         mpAreaCache.clear();
+
+        Set<OsmPrimitive> surrounding = new HashSet<>();
+        for (Entry<String, Set<TagCheck>> entry : checks.entrySet()) {
+            if (isCanceled()) {
+                break;
+            }
+            visit(entry.getKey(), entry.getValue(), selection, surrounding);
+        }
     }
 
-    @Override
-    public synchronized void endTest() {
+    /**
+     * Perform the checks for one check url
+     * @param url the url for the checks
+     * @param checksForUrl the checks to perform
+     * @param selection collection primitives
+     * @param surrounding surrounding primitives, evtl. filled by this routine
+     */
+    private void visit(String url, Set<TagCheck> checksForUrl, Collection<OsmPrimitive> selection,
+            Set<OsmPrimitive> surrounding) {
+        MultiMap<String, TagCheck> currentCheck = new MultiMap<>();
+        currentCheck.putAll(url, checksForUrl);
+        indexData = createMapCSSTagCheckerIndex(currentCheck, includeOtherSeverityChecks(), ALL_TESTS);
+        Set<OsmPrimitive> tested = new HashSet<>();
+
+
+        String title = getTitle(url);
+        if (progressMonitor != null) {
+            progressMonitor.setExtraText(tr(" {0}", title));
+        }
+        long cnt = 0;
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        for (OsmPrimitive p : selection) {
+            if (isCanceled()) {
+                break;
+            }
+            if (isPrimitiveUsable(p)) {
+                check(p);
+                if (partialSelection) {
+                    tested.add(p);
+                }
+            }
+            if (progressMonitor != null) {
+                progressMonitor.worked(1);
+                cnt++;
+                // add frequently changing info to progress monitor so that it
+                // doesn't seem to hang when test takes longer than 0.5 seconds
+                if (cnt % 10000 == 0 && stopwatch.elapsed() >= 500) {
+                    progressMonitor.setExtraText(tr(" {0}: {1} of {2} elements done", title, cnt, selection.size()));
+                }
+            }
+        }
+
         if (partialSelection && !tested.isEmpty()) {
             // #14287: see https://josm.openstreetmap.de/ticket/14287#comment:15
             // execute tests for objects which might contain or cross previously tested elements
@@ -857,15 +930,17 @@
 
             // rebuild index with a reduced set of rules (those that use ChildOrParentSelector) and thus may have left selectors
             // matching the previously tested elements
-            indexData = createMapCSSTagCheckerIndex(checks, includeOtherSeverityChecks(), ONLY_SELECTED_TESTS);
+            indexData = createMapCSSTagCheckerIndex(currentCheck, includeOtherSeverityChecks(), ONLY_SELECTED_TESTS);
 
-            Set<OsmPrimitive> surrounding = new HashSet<>();
-            for (OsmPrimitive p : tested) {
-                if (p.getDataSet() != null) {
-                    surrounding.addAll(p.getDataSet().searchWays(p.getBBox()));
-                    surrounding.addAll(p.getDataSet().searchRelations(p.getBBox()));
+            if (surrounding.isEmpty()) {
+                for (OsmPrimitive p : tested) {
+                    if (p.getDataSet() != null) {
+                        surrounding.addAll(p.getDataSet().searchWays(p.getBBox()));
+                        surrounding.addAll(p.getDataSet().searchRelations(p.getBBox()));
+                    }
                 }
             }
+
             final boolean includeOtherSeverity = includeOtherSeverityChecks();
             for (OsmPrimitive p : surrounding) {
                 if (tested.contains(p))
@@ -876,12 +951,7 @@
                         addIfNotSimilar(e, errors);
                 }
             }
-            tested.clear();
         }
-        // no need to keep the index, it is quickly build and doubles the memory needs
-        indexData = null;
-        // always clear the cache to make sure that we catch changes in geometry
-        mpAreaCache.clear();
-        super.endTest();
     }
+
 }
