TEST_F(WebMeaningfulLayoutsTest, VisuallyNonEmptyMissingPump)
{
    SimRequest mainResource("https://example.com/index.html", "text/html");

    loadURL("https://example.com/index.html");

    mainResource.start();

    // Write <200 characters.
    mainResource.write("less than 200 characters.");

    compositor().beginFrame();

    mainResource.finish();

    // Even though the layout state is clean ...
    EXPECT_TRUE(document().lifecycle().state() >= DocumentLifecycle::LayoutClean);

    // We should still generate a request for another (possibly last) frame.
    EXPECT_TRUE(compositor().needsAnimate());

    // ... which we (the scheduler) happily provide.
    compositor().beginFrame();

    // ... which correctly signals the VisuallyNonEmpty.
    EXPECT_TRUE(webViewClient().hadVisuallyNonEmptyLayout());
}
TEST_F(WebMeaningfulLayoutsTest, FinishedParsingThenLoading)
{
    SimRequest mainResource("https://example.com/index.html", "text/html");
    SimRequest imageResource("https://example.com/cat.png", "image/png");

    loadURL("https://example.com/index.html");

    mainResource.start();

    mainResource.write("<img src=cat.png>");

    mainResource.finish();

    compositor().beginFrame();

    EXPECT_TRUE(webViewClient().hadFinishedParsingLayout());
    EXPECT_FALSE(webViewClient().hadFinishedLoadingLayout());

    imageResource.complete("image data");

    // Pump the message loop to process the image loading task.
    testing::runPendingTasks();

    compositor().beginFrame();

    EXPECT_TRUE(webViewClient().hadFinishedLoadingLayout());
}
TEST_F(WebMeaningfulLayoutsTest, VisuallyNonEmptyTextCharactersEventually)
{
    SimRequest mainResource("https://example.com/index.html", "text/html");

    loadURL("https://example.com/index.html");

    mainResource.start();

    // Write 200 characters.
    const char* tenCharacters = "0123456789";
    for (int i = 0; i < 20; ++i)
        mainResource.write(tenCharacters);

    // Pump a frame mid-load.
    compositor().beginFrame();

    EXPECT_FALSE(webViewClient().hadVisuallyNonEmptyLayout());

    // Write more than 200 characters.
    mainResource.write("!");

    mainResource.finish();

    // setting visually non-empty happens when the parsing finishes,
    // not as the character count goes over 200.
    compositor().beginFrame();

    EXPECT_TRUE(webViewClient().hadVisuallyNonEmptyLayout());
}
// NoOverflowInIncrementVisuallyNonEmptyPixelCount tests fail if the number of
// pixels is calculated in 32-bit integer, because 65536 * 65536 would become 0
// if it was calculated in 32-bit and thus it would be considered as empty.
TEST_F(WebMeaningfulLayoutsTest,
       NoOverflowInIncrementVisuallyNonEmptyPixelCount) {
  SimRequest mainResource("https://example.com/test.html", "text/html");
  SimRequest svgResource("https://example.com/test.svg", "image/svg+xml");

  loadURL("https://example.com/test.html");

  mainResource.start();
  mainResource.write("<DOCTYPE html><body><img src=\"test.svg\">");
  // Run pending tasks to initiate the request to test.svg.
  testing::runPendingTasks();
  EXPECT_EQ(0, webViewClient().visuallyNonEmptyLayoutCount());

  // We serve the SVG file and check visuallyNonEmptyLayoutCount() before
  // mainResource.finish() because finishing the main resource causes
  // |FrameView::m_isVisuallyNonEmpty| to be true and
  // visuallyNonEmptyLayoutCount() to be 1 irrespective of the SVG sizes.
  svgResource.start();
  svgResource.write(
      "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"65536\" "
      "width=\"65536\"></svg>");
  svgResource.finish();
  compositor().beginFrame();
  EXPECT_EQ(1, webViewClient().visuallyNonEmptyLayoutCount());

  mainResource.finish();
}
TEST_F(WebMeaningfulLayoutsTest, WithIFrames) {
  SimRequest mainResource("https://example.com/index.html", "text/html");
  SimRequest iframeResource("https://example.com/iframe.html", "text/html");

  loadURL("https://example.com/index.html");

  mainResource.complete("<iframe src=iframe.html></iframe>");

  compositor().beginFrame();

  EXPECT_EQ(1, webViewClient().visuallyNonEmptyLayoutCount());
  EXPECT_EQ(1, webViewClient().finishedParsingLayoutCount());
  EXPECT_EQ(0, webViewClient().finishedLoadingLayoutCount());

  iframeResource.complete("iframe data");

  // Pump the message loop to process the iframe loading task.
  testing::runPendingTasks();

  compositor().beginFrame();

  EXPECT_EQ(1, webViewClient().visuallyNonEmptyLayoutCount());
  EXPECT_EQ(1, webViewClient().finishedParsingLayoutCount());
  EXPECT_EQ(1, webViewClient().finishedLoadingLayoutCount());
}
TEST_F(WebMeaningfulLayoutsTest, FinishedParsing) {
  SimRequest mainResource("https://example.com/index.html", "text/html");

  loadURL("https://example.com/index.html");

  mainResource.complete("content");

  compositor().beginFrame();

  EXPECT_EQ(1, webViewClient().finishedParsingLayoutCount());
}
TEST_F(WebMeaningfulLayoutsTest, FinishedLoading)
{
    SimRequest mainResource("https://example.com/index.html", "text/html");

    loadURL("https://example.com/index.html");

    mainResource.start();

    mainResource.write("content");

    mainResource.finish();

    compositor().beginFrame();

    EXPECT_TRUE(webViewClient().hadFinishedLoadingLayout());
}
TEST_F(WebMeaningfulLayoutsTest, VisuallyNonEmptyTextCharacters) {
  SimRequest mainResource("https://example.com/index.html", "text/html");

  loadURL("https://example.com/index.html");

  mainResource.start();

  // Write 201 characters.
  const char* tenCharacters = "0123456789";
  for (int i = 0; i < 20; ++i)
    mainResource.write(tenCharacters);
  mainResource.write("!");

  mainResource.finish();

  compositor().beginFrame();

  EXPECT_EQ(1, webViewClient().visuallyNonEmptyLayoutCount());
}
TEST_F(IntersectionObserverTest, ObserveSchedulesFrame) {
    SimRequest mainResource("https://example.com/", "text/html");
    loadURL("https://example.com/");
    mainResource.complete("<div id='target'></div>");

    IntersectionObserverInit observerInit;
    TrackExceptionState exceptionState;
    TestIntersectionObserverCallback* observerCallback =
        new TestIntersectionObserverCallback(document());
    IntersectionObserver* observer = IntersectionObserver::create(
                                         observerInit, *observerCallback, exceptionState);
    ASSERT_FALSE(exceptionState.hadException());

    compositor().beginFrame();
    ASSERT_FALSE(compositor().needsBeginFrame());
    EXPECT_TRUE(observer->takeRecords(exceptionState).isEmpty());
    EXPECT_EQ(observerCallback->callCount(), 0);

    Element* target = document().getElementById("target");
    ASSERT_TRUE(target);
    observer->observe(target, exceptionState);
    EXPECT_TRUE(compositor().needsBeginFrame());
}
bool LegacyWebArchive::extract(CFDictionaryRef dictionary)
{
    ASSERT(dictionary);
    if (!dictionary) {
        LOG(Archives, "LegacyWebArchive - Null root CFDictionary, aborting invalid WebArchive");
        return false;
    }
    
    CFDictionaryRef mainResourceDict = static_cast<CFDictionaryRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveMainResourceKey));
    if (!mainResourceDict) {
        LOG(Archives, "LegacyWebArchive - No main resource in archive, aborting invalid WebArchive");
        return false;
    }
    if (CFGetTypeID(mainResourceDict) != CFDictionaryGetTypeID()) {
        LOG(Archives, "LegacyWebArchive - Main resource is not the expected CFDictionary, aborting invalid WebArchive");
        return false;
    }
    
    setMainResource(createResource(mainResourceDict));
    if (!mainResource()) {
        LOG(Archives, "LegacyWebArchive - Failed to parse main resource from CFDictionary or main resource does not exist, aborting invalid WebArchive");
        return false;
    }
    
    CFArrayRef subresourceArray = static_cast<CFArrayRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveSubresourcesKey));
    if (subresourceArray && CFGetTypeID(subresourceArray) != CFArrayGetTypeID()) {
        LOG(Archives, "LegacyWebArchive - Subresources is not the expected Array, aborting invalid WebArchive");
        return false;
    }
    
    if (subresourceArray) {
        CFIndex count = CFArrayGetCount(subresourceArray);
        for (CFIndex i = 0; i < count; ++i) {
            CFDictionaryRef subresourceDict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(subresourceArray, i));
            if (CFGetTypeID(subresourceDict) != CFDictionaryGetTypeID()) {
                LOG(Archives, "LegacyWebArchive - Subresource is not expected CFDictionary, aborting invalid WebArchive");
                return false;
            }
            addSubresource(createResource(subresourceDict));
        }
    }
    
    CFArrayRef subframeArray = static_cast<CFArrayRef>(CFDictionaryGetValue(dictionary, LegacyWebArchiveSubframeArchivesKey));
    if (subframeArray && CFGetTypeID(subframeArray) != CFArrayGetTypeID()) {
        LOG(Archives, "LegacyWebArchive - Subframe archives is not the expected Array, aborting invalid WebArchive");
        return false;
    }
    
    if (subframeArray) {
        CFIndex count = CFArrayGetCount(subframeArray);
        for (CFIndex i = 0; i < count; ++i) {
            CFDictionaryRef subframeDict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(subframeArray, i));
            if (CFGetTypeID(subframeDict) != CFDictionaryGetTypeID()) {
                LOG(Archives, "LegacyWebArchive - Subframe array is not expected CFDictionary, aborting invalid WebArchive");
                return false;
            }
            
            RefPtr<LegacyWebArchive> subframeArchive = create();
            if (subframeArchive->extract(subframeDict))
                addSubframeArchive(subframeArchive.release());
            else
                LOG(Archives, "LegacyWebArchive - Invalid subframe archive skipped");
        }
    }
    
    return true;
}