text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <set>
  #include <string>
  #include <vector>
  
  #include "base/logging.h"
  #include "base/path_service.h"
  #include "base/string_split.h"
  #include "base/string_util.h"
  #include "base/string16.h"
  #include "base/utf_string_conversions.h"
  #include "content/browser/accessibility/browser_accessibility.h"
  #include "content/browser/accessibility/browser_accessibility_manager.h"
  #include "content/browser/accessibility/dump_accessibility_tree_helper.h"
  #include "content/browser/renderer_host/render_view_host_impl.h"
  #include "content/port/browser/render_widget_host_view_port.h"
  #include "content/public/browser/notification_service.h"
  #include "content/public/browser/notification_types.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/common/content_paths.h"
  #include "content/public/test/test_utils.h"
  #include "content/test/content_browser_test.h"
  #include "content/test/content_browser_test_utils.h"
  #include "content/shell/shell.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace {
    static const char kCommentToken = '#';
    static const char* kMarkSkipFile = "#<skip";
    static const char* kMarkEndOfFile = "<-- End-of-file -->";
    static const char* kSignalDiff = "*";
  } // namespace
  
  namespace content {
  
  // This test takes a snapshot of the platform BrowserAccessibility tree and
  // tests it against an expected baseline.
  //
  // The flow of the test is as outlined below.
  // 1. Load an html file from chrome/test/data/accessibility.
  // 2. Read the expectation.
  // 3. Browse to the page and serialize the platform specific tree into a human
  //    readable string.
  // 4. Perform a comparison between actual and expected and fail if they do not
  //    exactly match.
  class DumpAccessibilityTreeTest : public ContentBrowserTest {
   public:
    // Utility helper that does a comment aware equality check.
    // Returns array of lines from expected file which are different.
    std::vector<int> DiffLines(std::vector<std::string>& expected_lines,
                               std::vector<std::string>& actual_lines) {
      int actual_lines_count = actual_lines.size();
      int expected_lines_count = expected_lines.size();
      std::vector<int> diff_lines;
      int i = 0, j = 0;
      while (i < actual_lines_count && j < expected_lines_count) {
        if (expected_lines[j].size() == 0 ||
            expected_lines[j][0] == kCommentToken) {
          // Skip comment lines and blank lines in expected output.
          ++j;
          continue;
        }
  
        if (actual_lines[i] != expected_lines[j])
          diff_lines.push_back(j);
        ++i;
        ++j;
      }
  
      // Actual file has been fully checked.
      return diff_lines;
    }
  
    void AddDefaultFilters(std::set<string16>* allow_filters,
                           std::set<string16>* deny_filters) {
      allow_filters->insert(ASCIIToUTF16("FOCUSABLE"));
      allow_filters->insert(ASCIIToUTF16("READONLY"));
    }
  
    void ParseFilters(const std::string& test_html,
                      std::set<string16>* allow_filters,
                      std::set<string16>* deny_filters) {
      std::vector<std::string> lines;
      base::SplitString(test_html, '\n', &lines);
      for (std::vector<std::string>::const_iterator iter = lines.begin();
           iter != lines.end();
           ++iter) {
        const std::string& line = *iter;
        const std::string& allow_str = helper_.GetAllowString();
        const std::string& deny_str = helper_.GetDenyString();
        if (StartsWithASCII(line, allow_str, true))
          allow_filters->insert(UTF8ToUTF16(line.substr(allow_str.size())));
        else if (StartsWithASCII(line, deny_str, true))
          deny_filters->insert(UTF8ToUTF16(line.substr(deny_str.size())));
      }
    }
  
    void RunTest(const FilePath::CharType* file_path);
  
    DumpAccessibilityTreeHelper helper_;
  };
  
  void DumpAccessibilityTreeTest::RunTest(const FilePath::CharType* file_path) {
    NavigateToURL(shell(), GURL("about:blank"));
    RenderWidgetHostViewPort* host_view = static_cast<RenderWidgetHostViewPort*>(
        shell()->web_contents()->GetRenderWidgetHostView());
    RenderWidgetHostImpl* host =
        RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost());
    RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(host);
    view_host->set_save_accessibility_tree_for_testing(true);
    view_host->SetAccessibilityMode(AccessibilityModeComplete);
  
    // Setup test paths.
    FilePath dir_test_data;
    EXPECT_TRUE(PathService::Get(DIR_TEST_DATA, &dir_test_data));
    FilePath test_path(dir_test_data.Append(FILE_PATH_LITERAL("accessibility")));
    EXPECT_TRUE(file_util::PathExists(test_path))
        << test_path.LossyDisplayName();
  
    FilePath html_file = test_path.Append(FilePath(file_path));
    // Output the test path to help anyone who encounters a failure and needs
    // to know where to look.
    printf("Testing: %s\n", html_file.MaybeAsASCII().c_str());
  
    std::string html_contents;
    file_util::ReadFileToString(html_file, &html_contents);
  
    // Parse filters in the test file.
    std::set<string16> allow_filters;
    std::set<string16> deny_filters;
    AddDefaultFilters(&allow_filters, &deny_filters);
    ParseFilters(html_contents, &allow_filters, &deny_filters);
    helper_.SetFilters(allow_filters, deny_filters);
  
    // Read the expected file.
    std::string expected_contents_raw;
    FilePath expected_file =
      FilePath(html_file.RemoveExtension().value() +
               helper_.GetExpectedFileSuffix());
    file_util::ReadFileToString(
        expected_file,
        &expected_contents_raw);
  
    // Tolerate Windows-style line endings (\r\n) in the expected file:
    // normalize by deleting all \r from the file (if any) to leave only \n.
    std::string expected_contents;
    RemoveChars(expected_contents_raw, "\r", &expected_contents);
  
    if (!expected_contents.compare(0, strlen(kMarkSkipFile), kMarkSkipFile)) {
      printf("Skipping this test on this platform.\n");
      return;
    }
  
    // Load the page.
    WindowedNotificationObserver tree_updated_observer(
        NOTIFICATION_ACCESSIBILITY_LOAD_COMPLETE,
        NotificationService::AllSources());
    string16 html_contents16;
    html_contents16 = UTF8ToUTF16(html_contents);
    GURL url = GetTestUrl("accessibility",
                          html_file.BaseName().MaybeAsASCII().c_str());
    NavigateToURL(shell(), url);
  
    // Wait for the tree.
    tree_updated_observer.Wait();
  
    // Perform a diff (or write the initial baseline).
    string16 actual_contents_utf16;
    helper_.DumpAccessibilityTree(
        host_view->GetBrowserAccessibilityManager()->GetRoot(),
        &actual_contents_utf16);
    std::string actual_contents = UTF16ToUTF8(actual_contents_utf16);
    std::vector<std::string> actual_lines, expected_lines;
    Tokenize(actual_contents, "\n", &actual_lines);
    Tokenize(expected_contents, "\n", &expected_lines);
    // Marking the end of the file with a line of text ensures that
    // file length differences are found.
    expected_lines.push_back(kMarkEndOfFile);
    actual_lines.push_back(kMarkEndOfFile);
  
    std::vector<int> diff_lines = DiffLines(expected_lines, actual_lines);
    bool is_different = diff_lines.size() > 0;
    EXPECT_FALSE(is_different);
    if (is_different) {
      // Mark the expected lines which did not match actual output with a *.
      printf("* Line Expected\n");
      printf("- ---- --------\n");
      for (int line = 0, diff_index = 0;
           line < static_cast<int>(expected_lines.size());
           ++line) {
        bool is_diff = false;
        if (diff_index < static_cast<int>(diff_lines.size()) &&
            diff_lines[diff_index] == line) {
          is_diff = true;
          ++ diff_index;
        }
        printf("%1s %4d %s\n", is_diff? kSignalDiff : "", line + 1,
               expected_lines[line].c_str());
      }
      printf("\nActual\n");
      printf("------\n");
      printf("%s\n", actual_contents.c_str());
    }
  
    if (!file_util::PathExists(expected_file)) {
      FilePath actual_file =
        FilePath(html_file.RemoveExtension().value() +
                 helper_.GetActualFileSuffix());
  
      EXPECT_TRUE(file_util::WriteFile(
          actual_file, actual_contents.c_str(), actual_contents.size()));
  
      ADD_FAILURE() << "No expectation found. Create it by doing:\n"
                    << "mv " << actual_file.LossyDisplayName() << " "
                    << expected_file.LossyDisplayName();
    }
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityA) {
    RunTest(FILE_PATH_LITERAL("a.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAName) {
    RunTest(FILE_PATH_LITERAL("a-name.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAOnclick) {
    RunTest(FILE_PATH_LITERAL("a-onclick.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                         AccessibilityAriaApplication) {
    RunTest(FILE_PATH_LITERAL("aria-application.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAWithImg) {
    RunTest(FILE_PATH_LITERAL("a-with-img.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityButtonNameCalc) {
    RunTest(FILE_PATH_LITERAL("button-name-calc.html"));
  }
  
  // TODO(dmazzoni): rebaseline and enable after this WebKit change is rolled:
  // https://bugs.webkit.org/show_bug.cgi?id=96323
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                         DISABLED_AccessibilityCanvas) {
    RunTest(FILE_PATH_LITERAL("canvas.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                         AccessibilityCheckboxNameCalc) {
    RunTest(FILE_PATH_LITERAL("checkbox-name-calc.html"));
  }
  
  // TODO(dimich): Started to fail in Chrome r149732 (crbug 140397)
  #if defined(OS_WIN)
  #define MAYBE_AccessibilityContenteditableDescendants \
      DISABLED_AccessibilityContenteditableDescendants
  #else
  #define MAYBE_AccessibilityContenteditableDescendants \
      AccessibilityContenteditableDescendants
  #endif
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityDiv) {
    RunTest(FILE_PATH_LITERAL("div.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                         MAYBE_AccessibilityContenteditableDescendants) {
    RunTest(FILE_PATH_LITERAL("contenteditable-descendants.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityFooter) {
    RunTest(FILE_PATH_LITERAL("footer.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityForm) {
    RunTest(FILE_PATH_LITERAL("form.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityHR) {
    RunTest(FILE_PATH_LITERAL("hr.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                         AccessibilityInputTextNameCalc) {
    RunTest(FILE_PATH_LITERAL("input-text-name-calc.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityLabel) {
    RunTest(FILE_PATH_LITERAL("label.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityListMarkers) {
    RunTest(FILE_PATH_LITERAL("list-markers.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityP) {
    RunTest(FILE_PATH_LITERAL("p.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySpinButton) {
    RunTest(FILE_PATH_LITERAL("spinbutton.html"));
  }
  
  // TODO(dmazzoni): rebaseline and enable after this WebKit change is rolled:
  // https://bugs.webkit.org/show_bug.cgi?id=96323
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                         DISABLED_AccessibilityToggleButton) {
    RunTest(FILE_PATH_LITERAL("togglebutton.html"));
  }
  
  IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityUl) {
    RunTest(FILE_PATH_LITERAL("ul.html"));
  }
  
  }  // namespace content
  