Title: [288102] trunk
Revision
288102
Author
wenson_hs...@apple.com
Date
2022-01-17 12:17:20 -0800 (Mon, 17 Jan 2022)

Log Message

ImageAnalysisQueue should analyze image elements that are loaded after the call to enqueueAllImages()
https://bugs.webkit.org/show_bug.cgi?id=233266
rdar://85731875

Reviewed by Darin Adler.

Source/WebCore:

Make a few adjustments to ImageAnalysisQueue, such that it continues to analyze images in the document that have
finished loading after triggering the initial call to `enqueueAllImages()` in the document.

Test:   ImageAnalysisTests.AnalyzeDynamicallyLoadedImages
        ImageAnalysisTests.ResetImageAnalysisAfterNavigation

* page/ImageAnalysisQueue.cpp:
(WebCore::ImageAnalysisQueue::enqueueIfNeeded):

Pull logic for queueing an image element for analysis into a separate helper method; we only attempt to analyze
image elements that have successfully loaded and contain a `CachedImage` that is larger than an arbitrarily
chosen size of 20px by 20px.

(WebCore::ImageAnalysisQueue::enqueueAllImages):

Refactor this to use the new `enqueueIfNeeded` method above.

(WebCore::ImageAnalysisQueue::resumeProcessing):
(WebCore::ImageAnalysisQueue::clear):
* page/ImageAnalysisQueue.h:

Add a weak hashset of elements that we've already added to the image analysis queue at some point. This prevents
us from continually performing image analysis on the same image if it's only being removed and reinserted in the
document.

* page/Page.cpp:
(WebCore::Page::didCommitLoad):

Additionally reset the image analysis queue when committing page load, so that the image analysis queue doesn't
persist and continue analyzing images even after reloading the page (or navigating away).

(WebCore::Page::didFinishLoadingImageForElement):

If it exists, tell the page's image analysis queue to add the newly loaded image.

(WebCore::Page::resetImageAnalysisQueue):
* page/Page.h:

Tools:

Add new API tests to check that image elements that are created and inserted after kicking off the image
analysis queue are successfully added to the queue, and result in platform image analysis requests, and to also
verify that the image analysis queue is reset after navigation.

* TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm:
(TestWebKitAPI::processRequestWithResults):
(TestWebKitAPI::makeImageAnalysisRequestSwizzler):
(TestWebKitAPI::processRequestWithError):
(TestWebKitAPI::TEST):
(TestWebKitAPI::swizzledProcessRequestWithResults): Deleted.
(TestWebKitAPI::swizzledProcessRequestWithError): Deleted.
* TestWebKitAPI/Tests/WebKitCocoa/multiple-images.html:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (288101 => 288102)


--- trunk/Source/WebCore/ChangeLog	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Source/WebCore/ChangeLog	2022-01-17 20:17:20 UTC (rev 288102)
@@ -1,3 +1,49 @@
+2022-01-17  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        ImageAnalysisQueue should analyze image elements that are loaded after the call to enqueueAllImages()
+        https://bugs.webkit.org/show_bug.cgi?id=233266
+        rdar://85731875
+
+        Reviewed by Darin Adler.
+
+        Make a few adjustments to ImageAnalysisQueue, such that it continues to analyze images in the document that have
+        finished loading after triggering the initial call to `enqueueAllImages()` in the document.
+
+        Test:   ImageAnalysisTests.AnalyzeDynamicallyLoadedImages
+                ImageAnalysisTests.ResetImageAnalysisAfterNavigation
+
+        * page/ImageAnalysisQueue.cpp:
+        (WebCore::ImageAnalysisQueue::enqueueIfNeeded):
+
+        Pull logic for queueing an image element for analysis into a separate helper method; we only attempt to analyze
+        image elements that have successfully loaded and contain a `CachedImage` that is larger than an arbitrarily
+        chosen size of 20px by 20px.
+
+        (WebCore::ImageAnalysisQueue::enqueueAllImages):
+
+        Refactor this to use the new `enqueueIfNeeded` method above.
+
+        (WebCore::ImageAnalysisQueue::resumeProcessing):
+        (WebCore::ImageAnalysisQueue::clear):
+        * page/ImageAnalysisQueue.h:
+
+        Add a weak hashset of elements that we've already added to the image analysis queue at some point. This prevents
+        us from continually performing image analysis on the same image if it's only being removed and reinserted in the
+        document.
+
+        * page/Page.cpp:
+        (WebCore::Page::didCommitLoad):
+
+        Additionally reset the image analysis queue when committing page load, so that the image analysis queue doesn't
+        persist and continue analyzing images even after reloading the page (or navigating away).
+
+        (WebCore::Page::didFinishLoadingImageForElement):
+
+        If it exists, tell the page's image analysis queue to add the newly loaded image.
+
+        (WebCore::Page::resetImageAnalysisQueue):
+        * page/Page.h:
+
 2022-01-17  Antoine Quint  <grao...@webkit.org>
 
         Crash may occur under ComputedStyleExtractor::propertyValue()

Modified: trunk/Source/WebCore/page/ImageAnalysisQueue.cpp (288101 => 288102)


--- trunk/Source/WebCore/page/ImageAnalysisQueue.cpp	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Source/WebCore/page/ImageAnalysisQueue.cpp	2022-01-17 20:17:20 UTC (rev 288102)
@@ -31,7 +31,7 @@
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "HTMLCollection.h"
-#include "HTMLElement.h"
+#include "HTMLImageElement.h"
 #include "ImageOverlay.h"
 #include "RenderImage.h"
 #include "Timer.h"
@@ -51,33 +51,38 @@
 
 ImageAnalysisQueue::~ImageAnalysisQueue() = default;
 
-void ImageAnalysisQueue::enqueueAllImages(Document& document, const String& identifier)
+void ImageAnalysisQueue::enqueueIfNeeded(HTMLImageElement& element)
 {
-    if (!m_page)
+    if (!is<RenderImage>(element.renderer()))
         return;
 
-    // FIXME (233266): Analyze image elements that are loaded after we've enqueued all images in the document.
-    auto imageIterator = document.images()->createIterator();
-    for (RefPtr node = imageIterator.next(); node; node = imageIterator.next()) {
-        if (!is<HTMLElement>(*node))
-            continue;
+    auto& renderer = downcast<RenderImage>(*element.renderer());
+    auto* cachedImage = renderer.cachedImage();
+    if (!cachedImage || cachedImage->errorOccurred())
+        return;
 
-        auto& element = downcast<HTMLElement>(*node);
-        if (!is<RenderImage>(element.renderer()))
-            continue;
+    if (renderer.size().width() < minimumWidthForAnalysis || renderer.size().height() < minimumHeightForAnalysis)
+        return;
 
-        auto& renderImage = downcast<RenderImage>(*element.renderer());
-        auto* cachedImage = renderImage.cachedImage();
-        if (!cachedImage || cachedImage->errorOccurred())
-            continue;
+    if (!m_queuedElements.add(element).isNewEntry)
+        return;
 
-        if (renderImage.size().width() < minimumWidthForAnalysis || renderImage.size().height() < minimumHeightForAnalysis)
-            continue;
+    m_queue.append({ element });
+    resumeProcessing();
+}
 
-        m_queue.append({ WeakPtr { element }, identifier });
+void ImageAnalysisQueue::enqueueAllImages(Document& document, const String& identifier)
+{
+    if (!m_page)
+        return;
+
+    if (m_identifier != identifier) {
+        clear();
+        m_identifier = identifier;
     }
 
-    resumeProcessing();
+    for (auto& image : descendantsOfType<HTMLImageElement>(document))
+        enqueueIfNeeded(image);
 }
 
 void ImageAnalysisQueue::resumeProcessing()
@@ -86,7 +91,7 @@
         return;
 
     while (!m_queue.isEmpty() && m_pendingRequestCount < maximumPendingImageAnalysisCount) {
-        auto [weakElement, identifier] = m_queue.takeFirst();
+        auto weakElement = m_queue.takeFirst();
         RefPtr element = weakElement.get();
         if (!element || !element->isConnected())
             continue;
@@ -93,7 +98,7 @@
 
         m_pendingRequestCount++;
         m_page->resetTextRecognitionResult(*element);
-        m_page->chrome().client().requestTextRecognition(*element, identifier, [this, page = m_page] (auto&&) {
+        m_page->chrome().client().requestTextRecognition(*element, m_identifier, [this, page = m_page] (auto&&) {
             if (!page)
                 return;
 
@@ -112,6 +117,8 @@
     m_pendingRequestCount = 0;
     m_resumeProcessingTimer.stop();
     m_queue.clear();
+    m_queuedElements.clear();
+    m_identifier = { };
 }
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/page/ImageAnalysisQueue.h (288101 => 288102)


--- trunk/Source/WebCore/page/ImageAnalysisQueue.h	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Source/WebCore/page/ImageAnalysisQueue.h	2022-01-17 20:17:20 UTC (rev 288102)
@@ -29,12 +29,13 @@
 
 #include <wtf/Deque.h>
 #include <wtf/FastMalloc.h>
+#include <wtf/WeakHashSet.h>
 #include <wtf/WeakPtr.h>
 
 namespace WebCore {
 
 class Document;
-class HTMLElement;
+class HTMLImageElement;
 class Page;
 class Timer;
 
@@ -45,19 +46,18 @@
     ~ImageAnalysisQueue();
 
     WEBCORE_EXPORT void enqueueAllImages(Document&, const String& identifier);
-    WEBCORE_EXPORT void clear();
+    void clear();
 
+    void enqueueIfNeeded(HTMLImageElement&);
+
 private:
     void resumeProcessing();
 
-    struct Task {
-        WeakPtr<HTMLElement> element;
-        String identifier;
-    };
-
+    String m_identifier;
     WeakPtr<Page> m_page;
     Timer m_resumeProcessingTimer;
-    Deque<Task> m_queue;
+    WeakHashSet<HTMLImageElement> m_queuedElements;
+    Deque<WeakPtr<HTMLImageElement>> m_queue;
     unsigned m_pendingRequestCount { 0 };
 };
 

Modified: trunk/Source/WebCore/page/Page.cpp (288101 => 288102)


--- trunk/Source/WebCore/page/Page.cpp	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Source/WebCore/page/Page.cpp	2022-01-17 20:17:20 UTC (rev 288102)
@@ -1314,6 +1314,7 @@
 
 #if ENABLE(IMAGE_ANALYSIS)
     resetTextRecognitionResults();
+    resetImageAnalysisQueue();
 #endif
 }
 
@@ -3615,8 +3616,17 @@
             return;
 
         frame->editor().revealSelectionIfNeededAfterLoadingImageForElement(element);
-        if (auto* page = frame->page(); element->document().frame() == frame)
+
+        if (element->document().frame() != frame)
+            return;
+
+        if (auto* page = frame->page()) {
+#if ENABLE(IMAGE_ANALYSIS)
+            if (auto* queue = page->imageAnalysisQueueIfExists())
+                queue->enqueueIfNeeded(element);
+#endif
             page->chrome().client().didFinishLoadingImageForElement(element);
+        }
     });
 }
 
@@ -3756,6 +3766,12 @@
     return *m_imageAnalysisQueue;
 }
 
+void Page::resetImageAnalysisQueue()
+{
+    if (auto previousQueue = std::exchange(m_imageAnalysisQueue, { }))
+        previousQueue->clear();
+}
+
 void Page::updateElementsWithTextRecognitionResults()
 {
     if (m_textRecognitionResults.isEmptyIgnoringNullReferences())

Modified: trunk/Source/WebCore/page/Page.h (288101 => 288102)


--- trunk/Source/WebCore/page/Page.h	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Source/WebCore/page/Page.h	2022-01-17 20:17:20 UTC (rev 288102)
@@ -915,6 +915,7 @@
     WEBCORE_EXPORT bool hasCachedTextRecognitionResult(const HTMLElement&) const;
     void cacheTextRecognitionResult(const HTMLElement&, const IntRect& containerRect, const TextRecognitionResult&);
     void resetTextRecognitionResult(const HTMLElement&);
+    void resetImageAnalysisQueue();
 #endif
 
     WEBCORE_EXPORT PermissionController& permissionController();

Modified: trunk/Tools/ChangeLog (288101 => 288102)


--- trunk/Tools/ChangeLog	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Tools/ChangeLog	2022-01-17 20:17:20 UTC (rev 288102)
@@ -1,3 +1,24 @@
+2022-01-17  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        ImageAnalysisQueue should analyze image elements that are loaded after the call to enqueueAllImages()
+        https://bugs.webkit.org/show_bug.cgi?id=233266
+        rdar://85731875
+
+        Reviewed by Darin Adler.
+
+        Add new API tests to check that image elements that are created and inserted after kicking off the image
+        analysis queue are successfully added to the queue, and result in platform image analysis requests, and to also
+        verify that the image analysis queue is reset after navigation.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm:
+        (TestWebKitAPI::processRequestWithResults):
+        (TestWebKitAPI::makeImageAnalysisRequestSwizzler):
+        (TestWebKitAPI::processRequestWithError):
+        (TestWebKitAPI::TEST):
+        (TestWebKitAPI::swizzledProcessRequestWithResults): Deleted.
+        (TestWebKitAPI::swizzledProcessRequestWithError): Deleted.
+        * TestWebKitAPI/Tests/WebKitCocoa/multiple-images.html:
+
 2022-01-17  Sihui Liu  <sihui_...@apple.com>
 
         Add an API test to ensure indexedDB.databases() does not create files on disk

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm (288101 => 288102)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm	2022-01-17 20:17:20 UTC (rev 288102)
@@ -104,15 +104,21 @@
     return adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get()]);
 }
 
-static void swizzledProcessRequestWithResults(id, SEL, VKImageAnalyzerRequest *, void (^)(double progress), void (^completion)(VKImageAnalysis *, NSError *))
+static void processRequestWithResults(id, SEL, VKImageAnalyzerRequest *, void (^)(double progress), void (^completion)(VKImageAnalysis *, NSError *))
 {
     gDidProcessRequestCount++;
     completion(createImageAnalysisWithSimpleFixedResults().get(), nil);
 }
 
+template <typename FunctionType>
+InstanceMethodSwizzler makeImageAnalysisRequestSwizzler(FunctionType function)
+{
+    return InstanceMethodSwizzler { PAL::getVKImageAnalyzerClass(), @selector(processRequest:progressHandler:completionHandler:), reinterpret_cast<IMP>(function) };
+}
+
 #if PLATFORM(IOS_FAMILY)
 
-static void swizzledProcessRequestWithError(id, SEL, VKImageAnalyzerRequest *, void (^)(double progress), void (^completion)(VKImageAnalysis *analysis, NSError *error))
+static void processRequestWithError(id, SEL, VKImageAnalyzerRequest *, void (^)(double progress), void (^completion)(VKImageAnalysis *analysis, NSError *error))
 {
     gDidProcessRequestCount++;
     completion(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:1 userInfo:nil]);
@@ -120,7 +126,7 @@
 
 TEST(ImageAnalysisTests, DoNotAnalyzeImagesInEditableContent)
 {
-    InstanceMethodSwizzler imageAnalysisRequestSwizzler { PAL::getVKImageAnalyzerClass(), @selector(processRequest:progressHandler:completionHandler:), reinterpret_cast<IMP>(swizzledProcessRequestWithError) };
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithError);
 
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)]);
     [webView _setEditable:YES];
@@ -130,7 +136,7 @@
 
 TEST(ImageAnalysisTests, HandleImageAnalyzerErrors)
 {
-    InstanceMethodSwizzler imageAnalysisRequestSwizzler { PAL::getVKImageAnalyzerClass(), @selector(processRequest:progressHandler:completionHandler:), reinterpret_cast<IMP>(swizzledProcessRequestWithError) };
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithError);
 
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)]);
     [webView synchronouslyLoadTestPageNamed:@"image"];
@@ -140,7 +146,7 @@
 
 TEST(ImageAnalysisTests, DoNotCrashWhenHitTestingOutsideOfWebView)
 {
-    InstanceMethodSwizzler imageAnalysisRequestSwizzler { PAL::getVKImageAnalyzerClass(), @selector(processRequest:progressHandler:completionHandler:), reinterpret_cast<IMP>(swizzledProcessRequestWithError) };
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithError);
 
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)]);
     [webView synchronouslyLoadTestPageNamed:@"image"];
@@ -151,7 +157,7 @@
 
 TEST(ImageAnalysisTests, AvoidRedundantTextRecognitionRequests)
 {
-    InstanceMethodSwizzler imageAnalysisRequestSwizzler { PAL::getVKImageAnalyzerClass(), @selector(processRequest:progressHandler:completionHandler:), reinterpret_cast<IMP>(swizzledProcessRequestWithResults) };
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithResults);
 
     auto webView = createWebViewWithTextRecognitionEnhancements();
     [webView synchronouslyLoadTestPageNamed:@"image"];
@@ -166,7 +172,7 @@
 
 TEST(ImageAnalysisTests, StartImageAnalysisWithoutIdentifier)
 {
-    InstanceMethodSwizzler imageAnalysisRequestSwizzler { PAL::getVKImageAnalyzerClass(), @selector(processRequest:progressHandler:completionHandler:), reinterpret_cast<IMP>(swizzledProcessRequestWithResults) };
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithResults);
 
     auto webView = createWebViewWithTextRecognitionEnhancements();
     [webView synchronouslyLoadTestPageNamed:@"multiple-images"];
@@ -179,6 +185,45 @@
         EXPECT_WK_STREQ(overlayText, @"Foo bar");
 }
 
+TEST(ImageAnalysisTests, AnalyzeDynamicallyLoadedImages)
+{
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithResults);
+
+    auto webView = createWebViewWithTextRecognitionEnhancements();
+    [webView synchronouslyLoadTestPageNamed:@"multiple-images"];
+    [webView _startImageAnalysis:nil];
+    [webView waitForImageAnalysisRequests:5];
+
+    [webView objectByEvaluatingJavaScript:@"appendImage('apple.gif')"];
+    [webView waitForImageAnalysisRequests:6];
+
+    [webView objectByEvaluatingJavaScript:@"appendImage('icon.png')"];
+    [webView waitForImageAnalysisRequests:7];
+
+    [webView objectByEvaluatingJavaScript:@"hideAllImages()"];
+    [webView waitForNextPresentationUpdate];
+    [webView objectByEvaluatingJavaScript:@"showAllImages()"];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_EQ(gDidProcessRequestCount, 7U);
+}
+
+TEST(ImageAnalysisTests, ResetImageAnalysisAfterNavigation)
+{
+    auto requestSwizzler = makeImageAnalysisRequestSwizzler(processRequestWithResults);
+
+    auto webView = createWebViewWithTextRecognitionEnhancements();
+    [webView synchronouslyLoadTestPageNamed:@"multiple-images"];
+    [webView _startImageAnalysis:nil];
+    [webView waitForImageAnalysisRequests:5];
+
+    [webView synchronouslyLoadTestPageNamed:@"simple"];
+    [webView waitForNextPresentationUpdate];
+
+    [webView synchronouslyLoadTestPageNamed:@"multiple-images"];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_EQ(gDidProcessRequestCount, 5U);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(IMAGE_ANALYSIS)

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/multiple-images.html (288101 => 288102)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/multiple-images.html	2022-01-17 20:10:13 UTC (rev 288101)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/multiple-images.html	2022-01-17 20:17:20 UTC (rev 288102)
@@ -19,6 +19,23 @@
                 }
                 return result;
             }
+
+            function appendImage(imageSource)
+            {
+                const image = document.createElement("img");
+                image.src = ""
+                document.body.appendChild(image);
+            }
+
+            function hideAllImages()
+            {
+                Array.from(document.images).forEach(image => image.style.setProperty("display", "none"));
+            }
+
+            function showAllImages()
+            {
+                Array.from(document.images).forEach(image => image.style.removeProperty("display"));
+            }
         </script>
     </head>
     <body style='margin: 0px;'>
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to