diff options
author | Qt Forward Merge Bot <[email protected]> | 2019-10-11 12:44:19 +0200 |
---|---|---|
committer | Qt Forward Merge Bot <[email protected]> | 2019-10-11 12:44:19 +0200 |
commit | ab1bd15209abaf7effc51dbc2f272c5681af7223 (patch) | |
tree | 680bfbc4ab13514a9d2288609377bd8461f1d9f6 /tests | |
parent | 5909e6d0d10de3e1370b3ea0bc596f580101e3b4 (diff) | |
parent | 2eac3aeb98fca0e6c13aaaff481861c5ef679e68 (diff) |
Change-Id: I2b773e6958cf1d3699ff7887f2807572f1dafa8d
Diffstat (limited to 'tests')
111 files changed, 14262 insertions, 0 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..306095ae0 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(webkitwidgets) diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 000000000..483a6691b --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += webkitwidgets diff --git a/tests/webkitwidgets/CMakeLists.txt b/tests/webkitwidgets/CMakeLists.txt new file mode 100644 index 000000000..42d636573 --- /dev/null +++ b/tests/webkitwidgets/CMakeLists.txt @@ -0,0 +1,66 @@ +remove_definitions(-DQT_ASCII_CAST_WARNINGS) + +include_directories( + "${CMAKE_SOURCE_DIR}/Source" + "${FORWARDING_HEADERS_DIR}" + "${WEBKIT_DIR}/qt/Api" + "${WEBKIT_DIR}/qt/WidgetApi" +) + +include_directories(SYSTEM + ${ICU_INCLUDE_DIRS} + ${Qt5Gui_PRIVATE_INCLUDE_DIRS} + ${Qt5Widgets_INCLUDE_DIRS} + ${Qt5Test_INCLUDE_DIRS} +) + +add_definitions(-DTESTS_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/") + +if (ENABLE_TEST_SUPPORT) + add_definitions(-DHAVE_QTTESTSUPPORT) +endif () + +set(QtWK1ApiTests_LIBRARIES + ${Qt5Gui_LIBRARIES} + ${Qt5Network_LIBRARIES} + ${Qt5Test_LIBRARIES} + ${Qt5Widgets_LIBRARIES} + WebKitWidgets +) + +# Inspired by EFL WK2 tests +set(QtWK1ApiTests_RUNTIME_OUTPUT_DIRECTORY + ${CMAKE_BINARY_DIR}/tests +) + +set(QtWK1ApiTests + hybridPixmap + qgraphicswebview + qobjectbridge + qwebelement + qwebframe + qwebhistory + qwebhistoryinterface + qwebinspector + qwebpage + qwebsecurityorigin + qwebview +) + +set(tst_hybridPixmap_SOURCES hybridPixmap/widget.cpp) +qt5_wrap_ui(tst_hybridPixmap_SOURCES hybridPixmap/widget.ui) + +foreach (testName ${QtWK1ApiTests}) + list(APPEND tst_${testName}_SOURCES ${testName}/tst_${testName}.cpp) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${testName}/tst_${testName}.qrc") + qt5_add_resources(tst_${testName}_SOURCES ${testName}/tst_${testName}.qrc) + endif () + + add_executable(tst_${testName} ${tst_${testName}_SOURCES}) + target_include_directories(tst_${testName} PRIVATE ${testName}) + target_link_libraries(tst_${testName} ${QtWK1ApiTests_LIBRARIES}) + set_target_properties(tst_${testName} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${QtWK1ApiTests_RUNTIME_OUTPUT_DIRECTORY} AUTOMOC ON) + + add_test(${testName} "${QtWK1ApiTests_RUNTIME_OUTPUT_DIRECTORY}/tst_${testName}") + set_tests_properties(${testName} PROPERTIES TIMEOUT 60) +endforeach () diff --git a/tests/webkitwidgets/MIMESniffing/TestData.h b/tests/webkitwidgets/MIMESniffing/TestData.h new file mode 100644 index 000000000..c04bd6b89 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/TestData.h @@ -0,0 +1,984 @@ +/* + Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef TestData_h +#define TestData_h + +typedef struct _TestData { + const char* file; + const char* advertisedType; + bool isImage; + const char* sniffedType; +} TestData; + +static const TestData testList[] = { + {":/application_atom+xml", "text/plain", false, "text/plain"}, + {":/application_atom+xml", "text/plain", true, "text/plain"}, + {":/application_atom+xml", "unknown/unknown", false, "text/xml"}, + {":/application_atom+xml", "unknown/unknown", true, "text/xml"}, + {":/application_atom+xml", "application/unknown", false, "text/xml"}, + {":/application_atom+xml", "application/unknown", true, "text/xml"}, + {":/application_atom+xml", "*/*", false, "text/xml"}, + {":/application_atom+xml", "*/*", true, "text/xml"}, + {":/application_atom+xml", "text/xml", false, 0}, + {":/application_atom+xml", "text/xml", true, 0}, + {":/application_atom+xml", "application/xml", false, 0}, + {":/application_atom+xml", "application/xml", true, 0}, + {":/application_atom+xml", "text/html", false, "application/atom+xml"}, + {":/application_atom+xml", "text/html", true, 0}, + {":/application_atom+xml", "text/xml", false, 0}, + {":/application_atom+xml", "text/xml", true, 0}, + {":/application_atom+xml", "application/pdf", false, 0}, + {":/application_atom+xml", "application/pdf", true, 0}, + {":/application_atom+xml", "application/postscript", false, 0}, + {":/application_atom+xml", "application/postscript", true, 0}, + {":/application_atom+xml", "application/ogg", false, 0}, + {":/application_atom+xml", "application/ogg", true, 0}, + {":/application_atom+xml", "video/webm", false, 0}, + {":/application_atom+xml", "video/webm", true, 0}, + {":/application_atom+xml", "application/x-rar-compressed", false, 0}, + {":/application_atom+xml", "application/x-rar-compressed", true, 0}, + {":/application_atom+xml", "application/zip", false, 0}, + {":/application_atom+xml", "application/zip", true, 0}, + {":/application_atom+xml", "application/x-gzip", false, 0}, + {":/application_atom+xml", "application/x-gzip", true, 0}, + {":/application_atom+xml", "audio/x-wave", false, 0}, + {":/application_atom+xml", "audio/x-wave", true, 0}, + {":/application_atom+xml", "image/webp", false, 0}, + {":/application_atom+xml", "image/webp", true, 0}, + {":/application_atom+xml", "image/gif", false, 0}, + {":/application_atom+xml", "image/gif", true, 0}, + {":/application_atom+xml", "image/png", false, 0}, + {":/application_atom+xml", "image/png", true, 0}, + {":/application_atom+xml", "image/jpeg", false, 0}, + {":/application_atom+xml", "image/jpeg", true, 0}, + {":/application_atom+xml", "image/bmp", false, 0}, + {":/application_atom+xml", "image/bmp", true, 0}, + {":/application_atom+xml", "image/vnd.microsoft.icon", false, 0}, + {":/application_atom+xml", "image/vnd.microsoft.icon", true, 0}, + {":/application_atom+xml", "application/rdf+xml", false, 0}, + {":/application_atom+xml", "application/rdf+xml", true, 0}, + {":/application_atom+xml", "application/rss+xml", false, 0}, + {":/application_atom+xml", "application/rss+xml", true, 0}, + {":/application_atom+xml", "application/atom+xml", false, 0}, + {":/application_atom+xml", "application/atom+xml", true, 0}, + {":/application_ogg", "text/plain", false, "application/ogg"}, + {":/application_ogg", "text/plain", true, "application/ogg"}, + {":/application_ogg", "unknown/unknown", false, "application/ogg"}, + {":/application_ogg", "unknown/unknown", true, "application/ogg"}, + {":/application_ogg", "application/unknown", false, "application/ogg"}, + {":/application_ogg", "application/unknown", true, "application/ogg"}, + {":/application_ogg", "*/*", false, "application/ogg"}, + {":/application_ogg", "*/*", true, "application/ogg"}, + {":/application_ogg", "text/xml", false, 0}, + {":/application_ogg", "text/xml", true, 0}, + {":/application_ogg", "application/xml", false, 0}, + {":/application_ogg", "application/xml", true, 0}, + {":/application_ogg", "text/html", false, 0}, + {":/application_ogg", "text/html", true, 0}, + {":/application_ogg", "text/xml", false, 0}, + {":/application_ogg", "text/xml", true, 0}, + {":/application_ogg", "application/pdf", false, 0}, + {":/application_ogg", "application/pdf", true, 0}, + {":/application_ogg", "application/postscript", false, 0}, + {":/application_ogg", "application/postscript", true, 0}, + {":/application_ogg", "application/ogg", false, 0}, + {":/application_ogg", "application/ogg", true, 0}, + {":/application_ogg", "video/webm", false, 0}, + {":/application_ogg", "video/webm", true, 0}, + {":/application_ogg", "application/x-rar-compressed", false, 0}, + {":/application_ogg", "application/x-rar-compressed", true, 0}, + {":/application_ogg", "application/zip", false, 0}, + {":/application_ogg", "application/zip", true, 0}, + {":/application_ogg", "application/x-gzip", false, 0}, + {":/application_ogg", "application/x-gzip", true, 0}, + {":/application_ogg", "audio/x-wave", false, 0}, + {":/application_ogg", "audio/x-wave", true, 0}, + {":/application_ogg", "image/webp", false, 0}, + {":/application_ogg", "image/webp", true, 0}, + {":/application_ogg", "image/gif", false, 0}, + {":/application_ogg", "image/gif", true, 0}, + {":/application_ogg", "image/png", false, 0}, + {":/application_ogg", "image/png", true, 0}, + {":/application_ogg", "image/jpeg", false, 0}, + {":/application_ogg", "image/jpeg", true, 0}, + {":/application_ogg", "image/bmp", false, 0}, + {":/application_ogg", "image/bmp", true, 0}, + {":/application_ogg", "image/vnd.microsoft.icon", false, 0}, + {":/application_ogg", "image/vnd.microsoft.icon", true, 0}, + {":/application_ogg", "application/rdf+xml", false, 0}, + {":/application_ogg", "application/rdf+xml", true, 0}, + {":/application_ogg", "application/rss+xml", false, 0}, + {":/application_ogg", "application/rss+xml", true, 0}, + {":/application_ogg", "application/atom+xml", false, 0}, + {":/application_ogg", "application/atom+xml", true, 0}, + {":/application_pdf", "text/plain", false, "application/octet-stream"}, + {":/application_pdf", "text/plain", true, "application/octet-stream"}, + {":/application_pdf", "unknown/unknown", false, "application/pdf"}, + {":/application_pdf", "unknown/unknown", true, "application/pdf"}, + {":/application_pdf", "application/unknown", false, "application/pdf"}, + {":/application_pdf", "application/unknown", true, "application/pdf"}, + {":/application_pdf", "*/*", false, "application/pdf"}, + {":/application_pdf", "*/*", true, "application/pdf"}, + {":/application_pdf", "text/xml", false, 0}, + {":/application_pdf", "text/xml", true, 0}, + {":/application_pdf", "application/xml", false, 0}, + {":/application_pdf", "application/xml", true, 0}, + {":/application_pdf", "text/html", false, 0}, + {":/application_pdf", "text/html", true, 0}, + {":/application_pdf", "text/xml", false, 0}, + {":/application_pdf", "text/xml", true, 0}, + {":/application_pdf", "application/pdf", false, 0}, + {":/application_pdf", "application/pdf", true, 0}, + {":/application_pdf", "application/postscript", false, 0}, + {":/application_pdf", "application/postscript", true, 0}, + {":/application_pdf", "application/ogg", false, 0}, + {":/application_pdf", "application/ogg", true, 0}, + {":/application_pdf", "video/webm", false, 0}, + {":/application_pdf", "video/webm", true, 0}, + {":/application_pdf", "application/x-rar-compressed", false, 0}, + {":/application_pdf", "application/x-rar-compressed", true, 0}, + {":/application_pdf", "application/zip", false, 0}, + {":/application_pdf", "application/zip", true, 0}, + {":/application_pdf", "application/x-gzip", false, 0}, + {":/application_pdf", "application/x-gzip", true, 0}, + {":/application_pdf", "audio/x-wave", false, 0}, + {":/application_pdf", "audio/x-wave", true, 0}, + {":/application_pdf", "image/webp", false, 0}, + {":/application_pdf", "image/webp", true, 0}, + {":/application_pdf", "image/gif", false, 0}, + {":/application_pdf", "image/gif", true, 0}, + {":/application_pdf", "image/png", false, 0}, + {":/application_pdf", "image/png", true, 0}, + {":/application_pdf", "image/jpeg", false, 0}, + {":/application_pdf", "image/jpeg", true, 0}, + {":/application_pdf", "image/bmp", false, 0}, + {":/application_pdf", "image/bmp", true, 0}, + {":/application_pdf", "image/vnd.microsoft.icon", false, 0}, + {":/application_pdf", "image/vnd.microsoft.icon", true, 0}, + {":/application_pdf", "application/rdf+xml", false, 0}, + {":/application_pdf", "application/rdf+xml", true, 0}, + {":/application_pdf", "application/rss+xml", false, 0}, + {":/application_pdf", "application/rss+xml", true, 0}, + {":/application_pdf", "application/atom+xml", false, 0}, + {":/application_pdf", "application/atom+xml", true, 0}, + {":/application_postscript", "text/plain", false, "text/plain"}, + {":/application_postscript", "text/plain", true, "text/plain"}, + {":/application_postscript", "unknown/unknown", false, "application/postscript"}, + {":/application_postscript", "unknown/unknown", true, "application/postscript"}, + {":/application_postscript", "application/unknown", false, "application/postscript"}, + {":/application_postscript", "application/unknown", true, "application/postscript"}, + {":/application_postscript", "*/*", false, "application/postscript"}, + {":/application_postscript", "*/*", true, "application/postscript"}, + {":/application_postscript", "text/xml", false, 0}, + {":/application_postscript", "text/xml", true, 0}, + {":/application_postscript", "application/xml", false, 0}, + {":/application_postscript", "application/xml", true, 0}, + {":/application_postscript", "text/html", false, 0}, + {":/application_postscript", "text/html", true, 0}, + {":/application_postscript", "text/xml", false, 0}, + {":/application_postscript", "text/xml", true, 0}, + {":/application_postscript", "application/pdf", false, 0}, + {":/application_postscript", "application/pdf", true, 0}, + {":/application_postscript", "application/postscript", false, 0}, + {":/application_postscript", "application/postscript", true, 0}, + {":/application_postscript", "application/ogg", false, 0}, + {":/application_postscript", "application/ogg", true, 0}, + {":/application_postscript", "video/webm", false, 0}, + {":/application_postscript", "video/webm", true, 0}, + {":/application_postscript", "application/x-rar-compressed", false, 0}, + {":/application_postscript", "application/x-rar-compressed", true, 0}, + {":/application_postscript", "application/zip", false, 0}, + {":/application_postscript", "application/zip", true, 0}, + {":/application_postscript", "application/x-gzip", false, 0}, + {":/application_postscript", "application/x-gzip", true, 0}, + {":/application_postscript", "audio/x-wave", false, 0}, + {":/application_postscript", "audio/x-wave", true, 0}, + {":/application_postscript", "image/webp", false, 0}, + {":/application_postscript", "image/webp", true, 0}, + {":/application_postscript", "image/gif", false, 0}, + {":/application_postscript", "image/gif", true, 0}, + {":/application_postscript", "image/png", false, 0}, + {":/application_postscript", "image/png", true, 0}, + {":/application_postscript", "image/jpeg", false, 0}, + {":/application_postscript", "image/jpeg", true, 0}, + {":/application_postscript", "image/bmp", false, 0}, + {":/application_postscript", "image/bmp", true, 0}, + {":/application_postscript", "image/vnd.microsoft.icon", false, 0}, + {":/application_postscript", "image/vnd.microsoft.icon", true, 0}, + {":/application_postscript", "application/rdf+xml", false, 0}, + {":/application_postscript", "application/rdf+xml", true, 0}, + {":/application_postscript", "application/rss+xml", false, 0}, + {":/application_postscript", "application/rss+xml", true, 0}, + {":/application_postscript", "application/atom+xml", false, 0}, + {":/application_postscript", "application/atom+xml", true, 0}, + {":/application_rdf+xml", "text/plain", false, "text/plain"}, + {":/application_rdf+xml", "text/plain", true, "text/plain"}, + {":/application_rdf+xml", "unknown/unknown", false, "text/xml"}, + {":/application_rdf+xml", "unknown/unknown", true, "text/xml"}, + {":/application_rdf+xml", "application/unknown", false, "text/xml"}, + {":/application_rdf+xml", "application/unknown", true, "text/xml"}, + {":/application_rdf+xml", "*/*", false, "text/xml"}, + {":/application_rdf+xml", "*/*", true, "text/xml"}, + {":/application_rdf+xml", "text/xml", false, 0}, + {":/application_rdf+xml", "text/xml", true, 0}, + {":/application_rdf+xml", "application/xml", false, 0}, + {":/application_rdf+xml", "application/xml", true, 0}, + {":/application_rdf+xml", "text/html", false, "application/rdf+xml"}, + {":/application_rdf+xml", "text/html", true, 0}, + {":/application_rdf+xml", "text/xml", false, 0}, + {":/application_rdf+xml", "text/xml", true, 0}, + {":/application_rdf+xml", "application/pdf", false, 0}, + {":/application_rdf+xml", "application/pdf", true, 0}, + {":/application_rdf+xml", "application/postscript", false, 0}, + {":/application_rdf+xml", "application/postscript", true, 0}, + {":/application_rdf+xml", "application/ogg", false, 0}, + {":/application_rdf+xml", "application/ogg", true, 0}, + {":/application_rdf+xml", "video/webm", false, 0}, + {":/application_rdf+xml", "video/webm", true, 0}, + {":/application_rdf+xml", "application/x-rar-compressed", false, 0}, + {":/application_rdf+xml", "application/x-rar-compressed", true, 0}, + {":/application_rdf+xml", "application/zip", false, 0}, + {":/application_rdf+xml", "application/zip", true, 0}, + {":/application_rdf+xml", "application/x-gzip", false, 0}, + {":/application_rdf+xml", "application/x-gzip", true, 0}, + {":/application_rdf+xml", "audio/x-wave", false, 0}, + {":/application_rdf+xml", "audio/x-wave", true, 0}, + {":/application_rdf+xml", "image/webp", false, 0}, + {":/application_rdf+xml", "image/webp", true, 0}, + {":/application_rdf+xml", "image/gif", false, 0}, + {":/application_rdf+xml", "image/gif", true, 0}, + {":/application_rdf+xml", "image/png", false, 0}, + {":/application_rdf+xml", "image/png", true, 0}, + {":/application_rdf+xml", "image/jpeg", false, 0}, + {":/application_rdf+xml", "image/jpeg", true, 0}, + {":/application_rdf+xml", "image/bmp", false, 0}, + {":/application_rdf+xml", "image/bmp", true, 0}, + {":/application_rdf+xml", "image/vnd.microsoft.icon", false, 0}, + {":/application_rdf+xml", "image/vnd.microsoft.icon", true, 0}, + {":/application_rdf+xml", "application/rdf+xml", false, 0}, + {":/application_rdf+xml", "application/rdf+xml", true, 0}, + {":/application_rdf+xml", "application/rss+xml", false, 0}, + {":/application_rdf+xml", "application/rss+xml", true, 0}, + {":/application_rdf+xml", "application/atom+xml", false, 0}, + {":/application_rdf+xml", "application/atom+xml", true, 0}, + {":/application_rss+xml", "text/plain", false, "text/plain"}, + {":/application_rss+xml", "text/plain", true, "text/plain"}, + {":/application_rss+xml", "unknown/unknown", false, "text/xml"}, + {":/application_rss+xml", "unknown/unknown", true, "text/xml"}, + {":/application_rss+xml", "application/unknown", false, "text/xml"}, + {":/application_rss+xml", "application/unknown", true, "text/xml"}, + {":/application_rss+xml", "*/*", false, "text/xml"}, + {":/application_rss+xml", "*/*", true, "text/xml"}, + {":/application_rss+xml", "text/xml", false, 0}, + {":/application_rss+xml", "text/xml", true, 0}, + {":/application_rss+xml", "application/xml", false, 0}, + {":/application_rss+xml", "application/xml", true, 0}, + {":/application_rss+xml", "text/html", false, "application/rss+xml"}, + {":/application_rss+xml", "text/html", true, 0}, + {":/application_rss+xml", "text/xml", false, 0}, + {":/application_rss+xml", "text/xml", true, 0}, + {":/application_rss+xml", "application/pdf", false, 0}, + {":/application_rss+xml", "application/pdf", true, 0}, + {":/application_rss+xml", "application/postscript", false, 0}, + {":/application_rss+xml", "application/postscript", true, 0}, + {":/application_rss+xml", "application/ogg", false, 0}, + {":/application_rss+xml", "application/ogg", true, 0}, + {":/application_rss+xml", "video/webm", false, 0}, + {":/application_rss+xml", "video/webm", true, 0}, + {":/application_rss+xml", "application/x-rar-compressed", false, 0}, + {":/application_rss+xml", "application/x-rar-compressed", true, 0}, + {":/application_rss+xml", "application/zip", false, 0}, + {":/application_rss+xml", "application/zip", true, 0}, + {":/application_rss+xml", "application/x-gzip", false, 0}, + {":/application_rss+xml", "application/x-gzip", true, 0}, + {":/application_rss+xml", "audio/x-wave", false, 0}, + {":/application_rss+xml", "audio/x-wave", true, 0}, + {":/application_rss+xml", "image/webp", false, 0}, + {":/application_rss+xml", "image/webp", true, 0}, + {":/application_rss+xml", "image/gif", false, 0}, + {":/application_rss+xml", "image/gif", true, 0}, + {":/application_rss+xml", "image/png", false, 0}, + {":/application_rss+xml", "image/png", true, 0}, + {":/application_rss+xml", "image/jpeg", false, 0}, + {":/application_rss+xml", "image/jpeg", true, 0}, + {":/application_rss+xml", "image/bmp", false, 0}, + {":/application_rss+xml", "image/bmp", true, 0}, + {":/application_rss+xml", "image/vnd.microsoft.icon", false, 0}, + {":/application_rss+xml", "image/vnd.microsoft.icon", true, 0}, + {":/application_rss+xml", "application/rdf+xml", false, 0}, + {":/application_rss+xml", "application/rdf+xml", true, 0}, + {":/application_rss+xml", "application/rss+xml", false, 0}, + {":/application_rss+xml", "application/rss+xml", true, 0}, + {":/application_rss+xml", "application/atom+xml", false, 0}, + {":/application_rss+xml", "application/atom+xml", true, 0}, + {":/application_x-gzip", "text/plain", false, "application/x-gzip"}, + {":/application_x-gzip", "text/plain", true, "application/x-gzip"}, + {":/application_x-gzip", "unknown/unknown", false, "application/x-gzip"}, + {":/application_x-gzip", "unknown/unknown", true, "application/x-gzip"}, + {":/application_x-gzip", "application/unknown", false, "application/x-gzip"}, + {":/application_x-gzip", "application/unknown", true, "application/x-gzip"}, + {":/application_x-gzip", "*/*", false, "application/x-gzip"}, + {":/application_x-gzip", "*/*", true, "application/x-gzip"}, + {":/application_x-gzip", "text/xml", false, 0}, + {":/application_x-gzip", "text/xml", true, 0}, + {":/application_x-gzip", "application/xml", false, 0}, + {":/application_x-gzip", "application/xml", true, 0}, + {":/application_x-gzip", "text/html", false, 0}, + {":/application_x-gzip", "text/html", true, 0}, + {":/application_x-gzip", "text/xml", false, 0}, + {":/application_x-gzip", "text/xml", true, 0}, + {":/application_x-gzip", "application/pdf", false, 0}, + {":/application_x-gzip", "application/pdf", true, 0}, + {":/application_x-gzip", "application/postscript", false, 0}, + {":/application_x-gzip", "application/postscript", true, 0}, + {":/application_x-gzip", "application/ogg", false, 0}, + {":/application_x-gzip", "application/ogg", true, 0}, + {":/application_x-gzip", "video/webm", false, 0}, + {":/application_x-gzip", "video/webm", true, 0}, + {":/application_x-gzip", "application/x-rar-compressed", false, 0}, + {":/application_x-gzip", "application/x-rar-compressed", true, 0}, + {":/application_x-gzip", "application/zip", false, 0}, + {":/application_x-gzip", "application/zip", true, 0}, + {":/application_x-gzip", "application/x-gzip", false, 0}, + {":/application_x-gzip", "application/x-gzip", true, 0}, + {":/application_x-gzip", "audio/x-wave", false, 0}, + {":/application_x-gzip", "audio/x-wave", true, 0}, + {":/application_x-gzip", "image/webp", false, 0}, + {":/application_x-gzip", "image/webp", true, 0}, + {":/application_x-gzip", "image/gif", false, 0}, + {":/application_x-gzip", "image/gif", true, 0}, + {":/application_x-gzip", "image/png", false, 0}, + {":/application_x-gzip", "image/png", true, 0}, + {":/application_x-gzip", "image/jpeg", false, 0}, + {":/application_x-gzip", "image/jpeg", true, 0}, + {":/application_x-gzip", "image/bmp", false, 0}, + {":/application_x-gzip", "image/bmp", true, 0}, + {":/application_x-gzip", "image/vnd.microsoft.icon", false, 0}, + {":/application_x-gzip", "image/vnd.microsoft.icon", true, 0}, + {":/application_x-gzip", "application/rdf+xml", false, 0}, + {":/application_x-gzip", "application/rdf+xml", true, 0}, + {":/application_x-gzip", "application/rss+xml", false, 0}, + {":/application_x-gzip", "application/rss+xml", true, 0}, + {":/application_x-gzip", "application/atom+xml", false, 0}, + {":/application_x-gzip", "application/atom+xml", true, 0}, + {":/application_x-rar-compressed", "text/plain", false, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "text/plain", true, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "unknown/unknown", false, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "unknown/unknown", true, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "application/unknown", false, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "application/unknown", true, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "*/*", false, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "*/*", true, "application/x-rar-compressed"}, + {":/application_x-rar-compressed", "text/xml", false, 0}, + {":/application_x-rar-compressed", "text/xml", true, 0}, + {":/application_x-rar-compressed", "application/xml", false, 0}, + {":/application_x-rar-compressed", "application/xml", true, 0}, + {":/application_x-rar-compressed", "text/html", false, 0}, + {":/application_x-rar-compressed", "text/html", true, 0}, + {":/application_x-rar-compressed", "text/xml", false, 0}, + {":/application_x-rar-compressed", "text/xml", true, 0}, + {":/application_x-rar-compressed", "application/pdf", false, 0}, + {":/application_x-rar-compressed", "application/pdf", true, 0}, + {":/application_x-rar-compressed", "application/postscript", false, 0}, + {":/application_x-rar-compressed", "application/postscript", true, 0}, + {":/application_x-rar-compressed", "application/ogg", false, 0}, + {":/application_x-rar-compressed", "application/ogg", true, 0}, + {":/application_x-rar-compressed", "video/webm", false, 0}, + {":/application_x-rar-compressed", "video/webm", true, 0}, + {":/application_x-rar-compressed", "application/x-rar-compressed", false, 0}, + {":/application_x-rar-compressed", "application/x-rar-compressed", true, 0}, + {":/application_x-rar-compressed", "application/zip", false, 0}, + {":/application_x-rar-compressed", "application/zip", true, 0}, + {":/application_x-rar-compressed", "application/x-gzip", false, 0}, + {":/application_x-rar-compressed", "application/x-gzip", true, 0}, + {":/application_x-rar-compressed", "audio/x-wave", false, 0}, + {":/application_x-rar-compressed", "audio/x-wave", true, 0}, + {":/application_x-rar-compressed", "image/webp", false, 0}, + {":/application_x-rar-compressed", "image/webp", true, 0}, + {":/application_x-rar-compressed", "image/gif", false, 0}, + {":/application_x-rar-compressed", "image/gif", true, 0}, + {":/application_x-rar-compressed", "image/png", false, 0}, + {":/application_x-rar-compressed", "image/png", true, 0}, + {":/application_x-rar-compressed", "image/jpeg", false, 0}, + {":/application_x-rar-compressed", "image/jpeg", true, 0}, + {":/application_x-rar-compressed", "image/bmp", false, 0}, + {":/application_x-rar-compressed", "image/bmp", true, 0}, + {":/application_x-rar-compressed", "image/vnd.microsoft.icon", false, 0}, + {":/application_x-rar-compressed", "image/vnd.microsoft.icon", true, 0}, + {":/application_x-rar-compressed", "application/rdf+xml", false, 0}, + {":/application_x-rar-compressed", "application/rdf+xml", true, 0}, + {":/application_x-rar-compressed", "application/rss+xml", false, 0}, + {":/application_x-rar-compressed", "application/rss+xml", true, 0}, + {":/application_x-rar-compressed", "application/atom+xml", false, 0}, + {":/application_x-rar-compressed", "application/atom+xml", true, 0}, + {":/application_zip", "text/plain", false, "application/zip"}, + {":/application_zip", "text/plain", true, "application/zip"}, + {":/application_zip", "unknown/unknown", false, "application/zip"}, + {":/application_zip", "unknown/unknown", true, "application/zip"}, + {":/application_zip", "application/unknown", false, "application/zip"}, + {":/application_zip", "application/unknown", true, "application/zip"}, + {":/application_zip", "*/*", false, "application/zip"}, + {":/application_zip", "*/*", true, "application/zip"}, + {":/application_zip", "text/xml", false, 0}, + {":/application_zip", "text/xml", true, 0}, + {":/application_zip", "application/xml", false, 0}, + {":/application_zip", "application/xml", true, 0}, + {":/application_zip", "text/html", false, 0}, + {":/application_zip", "text/html", true, 0}, + {":/application_zip", "text/xml", false, 0}, + {":/application_zip", "text/xml", true, 0}, + {":/application_zip", "application/pdf", false, 0}, + {":/application_zip", "application/pdf", true, 0}, + {":/application_zip", "application/postscript", false, 0}, + {":/application_zip", "application/postscript", true, 0}, + {":/application_zip", "application/ogg", false, 0}, + {":/application_zip", "application/ogg", true, 0}, + {":/application_zip", "video/webm", false, 0}, + {":/application_zip", "video/webm", true, 0}, + {":/application_zip", "application/x-rar-compressed", false, 0}, + {":/application_zip", "application/x-rar-compressed", true, 0}, + {":/application_zip", "application/zip", false, 0}, + {":/application_zip", "application/zip", true, 0}, + {":/application_zip", "application/x-gzip", false, 0}, + {":/application_zip", "application/x-gzip", true, 0}, + {":/application_zip", "audio/x-wave", false, 0}, + {":/application_zip", "audio/x-wave", true, 0}, + {":/application_zip", "image/webp", false, 0}, + {":/application_zip", "image/webp", true, 0}, + {":/application_zip", "image/gif", false, 0}, + {":/application_zip", "image/gif", true, 0}, + {":/application_zip", "image/png", false, 0}, + {":/application_zip", "image/png", true, 0}, + {":/application_zip", "image/jpeg", false, 0}, + {":/application_zip", "image/jpeg", true, 0}, + {":/application_zip", "image/bmp", false, 0}, + {":/application_zip", "image/bmp", true, 0}, + {":/application_zip", "image/vnd.microsoft.icon", false, 0}, + {":/application_zip", "image/vnd.microsoft.icon", true, 0}, + {":/application_zip", "application/rdf+xml", false, 0}, + {":/application_zip", "application/rdf+xml", true, 0}, + {":/application_zip", "application/rss+xml", false, 0}, + {":/application_zip", "application/rss+xml", true, 0}, + {":/application_zip", "application/atom+xml", false, 0}, + {":/application_zip", "application/atom+xml", true, 0}, + {":/audio_x-wave", "text/plain", false, "audio/x-wave"}, + {":/audio_x-wave", "text/plain", true, "audio/x-wave"}, + {":/audio_x-wave", "unknown/unknown", false, "audio/x-wave"}, + {":/audio_x-wave", "unknown/unknown", true, "audio/x-wave"}, + {":/audio_x-wave", "application/unknown", false, "audio/x-wave"}, + {":/audio_x-wave", "application/unknown", true, "audio/x-wave"}, + {":/audio_x-wave", "*/*", false, "audio/x-wave"}, + {":/audio_x-wave", "*/*", true, "audio/x-wave"}, + {":/audio_x-wave", "text/xml", false, 0}, + {":/audio_x-wave", "text/xml", true, 0}, + {":/audio_x-wave", "application/xml", false, 0}, + {":/audio_x-wave", "application/xml", true, 0}, + {":/audio_x-wave", "text/html", false, 0}, + {":/audio_x-wave", "text/html", true, 0}, + {":/audio_x-wave", "text/xml", false, 0}, + {":/audio_x-wave", "text/xml", true, 0}, + {":/audio_x-wave", "application/pdf", false, 0}, + {":/audio_x-wave", "application/pdf", true, 0}, + {":/audio_x-wave", "application/postscript", false, 0}, + {":/audio_x-wave", "application/postscript", true, 0}, + {":/audio_x-wave", "application/ogg", false, 0}, + {":/audio_x-wave", "application/ogg", true, 0}, + {":/audio_x-wave", "video/webm", false, 0}, + {":/audio_x-wave", "video/webm", true, 0}, + {":/audio_x-wave", "application/x-rar-compressed", false, 0}, + {":/audio_x-wave", "application/x-rar-compressed", true, 0}, + {":/audio_x-wave", "application/zip", false, 0}, + {":/audio_x-wave", "application/zip", true, 0}, + {":/audio_x-wave", "application/x-gzip", false, 0}, + {":/audio_x-wave", "application/x-gzip", true, 0}, + {":/audio_x-wave", "audio/x-wave", false, 0}, + {":/audio_x-wave", "audio/x-wave", true, 0}, + {":/audio_x-wave", "image/webp", false, 0}, + {":/audio_x-wave", "image/webp", true, 0}, + {":/audio_x-wave", "image/gif", false, 0}, + {":/audio_x-wave", "image/gif", true, 0}, + {":/audio_x-wave", "image/png", false, 0}, + {":/audio_x-wave", "image/png", true, 0}, + {":/audio_x-wave", "image/jpeg", false, 0}, + {":/audio_x-wave", "image/jpeg", true, 0}, + {":/audio_x-wave", "image/bmp", false, 0}, + {":/audio_x-wave", "image/bmp", true, 0}, + {":/audio_x-wave", "image/vnd.microsoft.icon", false, 0}, + {":/audio_x-wave", "image/vnd.microsoft.icon", true, 0}, + {":/audio_x-wave", "application/rdf+xml", false, 0}, + {":/audio_x-wave", "application/rdf+xml", true, 0}, + {":/audio_x-wave", "application/rss+xml", false, 0}, + {":/audio_x-wave", "application/rss+xml", true, 0}, + {":/audio_x-wave", "application/atom+xml", false, 0}, + {":/audio_x-wave", "application/atom+xml", true, 0}, + {":/image_bmp", "text/plain", false, "image/bmp"}, + {":/image_bmp", "text/plain", true, "image/bmp"}, + {":/image_bmp", "unknown/unknown", false, "image/bmp"}, + {":/image_bmp", "unknown/unknown", true, "image/bmp"}, + {":/image_bmp", "application/unknown", false, "image/bmp"}, + {":/image_bmp", "application/unknown", true, "image/bmp"}, + {":/image_bmp", "*/*", false, "image/bmp"}, + {":/image_bmp", "*/*", true, "image/bmp"}, + {":/image_bmp", "text/xml", false, 0}, + {":/image_bmp", "text/xml", true, 0}, + {":/image_bmp", "application/xml", false, 0}, + {":/image_bmp", "application/xml", true, 0}, + {":/image_bmp", "text/html", false, 0}, + {":/image_bmp", "text/html", true, "image/bmp"}, + {":/image_bmp", "text/xml", false, 0}, + {":/image_bmp", "text/xml", true, 0}, + {":/image_bmp", "application/pdf", false, 0}, + {":/image_bmp", "application/pdf", true, "image/bmp"}, + {":/image_bmp", "application/postscript", false, 0}, + {":/image_bmp", "application/postscript", true, "image/bmp"}, + {":/image_bmp", "application/ogg", false, 0}, + {":/image_bmp", "application/ogg", true, "image/bmp"}, + {":/image_bmp", "video/webm", false, 0}, + {":/image_bmp", "video/webm", true, "image/bmp"}, + {":/image_bmp", "application/x-rar-compressed", false, 0}, + {":/image_bmp", "application/x-rar-compressed", true, "image/bmp"}, + {":/image_bmp", "application/zip", false, 0}, + {":/image_bmp", "application/zip", true, "image/bmp"}, + {":/image_bmp", "application/x-gzip", false, 0}, + {":/image_bmp", "application/x-gzip", true, "image/bmp"}, + {":/image_bmp", "audio/x-wave", false, 0}, + {":/image_bmp", "audio/x-wave", true, "image/bmp"}, + {":/image_bmp", "image/webp", false, 0}, + {":/image_bmp", "image/webp", true, "image/bmp"}, + {":/image_bmp", "image/gif", false, 0}, + {":/image_bmp", "image/gif", true, "image/bmp"}, + {":/image_bmp", "image/png", false, 0}, + {":/image_bmp", "image/png", true, "image/bmp"}, + {":/image_bmp", "image/jpeg", false, 0}, + {":/image_bmp", "image/jpeg", true, "image/bmp"}, + {":/image_bmp", "image/bmp", false, 0}, + {":/image_bmp", "image/bmp", true, "image/bmp"}, + {":/image_bmp", "image/vnd.microsoft.icon", false, 0}, + {":/image_bmp", "image/vnd.microsoft.icon", true, "image/bmp"}, + {":/image_bmp", "application/rdf+xml", false, 0}, + {":/image_bmp", "application/rdf+xml", true, 0}, + {":/image_bmp", "application/rss+xml", false, 0}, + {":/image_bmp", "application/rss+xml", true, 0}, + {":/image_bmp", "application/atom+xml", false, 0}, + {":/image_bmp", "application/atom+xml", true, 0}, + {":/image_gif", "text/plain", false, "image/gif"}, + {":/image_gif", "text/plain", true, "image/gif"}, + {":/image_gif", "unknown/unknown", false, "image/gif"}, + {":/image_gif", "unknown/unknown", true, "image/gif"}, + {":/image_gif", "application/unknown", false, "image/gif"}, + {":/image_gif", "application/unknown", true, "image/gif"}, + {":/image_gif", "*/*", false, "image/gif"}, + {":/image_gif", "*/*", true, "image/gif"}, + {":/image_gif", "text/xml", false, 0}, + {":/image_gif", "text/xml", true, 0}, + {":/image_gif", "application/xml", false, 0}, + {":/image_gif", "application/xml", true, 0}, + {":/image_gif", "text/html", false, 0}, + {":/image_gif", "text/html", true, "image/gif"}, + {":/image_gif", "text/xml", false, 0}, + {":/image_gif", "text/xml", true, 0}, + {":/image_gif", "application/pdf", false, 0}, + {":/image_gif", "application/pdf", true, "image/gif"}, + {":/image_gif", "application/postscript", false, 0}, + {":/image_gif", "application/postscript", true, "image/gif"}, + {":/image_gif", "application/ogg", false, 0}, + {":/image_gif", "application/ogg", true, "image/gif"}, + {":/image_gif", "video/webm", false, 0}, + {":/image_gif", "video/webm", true, "image/gif"}, + {":/image_gif", "application/x-rar-compressed", false, 0}, + {":/image_gif", "application/x-rar-compressed", true, "image/gif"}, + {":/image_gif", "application/zip", false, 0}, + {":/image_gif", "application/zip", true, "image/gif"}, + {":/image_gif", "application/x-gzip", false, 0}, + {":/image_gif", "application/x-gzip", true, "image/gif"}, + {":/image_gif", "audio/x-wave", false, 0}, + {":/image_gif", "audio/x-wave", true, "image/gif"}, + {":/image_gif", "image/webp", false, 0}, + {":/image_gif", "image/webp", true, "image/gif"}, + {":/image_gif", "image/gif", false, 0}, + {":/image_gif", "image/gif", true, "image/gif"}, + {":/image_gif", "image/png", false, 0}, + {":/image_gif", "image/png", true, "image/gif"}, + {":/image_gif", "image/jpeg", false, 0}, + {":/image_gif", "image/jpeg", true, "image/gif"}, + {":/image_gif", "image/bmp", false, 0}, + {":/image_gif", "image/bmp", true, "image/gif"}, + {":/image_gif", "image/vnd.microsoft.icon", false, 0}, + {":/image_gif", "image/vnd.microsoft.icon", true, "image/gif"}, + {":/image_gif", "application/rdf+xml", false, 0}, + {":/image_gif", "application/rdf+xml", true, 0}, + {":/image_gif", "application/rss+xml", false, 0}, + {":/image_gif", "application/rss+xml", true, 0}, + {":/image_gif", "application/atom+xml", false, 0}, + {":/image_gif", "application/atom+xml", true, 0}, + {":/image_jpeg", "text/plain", false, "image/jpeg"}, + {":/image_jpeg", "text/plain", true, "image/jpeg"}, + {":/image_jpeg", "unknown/unknown", false, "image/jpeg"}, + {":/image_jpeg", "unknown/unknown", true, "image/jpeg"}, + {":/image_jpeg", "application/unknown", false, "image/jpeg"}, + {":/image_jpeg", "application/unknown", true, "image/jpeg"}, + {":/image_jpeg", "*/*", false, "image/jpeg"}, + {":/image_jpeg", "*/*", true, "image/jpeg"}, + {":/image_jpeg", "text/xml", false, 0}, + {":/image_jpeg", "text/xml", true, 0}, + {":/image_jpeg", "application/xml", false, 0}, + {":/image_jpeg", "application/xml", true, 0}, + {":/image_jpeg", "text/html", false, 0}, + {":/image_jpeg", "text/html", true, "image/jpeg"}, + {":/image_jpeg", "text/xml", false, 0}, + {":/image_jpeg", "text/xml", true, 0}, + {":/image_jpeg", "application/pdf", false, 0}, + {":/image_jpeg", "application/pdf", true, "image/jpeg"}, + {":/image_jpeg", "application/postscript", false, 0}, + {":/image_jpeg", "application/postscript", true, "image/jpeg"}, + {":/image_jpeg", "application/ogg", false, 0}, + {":/image_jpeg", "application/ogg", true, "image/jpeg"}, + {":/image_jpeg", "video/webm", false, 0}, + {":/image_jpeg", "video/webm", true, "image/jpeg"}, + {":/image_jpeg", "application/x-rar-compressed", false, 0}, + {":/image_jpeg", "application/x-rar-compressed", true, "image/jpeg"}, + {":/image_jpeg", "application/zip", false, 0}, + {":/image_jpeg", "application/zip", true, "image/jpeg"}, + {":/image_jpeg", "application/x-gzip", false, 0}, + {":/image_jpeg", "application/x-gzip", true, "image/jpeg"}, + {":/image_jpeg", "audio/x-wave", false, 0}, + {":/image_jpeg", "audio/x-wave", true, "image/jpeg"}, + {":/image_jpeg", "image/webp", false, 0}, + {":/image_jpeg", "image/webp", true, "image/jpeg"}, + {":/image_jpeg", "image/gif", false, 0}, + {":/image_jpeg", "image/gif", true, "image/jpeg"}, + {":/image_jpeg", "image/png", false, 0}, + {":/image_jpeg", "image/png", true, "image/jpeg"}, + {":/image_jpeg", "image/jpeg", false, 0}, + {":/image_jpeg", "image/jpeg", true, "image/jpeg"}, + {":/image_jpeg", "image/bmp", false, 0}, + {":/image_jpeg", "image/bmp", true, "image/jpeg"}, + {":/image_jpeg", "image/vnd.microsoft.icon", false, 0}, + {":/image_jpeg", "image/vnd.microsoft.icon", true, "image/jpeg"}, + {":/image_jpeg", "application/rdf+xml", false, 0}, + {":/image_jpeg", "application/rdf+xml", true, 0}, + {":/image_jpeg", "application/rss+xml", false, 0}, + {":/image_jpeg", "application/rss+xml", true, 0}, + {":/image_jpeg", "application/atom+xml", false, 0}, + {":/image_jpeg", "application/atom+xml", true, 0}, + {":/image_png", "text/plain", false, "image/png"}, + {":/image_png", "text/plain", true, "image/png"}, + {":/image_png", "unknown/unknown", false, "image/png"}, + {":/image_png", "unknown/unknown", true, "image/png"}, + {":/image_png", "application/unknown", false, "image/png"}, + {":/image_png", "application/unknown", true, "image/png"}, + {":/image_png", "*/*", false, "image/png"}, + {":/image_png", "*/*", true, "image/png"}, + {":/image_png", "text/xml", false, 0}, + {":/image_png", "text/xml", true, 0}, + {":/image_png", "application/xml", false, 0}, + {":/image_png", "application/xml", true, 0}, + {":/image_png", "text/html", false, 0}, + {":/image_png", "text/html", true, "image/png"}, + {":/image_png", "text/xml", false, 0}, + {":/image_png", "text/xml", true, 0}, + {":/image_png", "application/pdf", false, 0}, + {":/image_png", "application/pdf", true, "image/png"}, + {":/image_png", "application/postscript", false, 0}, + {":/image_png", "application/postscript", true, "image/png"}, + {":/image_png", "application/ogg", false, 0}, + {":/image_png", "application/ogg", true, "image/png"}, + {":/image_png", "video/webm", false, 0}, + {":/image_png", "video/webm", true, "image/png"}, + {":/image_png", "application/x-rar-compressed", false, 0}, + {":/image_png", "application/x-rar-compressed", true, "image/png"}, + {":/image_png", "application/zip", false, 0}, + {":/image_png", "application/zip", true, "image/png"}, + {":/image_png", "application/x-gzip", false, 0}, + {":/image_png", "application/x-gzip", true, "image/png"}, + {":/image_png", "audio/x-wave", false, 0}, + {":/image_png", "audio/x-wave", true, "image/png"}, + {":/image_png", "image/webp", false, 0}, + {":/image_png", "image/webp", true, "image/png"}, + {":/image_png", "image/gif", false, 0}, + {":/image_png", "image/gif", true, "image/png"}, + {":/image_png", "image/png", false, 0}, + {":/image_png", "image/png", true, "image/png"}, + {":/image_png", "image/jpeg", false, 0}, + {":/image_png", "image/jpeg", true, "image/png"}, + {":/image_png", "image/bmp", false, 0}, + {":/image_png", "image/bmp", true, "image/png"}, + {":/image_png", "image/vnd.microsoft.icon", false, 0}, + {":/image_png", "image/vnd.microsoft.icon", true, "image/png"}, + {":/image_png", "application/rdf+xml", false, 0}, + {":/image_png", "application/rdf+xml", true, 0}, + {":/image_png", "application/rss+xml", false, 0}, + {":/image_png", "application/rss+xml", true, 0}, + {":/image_png", "application/atom+xml", false, 0}, + {":/image_png", "application/atom+xml", true, 0}, + {":/image_vnd.microsoft.icon", "text/plain", false, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "text/plain", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "unknown/unknown", false, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "unknown/unknown", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/unknown", false, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/unknown", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "*/*", false, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "*/*", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "text/xml", false, 0}, + {":/image_vnd.microsoft.icon", "text/xml", true, 0}, + {":/image_vnd.microsoft.icon", "application/xml", false, 0}, + {":/image_vnd.microsoft.icon", "application/xml", true, 0}, + {":/image_vnd.microsoft.icon", "text/html", false, 0}, + {":/image_vnd.microsoft.icon", "text/html", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "text/xml", false, 0}, + {":/image_vnd.microsoft.icon", "text/xml", true, 0}, + {":/image_vnd.microsoft.icon", "application/pdf", false, 0}, + {":/image_vnd.microsoft.icon", "application/pdf", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/postscript", false, 0}, + {":/image_vnd.microsoft.icon", "application/postscript", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/ogg", false, 0}, + {":/image_vnd.microsoft.icon", "application/ogg", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "video/webm", false, 0}, + {":/image_vnd.microsoft.icon", "video/webm", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/x-rar-compressed", false, 0}, + {":/image_vnd.microsoft.icon", "application/x-rar-compressed", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/zip", false, 0}, + {":/image_vnd.microsoft.icon", "application/zip", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/x-gzip", false, 0}, + {":/image_vnd.microsoft.icon", "application/x-gzip", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "audio/x-wave", false, 0}, + {":/image_vnd.microsoft.icon", "audio/x-wave", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "image/webp", false, 0}, + {":/image_vnd.microsoft.icon", "image/webp", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "image/gif", false, 0}, + {":/image_vnd.microsoft.icon", "image/gif", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "image/png", false, 0}, + {":/image_vnd.microsoft.icon", "image/png", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "image/jpeg", false, 0}, + {":/image_vnd.microsoft.icon", "image/jpeg", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "image/bmp", false, 0}, + {":/image_vnd.microsoft.icon", "image/bmp", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "image/vnd.microsoft.icon", false, 0}, + {":/image_vnd.microsoft.icon", "image/vnd.microsoft.icon", true, "image/vnd.microsoft.icon"}, + {":/image_vnd.microsoft.icon", "application/rdf+xml", false, 0}, + {":/image_vnd.microsoft.icon", "application/rdf+xml", true, 0}, + {":/image_vnd.microsoft.icon", "application/rss+xml", false, 0}, + {":/image_vnd.microsoft.icon", "application/rss+xml", true, 0}, + {":/image_vnd.microsoft.icon", "application/atom+xml", false, 0}, + {":/image_vnd.microsoft.icon", "application/atom+xml", true, 0}, + {":/image_webp", "text/plain", false, "image/webp"}, + {":/image_webp", "text/plain", true, "image/webp"}, + {":/image_webp", "unknown/unknown", false, "image/webp"}, + {":/image_webp", "unknown/unknown", true, "image/webp"}, + {":/image_webp", "application/unknown", false, "image/webp"}, + {":/image_webp", "application/unknown", true, "image/webp"}, + {":/image_webp", "*/*", false, "image/webp"}, + {":/image_webp", "*/*", true, "image/webp"}, + {":/image_webp", "text/xml", false, 0}, + {":/image_webp", "text/xml", true, 0}, + {":/image_webp", "application/xml", false, 0}, + {":/image_webp", "application/xml", true, 0}, + {":/image_webp", "text/html", false, 0}, + {":/image_webp", "text/html", true, "image/webp"}, + {":/image_webp", "text/xml", false, 0}, + {":/image_webp", "text/xml", true, 0}, + {":/image_webp", "application/pdf", false, 0}, + {":/image_webp", "application/pdf", true, "image/webp"}, + {":/image_webp", "application/postscript", false, 0}, + {":/image_webp", "application/postscript", true, "image/webp"}, + {":/image_webp", "application/ogg", false, 0}, + {":/image_webp", "application/ogg", true, "image/webp"}, + {":/image_webp", "video/webm", false, 0}, + {":/image_webp", "video/webm", true, "image/webp"}, + {":/image_webp", "application/x-rar-compressed", false, 0}, + {":/image_webp", "application/x-rar-compressed", true, "image/webp"}, + {":/image_webp", "application/zip", false, 0}, + {":/image_webp", "application/zip", true, "image/webp"}, + {":/image_webp", "application/x-gzip", false, 0}, + {":/image_webp", "application/x-gzip", true, "image/webp"}, + {":/image_webp", "audio/x-wave", false, 0}, + {":/image_webp", "audio/x-wave", true, "image/webp"}, + {":/image_webp", "image/webp", false, 0}, + {":/image_webp", "image/webp", true, "image/webp"}, + {":/image_webp", "image/gif", false, 0}, + {":/image_webp", "image/gif", true, "image/webp"}, + {":/image_webp", "image/png", false, 0}, + {":/image_webp", "image/png", true, "image/webp"}, + {":/image_webp", "image/jpeg", false, 0}, + {":/image_webp", "image/jpeg", true, "image/webp"}, + {":/image_webp", "image/bmp", false, 0}, + {":/image_webp", "image/bmp", true, "image/webp"}, + {":/image_webp", "image/vnd.microsoft.icon", false, 0}, + {":/image_webp", "image/vnd.microsoft.icon", true, "image/webp"}, + {":/image_webp", "application/rdf+xml", false, 0}, + {":/image_webp", "application/rdf+xml", true, 0}, + {":/image_webp", "application/rss+xml", false, 0}, + {":/image_webp", "application/rss+xml", true, 0}, + {":/image_webp", "application/atom+xml", false, 0}, + {":/image_webp", "application/atom+xml", true, 0}, + {":/text_html", "text/plain", false, "text/plain"}, + {":/text_html", "text/plain", true, "text/plain"}, + {":/text_html", "unknown/unknown", false, "text/html"}, + {":/text_html", "unknown/unknown", true, "text/html"}, + {":/text_html", "application/unknown", false, "text/html"}, + {":/text_html", "application/unknown", true, "text/html"}, + {":/text_html", "*/*", false, "text/html"}, + {":/text_html", "*/*", true, "text/html"}, + {":/text_html", "text/xml", false, 0}, + {":/text_html", "text/xml", true, 0}, + {":/text_html", "application/xml", false, 0}, + {":/text_html", "application/xml", true, 0}, + {":/text_html", "text/html", false, 0}, + {":/text_html", "text/html", true, 0}, + {":/text_html", "text/xml", false, 0}, + {":/text_html", "text/xml", true, 0}, + {":/text_html", "application/pdf", false, 0}, + {":/text_html", "application/pdf", true, 0}, + {":/text_html", "application/postscript", false, 0}, + {":/text_html", "application/postscript", true, 0}, + {":/text_html", "application/ogg", false, 0}, + {":/text_html", "application/ogg", true, 0}, + {":/text_html", "video/webm", false, 0}, + {":/text_html", "video/webm", true, 0}, + {":/text_html", "application/x-rar-compressed", false, 0}, + {":/text_html", "application/x-rar-compressed", true, 0}, + {":/text_html", "application/zip", false, 0}, + {":/text_html", "application/zip", true, 0}, + {":/text_html", "application/x-gzip", false, 0}, + {":/text_html", "application/x-gzip", true, 0}, + {":/text_html", "audio/x-wave", false, 0}, + {":/text_html", "audio/x-wave", true, 0}, + {":/text_html", "image/webp", false, 0}, + {":/text_html", "image/webp", true, 0}, + {":/text_html", "image/gif", false, 0}, + {":/text_html", "image/gif", true, 0}, + {":/text_html", "image/png", false, 0}, + {":/text_html", "image/png", true, 0}, + {":/text_html", "image/jpeg", false, 0}, + {":/text_html", "image/jpeg", true, 0}, + {":/text_html", "image/bmp", false, 0}, + {":/text_html", "image/bmp", true, 0}, + {":/text_html", "image/vnd.microsoft.icon", false, 0}, + {":/text_html", "image/vnd.microsoft.icon", true, 0}, + {":/text_html", "application/rdf+xml", false, 0}, + {":/text_html", "application/rdf+xml", true, 0}, + {":/text_html", "application/rss+xml", false, 0}, + {":/text_html", "application/rss+xml", true, 0}, + {":/text_html", "application/atom+xml", false, 0}, + {":/text_html", "application/atom+xml", true, 0}, + {":/text_xml", "text/plain", false, "text/plain"}, + {":/text_xml", "text/plain", true, "text/plain"}, + {":/text_xml", "unknown/unknown", false, "text/xml"}, + {":/text_xml", "unknown/unknown", true, "text/xml"}, + {":/text_xml", "application/unknown", false, "text/xml"}, + {":/text_xml", "application/unknown", true, "text/xml"}, + {":/text_xml", "*/*", false, "text/xml"}, + {":/text_xml", "*/*", true, "text/xml"}, + {":/text_xml", "text/xml", false, 0}, + {":/text_xml", "text/xml", true, 0}, + {":/text_xml", "application/xml", false, 0}, + {":/text_xml", "application/xml", true, 0}, + {":/text_xml", "text/html", false, 0}, + {":/text_xml", "text/html", true, 0}, + {":/text_xml", "text/xml", false, 0}, + {":/text_xml", "text/xml", true, 0}, + {":/text_xml", "application/pdf", false, 0}, + {":/text_xml", "application/pdf", true, 0}, + {":/text_xml", "application/postscript", false, 0}, + {":/text_xml", "application/postscript", true, 0}, + {":/text_xml", "application/ogg", false, 0}, + {":/text_xml", "application/ogg", true, 0}, + {":/text_xml", "video/webm", false, 0}, + {":/text_xml", "video/webm", true, 0}, + {":/text_xml", "application/x-rar-compressed", false, 0}, + {":/text_xml", "application/x-rar-compressed", true, 0}, + {":/text_xml", "application/zip", false, 0}, + {":/text_xml", "application/zip", true, 0}, + {":/text_xml", "application/x-gzip", false, 0}, + {":/text_xml", "application/x-gzip", true, 0}, + {":/text_xml", "audio/x-wave", false, 0}, + {":/text_xml", "audio/x-wave", true, 0}, + {":/text_xml", "image/webp", false, 0}, + {":/text_xml", "image/webp", true, 0}, + {":/text_xml", "image/gif", false, 0}, + {":/text_xml", "image/gif", true, 0}, + {":/text_xml", "image/png", false, 0}, + {":/text_xml", "image/png", true, 0}, + {":/text_xml", "image/jpeg", false, 0}, + {":/text_xml", "image/jpeg", true, 0}, + {":/text_xml", "image/bmp", false, 0}, + {":/text_xml", "image/bmp", true, 0}, + {":/text_xml", "image/vnd.microsoft.icon", false, 0}, + {":/text_xml", "image/vnd.microsoft.icon", true, 0}, + {":/text_xml", "application/rdf+xml", false, 0}, + {":/text_xml", "application/rdf+xml", true, 0}, + {":/text_xml", "application/rss+xml", false, 0}, + {":/text_xml", "application/rss+xml", true, 0}, + {":/text_xml", "application/atom+xml", false, 0}, + {":/text_xml", "application/atom+xml", true, 0}, + {":/video_webm", "text/plain", false, "video/webm"}, + {":/video_webm", "text/plain", true, "video/webm"}, + {":/video_webm", "unknown/unknown", false, "video/webm"}, + {":/video_webm", "unknown/unknown", true, "video/webm"}, + {":/video_webm", "application/unknown", false, "video/webm"}, + {":/video_webm", "application/unknown", true, "video/webm"}, + {":/video_webm", "*/*", false, "video/webm"}, + {":/video_webm", "*/*", true, "video/webm"}, + {":/video_webm", "text/xml", false, 0}, + {":/video_webm", "text/xml", true, 0}, + {":/video_webm", "application/xml", false, 0}, + {":/video_webm", "application/xml", true, 0}, + {":/video_webm", "text/html", false, 0}, + {":/video_webm", "text/html", true, 0}, + {":/video_webm", "text/xml", false, 0}, + {":/video_webm", "text/xml", true, 0}, + {":/video_webm", "application/pdf", false, 0}, + {":/video_webm", "application/pdf", true, 0}, + {":/video_webm", "application/postscript", false, 0}, + {":/video_webm", "application/postscript", true, 0}, + {":/video_webm", "application/ogg", false, 0}, + {":/video_webm", "application/ogg", true, 0}, + {":/video_webm", "video/webm", false, 0}, + {":/video_webm", "video/webm", true, 0}, + {":/video_webm", "application/x-rar-compressed", false, 0}, + {":/video_webm", "application/x-rar-compressed", true, 0}, + {":/video_webm", "application/zip", false, 0}, + {":/video_webm", "application/zip", true, 0}, + {":/video_webm", "application/x-gzip", false, 0}, + {":/video_webm", "application/x-gzip", true, 0}, + {":/video_webm", "audio/x-wave", false, 0}, + {":/video_webm", "audio/x-wave", true, 0}, + {":/video_webm", "image/webp", false, 0}, + {":/video_webm", "image/webp", true, 0}, + {":/video_webm", "image/gif", false, 0}, + {":/video_webm", "image/gif", true, 0}, + {":/video_webm", "image/png", false, 0}, + {":/video_webm", "image/png", true, 0}, + {":/video_webm", "image/jpeg", false, 0}, + {":/video_webm", "image/jpeg", true, 0}, + {":/video_webm", "image/bmp", false, 0}, + {":/video_webm", "image/bmp", true, 0}, + {":/video_webm", "image/vnd.microsoft.icon", false, 0}, + {":/video_webm", "image/vnd.microsoft.icon", true, 0}, + {":/video_webm", "application/rdf+xml", false, 0}, + {":/video_webm", "application/rdf+xml", true, 0}, + {":/video_webm", "application/rss+xml", false, 0}, + {":/video_webm", "application/rss+xml", true, 0}, + {":/video_webm", "application/atom+xml", false, 0}, + {":/video_webm", "application/atom+xml", true, 0} +}; +static const size_t testListSize = sizeof(testList) / sizeof(testList[0]); + +#endif // TestData_h diff --git a/tests/webkitwidgets/MIMESniffing/resources.qrc b/tests/webkitwidgets/MIMESniffing/resources.qrc new file mode 100644 index 000000000..b4afb321e --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources.qrc @@ -0,0 +1,23 @@ +<RCC> + <qresource prefix="/"> + <file alias="application_atom+xml">resources/application_atom+xml</file> + <file alias="application_ogg">resources/application_ogg</file> + <file alias="application_pdf">resources/application_pdf</file> + <file alias="application_postscript">resources/application_postscript</file> + <file alias="application_rdf+xml">resources/application_rdf+xml</file> + <file alias="application_rss+xml">resources/application_rss+xml</file> + <file alias="application_x-gzip">resources/application_x-gzip</file> + <file alias="application_x-rar-compressed">resources/application_x-rar-compressed</file> + <file alias="application_zip">resources/application_zip</file> + <file alias="audio_x-wave">resources/audio_x-wave</file> + <file alias="image_bmp">resources/image_bmp</file> + <file alias="image_gif">resources/image_gif</file> + <file alias="image_jpeg">resources/image_jpeg</file> + <file alias="image_png">resources/image_png</file> + <file alias="image_vnd.microsoft.icon">resources/image_vnd.microsoft.icon</file> + <file alias="image_webp">resources/image_webp</file> + <file alias="text_html">resources/text_html</file> + <file alias="text_xml">resources/text_xml</file> + <file alias="video_webm">resources/video_webm</file> + </qresource> +</RCC> diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_atom+xml b/tests/webkitwidgets/MIMESniffing/resources/application_atom+xml new file mode 100644 index 000000000..54086a7be --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_atom+xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + <title>Example Feed</title> + <link href="http://example.org/"/> + <updated>2003-12-13T18:30:02Z</updated> + <author> + <name>John Doe</name> + </author> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <entry> + <title>Atom-Powered Robots Run Amok</title> + <link href="http://example.org/2003/12/13/atom03"/> + <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id> + <updated>2003-12-13T18:30:02Z</updated> + <summary>Some text.</summary> + </entry> +</feed> diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_ogg b/tests/webkitwidgets/MIMESniffing/resources/application_ogg Binary files differnew file mode 100644 index 000000000..b9cc1b291 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_ogg diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_pdf b/tests/webkitwidgets/MIMESniffing/resources/application_pdf Binary files differnew file mode 100644 index 000000000..7f8996f6a --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_pdf diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_postscript b/tests/webkitwidgets/MIMESniffing/resources/application_postscript new file mode 100644 index 000000000..c4b9ae6cb --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_postscript @@ -0,0 +1,137 @@ +%!PS-Adobe-2.0 EPSF-1.2
+%%Creator: HiJaak 2.1
+%%CreationDate: 12/29/93 13:52:08
+%%BoundingBox:126 216 486 576
+%%EndComments
+/ld {load def} bind def
+/s /stroke ld /f /fill ld /m /moveto ld /l /lineto ld /c /curveto ld /rgb {255 div 3 1 roll 255 div 3 1 roll 255 div 3 1 roll setrgbcolor} def
+126 216 translate
+360.0000 360.0000 scale
+/picstr 124 string def
+124 124 8 [124 0 0 -124 0 124] {currentfile picstr readhexstring pop} image
+65656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565ADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADAD1B1B1B1B1B1B1B1B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565006565656565006565656500656565656565000000656565656565656565656565656565656565656565656565656565656565ADADAD00ADADAD000000ADAD00ADADADADAD00AD00ADADADAD00ADADADAD00000000001B1B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565000065656500006565650065006565656500656565006565656565656565656565656565656565656565656565656565656565ADADAD00ADAD00ADADAD00ADAD00ADADAD00ADAD0000ADADAD00ADADADAD001B1B1B1B001B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565000065656500006565650065006565650065656565650065656565656565656565656565656565656565656565656565656565ADADAD00AD00ADADADADADADAD00ADADAD00ADAD00AD00ADAD00ADADADAD001B1B1B1B001B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565006500650065006565650065006565650065656565656565656565656565656565656565656565656565656565656565656565ADADAD00AD00ADADADADADADADAD00AD00ADADAD00AD00ADAD00ADADADAD00000000001B1B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565006500650065006565006565650065650065656500000065656565656565656565656565656565656565656565656565656565ADADAD00AD00ADADADADADADADADAD00ADADADAD00ADAD00AD00ADADADAD001B1B1B1B001B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565006500650065006565000000000065650065656565650065656565656565656565656565656565656565656565656565656565ADADAD00AD00ADADADADADADADADAD00ADADADAD00ADAD00AD00ADADADAD001B1B1B1B001B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565006565006565006500656565656500656500656565006565656565656565656565656565656565656565656565656565656565ADADAD00ADAD00ADADAD00ADADADAD00ADADADAD00ADADAD0000ADADADAD001B1B1B1B001B001B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565006565006565006500656565656500656565000000656565656565656565656565656565656565656565656565656565656565ADADAD00ADADAD000000ADADADADAD00ADADADAD00ADADADAD00ADADADAD00000000001B1B00000000FF
+65656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565ADADAD00ADADADADADADADADADADADADADADADADADADADADADADADADADADAD1B1B1B1B1B1B1B1B1B1BFF
+65656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565ADADAD00ADADADADADADADADADADADADADADADADADADADADADADADADADADAD1B1B1B1B1B1B1B1B1B1BFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC0000DCDC0000DC0000000000DC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292000000009292000000000092920000929200009292929292ADADADAD00000000ADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC0000DCDC0000DC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929200009292000092000092920000920000929200009292929292ADADAD0000ADAD0000ADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC00DCDC00DCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292920000929292929292000092920000920000009200009292929292ADAD0000ADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC00000000DCDC0000000000DC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292920000929292929292000092920000920000009200009292929292ADAD0000ADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC0000DCDCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292920000929200000092000000000092920000920000009292929292ADAD0000ADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC0000DCDCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292920000929292000092000092000092920000920000009292929292ADAD0000ADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC0000DCDCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929200009292000092000092920000920000929200009292929292ADADAD0000ADAD0000ADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC0000DCDCDC0000000000DC000000000000DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292000000009292000092929200000000929200009292929292ADADADAD00000000ADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+DCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC92929292929292929292929292929292929292929292929292929292929292ADADADADADADADADADADFF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00000000004A4A00000000004A00000000004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDC0000DCDC0000DC0000000000DC0000DCDCDCDCDCDCDCDCDCDCDC00000065656500000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00004A4A00004A00004A4A4A4A00004A4A00004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDC0000DCDC0000DC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDC00000065656500000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00004A4A00004A00004A4A4A4A00004A4A00004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDC00DCDC00DCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDC00000000650000000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00004A4A00004A00000000004A00004A4A00004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDC00000000DCDC0000000000DC0000DCDCDCDCDCDCDCDCDCDCDC00000000650000000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00000000004A4A00004A4A4A4A00004A4A00004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDC0000DCDCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDC00006500650065000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00004A00004A4A00004A4A4A4A00004A4A00004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDC0000DCDCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDC00006500000065000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00004A4A00004A00004A4A4A4A00004A4A00004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDC0000DCDCDC0000DCDCDCDC0000DCDCDCDCDCDCDCDCDCDCDC00006500000065000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A00004A4A4A000000000000004A00000000004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDC0000DCDCDC0000000000DC000000000000DCDCDCDCDCDCDC00006565006565000065FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A00000000004A4A4A4A0000004A4A4A00000000004A4A4A4A004A4A4A4A4A00000000004A00000000004A00000000004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A004A4A4A4A004A4A004A4A4A004A4A4A4A004A4A4A4A4A4A004A4A4A4A4A004A4A4A4A4A004A4A4A4A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A004A4A4A4A004A004A4A4A4A4A004A4A4A004A4A4A4A4A4A004A4A4A4A4A004A4A4A4A4A004A4A4A4A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A00000000004A4A004A4A4A4A4A004A4A4A004A4A4A4A4A4A004A4A4A4A4A00000000004A000000004A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A004A4A4A4A004A004A4A4A4A4A004A4A4A004A4A4A4A4A4A004A4A4A4A4A004A4A4A4A4A004A4A4A4A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A004A4A4A4A004A004A4A4A4A4A004A4A4A004A4A4A4A4A4A004A4A4A4A4A004A4A4A4A4A004A4A4A4A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A004A4A4A4A004A4A004A4A4A004A4A4A4A004A4A4A4A4A4A004A4A4A4A4A004A4A4A4A4A004A4A4A4A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A00000000004A4A4A4A0000004A4A4A4A4A004A4A4A4A4A4A00000000004A00000000004A004A4A4A4A4A4A4A004A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4ADCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDC65656565656565656565FF
+showpage
+
\ No newline at end of file diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_rdf+xml b/tests/webkitwidgets/MIMESniffing/resources/application_rdf+xml new file mode 100644 index 000000000..e21414543 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_rdf+xml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> + +<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/"> + + <channel rdf:about="http://www.xml.com/xml/news.rss"> + <title>XML.com</title> + <link>http://xml.com/pub</link> + <description> + XML.com features a rich mix of information and services + for the XML community. + </description> + + <image rdf:resource="http://xml.com/universal/images/xml_tiny.gif" /> + + <items> + <rdf:Seq> + <rdf:li resource="http://xml.com/pub/2000/08/09/xslt/xslt.html" /> + <rdf:li resource="http://xml.com/pub/2000/08/09/rdfdb/index.html" /> + </rdf:Seq> + </items> + + </channel> + + <image rdf:about="http://xml.com/universal/images/xml_tiny.gif"> + <title>XML.com</title> + <link>http://www.xml.com</link> + <url>http://xml.com/universal/images/xml_tiny.gif</url> + </image> + + <item rdf:about="http://xml.com/pub/2000/08/09/xslt/xslt.html"> + <title>Processing Inclusions with XSLT</title> + <link>http://xml.com/pub/2000/08/09/xslt/xslt.html</link> + <description> + Processing document inclusions with general XML tools can be + problematic. This article proposes a way of preserving inclusion + information through SAX-based processing. + </description> + </item> + + <item rdf:about="http://xml.com/pub/2000/08/09/rdfdb/index.html"> + <title>Putting RDF to Work</title> + <link>http://xml.com/pub/2000/08/09/rdfdb/index.html</link> + <description> + Tool and API support for the Resource Description Framework + is slowly coming of age. Edd Dumbill takes a look at RDFDB, + one of the most exciting new RDF toolkits. + </description> + </item> + +</rdf:RDF> diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_rss+xml b/tests/webkitwidgets/MIMESniffing/resources/application_rss+xml new file mode 100644 index 000000000..3537c41c5 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_rss+xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<rss version="0.91"> +<channel> + +<title>Folha.com - Ambiente - Principal</title> +<link>http://redir.folha.com.br/redir/online/ambiente/rss091/*http://www1.folha.uol.com.br/ambiente/</link> +<description>Primeiro jornal em tempo real em l�ngua portuguesa</description> +<language>pt-br</language> +<copyright>Copyright Folha.com. Todos os direitos reservados.</copyright> +<docs>http://redir.folha.com.br/redir/online/ambiente/rss091/*http://www1.folha.uol.com.br/folha/conheca/arquivo_e_copyright.shtml</docs> +<webMaster>[email protected] (Webmaster Folha.com)</webMaster> + +<image> +<title>Folha.com - Ambiente - Principal</title> +<url>http://www1.folha.uol.com.br/folha/images/lgo-folha_com-88x31.gif</url> +<link>http://redir.folha.com.br/redir/online/ambiente/rss091/*http://www1.folha.uol.com.br/ambiente/</link> +<width>88</width> +<height>31</height> +<description>Primeiro jornal em tempo real em l�ngua portuguesa</description> +</image> + +<item> +<title>Nasa dimensiona danos da seca na Amaz�nia em 2,4 mi de km2</title> +<link>http://redir.folha.com.br/redir/online/ambiente/rss091/*http://www1.folha.uol.com.br/ambiente/895526-nasa-dimensiona-danos-da-seca-na-amazonia-em-24-mi-de-km2.shtml</link> +<description> +Os sat�lites da Nasa (ag�ncia espacial americana) forneceram material para uma an�lise dos estragos provocados pela <a href="http://www1.folha.uol.com.br/ambiente/870588-amazonia-teve-a-pior-seca-dos-ultimos-cem-anos.shtml">pior seca a atingir a Amaz�nia em 2010</a>. +Pela tomada a�rea, estima-se que foram 2,5 milh�es de quil�metros quadrados afetados --pouco menos da metade do ecossistema amaz�nico. +<table class="articleGraphic"> +<tr> +<td rowspan="3" class="articleGraphicSpace"></td> +<td class="articleGraphicCredit">Universidade de Boston/Nasa</td> +<td rowspan="3" class="articleGraphicSpace"></td> +</tr> +<tr> +<td class="articleGraphicImage"><img src="http://f.i.uol.com.br/folha/ambiente/images/11090120.jpeg" alt="�rea (vermelho) mostra redu��o do �ndice do verdor, onde a vegeta��o ficou menos verde e mais seca" border="0" /></td> +</tr> +<tr> +<td class="articleGraphicCaption">�rea (vermelho) mostra redu��o do �ndice do verdor, onde a vegeta��o ficou menos verde e mais seca</td> +</tr> +</table> +<a href="http://redir.folha.com.br/redir/online/ambiente/rss091/*http://www1.folha.uol.com.br/ambiente/895526-nasa-dimensiona-danos-da-seca-na-amazonia-em-24-mi-de-km2.shtml">Leia mais</a> (29/03/2011 - 18h04)</description> +<pubDate>29 Mar 2011 18:04:00 -0300</pubDate> +</item> +</channel> +</rss> diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_x-gzip b/tests/webkitwidgets/MIMESniffing/resources/application_x-gzip Binary files differnew file mode 100644 index 000000000..a5e7d3131 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_x-gzip diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_x-rar-compressed b/tests/webkitwidgets/MIMESniffing/resources/application_x-rar-compressed Binary files differnew file mode 100644 index 000000000..d9cb6ae25 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_x-rar-compressed diff --git a/tests/webkitwidgets/MIMESniffing/resources/application_zip b/tests/webkitwidgets/MIMESniffing/resources/application_zip Binary files differnew file mode 100644 index 000000000..8aec52fb2 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/application_zip diff --git a/tests/webkitwidgets/MIMESniffing/resources/audio_x-wave b/tests/webkitwidgets/MIMESniffing/resources/audio_x-wave Binary files differnew file mode 100644 index 000000000..a0f9b85ff --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/audio_x-wave diff --git a/tests/webkitwidgets/MIMESniffing/resources/image_bmp b/tests/webkitwidgets/MIMESniffing/resources/image_bmp Binary files differnew file mode 100644 index 000000000..b61d3688f --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/image_bmp diff --git a/tests/webkitwidgets/MIMESniffing/resources/image_gif b/tests/webkitwidgets/MIMESniffing/resources/image_gif Binary files differnew file mode 100644 index 000000000..32b1ea23f --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/image_gif diff --git a/tests/webkitwidgets/MIMESniffing/resources/image_jpeg b/tests/webkitwidgets/MIMESniffing/resources/image_jpeg Binary files differnew file mode 100644 index 000000000..187457663 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/image_jpeg diff --git a/tests/webkitwidgets/MIMESniffing/resources/image_png b/tests/webkitwidgets/MIMESniffing/resources/image_png Binary files differnew file mode 100644 index 000000000..bef59c785 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/image_png diff --git a/tests/webkitwidgets/MIMESniffing/resources/image_vnd.microsoft.icon b/tests/webkitwidgets/MIMESniffing/resources/image_vnd.microsoft.icon Binary files differnew file mode 100644 index 000000000..58921b8bb --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/image_vnd.microsoft.icon diff --git a/tests/webkitwidgets/MIMESniffing/resources/image_webp b/tests/webkitwidgets/MIMESniffing/resources/image_webp Binary files differnew file mode 100644 index 000000000..0da983e2c --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/image_webp diff --git a/tests/webkitwidgets/MIMESniffing/resources/text_html b/tests/webkitwidgets/MIMESniffing/resources/text_html new file mode 100644 index 000000000..21eeee345 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/text_html @@ -0,0 +1,3 @@ + +<!-- saved from url=(0017)http://127.0.0.1/ --> +<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"></HEAD><BODY><H1>It works!</H1></BODY></HTML>
\ No newline at end of file diff --git a/tests/webkitwidgets/MIMESniffing/resources/text_xml b/tests/webkitwidgets/MIMESniffing/resources/text_xml new file mode 100644 index 000000000..38a9fe548 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/text_xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="ISO-8859-1"?>
+<CATALOG>
+ <CD>
+ <TITLE>Empire Burlesque</TITLE>
+ <ARTIST>Bob Dylan</ARTIST>
+ <COUNTRY>USA</COUNTRY>
+ <COMPANY>Columbia</COMPANY>
+ <PRICE>10.90</PRICE>
+ <YEAR>1985</YEAR>
+ </CD>
+ <CD>
+ <TITLE>Unchain my heart</TITLE>
+ <ARTIST>Joe Cocker</ARTIST>
+ <COUNTRY>USA</COUNTRY>
+ <COMPANY>EMI</COMPANY>
+ <PRICE>8.20</PRICE>
+ <YEAR>1987</YEAR>
+ </CD>
+</CATALOG>
diff --git a/tests/webkitwidgets/MIMESniffing/resources/video_webm b/tests/webkitwidgets/MIMESniffing/resources/video_webm Binary files differnew file mode 100644 index 000000000..95d5031a8 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/resources/video_webm diff --git a/tests/webkitwidgets/MIMESniffing/tst_MIMESniffing.cpp b/tests/webkitwidgets/MIMESniffing/tst_MIMESniffing.cpp new file mode 100644 index 000000000..8c5417f26 --- /dev/null +++ b/tests/webkitwidgets/MIMESniffing/tst_MIMESniffing.cpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "MIMESniffing.h" + +#include "TestData.h" + +#include <QtCore/QFile> +#include <QtCore/QString> +#include <QtTest/QtTest> + +class tst_MIMESniffing : public QObject { + Q_OBJECT + +public: + tst_MIMESniffing(); + +private Q_SLOTS: + void testCase1(); +}; + +tst_MIMESniffing::tst_MIMESniffing() +{ +} + +static inline const char* errorText(const TestData& data, const char* sniffedType) +{ + return QString("file: %1, advertised: %2, image: %3. sniffed mime type was expected to be \"%4\" but instead was \"%5\"").arg(data.file).arg(data.advertisedType).arg(data.isImage).arg(data.sniffedType).arg(sniffedType).toLatin1(); +} + +void tst_MIMESniffing::testCase1() +{ + + for (int i = 0; i < testListSize; ++i) { + QFile file(testList[i].file); + QVERIFY2(file.open(QIODevice::ReadOnly), QString("unable to open file %1").arg(file.fileName()).toLatin1()); + + MIMESniffer sniffer(testList[i].advertisedType, testList[i].isImage); + QByteArray data = file.peek(sniffer.dataSize()); + + const char* sniffedType = sniffer.sniff(data.constData(), data.size()); + + QVERIFY2(!(sniffedType || testList[i].sniffedType) || (sniffedType && testList[i].sniffedType), errorText(testList[i], sniffedType)); + + if (sniffedType) + QVERIFY2(!strcmp(sniffedType, testList[i].sniffedType), errorText(testList[i], sniffedType)); + + } + + QVERIFY2(true, "Failure"); +} + +QTEST_APPLESS_MAIN(tst_MIMESniffing); + +#include "tst_MIMESniffing.moc" diff --git a/tests/webkitwidgets/benchmarks/loading/loading.pro b/tests/webkitwidgets/benchmarks/loading/loading.pro new file mode 100644 index 000000000..2677a2200 --- /dev/null +++ b/tests/webkitwidgets/benchmarks/loading/loading.pro @@ -0,0 +1,2 @@ +include(../../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/benchmarks/loading/tst_loading.cpp b/tests/webkitwidgets/benchmarks/loading/tst_loading.cpp new file mode 100644 index 000000000..c67f422c0 --- /dev/null +++ b/tests/webkitwidgets/benchmarks/loading/tst_loading.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2009 Holger Hans Peter Freyther + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef QT_NO_BEARERMANAGEMENT +#include <QNetworkConfigurationManager> +#endif + +#include <QtTest/QtTest> + +#include <qwebframe.h> +#include <qwebview.h> +#include <qpainter.h> + +#include "util.h" + +class tst_Loading : public QObject +{ + Q_OBJECT + +public: + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void load_data(); + void load(); + +private: +#ifndef QT_NO_BEARERMANAGEMENT + QNetworkConfigurationManager m_manager; +#endif + QWebView* m_view; + QWebPage* m_page; +}; + +void tst_Loading::init() +{ + m_view = new QWebView; + m_page = m_view->page(); + + QSize viewportSize(1024, 768); + m_view->setFixedSize(viewportSize); + m_page->setViewportSize(viewportSize); +} + +void tst_Loading::cleanup() +{ + delete m_view; +} + +void tst_Loading::load_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::newRow("amazon") << QUrl("http://www.amazon.com/"); + QTest::newRow("kde") << QUrl("http://www.kde.org/"); + QTest::newRow("apple") << QUrl("http://www.apple.com/"); +} + +void tst_Loading::load() +{ + QFETCH(QUrl, url); + +#ifndef QT_NO_BEARERMANAGEMENT + if (!m_manager.isOnline()) + QSKIP("This test requires an active network connection", SkipSingle); +#endif + + QBENCHMARK { + m_view->load(url); + + // really wait for loading, painting is in another test + ::waitForSignal(m_view, SIGNAL(loadFinished(bool)), 0); + } +} + +QTEST_MAIN(tst_Loading) +#include "tst_loading.moc" diff --git a/tests/webkitwidgets/benchmarks/painting/painting.pro b/tests/webkitwidgets/benchmarks/painting/painting.pro new file mode 100644 index 000000000..2677a2200 --- /dev/null +++ b/tests/webkitwidgets/benchmarks/painting/painting.pro @@ -0,0 +1,2 @@ +include(../../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/benchmarks/painting/tst_painting.cpp b/tests/webkitwidgets/benchmarks/painting/tst_painting.cpp new file mode 100644 index 000000000..f1456f9e3 --- /dev/null +++ b/tests/webkitwidgets/benchmarks/painting/tst_painting.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009 Holger Hans Peter Freyther + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef QT_NO_BEARERMANAGEMENT +#include <QNetworkConfigurationManager> +#endif + +#include <QtTest/QtTest> + +#include <qwebelement.h> +#include <qwebframe.h> +#include <qwebview.h> +#include <qpainter.h> + +#include "util.h" + +class tst_Painting : public QObject +{ + Q_OBJECT + +public: + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void paint_data(); + void paint(); + void textAreas(); + +private: +#ifndef QT_NO_BEARERMANAGEMENT + QNetworkConfigurationManager m_manager; +#endif + QWebView* m_view; + QWebPage* m_page; +}; + +void tst_Painting::init() +{ + m_view = new QWebView; + m_page = m_view->page(); + + QSize viewportSize(1024, 768); + m_view->setFixedSize(viewportSize); + m_page->setViewportSize(viewportSize); +} + +void tst_Painting::cleanup() +{ + delete m_view; +} + +void tst_Painting::paint_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::newRow("amazon") << QUrl("http://www.amazon.com/"); +} + +void tst_Painting::paint() +{ + QFETCH(QUrl, url); + +#ifndef QT_NO_BEARERMANAGEMENT + if (!m_manager.isOnline()) + QSKIP("This test requires an active network connection", SkipSingle); +#endif + + m_view->load(url); + ::waitForSignal(m_view, SIGNAL(loadFinished(bool)), 0); + + /* force a layout */ + QWebFrame* mainFrame = m_page->mainFrame(); + mainFrame->toPlainText(); + + QPixmap pixmap(m_page->viewportSize()); + QBENCHMARK { + QPainter painter(&pixmap); + mainFrame->render(&painter, QRect(QPoint(0, 0), m_page->viewportSize())); + painter.end(); + } +} + +void tst_Painting::textAreas() +{ + m_view->load(QUrl("data:text/html;<html><body></body></html>")); + ::waitForSignal(m_view, SIGNAL(loadFinished(bool)), 0); + + QWebElement bodyElement = m_page->mainFrame()->findFirstElement("body"); + + int count = 100; + while (count--) { + QString markup("<textarea cols='1' rows='1'></textarea>"); + bodyElement.appendInside(markup); + } + + /* force a layout */ + QWebFrame* mainFrame = m_page->mainFrame(); + mainFrame->toPlainText(); + + QPixmap pixmap(mainFrame->contentsSize()); + QBENCHMARK { + QPainter painter(&pixmap); + mainFrame->render(&painter, QRect(QPoint(0, 0), mainFrame->contentsSize())); + painter.end(); + } +} + +QTEST_MAIN(tst_Painting) +#include "tst_painting.moc" diff --git a/tests/webkitwidgets/benchmarks/webgl/10000_triangles.html b/tests/webkitwidgets/benchmarks/webgl/10000_triangles.html new file mode 100644 index 000000000..fd061aa27 --- /dev/null +++ b/tests/webkitwidgets/benchmarks/webgl/10000_triangles.html @@ -0,0 +1,59 @@ +<html> + <body style="margin: 0"> + <canvas width="1000" height="1000"></canvas> + </body> +</html> +<script> + var canvas = document.getElementsByTagName("canvas")[0]; + gl = canvas.getContext("experimental-webgl"); + gl.clearColor(0.0, 1.0, 0.0, 1.0); + gl.viewport(0, 0, canvas.width, canvas.height); + + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, "attribute vec4 vPosition;\nvoid main() { gl_Position = vPosition; }"); + gl.compileShader(vertexShader); + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }"); + gl.compileShader(fragmentShader); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.bindAttribLocation(shaderProgram, 0, "vPosition"); + gl.linkProgram(shaderProgram); + + gl.useProgram(shaderProgram); + + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + var vertices = []; + var seedX = -1.0; + var seedY = 1.0; + for (var i = 1; i <= 10000; ++i) { + vertices.push(seedX); + vertices.push(seedY); + vertices.push(0); + seedX += 0.01; + vertices.push(seedX); + vertices.push(seedY - 0.02); + vertices.push(0); + seedX += 0.01; + vertices.push(seedX); + vertices.push(seedY); + vertices.push(0); + if (!(i % 100)) { + seedX = -1.0; + seedY -= 0.02; + } + } + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + + + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLES, 0, 30000); + gl.flush(); +</script> diff --git a/tests/webkitwidgets/benchmarks/webgl/tst_webgl.cpp b/tests/webkitwidgets/benchmarks/webgl/tst_webgl.cpp new file mode 100644 index 000000000..14ff1a80f --- /dev/null +++ b/tests/webkitwidgets/benchmarks/webgl/tst_webgl.cpp @@ -0,0 +1,130 @@ +/* + Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "../../util.h" +#include <QGLWidget> +#include <QGraphicsView> +#include <QGraphicsWebView> +#include <QScopedPointer> +#include <QWebFrame> +#include <QtTest/QtTest> + +class GraphicsView; + +class tst_WebGlPerformance : public QObject { + Q_OBJECT + +private Q_SLOTS: + void init(); + void cleanup(); + + void benchSoftwareFallbackRgb16(); + void benchSoftwareFallbackRgb32(); + void benchSoftwareFallbackArgb32(); + void benchSoftwareFallbackArgb32Premultiplied(); + +private: + void benchmarkFrameRenderingOnImage(QImage::Format); + + QScopedPointer<GraphicsView> m_view; +}; + +class GraphicsView : public QGraphicsView { +public: + GraphicsView(); + QGraphicsWebView* m_webView; + +protected: + void resizeEvent(QResizeEvent*); +}; + +GraphicsView::GraphicsView() +{ + QGraphicsScene* const scene = new QGraphicsScene(this); + setScene(scene); + + m_webView = new QGraphicsWebView; + scene->addItem(m_webView); + + m_webView->page()->settings()->setAttribute(QWebSettings::WebGLEnabled, true); + + resize(800, 600); + setFrameShape(QFrame::NoFrame); + setViewport(new QGLWidget); +} + +void GraphicsView::resizeEvent(QResizeEvent* event) +{ + QGraphicsView::resizeEvent(event); + QRectF rect(QPoint(0, 0), event->size()); + m_webView->setGeometry(rect); + scene()->setSceneRect(rect); +} + +void tst_WebGlPerformance::init() +{ + m_view.reset(new GraphicsView); + m_view->showMaximized(); + QTest::qWaitForWindowShown(m_view.data()); +} + +void tst_WebGlPerformance::cleanup() +{ + m_view.reset(); +} + +void tst_WebGlPerformance::benchSoftwareFallbackRgb16() +{ + benchmarkFrameRenderingOnImage(QImage::Format_RGB16); +} + +void tst_WebGlPerformance::benchSoftwareFallbackRgb32() +{ + benchmarkFrameRenderingOnImage(QImage::Format_RGB32); +} + +void tst_WebGlPerformance::benchSoftwareFallbackArgb32() +{ + benchmarkFrameRenderingOnImage(QImage::Format_ARGB32); +} + +void tst_WebGlPerformance::benchSoftwareFallbackArgb32Premultiplied() +{ + benchmarkFrameRenderingOnImage(QImage::Format_ARGB32_Premultiplied); +} + +void tst_WebGlPerformance::benchmarkFrameRenderingOnImage(QImage::Format format) +{ + m_view->m_webView->load(QUrl(QLatin1String("qrc:///testcases/10000_triangles.html"))); + const bool pageLoaded = waitForSignal(m_view->m_webView, SIGNAL(loadFinished(bool))); + Q_ASSERT(pageLoaded); + Q_UNUSED(pageLoaded); + + QImage target(m_view->size(), format); + QBENCHMARK { + QPainter painter(&target); + m_view->render(&painter); + painter.end(); + } +} + +QTEST_MAIN(tst_WebGlPerformance) + +#include "tst_webgl.moc" diff --git a/tests/webkitwidgets/benchmarks/webgl/tst_webgl.qrc b/tests/webkitwidgets/benchmarks/webgl/tst_webgl.qrc new file mode 100644 index 000000000..b849448d7 --- /dev/null +++ b/tests/webkitwidgets/benchmarks/webgl/tst_webgl.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/testcases"> + <file>10000_triangles.html</file> + </qresource> +</RCC> diff --git a/tests/webkitwidgets/benchmarks/webgl/webgl.pro b/tests/webkitwidgets/benchmarks/webgl/webgl.pro new file mode 100644 index 000000000..a471269bd --- /dev/null +++ b/tests/webkitwidgets/benchmarks/webgl/webgl.pro @@ -0,0 +1,3 @@ +include(../../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc +QT += opengl diff --git a/tests/webkitwidgets/cmake/cmake.pro b/tests/webkitwidgets/cmake/cmake.pro new file mode 100644 index 000000000..bf2dbcb77 --- /dev/null +++ b/tests/webkitwidgets/cmake/cmake.pro @@ -0,0 +1,5 @@ + +# Cause make to do nothing. +TEMPLATE = subdirs + +CONFIG += ctest_testcase diff --git a/tests/webkitwidgets/hybridPixmap/hybridPixmap.pro b/tests/webkitwidgets/hybridPixmap/hybridPixmap.pro new file mode 100644 index 000000000..68e565941 --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/hybridPixmap.pro @@ -0,0 +1,10 @@ +# ------------------------------------------------- +# Project created by QtCreator 2009-12-10T11:25:02 +# ------------------------------------------------- +include(../tests.pri) +TARGET = hybridPixmap +SOURCES += widget.cpp +HEADERS += widget.h +FORMS += widget.ui +RESOURCES += tst_hybridPixmap.qrc +CONFIG += console diff --git a/tests/webkitwidgets/hybridPixmap/test.html b/tests/webkitwidgets/hybridPixmap/test.html new file mode 100644 index 000000000..a6cbed1fb --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/test.html @@ -0,0 +1,99 @@ +<html> + <head> + <style> + img { display: block; border-style: groove} + </style> + <script> + function testImageData() { + var obj = myWidget.image; + var pxm = myWidget.pixmap; + + function compareImageDataSize(o, imageData) { + myWidget.compare(imageData.height, o.height); + myWidget.compare(imageData.width, o.width); + } + compareImageDataSize(obj, obj.toImageData()); + compareImageDataSize(pxm, pxm.toImageData()); + + function compareImageDataPixel(o, imageData) { + compareImageDataSize(o, imageData); + // Make sure pixels are 0xAABBCCFF + var data = imageData.data; + for (var i = 0; i < data.length; i += 4) { + myWidget.compare(data[i], 0xaa); // R + myWidget.compare(data[i+1], 0xbb); // G + myWidget.compare(data[i+2], 0xcc); // B + myWidget.compare(data[i+3], 0xff); // A + } + } + var objARGB32 = myWidget.abcImage(5); + compareImageDataPixel(objARGB32, objARGB32.toImageData()); + var objRGB32 = myWidget.abcImage(4); + compareImageDataPixel(objRGB32, objRGB32.toImageData()); + var objRGB888 = myWidget.abcImage(13); + compareImageDataPixel(objRGB888, objRGB888.toImageData()); + var objRGB444 = myWidget.abcImage(14); + compareImageDataPixel(objRGB444, objRGB444.toImageData()); + } + + function startTest() + { + testImageData(); + + var obj = myWidget.image; + var pxm = myWidget.pixmap; + + var img = new Image; + obj.assignToHTMLImageElement(img); + var img1 = document.getElementById("img1"); + var img2 = document.getElementById("img2"); + var img3 = document.getElementById("img3"); + var img4 = document.getElementById("img4"); + document.body.appendChild(img); + obj.assignToHTMLImageElement(img3); + pxm.assignToHTMLImageElement(img4); + myWidget.compare(pxm.width, img4.width); + myWidget.compare(obj.width, img3.width); + var signalsFired = 0; + myWidget.compare(obj.toString(),"[Qt Native Pixmap "+obj.width+","+obj.height+"]"); + myWidget.compare(String(pxm),"[Qt Native Pixmap "+pxm.width+","+pxm.height+"]"); + + // this shouldn't work but shouldn't crash + myWidget.randomSlot("foobar"); + + myWidget.pixmapSignal.connect(function(imgFromSignal) { + myWidget.compare(imgFromSignal.height, img2.height); + if (++signalsFired == 2) + myWidget.completeTest(); + }); + + myWidget.imageSignal.connect(function(imgFromSignal) { + myWidget.compare(pxm.height, img2.height); + if (++signalsFired == 2) + myWidget.completeTest(); + }); + + function continueTestAfterImagesAreLoaded() + { + if (img1.complete && img2.complete) { + myWidget.compare(pxm.height, img2.height); + myWidget.pixmapSlot(img); + myWidget.imageSlot(pxm); + } + } + img1.onload = continueTestAfterImagesAreLoaded; + img2.onload = continueTestAfterImagesAreLoaded; + img1.src = obj.toDataUrl(); + img2.src = myWidget.pixmap.toDataUrl(); + myWidget.image = pxm; + myWidget.pixmap = img; + } + </script> + </head> + <body onload="startTest()"> + <img id="img1" /> + <img id="img2" /> + <img id="img3" /> + <img id="img4" /> + </body> +</html> diff --git a/tests/webkitwidgets/hybridPixmap/tst_hybridPixmap.cpp b/tests/webkitwidgets/hybridPixmap/tst_hybridPixmap.cpp new file mode 100644 index 000000000..84891580a --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/tst_hybridPixmap.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "../util.h" + +#include "widget.h" +#include <QtTest/QtTest> + +class tst_hybridPixmap : public QObject { + Q_OBJECT + +public: + tst_hybridPixmap(QObject* o = 0) : QObject(o) {} + +public Q_SLOTS: + void init() + { + } + + void cleanup() + { + } + +private Q_SLOTS: + void hybridPixmap() + { + Widget widget; + widget.show(); + widget.start(); + waitForSignal(&widget, SIGNAL(testComplete())); + } +}; + +QTEST_MAIN(tst_hybridPixmap) + +#include <tst_hybridPixmap.moc> diff --git a/tests/webkitwidgets/hybridPixmap/tst_hybridPixmap.qrc b/tests/webkitwidgets/hybridPixmap/tst_hybridPixmap.qrc new file mode 100644 index 000000000..5fd47e32d --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/tst_hybridPixmap.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>test.html</file> + </qresource> +</RCC> diff --git a/tests/webkitwidgets/hybridPixmap/widget.cpp b/tests/webkitwidgets/hybridPixmap/widget.cpp new file mode 100644 index 000000000..6c4d2cd49 --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/widget.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "widget.h" + +#include "qwebelement.h" +#include "qwebframe.h" +#include "ui_widget.h" +#include <QPainter> +#include <QtTest/QtTest> + +Widget::Widget(QWidget* parent) : + QWidget(parent), + ui(new Ui::Widget), + abcFilledImage(32, 32, QImage::Format_ARGB32) +{ + ui->setupUi(this); + abcFilledImage.fill(qRgba(0xaa, 0xbb, 0xcc, 0xff)); +} + +void Widget::refreshJS() +{ + ui->webView->page()->mainFrame()->addToJavaScriptWindowObject("myWidget", this); +} +void Widget::start() +{ + ui->webView->load(QUrl("qrc:///test.html")); + connect(ui->webView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(refreshJS())); + ui->webView->page()->mainFrame()->addToJavaScriptWindowObject("myWidget", this); +} + +void Widget::completeTest() +{ + QCOMPARE(ui->lbl1->pixmap()->size(), ui->lbl2->size()); + QCOMPARE(ui->lbl3->size(), ui->lbl4->pixmap()->size()); + QCOMPARE(ui->lbl2->size().width(), ui->webView->page()->mainFrame()->findFirstElement("#img1").evaluateJavaScript("this.width").toInt()); + QCOMPARE(ui->lbl3->size().width(), ui->webView->page()->mainFrame()->findFirstElement("#img2").evaluateJavaScript("this.width").toInt()); + emit testComplete(); +} + +void Widget::setPixmap(const QPixmap& p) +{ + ui->lbl1->setPixmap(p); +} +QPixmap Widget::pixmap() const +{ + QPixmap px(ui->lbl3->size()); + { + QPainter p(&px); + ui->lbl3->render(&p); + } + return px; +} +void Widget::setImage(const QImage& img) +{ + ui->lbl4->setPixmap(QPixmap::fromImage(img)); +} + +QImage Widget::image() const +{ + QImage img(ui->lbl2->size(), QImage::Format_ARGB32); + { + QPainter p(&img); + ui->lbl2->render(&p); + } + return img; +} + +QImage Widget::abcImage(int format) +{ + return abcFilledImage.convertToFormat(static_cast<QImage::Format>(format)); +} + +Widget::~Widget() +{ + delete ui; +} + +void Widget::changeEvent(QEvent* e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} +void Widget::compare(const QVariant& a, const QVariant& b) +{ + QCOMPARE(a, b); +} + +void Widget::imageSlot(const QImage& img) +{ + QCOMPARE(img.size(), ui->lbl3->size()); + emit pixmapSignal(QPixmap::fromImage(img)); +} + +void Widget::pixmapSlot(const QPixmap& pxm) +{ + QCOMPARE(pxm.size(), ui->lbl2->size()); + emit imageSignal(ui->lbl4->pixmap()->toImage()); +} + +void Widget::randomSlot(const QPixmap& pxm) +{ + QVERIFY(pxm.isNull()); +} diff --git a/tests/webkitwidgets/hybridPixmap/widget.h b/tests/webkitwidgets/hybridPixmap/widget.h new file mode 100644 index 000000000..b78df8b5e --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/widget.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef widget_h +#define widget_h + +#include <QImage> +#include <QPixmap> +#include <QWidget> +#include "qwebview.h" + +typedef QWebView WebView; + +QT_BEGIN_NAMESPACE +namespace Ui { +class Widget; +} +QT_END_NAMESPACE + +class Widget : public QWidget { + Q_OBJECT + Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap) + Q_PROPERTY(QImage image READ image WRITE setImage) + +public: + Widget(QWidget* parent = 0); + ~Widget(); + void setPixmap(const QPixmap&); + QPixmap pixmap() const; + void setImage(const QImage&); + QImage image() const; + +private Q_SLOTS: + void refreshJS(); + +public Q_SLOTS: + void completeTest(); + void start(); + void compare(const QVariant& a, const QVariant& b); + void imageSlot(const QImage&); + void pixmapSlot(const QPixmap&); + void randomSlot(const QPixmap&); + QImage abcImage(int format); + +Q_SIGNALS: + void testComplete(); + void imageSignal(const QImage&); + void pixmapSignal(const QPixmap&); + +protected: + void changeEvent(QEvent* e); + +private: + Ui::Widget* ui; + QImage abcFilledImage; +}; + +#endif // widget_h diff --git a/tests/webkitwidgets/hybridPixmap/widget.ui b/tests/webkitwidgets/hybridPixmap/widget.ui new file mode 100644 index 000000000..272d6a71f --- /dev/null +++ b/tests/webkitwidgets/hybridPixmap/widget.ui @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Widget</class> + <widget class="QWidget" name="Widget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>400</height> + </rect> + </property> + <property name="windowTitle"> + <string notr="true">Widget</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="WebView" name="webView" native="true"> + <property name="url" stdset="0"> + <url> + <string notr="true">about:blank</string> + </url> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="lbl1"> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lbl2"> + <property name="minimumSize"> + <size> + <width>120</width> + <height>30</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>120</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string notr="true">Image from Qt to HTML</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lbl3"> + <property name="text"> + <string notr="true">Pixmap from Qt to HTML</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lbl4"> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>WebView</class> + <extends>QWidget</extends> + <header>widget.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/tests/webkitwidgets/keyeddecoderqt/tst_keyeddecoderqt.cpp b/tests/webkitwidgets/keyeddecoderqt/tst_keyeddecoderqt.cpp new file mode 100644 index 000000000..515328028 --- /dev/null +++ b/tests/webkitwidgets/keyeddecoderqt/tst_keyeddecoderqt.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016 Konstantin Tokarev <[email protected]> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "KeyedDecoderQt.h" +#include "KeyedEncoderQt.h" + +#include <QtTest/QtTest> + +using WebCore::KeyedDecoder; +using WebCore::KeyedDecoderQt; + +static QVariantMap testData() +{ + return { + { "string", QStringLiteral("привет") }, + { "array", QVariantList { + QVariantMap { + { "baz", QVariantMap { { "int", 1 } } }, + { "string", "1" }, + }, + QVariantMap { + { "baz", QVariantMap { { "int", 2 } } }, + { "string", "2" }, + }, + QVariantMap { + { "baz", QVariantMap { { "int", 3 } } }, + { "string", "3" }, + }, + } }, + { "foo", QVariantMap { + { "begin", "beginFoo" }, + { "bar", QVariantMap { { "float", 2.5f } } }, + { "end", "endFoo" }, + } }, + { "bool", true }, + { "float", 1.5 }, + { "bytes", QByteArray::fromRawData("\0\0\1\0\0", 5) }, + { "longlong", 1234567890123456789ll } + }; +} + +static std::unique_ptr<KeyedDecoder> makeDecoder() +{ + return std::make_unique<KeyedDecoderQt>(testData()); +} + +class tst_KeyedDecoderQt : public QObject { + Q_OBJECT + +private slots: + void stringValue() + { + WTF::String s; + KeyedDecoderQt decoder(testData()); + QVERIFY(decoder.decodeString("string", s)); + QCOMPARE(s, WTF::String::fromUTF8("привет")); + } + + void boolValue() + { + bool b; + KeyedDecoderQt decoder(testData()); + QVERIFY(decoder.decodeBool("bool", b)); + QCOMPARE(b, true); + } + + void floatValue() + { + float f; + KeyedDecoderQt decoder(testData()); + QVERIFY(decoder.decodeFloat("float", f)); + QCOMPARE(f, 1.5f); + } + + void bytesValue() + { + const uint8_t* bytes; + size_t size; + KeyedDecoderQt decoder(testData()); + QVERIFY(decoder.decodeBytes("bytes", bytes, size)); + + const uint8_t expected[] = { 0, 0, 1, 0, 0 }; + QCOMPARE(size, (size_t)5); + QCOMPARE(bytes[0], (uint8_t)0); + QCOMPARE(bytes[1], (uint8_t)0); + QCOMPARE(bytes[2], (uint8_t)1); + QCOMPARE(bytes[3], (uint8_t)0); + QCOMPARE(bytes[4], (uint8_t)0); + } + + void int64Value() + { + int64_t i; + KeyedDecoderQt decoder(testData()); + QVERIFY(decoder.decodeInt64("longlong", i)); + QCOMPARE(i, (int64_t)1234567890123456789ll); + } + + void missingValue() + { + WTF::String s; + KeyedDecoderQt decoder(testData()); + QVERIFY(!decoder.decodeString("foobar", s)); + QCOMPARE(s, WTF::String()); + } + + void wrongType1() + { + float f = 1.0; + KeyedDecoderQt decoder(testData()); + QVERIFY(!decoder.decodeFloat("string", f)); + QCOMPARE(f, 1.0); + } + + void wrongType2() + { + WTF::String s; + KeyedDecoderQt decoder(testData()); + QVERIFY(!decoder.decodeString("array", s)); + QVERIFY(!decoder.decodeString("foo", s)); + } + + void object() + { + struct Foo { + WTF::String begin; + WTF::String end; + float f; + } foo; + + KeyedDecoderQt decoder(testData()); + decoder.decodeObject("foo", foo, [&](KeyedDecoder& d1, Foo& foo) -> bool { + if (!d1.decodeString("begin", foo.begin)) + return false; + if (!d1.decodeString("end", foo.end)) + return false; + return d1.decodeObject("bar", foo, [&](KeyedDecoder& d2, Foo& foo) { + return d2.decodeFloat("float", foo.f); + }); + }); + QCOMPARE(foo.begin, WTF::String("beginFoo")); + QCOMPARE(foo.end, WTF::String("endFoo")); + QCOMPARE(foo.f, 2.5f); + } + + void array() + { + struct Foo { + WTF::String s; + int i; + }; + + WTF::Vector<Foo> v; + + KeyedDecoderQt decoder(testData()); + decoder.decodeObjects("array", v, [&](KeyedDecoder& d1, Foo& foo) -> bool { + if (!d1.decodeString("string", foo.s)) + return false; + return d1.decodeObject("baz", foo, [&](KeyedDecoder& d2, Foo& foo) { + return d2.decodeInt32("int", foo.i); + }); + }); + QCOMPARE(v.size(), (size_t)3); + QCOMPARE(v[0].s, WTF::String("1")); + QCOMPARE(v[0].i, 1); + QCOMPARE(v[1].s, WTF::String("2")); + QCOMPARE(v[1].i, 2); + QCOMPARE(v[2].s, WTF::String("3")); + QCOMPARE(v[2].i, 3); + } +}; + +QTEST_GUILESS_MAIN(tst_KeyedDecoderQt) +#include "tst_keyeddecoderqt.moc" diff --git a/tests/webkitwidgets/keyedencoderqt/tst_keyedencoderqt.cpp b/tests/webkitwidgets/keyedencoderqt/tst_keyedencoderqt.cpp new file mode 100644 index 000000000..a61c02059 --- /dev/null +++ b/tests/webkitwidgets/keyedencoderqt/tst_keyedencoderqt.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 Konstantin Tokarev <[email protected]> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "KeyedEncoderQt.h" + +#include <QtTest/QtTest> + +using WebCore::KeyedEncoder; +using WebCore::KeyedEncoderQt; + +class tst_KeyedEncoderQt : public QObject { + Q_OBJECT + std::unique_ptr<KeyedEncoderQt> m_encoder { nullptr }; + +private slots: + void init() + { + m_encoder.reset(new KeyedEncoderQt); + } + + void cleanup() + { + m_encoder = nullptr; + } + + void simpleValues() + { + m_encoder->encodeBool("bool", true); + const uint8_t bytes[] = { 0, 0, 1, 0, 0 }; + m_encoder->encodeBytes("bytes", bytes, 5); + m_encoder->encodeString("string", QStringLiteral("привет")); + + auto result = m_encoder->toMap(); + QCOMPARE(result["bool"].type(), QVariant::Bool); + QCOMPARE(result["bool"].toBool(), true); + QCOMPARE(result["bytes"].type(), QVariant::ByteArray); + QCOMPARE(result["bytes"].toByteArray(), QByteArray::fromRawData("\0\0\1\0\0", 5)); + QCOMPARE(result["string"].type(), QVariant::String); + QCOMPARE(result["string"].toString(), QStringLiteral("привет")); + } + + void nestedObjects() + { + struct dummy {}; + + m_encoder->encodeString("begin", "begin"); + m_encoder->encodeObject("foo", dummy(), [](KeyedEncoder& e1, dummy) { + e1.encodeString("begin", "beginFoo"); + e1.encodeObject("bar", dummy(), [](KeyedEncoder& e2, dummy) { + e2.encodeFloat("float", 1.5); + }); + e1.encodeString("end", "endFoo"); + }); + m_encoder->encodeString("end", "end"); + + QVariantMap expected = { + { "begin", "begin" }, + { "foo", QVariantMap { + { "begin", "beginFoo" }, + { "bar", QVariantMap { { "float", 1.5f } } }, + { "end", "endFoo" }, + } }, + { "end", "end" }, + }; + + QCOMPARE(m_encoder->toMap(), expected); + } + + void array() + { + QList<int> values = { 1, 2, 3 }; + + m_encoder->encodeString("begin", "begin"); + m_encoder->encodeObjects("array", values.begin(), values.end(), + [](KeyedEncoder& e, int v) { + e.encodeUInt32("int", v); + e.encodeString("string", QString::number(v)); + }); + m_encoder->encodeString("end", "end"); + + QVariantMap expected = { + { "begin", "begin" }, + { "array", QVariantList { + QVariantMap { { "int", 1 }, { "string", "1" } }, + QVariantMap { { "int", 2 }, { "string", "2" } }, + QVariantMap { { "int", 3 }, { "string", "3" } }, + } }, + { "end", "end" }, + }; + + QCOMPARE(m_encoder->toMap(), expected); + } + + void arrayWithObjects() + { + QList<int> values = { 1, 2, 3 }; + struct dummy {}; + + m_encoder->encodeString("begin", "begin"); + m_encoder->encodeObjects("array", values.begin(), values.end(), + [](KeyedEncoder& e1, int v) { + e1.encodeObject("foo", dummy(), [v](KeyedEncoder& e2, dummy) { + e2.encodeInt32("int", v); + }); + e1.encodeString("string", QString::number(v)); + }); + m_encoder->encodeString("end", "end"); + + QVariantMap expected = { + { "begin", "begin" }, + { "array", QVariantList { + QVariantMap { + { "foo", QVariantMap { { "int", 1 } } }, + { "string", "1" }, + }, + QVariantMap { + { "foo", QVariantMap { { "int", 2 } } }, + { "string", "2" }, + }, + QVariantMap { + { "foo", QVariantMap { { "int", 3 } } }, + { "string", "3" }, + }, + } }, + { "end", "end" }, + }; + + QCOMPARE(m_encoder->toMap(), expected); + } + +}; + +QTEST_GUILESS_MAIN(tst_KeyedEncoderQt) +#include "tst_keyedencoderqt.moc" diff --git a/tests/webkitwidgets/qgraphicswebview/qgraphicswebview.pro b/tests/webkitwidgets/qgraphicswebview/qgraphicswebview.pro new file mode 100644 index 000000000..78e17a06d --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/qgraphicswebview.pro @@ -0,0 +1,6 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc + +enable?(WEBGL) { + QT += opengl +} diff --git a/tests/webkitwidgets/qgraphicswebview/resources/greendiv.html b/tests/webkitwidgets/qgraphicswebview/resources/greendiv.html new file mode 100644 index 000000000..2f7fa97ca --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/resources/greendiv.html @@ -0,0 +1,8 @@ +<body style="background-color: white"> + <div id="1" style="width: 50%; height: 50%; background-color: green"/> + <script> + function resizeDiv() { + document.getElementById("1").setAttribute("style", "width: 150%; height: 150%; background-color: green"); + } + </script> +</body> diff --git a/tests/webkitwidgets/qgraphicswebview/resources/input_types.html b/tests/webkitwidgets/qgraphicswebview/resources/input_types.html new file mode 100644 index 000000000..18ab314bf --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/resources/input_types.html @@ -0,0 +1,8 @@ +<html><body> +<input type='text' maxlength='20' style='position: absolute; left: 10px; top: 0px; height: 50px; width: 100px;'/><br> +<input type='password' style='position: absolute; left: 10px; top: 50px; height: 50px; width: 100px;'/><br> +<input type='tel' style='position: absolute; left: 10px; top: 100px; height: 50px; width: 100px;'/><br> +<input type='number' style='position: absolute; left: 10px; top: 150px; height: 50px; width: 100px;'/><br> +<input type='email' style='position: absolute; left: 10px; top: 200px; height: 50px; width: 100px;'/><br> +<input type='url' style='position: absolute; left: 10px; top: 250px; height: 50px; width: 100px;'/><br>" +</body></html>
\ No newline at end of file diff --git a/tests/webkitwidgets/qgraphicswebview/resources/pointing_right.html b/tests/webkitwidgets/qgraphicswebview/resources/pointing_right.html new file mode 100644 index 000000000..bc592fbde --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/resources/pointing_right.html @@ -0,0 +1,45 @@ +<html> + <body style="margin: 0"> + <canvas width="100" height="100"></canvas> + </body> +</html> +<script> + var canvas = document.getElementsByTagName("canvas")[0]; + gl = canvas.getContext("experimental-webgl"); + gl.clearColor(0.0, 1.0, 0.0, 1.0); + gl.viewport(0, 0, canvas.width, canvas.height); + + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, "attribute vec4 vPosition;\nvoid main() { gl_Position = vPosition; }"); + gl.compileShader(vertexShader); + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }"); + gl.compileShader(fragmentShader); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.bindAttribLocation(shaderProgram, 0, "vPosition"); + gl.linkProgram(shaderProgram); + + gl.useProgram(shaderProgram); + + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + var vertices = [-1.0, -1.0, + 0.0, 0.0, + -1.0, 1.0]; + var seedX = -1.0; + var seedY = 1.0; + vertices + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + + + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLES, 0, 3); + gl.flush(); +</script> diff --git a/tests/webkitwidgets/qgraphicswebview/resources/pointing_up.html b/tests/webkitwidgets/qgraphicswebview/resources/pointing_up.html new file mode 100644 index 000000000..474a56d3f --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/resources/pointing_up.html @@ -0,0 +1,46 @@ +<html> + <body style="margin: 0"> + <canvas width="100" height="100"></canvas> + </body> +</html> +<script> + var canvas = document.getElementsByTagName("canvas")[0]; + gl = canvas.getContext("experimental-webgl"); + gl.clearColor(0.0, 1.0, 0.0, 1.0); + gl.viewport(0, 0, canvas.width, canvas.height); + + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, "attribute vec4 vPosition;\nvoid main() { gl_Position = vPosition; }"); + gl.compileShader(vertexShader); + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }"); + gl.compileShader(fragmentShader); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.bindAttribLocation(shaderProgram, 0, "vPosition"); + gl.linkProgram(shaderProgram); + + gl.useProgram(shaderProgram); + + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + var vertices = [-1.0, -1.0, + 0.0, 0.0, + 1.0, -1.0]; + var seedX = -1.0; + var seedY = 1.0; + vertices + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + + + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLES, 0, 3); + gl.flush(); + gl.finish(); +</script> diff --git a/tests/webkitwidgets/qgraphicswebview/resources/scrolltest_page.html b/tests/webkitwidgets/qgraphicswebview/resources/scrolltest_page.html new file mode 100644 index 000000000..18fcbbebe --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/resources/scrolltest_page.html @@ -0,0 +1,6 @@ +<html> +<head><title>Scrolling test</title></head> +<body> + <div style="width: 1000px; height: 1000px; background-color: green"/> +</body> +</html> diff --git a/tests/webkitwidgets/qgraphicswebview/tst_qgraphicswebview.cpp b/tests/webkitwidgets/qgraphicswebview/tst_qgraphicswebview.cpp new file mode 100644 index 000000000..0e8577d8c --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/tst_qgraphicswebview.cpp @@ -0,0 +1,730 @@ +/* + Copyright (C) 2009 Jakub Wieczorek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "../util.h" +#include <QtTest/QtTest> +#include <QGraphicsSceneMouseEvent> +#include <QGraphicsView> +#include <QStyleOptionGraphicsItem> +#include <qgraphicswebview.h> +#include <qwebpage.h> +#include <qwebframe.h> + +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL +#include <QGLWidget> +#endif + +class tst_QGraphicsWebView : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void qgraphicswebview(); + void crashOnViewlessWebPages(); + void microFocusCoordinates(); + void focusInputTypes(); + void crashOnSetScaleBeforeSetUrl(); + void widgetsRenderingThroughCache(); + void windowResizeEvent(); + void horizontalScrollbarTest(); + +#if !(defined(USE_QT_MOBILE_THEME) && USE_QT_MOBILE_THEME) + void setPalette_data(); + void setPalette(); +#endif + void renderHints(); +#if defined(USE_TILED_BACKING_STORE) && USE_TILED_BACKING_STORE + void bug57798(); + void bug56929(); +#endif +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL + void webglSoftwareFallbackVerticalOrientation(); + void webglSoftwareFallbackHorizontalOrientation(); + +private: + void compareCanvasToImage(const QUrl&, const QImage&); +#endif +}; + +void tst_QGraphicsWebView::qgraphicswebview() +{ + QGraphicsWebView item; + item.url(); + item.title(); + item.icon(); + item.zoomFactor(); + item.history(); + item.settings(); + item.page(); + item.setPage(0); + item.page(); + item.setUrl(QUrl()); + item.setZoomFactor(0); + item.load(QUrl()); + item.setHtml(QString()); + item.setContent(QByteArray()); + item.isModified(); +} + +class WebPage : public QWebPage +{ + Q_OBJECT + +public: + WebPage(QObject* parent = 0): QWebPage(parent) + { + } + + QGraphicsWebView* webView; + +private Q_SLOTS: + // Force a webview deletion during the load. + // It should not cause WebPage to crash due to + // it accessing invalid pageClient pointer. + void aborting() + { + delete webView; + } +}; + +class GraphicsWebView : public QGraphicsWebView +{ + Q_OBJECT + +public: + GraphicsWebView(QGraphicsItem* parent = 0): QGraphicsWebView(parent) + { + } + + void fireMouseClick(QPointF point) { + QGraphicsSceneMouseEvent presEv(QEvent::GraphicsSceneMousePress); + presEv.setPos(point); + presEv.setButton(Qt::LeftButton); + presEv.setButtons(Qt::LeftButton); + QGraphicsSceneMouseEvent relEv(QEvent::GraphicsSceneMouseRelease); + relEv.setPos(point); + relEv.setButton(Qt::LeftButton); + relEv.setButtons(Qt::LeftButton); + QGraphicsWebView::sceneEvent(&presEv); + QGraphicsWebView::sceneEvent(&relEv); + } +}; + +void tst_QGraphicsWebView::crashOnViewlessWebPages() +{ + QGraphicsScene scene; + QGraphicsView view(&scene); + + QGraphicsWebView* webView = new QGraphicsWebView; + WebPage* page = new WebPage; + webView->setPage(page); + page->webView = webView; + scene.addItem(webView); + + view.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + view.resize(600, 480); + webView->resize(view.geometry().size()); + + QCoreApplication::processEvents(); + view.show(); + + // Resizing the page will resize and layout the empty "about:blank" + // page, so we first connect the signal afterward. + connect(page->mainFrame(), SIGNAL(initialLayoutCompleted()), page, SLOT(aborting())); + + page->mainFrame()->load(QUrl("data:text/html," + "<frameset cols=\"25%,75%\">" + "<frame src=\"data:text/html,foo \">" + "<frame src=\"data:text/html,bar\">" + "</frameset>")); + + QVERIFY(waitForSignal(page, SIGNAL(loadFinished(bool)))); + delete page; +} + +void tst_QGraphicsWebView::crashOnSetScaleBeforeSetUrl() +{ + QGraphicsWebView* webView = new QGraphicsWebView; + webView->setScale(2.0); + delete webView; +} + +void tst_QGraphicsWebView::widgetsRenderingThroughCache() +{ + QSKIP("TiledBackingStore does not work -- https://github.com/qtwebkit/qtwebkit/issues/407"); + + // Widgets should be rendered the same way with and without + // intermediate cache (tiling for example). + // See bug https://bugs.webkit.org/show_bug.cgi?id=47767 where + // widget are rendered as disabled when caching is using. + + QGraphicsWebView* webView = new QGraphicsWebView; + webView->setHtml(QLatin1String("<body style=\"background-color: white\"><input type=range></input><input type=checkbox></input><input type=radio></input><input type=file></input></body>")); + + QGraphicsView view; + // Disable the scrollbars on the graphics view because QtWebKit handles scrolling and scrollbar automatically + view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view.show(); + QGraphicsScene* scene = new QGraphicsScene(&view); + view.setScene(scene); + scene->addItem(webView); + view.setGeometry(QRect(0, 0, 500, 500)); + QWidget *const widget = &view; + QTest::qWaitForWindowExposed(widget); + + // 1. Reference without tiling. + webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, false); + QPixmap referencePixmap(view.size()); + QApplication::processEvents(); + widget->render(&referencePixmap); + + // 2. With tiling. + webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, true); + QPixmap viewWithTiling(view.size()); + widget->render(&viewWithTiling); + QApplication::processEvents(); + widget->render(&viewWithTiling); + + QCOMPARE(referencePixmap.toImage(), viewWithTiling.toImage()); +} + +#if defined(USE_TILED_BACKING_STORE) && USE_TILED_BACKING_STORE +void tst_QGraphicsWebView::bug57798() +{ + // When content size grows from less than viewport size to more than that, tiles may need to be regenerated. + + QGraphicsWebView* webView = new QGraphicsWebView(); + webView->setGeometry(QRectF(0.0, 0.0, 100.0, 100.0)); + QGraphicsView view(new QGraphicsScene()); + view.scene()->setParent(&view); + view.scene()->addItem(webView); + webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, true); + QStyleOptionGraphicsItem option; + option.exposedRect = view.sceneRect(); + QImage img(view.width(), view.height(), + QImage::Format_ARGB32_Premultiplied); + QPainter painter(&img); + // This will not paint anything as the tiles are not ready, but will trigger tile creation with size (0, 0). + webView->paint(&painter, &option); + QApplication::processEvents(); + QUrl url("/service/qrc:///resources/greendiv.html"); + webView->load(url); + QVERIFY(waitForSignal(webView, SIGNAL(loadFinished(bool)))); + // This should trigger the recreation of the tiles. + webView->paint(&painter, &option); + QApplication::processEvents(); + painter.fillRect(option.exposedRect, Qt::red); // This is here to ensure failure if paint does not paint anything + webView->paint(&painter, &option); + QCOMPARE(img.pixel(option.exposedRect.width() / 4, option.exposedRect.height() / 4), qRgba(0, 128, 0, 255)); +} + +void tst_QGraphicsWebView::bug56929() +{ + // When rendering from tiles sychronous layout should not be triggered + // and scrollbars should be in sync with the size of the document in the displayed state. + + QGraphicsWebView* webView = new QGraphicsWebView(); + webView->setGeometry(QRectF(0.0, 0.0, 100.0, 100.0)); + QGraphicsView view(new QGraphicsScene()); + view.scene()->setParent(&view); + view.scene()->addItem(webView); + webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, true); + QUrl url("/service/qrc:///resources/greendiv.html"); + webView->load(url); + QVERIFY(waitForSignal(webView, SIGNAL(loadFinished(bool)))); + QStyleOptionGraphicsItem option; + option.exposedRect = webView->geometry(); + QImage img(option.exposedRect.width(), option.exposedRect.height(), QImage::Format_ARGB32_Premultiplied); + QPainter painter(&img); + // This will not paint anything as the tiles are not ready, yet. + webView->paint(&painter, &option); + QApplication::processEvents(); + webView->paint(&painter, &option); + QCOMPARE(img.pixel(option.exposedRect.width() - 2, option.exposedRect.height() / 2), qRgba(255, 255, 255, 255)); + painter.fillRect(option.exposedRect, Qt::black); + QCOMPARE(img.pixel(option.exposedRect.width() - 2, option.exposedRect.height() / 2), qRgba(0, 0, 0, 255)); + webView->page()->mainFrame()->evaluateJavaScript(QString("resizeDiv();")); + webView->paint(&painter, &option); + QCOMPARE(img.pixel(option.exposedRect.width() - 2, option.exposedRect.height() / 2), qRgba(255, 255, 255, 255)); +} +#endif + +void tst_QGraphicsWebView::microFocusCoordinates() +{ + QWebPage* page = new QWebPage; + QGraphicsWebView* webView = new QGraphicsWebView; + webView->setPage( page ); + QGraphicsView* view = new QGraphicsView; + QGraphicsScene* scene = new QGraphicsScene(view); + view->setScene(scene); + scene->addItem(webView); + view->setGeometry(QRect(0,0,500,500)); + + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font--family: serif' value='' maxlength='20'/><br>" \ + "<canvas id='canvas1' width='500' height='500'></canvas>" \ + "<input type='password'/><br>" \ + "<canvas id='canvas2' width='500' height='500'></canvas>" \ + "</body></html>"); + + page->mainFrame()->setFocus(); + + QVariant initialMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(initialMicroFocus.isValid()); + + page->mainFrame()->scroll(0,300); + + QVariant currentMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(currentMicroFocus.isValid()); + + QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-300)), currentMicroFocus.toRect()); + + delete view; +} + +void tst_QGraphicsWebView::focusInputTypes() +{ + QWebPage* page = new QWebPage; + GraphicsWebView* webView = new GraphicsWebView; + webView->setPage( page ); + QGraphicsView* view = new QGraphicsView; + QGraphicsScene* scene = new QGraphicsScene(view); + view->setScene(scene); + scene->addItem(webView); + view->setGeometry(QRect(0,0,500,500)); + QCoreApplication::processEvents(); + QUrl url("/service/qrc:///resources/input_types.html"); + page->mainFrame()->load(url); + page->mainFrame()->setFocus(); + + QVERIFY(waitForSignal(page, SIGNAL(loadFinished(bool)))); + + // 'text' type + webView->fireMouseClick(QPointF(20.0, 10.0)); + QVERIFY(webView->inputMethodHints() == Qt::ImhNone); + + // 'password' field + webView->fireMouseClick(QPointF(20.0, 60.0)); + QVERIFY(webView->inputMethodHints() & Qt::ImhHiddenText); + + // 'tel' field + webView->fireMouseClick(QPointF(20.0, 110.0)); + QVERIFY(webView->inputMethodHints() & Qt::ImhDialableCharactersOnly); + + // 'number' field + webView->fireMouseClick(QPointF(20.0, 160.0)); + QVERIFY(webView->inputMethodHints() & Qt::ImhDigitsOnly); + + // 'email' field + webView->fireMouseClick(QPointF(20.0, 210.0)); + QVERIFY(webView->inputMethodHints() & Qt::ImhEmailCharactersOnly); + + // 'url' field + webView->fireMouseClick(QPointF(20.0, 260.0)); + QVERIFY(webView->inputMethodHints() & Qt::ImhUrlCharactersOnly); + + delete webView; + delete view; +} + +#if !(defined(USE_QT_MOBILE_THEME) && USE_QT_MOBILE_THEME) +void tst_QGraphicsWebView::setPalette_data() +{ + QTest::addColumn<bool>("active"); + QTest::addColumn<bool>("background"); + QTest::newRow("activeBG") << true << true; + QTest::newRow("activeFG") << true << false; + QTest::newRow("inactiveBG") << false << true; + QTest::newRow("inactiveFG") << false << false; +} + +// Render a QGraphicsWebView to a QImage twice, each time with a different palette set, +// verify that images rendered are not the same, confirming WebCore usage of +// custom palette on selections. +void tst_QGraphicsWebView::setPalette() +{ + QString html = "<html><head></head>" + "<body>" + "Some text here" + "</body>" + "</html>"; + + QFETCH(bool, active); + QFETCH(bool, background); + + QWidget* activeView = 0; + + // Use controlView to manage active/inactive state of test views by raising + // or lowering their position in the window stack. + QGraphicsScene controlScene; + QGraphicsView controlView(&controlScene); + QGraphicsWebView controlWebView; + controlScene.addItem(&controlWebView); + controlWebView.setHtml(html); + controlWebView.setGeometry(QRectF(0, 0, 200, 200)); + + QGraphicsScene scene1; + QGraphicsView view1(&scene1); + view1.setSceneRect(0, 0, 300, 300); + QGraphicsWebView webView1; + webView1.setResizesToContents(true); + scene1.addItem(&webView1); + webView1.setFocus(); + + QPalette palette1; + QBrush brush1(Qt::red); + brush1.setStyle(Qt::SolidPattern); + if (active && background) { + // Rendered image must have red background on an active QGraphicsWebView. + palette1.setBrush(QPalette::Active, QPalette::Highlight, brush1); + } else if (active && !background) { + // Rendered image must have red foreground on an active QGraphicsWebView. + palette1.setBrush(QPalette::Active, QPalette::HighlightedText, brush1); + } else if (!active && background) { + // Rendered image must have red background on an inactive QGraphicsWebView. + palette1.setBrush(QPalette::Inactive, QPalette::Highlight, brush1); + } else if (!active && !background) { + // Rendered image must have red foreground on an inactive QGraphicsWebView. + palette1.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush1); + } + + webView1.setHtml(html); + view1.resize(webView1.page()->viewportSize()); + webView1.setPalette(palette1); + view1.show(); + + QVERIFY(webView1.palette() == palette1); + QVERIFY(webView1.page()->palette() == palette1); + + QTest::qWaitForWindowExposed(&view1); + + if (!active) { + controlView.show(); + QTest::qWaitForWindowExposed(&controlView); + QApplication::setActiveWindow(&controlView); + activeView = &controlView; + controlView.activateWindow(); + } else { + QApplication::setActiveWindow(&view1); + view1.activateWindow(); + activeView = &view1; + } + + QTRY_COMPARE(QApplication::activeWindow(), activeView); + + webView1.page()->triggerAction(QWebPage::SelectAll); + + QImage img1(webView1.page()->viewportSize(), QImage::Format_ARGB32); + QPainter painter1(&img1); + webView1.page()->currentFrame()->render(&painter1); + painter1.end(); + view1.close(); + controlView.close(); + + QGraphicsScene scene2; + QGraphicsView view2(&scene2); + view2.setSceneRect(0, 0, 300, 300); + QGraphicsWebView webView2; + webView2.setResizesToContents(true); + scene2.addItem(&webView2); + webView2.setFocus(); + + QPalette palette2; + QBrush brush2(Qt::blue); + brush2.setStyle(Qt::SolidPattern); + if (active && background) { + // Rendered image must have blue background on an active QGraphicsWebView. + palette2.setBrush(QPalette::Active, QPalette::Highlight, brush2); + } else if (active && !background) { + // Rendered image must have blue foreground on an active QGraphicsWebView. + palette2.setBrush(QPalette::Active, QPalette::HighlightedText, brush2); + } else if (!active && background) { + // Rendered image must have blue background on an inactive QGraphicsWebView. + palette2.setBrush(QPalette::Inactive, QPalette::Highlight, brush2); + } else if (!active && !background) { + // Rendered image must have blue foreground on an inactive QGraphicsWebView. + palette2.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush2); + } + + webView2.setHtml(html); + view2.resize(webView2.page()->viewportSize()); + webView2.setPalette(palette2); + view2.show(); + + QTest::qWaitForWindowExposed(&view2); + + if (!active) { + controlView.show(); + QTest::qWaitForWindowExposed(&controlView); + QApplication::setActiveWindow(&controlView); + activeView = &controlView; + controlView.activateWindow(); + } else { + QApplication::setActiveWindow(&view2); + view2.activateWindow(); + activeView = &view2; + } + + QTRY_COMPARE(QApplication::activeWindow(), activeView); + + webView2.page()->triggerAction(QWebPage::SelectAll); + + QImage img2(webView2.page()->viewportSize(), QImage::Format_ARGB32); + QPainter painter2(&img2); + webView2.page()->currentFrame()->render(&painter2); + painter2.end(); + + view2.close(); + controlView.close(); + + QVERIFY(img1 != img2); +} +#endif + +void tst_QGraphicsWebView::renderHints() +{ + QGraphicsWebView webView; + + // default is only text antialiasing + smooth pixmap transform + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::Antialiasing, true); + QVERIFY(webView.renderHints() & QPainter::Antialiasing); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::Antialiasing, false); + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::SmoothPixmapTransform, true); + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::SmoothPixmapTransform, false); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +} + +class GraphicsView : public QGraphicsView { +public: + GraphicsView(); + QGraphicsWebView* m_webView; +}; + +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL +bool compareImagesFuzzyPixelCount(const QImage& image1, const QImage& image2, qreal tolerance = 0.05) +{ + if (image1.size() != image2.size()) + return false; + + unsigned diffPixelCount = 0; + for (int row = 0; row < image1.size().width(); ++row) { + for (int column = 0; column < image1.size().height(); ++column) + if (image1.pixel(row, column) != image2.pixel(row, column)) + ++diffPixelCount; + } + + if (diffPixelCount > (image1.size().width() * image1.size().height()) * tolerance) + return false; + + return true; +} + +GraphicsView::GraphicsView() +{ + QGraphicsScene* const scene = new QGraphicsScene(this); + setScene(scene); + + m_webView = new QGraphicsWebView; + scene->addItem(m_webView); + + m_webView->page()->settings()->setAttribute(QWebSettings::WebGLEnabled, true); + m_webView->setResizesToContents(true); + + setFrameShape(QFrame::NoFrame); + setViewport(new QGLWidget); +} + +void tst_QGraphicsWebView::webglSoftwareFallbackVerticalOrientation() +{ + QSKIP("Hangs on X11 -- https://bugs.webkit.org/show_bug.cgi?id=105820"); + QSize canvasSize(100, 100); + QImage reference(canvasSize, QImage::Format_ARGB32); + reference.fill(0xFF00FF00); + { // Reference. + QPainter painter(&reference); + QPolygonF triangleUp; + triangleUp << QPointF(0, canvasSize.height()) + << QPointF(canvasSize.width(), canvasSize.height()) + << QPointF(canvasSize.width() / 2.0, canvasSize.height() / 2.0); + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::red); + painter.drawPolygon(triangleUp); + } + + compareCanvasToImage(QUrl(QLatin1String("qrc:///resources/pointing_up.html")), reference); +} + +void tst_QGraphicsWebView::webglSoftwareFallbackHorizontalOrientation() +{ + QSKIP("Hangs on X11 -- https://bugs.webkit.org/show_bug.cgi?id=105820"); + QSize canvasSize(100, 100); + QImage reference(canvasSize, QImage::Format_ARGB32); + reference.fill(0xFF00FF00); + { // Reference. + QPainter painter(&reference); + QPolygonF triangleUp; + triangleUp << QPointF(0, 0) + << QPointF(0, canvasSize.height()) + << QPointF(canvasSize.width() / 2.0, canvasSize.height() / 2.0); + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::red); + painter.drawPolygon(triangleUp); + } + + compareCanvasToImage(QUrl(QLatin1String("qrc:///resources/pointing_right.html")), reference); +} + +void tst_QGraphicsWebView::compareCanvasToImage(const QUrl& url, const QImage& reference) +{ + GraphicsView view; + view.show(); + QTest::qWaitForWindowExposed(&view); + + QGraphicsWebView* const graphicsWebView = view.m_webView; + graphicsWebView->load(url); + QVERIFY(waitForSignal(graphicsWebView, SIGNAL(loadFinished(bool)))); + { // Force a render, to create the accelerated compositing tree. + QPixmap pixmap(view.size()); + QPainter painter(&pixmap); + view.render(&painter); + } + + const QSize imageSize = reference.size(); + + QImage target(imageSize, QImage::Format_ARGB32); + { // Web page content. + QPainter painter(&target); + QRectF renderRect(0, 0, imageSize.width(), imageSize.height()); + view.scene()->render(&painter, renderRect, renderRect); + } + QVERIFY(compareImagesFuzzyPixelCount(target, reference, 0.01)); +} +#endif + +class ResizeSpy : public QObject { + Q_OBJECT +public Q_SLOTS: + void receiveResize(int width, int height) + { + m_size = QSize(width, height); + emit resized(); + } + + QSize size() const + { + return m_size; + } + +Q_SIGNALS: + void resized(); + +private: + QSize m_size; +}; + +void tst_QGraphicsWebView::windowResizeEvent() +{ + QGraphicsWebView webView; + ResizeSpy resizeSpy; + resizeSpy.setProperty("resizeCount", 0); + + QString html = "<html><body><script>" + "function onResize() { window.resizeSpy.receiveResize(window.innerWidth, window.innerHeight); }" + "window.addEventListener('resize', onResize , false);" + "</script></body></html>"; + + webView.page()->mainFrame()->setHtml(html); + webView.page()->mainFrame()->addToJavaScriptWindowObject("resizeSpy", + &resizeSpy); + webView.setGeometry(QRect(0, 0, 50, 50)); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118670", Continue); + QVERIFY(::waitForSignal(&resizeSpy, SIGNAL(resized()), 1000)); + QCOMPARE(resizeSpy.size(), QSize(50, 50)); + + webView.page()->setActualVisibleContentRect(QRect(10, 10, 60, 60)); + webView.setGeometry(QRect(0, 0, 100, 100)); + waitForSignal(&resizeSpy, SIGNAL(resized()), 1000); + + // This will be triggered without the fix on DOMWindow::innerHeight/Width + QCOMPARE(resizeSpy.size(), QSize(60, 60)); +} + +void tst_QGraphicsWebView::horizontalScrollbarTest() +{ + QWebPage* page = new QWebPage; + GraphicsWebView* webView = new GraphicsWebView; + webView->setPage(page); + webView->setGeometry(QRect(0, 0, 600, 600)); + QGraphicsView* view = new QGraphicsView; + QGraphicsScene* scene = new QGraphicsScene(view); + view->setScene(scene); + scene->addItem(webView); + + // Turn off scrolling on the containing QGraphicsView, let the + // QGraphicsWebView handle the scrolling by itself. + view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view->show(); + QCoreApplication::processEvents(); + + QUrl url("/service/qrc:///resources/scrolltest_page.html"); + page->mainFrame()->load(url); + page->mainFrame()->setFocus(); + + QVERIFY(waitForSignal(page, SIGNAL(loadFinished(bool)))); + + QVERIFY(webView->page()->mainFrame()->scrollPosition() == QPoint(0, 0)); + + // Note: The test below assumes that the layout direction is Qt::LeftToRight. + webView->fireMouseClick(QPointF(550.0, 590.0)); + QVERIFY(page->mainFrame()->scrollPosition().x() > 0); + + // Note: The test below assumes that the layout direction is Qt::LeftToRight. + webView->fireMouseClick(QPointF(20.0, 590.0)); + QVERIFY(page->mainFrame()->scrollPosition() == QPoint(0, 0)); + + delete webView; + delete view; +} + +QTEST_MAIN(tst_QGraphicsWebView) + +#include "tst_qgraphicswebview.moc" diff --git a/tests/webkitwidgets/qgraphicswebview/tst_qgraphicswebview.qrc b/tests/webkitwidgets/qgraphicswebview/tst_qgraphicswebview.qrc new file mode 100644 index 000000000..ff06bd8c4 --- /dev/null +++ b/tests/webkitwidgets/qgraphicswebview/tst_qgraphicswebview.qrc @@ -0,0 +1,9 @@ +<RCC> + <qresource prefix="/"> + <file>resources/input_types.html</file> + <file>resources/pointing_right.html</file> + <file>resources/pointing_up.html</file> + <file>resources/greendiv.html</file> + <file>resources/scrolltest_page.html</file> + </qresource> +</RCC> diff --git a/tests/webkitwidgets/qobjectbridge/qobjectbridge.pro b/tests/webkitwidgets/qobjectbridge/qobjectbridge.pro new file mode 100644 index 000000000..f434ccbc1 --- /dev/null +++ b/tests/webkitwidgets/qobjectbridge/qobjectbridge.pro @@ -0,0 +1,3 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc + diff --git a/tests/webkitwidgets/qobjectbridge/tst_qobjectbridge.cpp b/tests/webkitwidgets/qobjectbridge/tst_qobjectbridge.cpp new file mode 100644 index 000000000..a46d39050 --- /dev/null +++ b/tests/webkitwidgets/qobjectbridge/tst_qobjectbridge.cpp @@ -0,0 +1,2271 @@ +/* + Copyright (C) 2008,2009 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <QtTest/QtTest> + +#include <qbrush.h> +#include <qwebelement.h> +#include <qwebframe.h> +#include <qwebpage.h> +#include <qwebview.h> + +struct CustomType { + QString string; +}; +Q_DECLARE_METATYPE(CustomType) + +Q_DECLARE_METATYPE(QBrush*) +Q_DECLARE_METATYPE(QObjectList) +Q_DECLARE_METATYPE(QList<int>) +Q_DECLARE_METATYPE(QVariantList) +Q_DECLARE_METATYPE(QVariantMap) + +class MyQObject : public QObject { + Q_OBJECT + + Q_PROPERTY(int intProperty READ intProperty WRITE setIntProperty) + Q_PROPERTY(QVariant variantProperty READ variantProperty WRITE setVariantProperty) + Q_PROPERTY(QVariantList variantListProperty READ variantListProperty WRITE setVariantListProperty) + Q_PROPERTY(QVariantMap variantMapProperty READ variantMapProperty WRITE setVariantMapProperty) + Q_PROPERTY(QString stringProperty READ stringProperty WRITE setStringProperty) + Q_PROPERTY(QStringList stringListProperty READ stringListProperty WRITE setStringListProperty) + Q_PROPERTY(QByteArray byteArrayProperty READ byteArrayProperty WRITE setByteArrayProperty) + Q_PROPERTY(QBrush brushProperty READ brushProperty WRITE setBrushProperty) + Q_PROPERTY(double hiddenProperty READ hiddenProperty WRITE setHiddenProperty SCRIPTABLE false) + Q_PROPERTY(int writeOnlyProperty WRITE setWriteOnlyProperty) + Q_PROPERTY(int readOnlyProperty READ readOnlyProperty) + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) + Q_PROPERTY(CustomType propWithCustomType READ propWithCustomType WRITE setPropWithCustomType) + Q_PROPERTY(QWebElement webElementProperty READ webElementProperty WRITE setWebElementProperty) + Q_PROPERTY(QObject* objectStarProperty READ objectStarProperty WRITE setObjectStarProperty) + Q_ENUMS(Policy Strategy) + Q_FLAGS(Ability) + +public: + enum Policy { + FooPolicy = 0, + BarPolicy, + BazPolicy + }; + + enum Strategy { + FooStrategy = 10, + BarStrategy, + BazStrategy + }; + + enum AbilityFlag { + NoAbility = 0x000, + FooAbility = 0x001, + BarAbility = 0x080, + BazAbility = 0x200, + AllAbility = FooAbility | BarAbility | BazAbility + }; + + Q_DECLARE_FLAGS(Ability, AbilityFlag) + + MyQObject(QObject* parent = 0) + : QObject(parent), + m_intValue(123), + m_variantValue(QLatin1String("foo")), + m_variantListValue(QVariantList() << QVariant(123) << QVariant(QLatin1String("foo"))), + m_stringValue(QLatin1String("bar")), + m_stringListValue(QStringList() << QLatin1String("zig") << QLatin1String("zag")), + m_brushValue(QColor(10, 20, 30, 40)), + m_hiddenValue(456.0), + m_writeOnlyValue(789), + m_readOnlyValue(987), + m_objectStar(0), + m_qtFunctionInvoked(-1) + { + m_variantMapValue.insert("a", QVariant(123)); + m_variantMapValue.insert("b", QVariant(QLatin1String("foo"))); + m_variantMapValue.insert("c", QVariant::fromValue<QObject*>(this)); + } + + ~MyQObject() { } + + int intProperty() const + { + return m_intValue; + } + void setIntProperty(int value) + { + m_intValue = value; + } + + QVariant variantProperty() const + { + return m_variantValue; + } + void setVariantProperty(const QVariant& value) + { + m_variantValue = value; + } + + QVariantList variantListProperty() const + { + return m_variantListValue; + } + void setVariantListProperty(const QVariantList& value) + { + m_variantListValue = value; + } + + QVariantMap variantMapProperty() const + { + return m_variantMapValue; + } + void setVariantMapProperty(const QVariantMap& value) + { + m_variantMapValue = value; + } + + QString stringProperty() const + { + return m_stringValue; + } + void setStringProperty(const QString& value) + { + m_stringValue = value; + } + + QStringList stringListProperty() const + { + return m_stringListValue; + } + void setStringListProperty(const QStringList& value) + { + m_stringListValue = value; + } + + QByteArray byteArrayProperty() const + { + return m_byteArrayValue; + } + void setByteArrayProperty(const QByteArray& value) + { + m_byteArrayValue = value; + } + + QBrush brushProperty() const + { + return m_brushValue; + } + void setBrushProperty(const QBrush& value) + { + m_brushValue = value; + } + + double hiddenProperty() const + { + return m_hiddenValue; + } + void setHiddenProperty(double value) + { + m_hiddenValue = value; + } + + int writeOnlyProperty() const + { + return m_writeOnlyValue; + } + void setWriteOnlyProperty(int value) + { + m_writeOnlyValue = value; + } + + int readOnlyProperty() const + { + return m_readOnlyValue; + } + + QKeySequence shortcut() const + { + return m_shortcut; + } + void setShortcut(const QKeySequence& seq) + { + m_shortcut = seq; + } + + QWebElement webElementProperty() const + { + return m_webElement; + } + void setWebElementProperty(const QWebElement& element) + { + m_webElement = element; + } + + CustomType propWithCustomType() const + { + return m_customType; + } + void setPropWithCustomType(const CustomType& c) + { + m_customType = c; + } + + QObject* objectStarProperty() const + { + return m_objectStar; + } + void setObjectStarProperty(QObject* object) + { + m_objectStar = object; + } + + + int qtFunctionInvoked() const + { + return m_qtFunctionInvoked; + } + + QVariantList qtFunctionActuals() const + { + return m_actuals; + } + + void resetQtFunctionInvoked() + { + m_qtFunctionInvoked = -1; + m_actuals.clear(); + } + + Q_INVOKABLE void myInvokable() + { + m_qtFunctionInvoked = 0; + } + Q_INVOKABLE void myInvokableWithIntArg(int arg) + { + m_qtFunctionInvoked = 1; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithLonglongArg(qlonglong arg) + { + m_qtFunctionInvoked = 2; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithFloatArg(float arg) + { + m_qtFunctionInvoked = 3; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithDoubleArg(double arg) + { + m_qtFunctionInvoked = 4; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithStringArg(const QString& arg) + { + m_qtFunctionInvoked = 5; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithIntArgs(int arg1, int arg2) + { + m_qtFunctionInvoked = 6; + m_actuals << arg1 << arg2; + } + Q_INVOKABLE int myInvokableReturningInt() + { + m_qtFunctionInvoked = 7; + return 123; + } + Q_INVOKABLE qlonglong myInvokableReturningLongLong() + { + m_qtFunctionInvoked = 39; + return 456; + } + Q_INVOKABLE QString myInvokableReturningString() + { + m_qtFunctionInvoked = 8; + return QLatin1String("ciao"); + } + Q_INVOKABLE void myInvokableWithIntArg(int arg1, int arg2) // Overload. + { + m_qtFunctionInvoked = 9; + m_actuals << arg1 << arg2; + } + Q_INVOKABLE void myInvokableWithEnumArg(Policy policy) + { + m_qtFunctionInvoked = 10; + m_actuals << policy; + } + Q_INVOKABLE void myInvokableWithQualifiedEnumArg(MyQObject::Policy policy) + { + m_qtFunctionInvoked = 36; + m_actuals << policy; + } + Q_INVOKABLE Policy myInvokableReturningEnum() + { + m_qtFunctionInvoked = 37; + return BazPolicy; + } + Q_INVOKABLE MyQObject::Policy myInvokableReturningQualifiedEnum() + { + m_qtFunctionInvoked = 38; + return BazPolicy; + } + Q_INVOKABLE QVector<int> myInvokableReturningVectorOfInt() + { + m_qtFunctionInvoked = 11; + return QVector<int>(); + } + Q_INVOKABLE void myInvokableWithVectorOfIntArg(const QVector<int>&) + { + m_qtFunctionInvoked = 12; + } + Q_INVOKABLE QObject* myInvokableReturningQObjectStar() + { + m_qtFunctionInvoked = 13; + return this; + } + Q_INVOKABLE QObjectList myInvokableWithQObjectListArg(const QObjectList& lst) + { + m_qtFunctionInvoked = 14; + m_actuals << QVariant::fromValue(lst); + return lst; + } + Q_INVOKABLE QVariant myInvokableWithVariantArg(const QVariant& v) + { + m_qtFunctionInvoked = 15; + m_actuals << v; + return v; + } + Q_INVOKABLE QVariantMap myInvokableWithVariantMapArg(const QVariantMap& vm) + { + m_qtFunctionInvoked = 16; + m_actuals << vm; + return vm; + } + Q_INVOKABLE QList<int> myInvokableWithListOfIntArg(const QList<int>& lst) + { + m_qtFunctionInvoked = 17; + m_actuals << QVariant::fromValue(lst); + return lst; + } + Q_INVOKABLE QObject* myInvokableWithQObjectStarArg(QObject* obj) + { + m_qtFunctionInvoked = 18; + m_actuals << QVariant::fromValue(obj); + return obj; + } + Q_INVOKABLE QBrush myInvokableWithQBrushArg(const QBrush& brush) + { + m_qtFunctionInvoked = 19; + m_actuals << QVariant::fromValue(brush); + return brush; + } + Q_INVOKABLE void myInvokableWithBrushStyleArg(Qt::BrushStyle style) + { + m_qtFunctionInvoked = 43; + // Qt::BrushStyle isn't registered and this shouldn't be reached. + QVERIFY(false); + } + Q_INVOKABLE void myInvokableWithVoidStarArg(void* arg) + { + m_qtFunctionInvoked = 44; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithAmbiguousArg(int arg) + { + m_qtFunctionInvoked = 45; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithAmbiguousArg(uint arg) + { + m_qtFunctionInvoked = 46; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithDefaultArgs(int arg1, const QString& arg2 = QString()) + { + m_qtFunctionInvoked = 47; + m_actuals << QVariant::fromValue(arg1) << qVariantFromValue(arg2); + } + Q_INVOKABLE QObject& myInvokableReturningRef() + { + m_qtFunctionInvoked = 48; + return *this; + } + Q_INVOKABLE const QObject& myInvokableReturningConstRef() const + { + const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 49; + return *this; + } + Q_INVOKABLE void myInvokableWithPointArg(const QPoint &arg) { + const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 50; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithPointArg(const QPointF &arg) { + const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 51; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithBoolArg(bool arg) { + m_qtFunctionInvoked = 52; + m_actuals << arg; + } + + void emitMySignal() + { + emit mySignal(); + } + void emitMySignalWithIntArg(int arg) + { + emit mySignalWithIntArg(arg); + } + void emitMySignal2(bool arg) + { + emit mySignal2(arg); + } + void emitMySignal2() + { + emit mySignal2(); + } + void emitMySignalWithDateTimeArg(QDateTime dt) + { + emit mySignalWithDateTimeArg(dt); + } + +public Q_SLOTS: + void mySlot() + { + m_qtFunctionInvoked = 20; + } + + void mySlotWithIntArg(int arg) + { + m_qtFunctionInvoked = 21; + m_actuals << arg; + } + + void mySlotWithDoubleArg(double arg) + { + m_qtFunctionInvoked = 22; + m_actuals << arg; + } + + void mySlotWithStringArg(const QString &arg) + { + m_qtFunctionInvoked = 23; + m_actuals << arg; + } + + void myOverloadedSlot() + { + m_qtFunctionInvoked = 24; + } + + void myOverloadedSlot(QObject* arg) + { + m_qtFunctionInvoked = 41; + m_actuals << QVariant::fromValue(arg); + } + + void myOverloadedSlot(bool arg) + { + m_qtFunctionInvoked = 25; + m_actuals << arg; + } + + void myOverloadedSlot(const QStringList &arg) + { + m_qtFunctionInvoked = 42; + m_actuals << arg; + } + + void myOverloadedSlot(double arg) + { + m_qtFunctionInvoked = 26; + m_actuals << arg; + } + + void myOverloadedSlot(float arg) + { + m_qtFunctionInvoked = 27; + m_actuals << arg; + } + + void myOverloadedSlot(int arg) + { + m_qtFunctionInvoked = 28; + m_actuals << arg; + } + + void myOverloadedSlot(const QString &arg) + { + m_qtFunctionInvoked = 29; + m_actuals << arg; + } + + void myOverloadedSlot(const QColor &arg) + { + m_qtFunctionInvoked = 30; + m_actuals << arg; + } + + void myOverloadedSlot(const QBrush &arg) + { + m_qtFunctionInvoked = 31; + m_actuals << arg; + } + + void myOverloadedSlot(const QDateTime &arg) + { + m_qtFunctionInvoked = 32; + m_actuals << arg; + } + + void myOverloadedSlot(const QDate &arg) + { + m_qtFunctionInvoked = 33; + m_actuals << arg; + } + + void myOverloadedSlot(const QVariant &arg) + { + m_qtFunctionInvoked = 35; + m_actuals << arg; + } + + void myOverloadedSlot(const QWebElement &arg) + { + m_qtFunctionInvoked = 36; + m_actuals << QVariant::fromValue<QWebElement>(arg); + } + +protected Q_SLOTS: + void myProtectedSlot() + { + m_qtFunctionInvoked = 36; + } + +private Q_SLOTS: + void myPrivateSlot() { } + +Q_SIGNALS: + void mySignal(); + void mySignalWithIntArg(int); + void mySignalWithDoubleArg(double); + void mySignal2(bool arg = false); + void mySignalWithDateTimeArg(QDateTime); + +private: + int m_intValue; + QVariant m_variantValue; + QVariantList m_variantListValue; + QVariantMap m_variantMapValue; + QString m_stringValue; + QStringList m_stringListValue; + QByteArray m_byteArrayValue; + QBrush m_brushValue; + double m_hiddenValue; + int m_writeOnlyValue; + int m_readOnlyValue; + QKeySequence m_shortcut; + QWebElement m_webElement; + CustomType m_customType; + QObject* m_objectStar; + int m_qtFunctionInvoked; + QVariantList m_actuals; +}; + +class MyWebElementSlotOnlyObject : public QObject { + Q_OBJECT + Q_PROPERTY(QString tagName READ tagName) +public Q_SLOTS: + void doSomethingWithWebElement(const QWebElement& element) + { + m_tagName = element.tagName(); + } + +public: + QString tagName() const + { + return m_tagName; + } +private: + QString m_tagName; +}; + +class MyOtherQObject : public MyQObject { +public: + MyOtherQObject(QObject* parent = 0) + : MyQObject(parent) { } +}; + +class tst_QObjectBridge : public QObject { + Q_OBJECT + +public: + tst_QObjectBridge(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void getSetStaticProperty(); + void getSetDynamicProperty(); + void getSetChildren(); + void callQtInvokable(); + void connectAndDisconnect(); + void overrideInvokable(); + void overloadedSlots(); + void webElementSlotOnly(); + void enumerate_data(); + void enumerate(); + void objectDeleted(); + void typeConversion(); + void arrayObjectEnumerable(); + void domCycles(); + void jsByteArray(); + void ownership(); + void nullValue(); + void qObjectWrapperWithSameIdentity(); + void introspectQtMethods_data(); + void introspectQtMethods(); + void scriptablePlugin(); + void exceptionInSlot(); + +private: + QString evalJS(const QString& s) + { + QVariant ret = evalJSV(s); + if (!ret.isValid()) + return "undefined"; + return ret.toString(); + } + + QVariant evalJSV(const QString& s) + { + return m_page->mainFrame()->evaluateJavaScript(s); + } + + QString evalJS(const QString& s, QString& type) + { + return evalJSV(s, type).toString(); + } + + QVariant evalJSV(const QString& s, QString& type) + { + // As a special measure, if we get an exception we set the type to 'error' + // (in ecma, an Error object has typeof object, but qtscript has a convenience function) + // Similarly, an array is an object, but we'd prefer to have a type of 'array' + QString escaped = s; + escaped.replace('\'', "\\'"); // Don't preescape your single quotes! + QString code("var retvalue; " + "var typevalue; " + "try { " + " retvalue = eval('%1'); " + " typevalue = typeof retvalue; " + " if (retvalue instanceof Array) " + " typevalue = 'array'; " + "} catch(e) { " + " retvalue = e.name + ': ' + e.message; " + " typevalue = 'error'; " + "}"); + evalJS(code.arg(escaped)); + + QVariant ret = evalJSV("retvalue"); + if (ret.isValid()) + type = evalJS("typevalue"); + else { + ret = QString("undefined"); + type = sUndefined; + } + evalJS("delete retvalue; delete typevalue"); + return ret; + } + + const QString sTrue; + const QString sFalse; + const QString sUndefined; + const QString sArray; + const QString sFunction; + const QString sError; + const QString sString; + const QString sObject; + const QString sNumber; + +private: + QWebView* m_view; + QWebPage* m_page; + MyQObject* m_myObject; +}; + +tst_QObjectBridge::tst_QObjectBridge() + : sTrue("true") + , sFalse("false") + , sUndefined("undefined") + , sArray("array") + , sFunction("function") + , sError("error") + , sString("string") + , sObject("object") + , sNumber("number") +{ +} + +void tst_QObjectBridge::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); + m_myObject = new MyQObject(); + m_page->mainFrame()->addToJavaScriptWindowObject("myObject", m_myObject); +} + +void tst_QObjectBridge::cleanup() +{ + delete m_view; + delete m_myObject; +} + +void tst_QObjectBridge::getSetStaticProperty() +{ + m_page->mainFrame()->setHtml("<html><head><body></body></html>"); + QCOMPARE(evalJS("typeof myObject.noSuchProperty"), sUndefined); + + // initial value (set in MyQObject constructor) + { + QString type; + QVariant ret = evalJSV("myObject.intProperty", type); + QCOMPARE(type, sNumber); + QCOMPARE(ret.type(), QVariant::Double); + QCOMPARE(ret.toInt(), 123); + } + QCOMPARE(evalJS("myObject.intProperty === 123.0"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.variantProperty", type); + QCOMPARE(type, sString); + QCOMPARE(ret.type(), QVariant::String); + QCOMPARE(ret.toString(), QLatin1String("foo")); + } + QCOMPARE(evalJS("myObject.variantProperty == 'foo'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.stringProperty", type); + QCOMPARE(type, sString); + QCOMPARE(ret.type(), QVariant::String); + QCOMPARE(ret.toString(), QLatin1String("bar")); + } + QCOMPARE(evalJS("myObject.stringProperty === 'bar'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.variantListProperty", type); + QCOMPARE(type, sArray); + QCOMPARE(ret.type(), QVariant::List); + QVariantList vl = ret.value<QVariantList>(); + QCOMPARE(vl.size(), 2); + QCOMPARE(vl.at(0).toInt(), 123); + QCOMPARE(vl.at(1).toString(), QLatin1String("foo")); + } + QCOMPARE(evalJS("myObject.variantListProperty.length === 2"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[0] === 123"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[1] === 'foo'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.variantMapProperty", type); + QCOMPARE(type, sObject); + QCOMPARE(ret.type(), QVariant::Map); + QVariantMap vm = ret.value<QVariantMap>(); + QCOMPARE(vm.size(), 3); + QCOMPARE(vm.value("a").toInt(), 123); + QCOMPARE(vm.value("b").toString(), QLatin1String("foo")); + QCOMPARE(vm.value("c").value<QObject*>(), static_cast<QObject*>(m_myObject)); + } + QCOMPARE(evalJS("myObject.variantMapProperty.a === 123"), sTrue); + QCOMPARE(evalJS("myObject.variantMapProperty.b === 'foo'"), sTrue); + QCOMPARE(evalJS("myObject.variantMapProperty.c.variantMapProperty.b === 'foo'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.stringListProperty", type); + QCOMPARE(type, sArray); + QCOMPARE(ret.type(), QVariant::List); + QVariantList vl = ret.value<QVariantList>(); + QCOMPARE(vl.size(), 2); + QCOMPARE(vl.at(0).toString(), QLatin1String("zig")); + QCOMPARE(vl.at(1).toString(), QLatin1String("zag")); + } + QCOMPARE(evalJS("myObject.stringListProperty.length === 2"), sTrue); + QCOMPARE(evalJS("typeof myObject.stringListProperty[0]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[0]"), QLatin1String("zig")); + QCOMPARE(evalJS("typeof myObject.stringListProperty[1]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[1]"), QLatin1String("zag")); + + // property change in C++ should be reflected in script + m_myObject->setIntProperty(456); + QCOMPARE(evalJS("myObject.intProperty == 456"), sTrue); + m_myObject->setIntProperty(789); + QCOMPARE(evalJS("myObject.intProperty == 789"), sTrue); + + m_myObject->setVariantProperty(QLatin1String("bar")); + QCOMPARE(evalJS("myObject.variantProperty === 'bar'"), sTrue); + m_myObject->setVariantProperty(42); + QCOMPARE(evalJS("myObject.variantProperty === 42"), sTrue); + m_myObject->setVariantProperty(QVariant::fromValue(QBrush())); + + m_myObject->setStringProperty(QLatin1String("baz")); + QCOMPARE(evalJS("myObject.stringProperty === 'baz'"), sTrue); + m_myObject->setStringProperty(QLatin1String("zab")); + QCOMPARE(evalJS("myObject.stringProperty === 'zab'"), sTrue); + + // property change in script should be reflected in C++ + QCOMPARE(evalJS("myObject.intProperty = 123"), QLatin1String("123")); + QCOMPARE(evalJS("myObject.intProperty == 123"), sTrue); + QCOMPARE(m_myObject->intProperty(), 123); + QCOMPARE(evalJS("myObject.intProperty = 'ciao!';" + "myObject.intProperty == 0"), sTrue); + QCOMPARE(m_myObject->intProperty(), 0); + QCOMPARE(evalJS("myObject.intProperty = '123';" + "myObject.intProperty == 123"), sTrue); + QCOMPARE(m_myObject->intProperty(), 123); + + QCOMPARE(evalJS("myObject.stringProperty = 'ciao'"), QLatin1String("ciao")); + QCOMPARE(evalJS("myObject.stringProperty"), QLatin1String("ciao")); + QCOMPARE(m_myObject->stringProperty(), QLatin1String("ciao")); + QCOMPARE(evalJS("myObject.stringProperty = 123;" + "myObject.stringProperty"), QLatin1String("123")); + QCOMPARE(m_myObject->stringProperty(), QLatin1String("123")); + QCOMPARE(evalJS("myObject.stringProperty = null"), QString()); + QCOMPARE(evalJS("myObject.stringProperty"), QString()); + QCOMPARE(m_myObject->stringProperty(), QString()); + QCOMPARE(evalJS("myObject.stringProperty = undefined"), sUndefined); + QCOMPARE(evalJS("myObject.stringProperty"), QString()); + QCOMPARE(m_myObject->stringProperty(), QString()); + + QCOMPARE(evalJS("myObject.variantProperty = new Number(1234);" + "myObject.variantProperty").toDouble(), 1234.0); + QCOMPARE(m_myObject->variantProperty().toDouble(), 1234.0); + + QCOMPARE(evalJS("myObject.variantProperty = new Boolean(1234);" + "myObject.variantProperty"), sTrue); + QCOMPARE(m_myObject->variantProperty().toBool(), true); + + QCOMPARE(evalJS("myObject.variantProperty = null;" + "myObject.variantProperty.valueOf()"), sUndefined); + QCOMPARE(m_myObject->variantProperty(), QVariant()); + QCOMPARE(evalJS("myObject.variantProperty = undefined;" + "myObject.variantProperty.valueOf()"), sUndefined); + QCOMPARE(m_myObject->variantProperty(), QVariant()); + + QCOMPARE(evalJS("myObject.variantProperty = 'foo';" + "myObject.variantProperty.valueOf()"), QLatin1String("foo")); + QCOMPARE(m_myObject->variantProperty(), QVariant(QLatin1String("foo"))); + QCOMPARE(evalJS("myObject.variantProperty = 42;" + "myObject.variantProperty").toDouble(), 42.0); + QCOMPARE(m_myObject->variantProperty().toDouble(), 42.0); + + QCOMPARE(evalJS("myObject.variantListProperty = [1, 'two', true];" + "myObject.variantListProperty.length == 3"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[0] === 1"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[1]"), QLatin1String("two")); + QCOMPARE(evalJS("myObject.variantListProperty[2] === true"), sTrue); + + QCOMPARE(evalJS("myObject.stringListProperty = [1, 'two', true];" + "myObject.stringListProperty.length == 3"), sTrue); + QCOMPARE(evalJS("typeof myObject.stringListProperty[0]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[0] == '1'"), sTrue); + QCOMPARE(evalJS("typeof myObject.stringListProperty[1]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[1]"), QLatin1String("two")); + QCOMPARE(evalJS("typeof myObject.stringListProperty[2]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[2]"), QLatin1String("true")); + evalJS("myObject.webElementProperty=document.body;"); + QCOMPARE(evalJS("myObject.webElementProperty.tagName"), QLatin1String("BODY")); + + // try to delete + QCOMPARE(evalJS("delete myObject.intProperty"), sFalse); + QCOMPARE(evalJS("myObject.intProperty == 123"), sTrue); + + QCOMPARE(evalJS("delete myObject.variantProperty"), sFalse); + QCOMPARE(evalJS("myObject.variantProperty").toDouble(), 42.0); + + // custom property + QCOMPARE(evalJS("myObject.customProperty"), sUndefined); + QCOMPARE(evalJS("myObject.customProperty = 123;" + "myObject.customProperty == 123"), sTrue); + QVariant v = m_page->mainFrame()->evaluateJavaScript("myObject.customProperty"); + QCOMPARE(v.type(), QVariant::Double); + QCOMPARE(v.toInt(), 123); + + // non-scriptable property + QCOMPARE(m_myObject->hiddenProperty(), 456.0); + QCOMPARE(evalJS("myObject.hiddenProperty"), sUndefined); + QCOMPARE(evalJS("myObject.hiddenProperty = 123;" + "myObject.hiddenProperty == 123"), sTrue); + QCOMPARE(m_myObject->hiddenProperty(), 456.0); + + // write-only property + QCOMPARE(m_myObject->writeOnlyProperty(), 789); + QCOMPARE(evalJS("typeof myObject.writeOnlyProperty"), sUndefined); + QCOMPARE(evalJS("myObject.writeOnlyProperty = 123;" + "typeof myObject.writeOnlyProperty"), sUndefined); + QCOMPARE(m_myObject->writeOnlyProperty(), 123); + + // read-only property + QCOMPARE(m_myObject->readOnlyProperty(), 987); + QCOMPARE(evalJS("myObject.readOnlyProperty == 987"), sTrue); + QCOMPARE(evalJS("myObject.readOnlyProperty = 654;" + "myObject.readOnlyProperty == 987"), sTrue); + QCOMPARE(m_myObject->readOnlyProperty(), 987); + + // QObject* property + m_myObject->setObjectStarProperty(0); + QCOMPARE(m_myObject->objectStarProperty(), (QObject*)0); + QCOMPARE(evalJS("myObject.objectStarProperty == null"), sTrue); + QCOMPARE(evalJS("typeof myObject.objectStarProperty"), sObject); + QCOMPARE(evalJS("Boolean(myObject.objectStarProperty)"), sFalse); + QCOMPARE(evalJS("String(myObject.objectStarProperty) == 'null'"), sTrue); + QCOMPARE(evalJS("myObject.objectStarProperty.objectStarProperty"), + sUndefined); + m_myObject->setObjectStarProperty(this); + QCOMPARE(evalJS("myObject.objectStarProperty != null"), sTrue); + QCOMPARE(evalJS("typeof myObject.objectStarProperty"), sObject); + QCOMPARE(evalJS("Boolean(myObject.objectStarProperty)"), sTrue); + QCOMPARE(evalJS("String(myObject.objectStarProperty) != 'null'"), sTrue); +} + +void tst_QObjectBridge::getSetDynamicProperty() +{ + // initially the object does not have the property + // In WebKit, RuntimeObjects do not inherit Object, so don't have hasOwnProperty + + // QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sFalse); + QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined); + + // add a dynamic property in C++ + QCOMPARE(m_myObject->setProperty("dynamicProperty", 123), false); + // QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sTrue); + QCOMPARE(evalJS("typeof myObject.dynamicProperty != 'undefined'"), sTrue); + QCOMPARE(evalJS("myObject.dynamicProperty == 123"), sTrue); + + // property change in script should be reflected in C++ + QCOMPARE(evalJS("myObject.dynamicProperty = 'foo';" + "myObject.dynamicProperty"), QLatin1String("foo")); + QCOMPARE(m_myObject->property("dynamicProperty").toString(), QLatin1String("foo")); + + // delete the property (XFAIL - can't delete properties) + QEXPECT_FAIL("", "can't delete properties", Continue); + QCOMPARE(evalJS("delete myObject.dynamicProperty"), sTrue); + /* + QCOMPARE(m_myObject->property("dynamicProperty").isValid(), false); + QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined); + // QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sFalse); + QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined); + */ +} + +void tst_QObjectBridge::getSetChildren() +{ + // initially the object does not have the child + // (again, no hasOwnProperty) + + // QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sFalse); + QCOMPARE(evalJS("typeof myObject.child"), sUndefined); + + // add a child + MyQObject* child = new MyQObject(m_myObject); + child->setObjectName("child"); +// QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sTrue); + QCOMPARE(evalJS("typeof myObject.child != 'undefined'"), sTrue); + + // add a grandchild + MyQObject* grandChild = new MyQObject(child); + grandChild->setObjectName("grandChild"); +// QCOMPARE(evalJS("myObject.child.hasOwnProperty('grandChild')"), sTrue); + QCOMPARE(evalJS("typeof myObject.child.grandChild != 'undefined'"), sTrue); + + // delete grandchild + delete grandChild; +// QCOMPARE(evalJS("myObject.child.hasOwnProperty('grandChild')"), sFalse); + QCOMPARE(evalJS("typeof myObject.child.grandChild == 'undefined'"), sTrue); + + // delete child + delete child; +// QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sFalse); + QCOMPARE(evalJS("typeof myObject.child == 'undefined'"), sTrue); +} + +Q_DECLARE_METATYPE(QVector<int>) +Q_DECLARE_METATYPE(QVector<double>) +Q_DECLARE_METATYPE(QVector<QString>) + +void tst_QObjectBridge::callQtInvokable() +{ + qRegisterMetaType<QObjectList>(); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokable()"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + // extra arguments should silently be ignored + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokable(10, 20, 30)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg('123')"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithLonglongArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toLongLong(), qlonglong(123)); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithFloatArg(123.5)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 3); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.5); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithDoubleArg(123.5)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 4); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.5); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithDoubleArg(new Number(1234.5))"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 4); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 1234.5); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithBoolArg(new Boolean(true))"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 52); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toBool(), true); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg('ciao')"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("ciao")); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("123")); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(null)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QString()); + QVERIFY(m_myObject->qtFunctionActuals().at(0).toString().isEmpty()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(undefined)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QString()); + QVERIFY(m_myObject->qtFunctionActuals().at(0).toString().isEmpty()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArgs(123, 456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 6); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toInt(), 456); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableReturningInt()"), QLatin1String("123")); + QCOMPARE(m_myObject->qtFunctionInvoked(), 7); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableReturningLongLong()"), QLatin1String("456")); + QCOMPARE(m_myObject->qtFunctionInvoked(), 39); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableReturningString()"), QLatin1String("ciao")); + QCOMPARE(m_myObject->qtFunctionInvoked(), 8); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg(123, 456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 9); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toInt(), 456); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithVoidStarArg(null)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 44); + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithVoidStarArg(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: incompatible type of argument(s) in call to myInvokableWithVoidStarArg(); candidates were\n myInvokableWithVoidStarArg(void*)")); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithAmbiguousArg(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: ambiguous call of overloaded function myInvokableWithAmbiguousArg(); candidates were\n myInvokableWithAmbiguousArg(int)\n myInvokableWithAmbiguousArg(uint)")); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithDefaultArgs(123, 'hello')", type); + QCOMPARE(type, sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 47); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toString(), QLatin1String("hello")); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithDefaultArgs(456)", type); + QCOMPARE(type, sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 47); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toString(), QString()); + } + + // calling function that returns (const)ref + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("typeof myObject.myInvokableReturningRef()"); + QCOMPARE(ret, sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 48); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("typeof myObject.myInvokableReturningConstRef()"); + QCOMPARE(ret, sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 49); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableReturningQObjectStar()", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 13); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 0); + QCOMPARE(type, sObject); + QCOMPARE(ret.userType(), int(QMetaType::QObjectStar)); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithQObjectListArg([myObject])", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 14); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(type, sArray); + QCOMPARE(ret.userType(), int(QVariant::List)); // All lists get downgraded to QVariantList + QVariantList vl = qvariant_cast<QVariantList>(ret); + QCOMPARE(vl.count(), 1); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + m_myObject->setVariantProperty(QVariant(123)); + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(myObject.variantProperty)", type); + QCOMPARE(type, sNumber); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), m_myObject->variantProperty()); + QCOMPARE(ret.userType(), int(QMetaType::Double)); // all JS numbers are doubles, even though this started as an int + QCOMPARE(ret.toInt(), 123); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(null)", type); + QCOMPARE(type, sObject); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant()); + QVERIFY(!m_myObject->qtFunctionActuals().at(0).isValid()); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(undefined)", type); + QCOMPARE(type, sObject); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant()); + QVERIFY(!m_myObject->qtFunctionActuals().at(0).isValid()); + } + + /* XFAIL - variant support + m_myObject->resetQtFunctionInvoked(); + { + m_myObject->setVariantProperty(QVariant::fromValue(QBrush())); + QVariant ret = evalJS("myObject.myInvokableWithVariantArg(myObject.variantProperty)"); + QVERIFY(ret.isVariant()); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(ret.toVariant(), m_myObject->qtFunctionActuals().at(0)); + QCOMPARE(ret.toVariant(), m_myObject->variantProperty()); + } + */ + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(123)", type); + QCOMPARE(type, sNumber); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant(123)); + QCOMPARE(ret.userType(), int(QMetaType::Double)); + QCOMPARE(ret.toInt(), 123); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantMapArg({ a:123, b:'ciao' })", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 16); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), int(QMetaType::QVariantMap)); + + QVariantMap vmap = qvariant_cast<QVariantMap>(v); + QCOMPARE(vmap.keys().size(), 2); + QCOMPARE(vmap.keys().at(0), QLatin1String("a")); + QCOMPARE(vmap.value("a"), QVariant(123)); + QCOMPARE(vmap.keys().at(1), QLatin1String("b")); + QCOMPARE(vmap.value("b"), QVariant("ciao")); + + QCOMPARE(type, sObject); + + QCOMPARE(ret.userType(), int(QMetaType::QVariantMap)); + vmap = qvariant_cast<QVariantMap>(ret); + QCOMPARE(vmap.keys().size(), 2); + QCOMPARE(vmap.keys().at(0), QLatin1String("a")); + QCOMPARE(vmap.value("a"), QVariant(123)); + QCOMPARE(vmap.keys().at(1), QLatin1String("b")); + QCOMPARE(vmap.value("b"), QVariant("ciao")); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithListOfIntArg([1, 5])", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 17); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), qMetaTypeId<QList<int> >()); + QList<int> ilst = qvariant_cast<QList<int> >(v); + QCOMPARE(ilst.size(), 2); + QCOMPARE(ilst.at(0), 1); + QCOMPARE(ilst.at(1), 5); + + QCOMPARE(type, sArray); + QCOMPARE(ret.userType(), int(QMetaType::QVariantList)); // ints get converted to doubles, so this is a qvariantlist + QVariantList vlst = qvariant_cast<QVariantList>(ret); + QCOMPARE(vlst.size(), 2); + QCOMPARE(vlst.at(0).toInt(), 1); + QCOMPARE(vlst.at(1).toInt(), 5); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithQObjectStarArg(myObject)", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 18); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), int(QMetaType::QObjectStar)); + QCOMPARE(qvariant_cast<QObject*>(v), (QObject*)m_myObject); + + QCOMPARE(ret.userType(), int(QMetaType::QObjectStar)); + QCOMPARE(qvariant_cast<QObject*>(ret), (QObject*)m_myObject); + + QCOMPARE(type, sObject); + } + + m_myObject->resetQtFunctionInvoked(); + { + // no implicit conversion from integer to QObject* + QString type; + evalJS("myObject.myInvokableWithQObjectStarArg(123)", type); + QCOMPARE(type, sError); + } + + /* + m_myObject->resetQtFunctionInvoked(); + { + QString fun = evalJS("myObject.myInvokableWithQBrushArg"); + Q_ASSERT(fun.isFunction()); + QColor color(10, 20, 30, 40); + // QColor should be converted to a QBrush + QVariant ret = fun.call(QString(), QStringList() + << qScriptValueFromValue(m_engine, color)); + QCOMPARE(m_myObject->qtFunctionInvoked(), 19); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), int(QMetaType::QBrush)); + QCOMPARE(qvariant_cast<QColor>(v), color); + + QCOMPARE(qscriptvalue_cast<QColor>(ret), color); + } + */ + + // private slots should not be part of the QObject binding + QCOMPARE(evalJS("typeof myObject.myPrivateSlot"), sUndefined); + + // protected slots should be fine + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myProtectedSlot()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 36); + + // call with too few arguments + { + QString type; + QString ret = evalJS("myObject.myInvokableWithIntArg()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: too few arguments in call to myInvokableWithIntArg(); candidates are\n myInvokableWithIntArg(int,int)\n myInvokableWithIntArg(int)")); + } + + // call function where not all types have been registered + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithBrushStyleArg(0)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot call myInvokableWithBrushStyleArg(): unknown type `Qt::BrushStyle'")); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + } + + // call function with incompatible argument type + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithQBrushArg(null)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: incompatible type of argument(s) in call to myInvokableWithQBrushArg(); candidates were\n myInvokableWithQBrushArg(QBrush)")); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + } +} + +void tst_QObjectBridge::connectAndDisconnect() +{ + // connect(function) + QCOMPARE(evalJS("typeof myObject.mySignal"), sFunction); + QCOMPARE(evalJS("typeof myObject.mySignal.connect"), sFunction); + QCOMPARE(evalJS("typeof myObject.mySignal.disconnect"), sFunction); + + { + QString type; + evalJS("myObject.mySignal.connect(123)", type); + QCOMPARE(type, sError); + } + + evalJS("myHandler = function() { window.gotSignal = true; window.signalArgs = arguments; window.slotThisObject = this; }"); + + QCOMPARE(evalJS("myObject.mySignal.connect(myHandler)"), sUndefined); + + evalJS("gotSignal = false"); + evalJS("myObject.mySignal()"); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 0"), sTrue); + QCOMPARE(evalJS("slotThisObject == window"), sTrue); + + evalJS("gotSignal = false"); + m_myObject->emitMySignal(); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 0"), sTrue); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myHandler)"), sUndefined); + + evalJS("gotSignal = false"); + m_myObject->emitMySignalWithIntArg(123); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("signalArgs[0] == 123.0"), sTrue); + + QCOMPARE(evalJS("myObject.mySignal.disconnect(myHandler)"), sUndefined); + { + QString type; + evalJS("myObject.mySignal.disconnect(myHandler)", type); + QCOMPARE(type, sError); + } + + evalJS("gotSignal = false"); + QCOMPARE(evalJS("myObject.mySignal2.connect(myHandler)"), sUndefined); + m_myObject->emitMySignal2(true); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("signalArgs[0]"), sTrue); + + QCOMPARE(evalJS("myObject.mySignal2.disconnect(myHandler)"), sUndefined); + + QCOMPARE(evalJS("typeof myObject['mySignal2()']"), sFunction); + QCOMPARE(evalJS("typeof myObject['mySignal2()'].connect"), sFunction); + QCOMPARE(evalJS("typeof myObject['mySignal2()'].disconnect"), sFunction); + + QCOMPARE(evalJS("myObject['mySignal2()'].connect(myHandler)"), sUndefined); + + evalJS("gotSignal = false"); + m_myObject->emitMySignal2(); + QCOMPARE(evalJS("gotSignal"), sTrue); + + QCOMPARE(evalJS("myObject['mySignal2()'].disconnect(myHandler)"), sUndefined); + + // connect(object, function) + evalJS("otherObject = { name:'foo' }"); + QCOMPARE(evalJS("myObject.mySignal.connect(otherObject, myHandler)"), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.disconnect(otherObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal(); + QCOMPARE(evalJS("gotSignal"), sFalse); + + { + QString type; + evalJS("myObject.mySignal.disconnect(otherObject, myHandler)", type); + QCOMPARE(type, sError); + } + + QCOMPARE(evalJS("myObject.mySignal.connect(otherObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal(); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 0"), sTrue); + QCOMPARE(evalJS("slotThisObject"), evalJS("otherObject")); + QCOMPARE(evalJS("slotThisObject.name"), QLatin1String("foo")); + QCOMPARE(evalJS("myObject.mySignal.disconnect(otherObject, myHandler)"), sUndefined); + + evalJS("yetAnotherObject = { name:'bar', func : function() { } }"); + QCOMPARE(evalJS("myObject.mySignal2.connect(yetAnotherObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal2(true); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("slotThisObject == yetAnotherObject"), sTrue); + QCOMPARE(evalJS("slotThisObject.name"), QLatin1String("bar")); + QCOMPARE(evalJS("myObject.mySignal2.disconnect(yetAnotherObject, myHandler)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignal2.connect(myObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal2(true); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("slotThisObject == myObject"), sTrue); + QCOMPARE(evalJS("myObject.mySignal2.disconnect(myObject, myHandler)"), sUndefined); + + // connect(obj, string) + { + QString type; + QCOMPARE(evalJS("myObject.mySignal.connect(yetAnotherObject, 'func')", type), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.connect(myObject, 'mySlot')", type), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.disconnect(yetAnotherObject, 'func')", type), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.disconnect(myObject, 'mySlot')", type), sUndefined); + } + + // check that emitting signals from script works + + // no arguments + QCOMPARE(evalJS("myObject.mySignal.connect(myObject.mySlot)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignal()"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 20); + QCOMPARE(evalJS("myObject.mySignal.disconnect(myObject.mySlot)"), sUndefined); + + // one argument + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithIntArg)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 21); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithIntArg)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithDoubleArg)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 22); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.0); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithDoubleArg)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithStringArg)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 23); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("123")); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithStringArg)"), sUndefined); + + // connecting to overloaded slot + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.myOverloadedSlot)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 26); // double overload + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.myOverloadedSlot)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject['myOverloadedSlot(int)'])"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 28); // int overload + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject['myOverloadedSlot(int)'])"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject, 'myOverloadedSlot(int)')"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 28); // int overload + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject, 'myOverloadedSlot(int)')"), sUndefined); + + // erroneous input +#define NOT_A_FUNCTION(f, expr) \ + "TypeError: " f " is not a function. (In '" expr "', '" f "' is undefined)" + + { + // ### QtScript adds .connect to all functions, WebKit does only to signals/slots + QString type; + QString ret = evalJS("(function() { }).connect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String(NOT_A_FUNCTION("(function() { }).connect", "(function() { }).connect()"))); + } + { + QString type; + QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect; o.connect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String(NOT_A_FUNCTION("o.connect", "o.connect()"))); + } + + { + QString type; + QString ret = evalJS("(function() { }).connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String(NOT_A_FUNCTION("(function() { }).connect", "(function() { }).connect(123)"))); + } + { + QString type; + QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect; o.connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String(NOT_A_FUNCTION("o.connect", "o.connect(123)"))); + } + + { + QString type; + QString ret = evalJS("myObject.myInvokable.connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.connect: MyQObject::myInvokable() is not a signal")); + } + { + QString type; + QString ret = evalJS("myObject.myInvokable.connect(function() { })", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.connect: MyQObject::myInvokable() is not a signal")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.connect: target is not a function")); + } + + { + QString type; + QString ret = evalJS("var randomObject = new Object; myObject.mySignal.connect(myObject, randomObject)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.connect: target is not a function")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.connect(myObject, 'nonExistantSlot')", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.connect: target is not a function")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: no arguments given")); + } + { + QString type; + QString ret = evalJS("var o = { }; o.disconnect = myObject.mySignal.disconnect; o.disconnect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: no arguments given")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect(myObject, 'nonExistantSlot')", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: target is not a function")); + } + + /* XFAIL - Function.prototype doesn't get connect/disconnect, just signals/slots + { + QString type; + QString ret = evalJS("(function() { }).disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: this object is not a signal")); + } + */ + + { + QString type; + QString ret = evalJS("var o = { }; o.disconnect = myObject.myInvokable.disconnect; o.disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal")); + } + + { + QString type; + QString ret = evalJS("myObject.myInvokable.disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal")); + } + { + QString type; + QString ret = evalJS("myObject.myInvokable.disconnect(function() { })", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: target is not a function")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect(function() { })", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: failed to disconnect from MyQObject::mySignal()")); + } + + // when the wrapper dies, the connection stays alive + QCOMPARE(evalJS("myObject.mySignal.connect(myObject.mySlot)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + m_myObject->emitMySignal(); + QCOMPARE(m_myObject->qtFunctionInvoked(), 20); + evalJS("myObject = null"); + evalJS("gc()"); + m_myObject->resetQtFunctionInvoked(); + m_myObject->emitMySignal(); + QCOMPARE(m_myObject->qtFunctionInvoked(), 20); +} + +void tst_QObjectBridge::overrideInvokable() +{ + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokable()"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + + /* XFAIL - can't write to functions with RuntimeObject + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myInvokable = function() { window.a = 123; }"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + QCOMPARE(evalJS("window.a").toDouble(), 123.0); + + evalJS("myObject.myInvokable = function() { window.a = 456; }"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + QCOMPARE(evalJS("window.a").toDouble(), 456.0); + */ + + evalJS("delete myObject.myInvokable"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + + /* XFAIL - ditto + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myInvokable = myObject.myInvokableWithIntArg"); + evalJS("myObject.myInvokable(123)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + */ + + evalJS("delete myObject.myInvokable"); + m_myObject->resetQtFunctionInvoked(); + // this form (with the '()') is read-only + evalJS("myObject['myInvokable()'] = function() { window.a = 123; }"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); +} + +void tst_QObjectBridge::overloadedSlots() +{ + // should pick myOverloadedSlot(double) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(10)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 26); + + // should pick myOverloadedSlot(double) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(10.0)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 26); + + // should pick myOverloadedSlot(QString) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot('10')"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 29); + + // should pick myOverloadedSlot(bool) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(true)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 25); + + // should pick myOverloadedSlot(QDateTime) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(new Date())"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 32); + + // should pick myOverloadedSlot(QVariant) + /* XFAIL + m_myObject->resetQtFunctionInvoked(); + QString f = evalJS("myObject.myOverloadedSlot"); + f.call(QString(), QStringList() << m_engine->newVariant(QVariant("ciao"))); + QCOMPARE(m_myObject->qtFunctionInvoked(), 35); + */ + + // Should pick myOverloadedSlot(QWebElement). + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(document.body)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 36); + + // should pick myOverloadedSlot(QObject*) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(myObject)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 41); + + // should pick myOverloadedSlot(QObject*) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(null)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 41); + + // should pick myOverloadedSlot(QStringList) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(['hello'])"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 42); +} + +class MyEnumTestQObject : public QObject { + Q_OBJECT + Q_PROPERTY(QString p1 READ p1) + Q_PROPERTY(QString p2 READ p2) + Q_PROPERTY(QString p3 READ p3 SCRIPTABLE false) + Q_PROPERTY(QString p4 READ p4) + Q_PROPERTY(QString p5 READ p5 SCRIPTABLE false) + Q_PROPERTY(QString p6 READ p6) +public: + MyEnumTestQObject(QObject* parent = 0) + : QObject(parent) { } + + QString p1() const { return QLatin1String("p1"); } + QString p2() const { return QLatin1String("p2"); } + QString p3() const { return QLatin1String("p3"); } + QString p4() const { return QLatin1String("p4"); } + QString p5() const { return QLatin1String("p5"); } + QString p6() const { return QLatin1String("p6"); } + +public Q_SLOTS: + void mySlot() { } + void myOtherSlot() { } +Q_SIGNALS: + void mySignal(); +}; + +void tst_QObjectBridge::enumerate_data() +{ + QTest::addColumn<QStringList>("expectedNames"); + + QTest::newRow("enumerate all") + << (QStringList() + // meta-object-defined properties: + // inherited + << "objectName" + // non-inherited + << "p1" << "p2" << "p4" << "p6" + // dynamic properties + << "dp1" << "dp2" << "dp3" + // inherited signals and slots + << "destroyed(QObject*)" << "destroyed()" + << "objectNameChanged(QString)" + << "deleteLater()" + // not included because it's private: + // << "_q_reregisterTimers(void*)" + // signals + << "mySignal()" + // slots + << "mySlot()" << "myOtherSlot()"); +} + +void tst_QObjectBridge::enumerate() +{ + QFETCH(QStringList, expectedNames); + + MyEnumTestQObject enumQObject; + // give it some dynamic properties + enumQObject.setProperty("dp1", "dp1"); + enumQObject.setProperty("dp2", "dp2"); + enumQObject.setProperty("dp3", "dp3"); + m_page->mainFrame()->addToJavaScriptWindowObject("myEnumObject", &enumQObject); + + // enumerate in script + { + evalJS("var enumeratedProperties = []"); + evalJS("for (var p in myEnumObject) { enumeratedProperties.push(p); }"); + QStringList result = evalJSV("enumeratedProperties").toStringList(); + QCOMPARE(result.size(), expectedNames.size()); + for (int i = 0; i < expectedNames.size(); ++i) + QCOMPARE(result.at(i), expectedNames.at(i)); + } +} + +void tst_QObjectBridge::objectDeleted() +{ + MyQObject* qobj = new MyQObject(); + m_page->mainFrame()->addToJavaScriptWindowObject("bar", qobj); + evalJS("bar.objectName = 'foo';"); + QCOMPARE(qobj->objectName(), QLatin1String("foo")); + evalJS("bar.intProperty = 123;"); + QCOMPARE(qobj->intProperty(), 123); + qobj->resetQtFunctionInvoked(); + evalJS("bar.myInvokable.call(bar);"); + QCOMPARE(qobj->qtFunctionInvoked(), 0); + + // do this, to ensure that we cache that it implements call + evalJS("bar()"); + + // now delete the object + delete qobj; + + QCOMPARE(evalJS("typeof bar"), sObject); + + // any attempt to access properties of the object should result in an exception + { + QString type; + QString ret = evalJS("bar.objectName", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject")); + } + { + QString type; + QString ret = evalJS("bar.objectName = 'foo'", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject")); + } + + // myInvokable is stored in member table (since we've accessed it before deletion) + { + QString type; + evalJS("bar.myInvokable", type); + QCOMPARE(type, sFunction); + } + + { + QString type; + QString ret = evalJS("bar.myInvokable.call(bar);", type); + ret = evalJS("bar.myInvokable(bar)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject")); + } + // myInvokableWithIntArg is not stored in member table (since we've not accessed it) + { + QString type; + QString ret = evalJS("bar.myInvokableWithIntArg", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `myInvokableWithIntArg' of deleted QObject")); + } + + // access from script + evalJS("window.o = bar;"); + { + QString type; + QString ret = evalJS("o.objectName", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject")); + } + { + QString type; + QString ret = evalJS("o.myInvokable()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject")); + } + { + QString type; + QString ret = evalJS("o.myInvokableWithIntArg(10)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `myInvokableWithIntArg' of deleted QObject")); + } +} + +void tst_QObjectBridge::typeConversion() +{ + m_myObject->resetQtFunctionInvoked(); + + QDateTime localdt(QDate(2008, 1, 18), QTime(12, 31, 0)); + QDateTime utclocaldt = localdt.toUTC(); + QDateTime utcdt(QDate(2008, 1, 18), QTime(12, 31, 0), Qt::UTC); + + // Dates in JS (default to local) + evalJS("myObject.myOverloadedSlot(new Date(2008,0,18,12,31,0))"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 32); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDateTime().toUTC(), utclocaldt); + + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(new Date(Date.UTC(2008,0,18,12,31,0)))"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 32); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDateTime().toUTC(), utcdt); + + // Pushing QDateTimes into JS + // Local + evalJS("function checkDate(d) {window.__date_equals = (d.toString() == new Date(2008,0,18,12,31,0))?true:false;}"); + evalJS("myObject.mySignalWithDateTimeArg.connect(checkDate)"); + m_myObject->emitMySignalWithDateTimeArg(localdt); + QCOMPARE(evalJS("window.__date_equals"), sTrue); + evalJS("delete window.__date_equals"); + m_myObject->emitMySignalWithDateTimeArg(utclocaldt); + QCOMPARE(evalJS("window.__date_equals"), sTrue); + evalJS("delete window.__date_equals"); + evalJS("myObject.mySignalWithDateTimeArg.disconnect(checkDate); delete checkDate;"); + + // UTC + evalJS("function checkDate(d) {window.__date_equals = (d.toString() == new Date(Date.UTC(2008,0,18,12,31,0)))?true:false; }"); + evalJS("myObject.mySignalWithDateTimeArg.connect(checkDate)"); + m_myObject->emitMySignalWithDateTimeArg(utcdt); + QCOMPARE(evalJS("window.__date_equals"), sTrue); + evalJS("delete window.__date_equals"); + evalJS("myObject.mySignalWithDateTimeArg.disconnect(checkDate); delete checkDate;"); +} + +class StringListTestObject : public QObject { + Q_OBJECT +public Q_SLOTS: + QVariant stringList() + { + return QStringList() << "Q" << "t"; + }; +}; + +void tst_QObjectBridge::arrayObjectEnumerable() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QObject* qobject = new StringListTestObject(); + frame->addToJavaScriptWindowObject("test", qobject, QWebFrame::ScriptOwnership); + + const QString script("var stringArray = test.stringList();" + "var result = '';" + "for (var i in stringArray) {" + " result += stringArray[i];" + "}" + "result;"); + QCOMPARE(frame->evaluateJavaScript(script).toString(), QString::fromLatin1("Qt")); +} + +void tst_QObjectBridge::domCycles() +{ + m_view->setHtml("<html><body>"); + QVariant v = m_page->mainFrame()->evaluateJavaScript("document"); + QVERIFY(v.type() == QVariant::Map); +} + +void tst_QObjectBridge::jsByteArray() +{ + QByteArray ba("hello world"); + m_myObject->setByteArrayProperty(ba); + + // read-only property + QCOMPARE(m_myObject->byteArrayProperty(), ba); + QString type; + QVariant v = evalJSV("myObject.byteArrayProperty"); + QCOMPARE(int(v.type()), int(QVariant::ByteArray)); + + QCOMPARE(v.toByteArray(), ba); +} + +void tst_QObjectBridge::ownership() +{ + // test ownership + { + QPointer<QObject> ptr = new QObject(); + QVERIFY(ptr); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", ptr.data(), QWebFrame::ScriptOwnership); + } + QVERIFY(!ptr); + } + { + QPointer<QObject> ptr = new QObject(); + QVERIFY(ptr); + QObject* before = ptr.data(); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", ptr.data(), QWebFrame::QtOwnership); + } + QVERIFY(ptr.data() == before); + delete ptr.data(); + } + { + QObject* parent = new QObject(); + QObject* child = new QObject(parent); + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", child, QWebFrame::QtOwnership); + QVariant v = frame->evaluateJavaScript("test"); + QCOMPARE(qvariant_cast<QObject*>(v), child); + delete parent; + v = frame->evaluateJavaScript("test"); + QCOMPARE(qvariant_cast<QObject*>(v), (QObject *)0); + } + { + QPointer<QObject> ptr = new QObject(); + QVERIFY(ptr); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", ptr.data(), QWebFrame::AutoOwnership); + } + // no parent, so it should be like ScriptOwnership + QVERIFY(!ptr); + } + { + QObject* parent = new QObject(); + QPointer<QObject> child = new QObject(parent); + QVERIFY(child); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", child.data(), QWebFrame::AutoOwnership); + } + // has parent, so it should be like QtOwnership + QVERIFY(child); + delete parent; + } +} + +void tst_QObjectBridge::nullValue() +{ + QVariant v = m_view->page()->mainFrame()->evaluateJavaScript("null"); + QVERIFY(v.isNull()); +} + +class TestFactory : public QObject { + Q_OBJECT +public: + TestFactory() + : obj(0), counter(0) + { } + + Q_INVOKABLE QObject* getNewObject() + { + delete obj; + obj = new QObject(this); + obj->setObjectName(QLatin1String("test") + QString::number(++counter)); + return obj; + + } + + QObject* obj; + int counter; +}; + +void tst_QObjectBridge::qObjectWrapperWithSameIdentity() +{ + m_view->setHtml("<script>function triggerBug() { document.getElementById('span1').innerText = test.getNewObject().objectName; }</script>" + "<body><span id='span1'>test</span></body>"); + + QWebFrame* mainFrame = m_view->page()->mainFrame(); + QCOMPARE(mainFrame->toPlainText(), QString("test")); + + mainFrame->addToJavaScriptWindowObject("test", new TestFactory, QWebFrame::ScriptOwnership); + + mainFrame->evaluateJavaScript("triggerBug();"); + QCOMPARE(mainFrame->toPlainText(), QString("test1")); + + mainFrame->evaluateJavaScript("triggerBug();"); + QCOMPARE(mainFrame->toPlainText(), QString("test2")); +} + +void tst_QObjectBridge::introspectQtMethods_data() +{ + QTest::addColumn<QString>("objectExpression"); + QTest::addColumn<QString>("methodName"); + QTest::addColumn<QStringList>("expectedPropertyNames"); + + QTest::newRow("myObject.mySignal") + << "myObject" << "mySignal" << (QStringList() << "connect" << "disconnect" << "name"); + QTest::newRow("myObject.mySlot") + << "myObject" << "mySlot" << (QStringList() << "connect" << "disconnect" << "name"); + QTest::newRow("myObject.myInvokable") + << "myObject" << "myInvokable" << (QStringList() << "connect" << "disconnect" << "name"); + QTest::newRow("myObject.mySignal.connect") + << "myObject.mySignal" << "connect" << (QStringList() << "name"); + QTest::newRow("myObject.mySignal.disconnect") + << "myObject.mySignal" << "disconnect" << (QStringList() << "name"); +} + +void tst_QObjectBridge::introspectQtMethods() +{ + QFETCH(QString, objectExpression); + QFETCH(QString, methodName); + QFETCH(QStringList, expectedPropertyNames); + + QString methodLookup = QString::fromLatin1("%0['%1']").arg(objectExpression).arg(methodName); + QCOMPARE(evalJSV(QString::fromLatin1("Object.getOwnPropertyNames(%0).sort()").arg(methodLookup)).toStringList(), expectedPropertyNames); + + for (int i = 0; i < expectedPropertyNames.size(); ++i) { + QString name = expectedPropertyNames.at(i); + QCOMPARE(evalJS(QString::fromLatin1("%0.hasOwnProperty('%1')").arg(methodLookup).arg(name)), sTrue); + evalJS(QString::fromLatin1("var descriptor = Object.getOwnPropertyDescriptor(%0, '%1')").arg(methodLookup).arg(name)); + QCOMPARE(evalJS("typeof descriptor"), QString::fromLatin1("object")); + QCOMPARE(evalJS("descriptor.get"), sUndefined); + QCOMPARE(evalJS("descriptor.set"), sUndefined); + QCOMPARE(evalJS(QString::fromLatin1("descriptor.value === %0['%1']").arg(methodLookup).arg(name)), sTrue); + QCOMPARE(evalJS(QString::fromLatin1("descriptor.enumerable")), sFalse); + QCOMPARE(evalJS(QString::fromLatin1("descriptor.configurable")), sFalse); + } + + QVERIFY(evalJSV("var props=[]; for (var p in myObject.deleteLater) {props.push(p);}; props.sort()").toStringList().isEmpty()); +} + +void tst_QObjectBridge::webElementSlotOnly() +{ + MyWebElementSlotOnlyObject object; + m_page->mainFrame()->setHtml("<html><head><body></body></html>"); + m_page->mainFrame()->addToJavaScriptWindowObject("myWebElementSlotObject", &object); + evalJS("myWebElementSlotObject.doSomethingWithWebElement(document.body)"); + QCOMPARE(evalJS("myWebElementSlotObject.tagName"), QString("BODY")); +} + +class TestPluginWidget : public QWidget { + Q_OBJECT +public: + TestPluginWidget() { } + +public Q_SLOTS: + int slotWithReturnValue() { return 42; } +}; + +class TestWebPage : public QWebPage { + Q_OBJECT +public: + TestWebPage(QObject* parent = 0) + : QWebPage(parent) + , creationCount(0) + { } + + int creationCount; + +protected: + virtual QObject* createPlugin(const QString&, const QUrl&, const QStringList&, const QStringList&) + { + creationCount++; + return new TestPluginWidget; + } +}; + +void tst_QObjectBridge::scriptablePlugin() +{ +//#if !PLUGIN_VIEW_IS_BROKEN + QWebView view; + TestWebPage* page = new TestWebPage; + view.setPage(page); + page->setParent(&view); + view.settings()->setAttribute(QWebSettings::PluginsEnabled, true); + + page->mainFrame()->setHtml("<object width=100 height=100 type=\"application/x-qt-plugin\"></object>"); + QCOMPARE(page->creationCount, 1); + + QVariant result = page->mainFrame()->evaluateJavaScript("document.querySelector(\"object\").slotWithReturnValue()"); + QCOMPARE(result.toString(), QLatin1String("42")); +//#endif +} + +class WebPageWithConsoleCapture : public QWebPage +{ +public: + void javaScriptConsoleMessage(const QString &message, int, const QString &) + { + consoleMessages << message; + } + + QStringList consoleMessages; +}; + +void tst_QObjectBridge::exceptionInSlot() +{ + WebPageWithConsoleCapture page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("myObject", m_myObject); + frame->evaluateJavaScript( + "myHandler = function() { window.gotSignal = true; throw 'exception in slot'; };" + "myObject.mySignal.connect(myHandler);" + "gotSignal = false;" + "myObject.mySignal();" + ); + QString ret = frame->evaluateJavaScript("gotSignal").toString(); + QCOMPARE(ret, sTrue); + QCOMPARE(page.consoleMessages, QStringList() << "exception in slot"); +} + +QTEST_MAIN(tst_QObjectBridge) +#include "tst_qobjectbridge.moc" diff --git a/tests/webkitwidgets/qwebelement/qwebelement.pro b/tests/webkitwidgets/qwebelement/qwebelement.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebelement/qwebelement.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebelement/resources/image.png b/tests/webkitwidgets/qwebelement/resources/image.png Binary files differnew file mode 100644 index 000000000..8d703640c --- /dev/null +++ b/tests/webkitwidgets/qwebelement/resources/image.png diff --git a/tests/webkitwidgets/qwebelement/resources/style.css b/tests/webkitwidgets/qwebelement/resources/style.css new file mode 100644 index 000000000..2713dfda9 --- /dev/null +++ b/tests/webkitwidgets/qwebelement/resources/style.css @@ -0,0 +1 @@ +#idP {color: black !important} diff --git a/tests/webkitwidgets/qwebelement/resources/style2.css b/tests/webkitwidgets/qwebelement/resources/style2.css new file mode 100644 index 000000000..6575dcb04 --- /dev/null +++ b/tests/webkitwidgets/qwebelement/resources/style2.css @@ -0,0 +1 @@ +#idP {color: green ! important} diff --git a/tests/webkitwidgets/qwebelement/tst_qwebelement.cpp b/tests/webkitwidgets/qwebelement/tst_qwebelement.cpp new file mode 100644 index 000000000..4ca5fe5f9 --- /dev/null +++ b/tests/webkitwidgets/qwebelement/tst_qwebelement.cpp @@ -0,0 +1,1080 @@ +/* + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include <QtTest/QtTest> +#include <qwebpage.h> +#include <qwidget.h> +#include <qwebview.h> +#include <qwebframe.h> +#include <qwebelement.h> +#include <util.h> +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QWebElement : public QObject +{ + Q_OBJECT + +public: + tst_QWebElement(); + virtual ~tst_QWebElement(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void textHtml(); + void simpleCollection(); + void attributes(); + void attributesNS(); + void listAttributes(); + void classes(); + void namespaceURI(); + void iteration(); + void nonConstIterator(); + void constIterator(); + void foreachManipulation(); + void emptyCollection(); + void appendCollection(); + void evaluateJavaScript(); + void documentElement(); + void frame(); + void style(); + void computedStyle(); + void appendAndPrepend(); + void insertBeforeAndAfter(); + void remove(); + void clear(); + void replaceWith(); + void encloseWith(); + void encloseContentsWith(); + void nullSelect(); + void firstChildNextSibling(); + void lastChildPreviousSibling(); + void hasSetFocus(); + void render(); + void addElementToHead(); + +private: + QWebView* m_view { nullptr }; + QWebPage* m_page { nullptr }; + QWebFrame* m_mainFrame { nullptr }; +}; + +tst_QWebElement::tst_QWebElement() +{ +} + +tst_QWebElement::~tst_QWebElement() +{ +} + +void tst_QWebElement::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); + m_mainFrame = m_page->mainFrame(); +} + +void tst_QWebElement::cleanup() +{ + delete m_view; +} + +void tst_QWebElement::textHtml() +{ + QString html = "<head></head><body><p>test</p></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + QVERIFY(!body.isNull()); + + QCOMPARE(body.toPlainText(), QString("test")); + QCOMPARE(body.toPlainText(), m_mainFrame->toPlainText()); + + QCOMPARE(body.toInnerXml(), html); +} + +void tst_QWebElement::simpleCollection() +{ + QString html = "<body><p>first para</p><p>second para</p></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + + QWebElementCollection list = body.findAll("p"); + QCOMPARE(list.count(), 2); + QCOMPARE(list.at(0).toPlainText(), QString("first para")); + QCOMPARE(list.at(1).toPlainText(), QString("second para")); +} + +void tst_QWebElement::attributes() +{ + m_mainFrame->setHtml("<body><p>Test"); + QWebElement body = m_mainFrame->documentElement(); + + QVERIFY(!body.hasAttribute("title")); + QVERIFY(!body.hasAttributes()); + + body.setAttribute("title", "test title"); + + QVERIFY(body.hasAttributes()); + QVERIFY(body.hasAttribute("title")); + + QCOMPARE(body.attribute("title"), QString("test title")); + + body.removeAttribute("title"); + + QVERIFY(!body.hasAttribute("title")); + QVERIFY(!body.hasAttributes()); + + QCOMPARE(body.attribute("does-not-exist", "testvalue"), QString("testvalue")); +} + +void tst_QWebElement::attributesNS() +{ + QString content = "<html xmlns=\"http://www.w3.org/1999/xhtml/" " + "xmlns:svg=\"http://www.w3.org/2000/svg/">" + "<body><svg:svg id=\"foobar\" width=\"400px\" height=\"300px\">" + "</svg:svg></body></html>"; + + m_mainFrame->setContent(content.toUtf8(), "application/xhtml+xml"); + + QWebElement svg = m_mainFrame->findFirstElement("svg"); + QVERIFY(!svg.isNull()); + + QVERIFY(!svg.hasAttributeNS("http://www.w3.org/2000/svg", "foobar")); + QCOMPARE(svg.attributeNS("http://www.w3.org/2000/svg", "foobar", "defaultblah"), QString("defaultblah")); + svg.setAttributeNS("http://www.w3.org/2000/svg", "svg:foobar", "true"); + QVERIFY(svg.hasAttributeNS("http://www.w3.org/2000/svg", "foobar")); + QCOMPARE(svg.attributeNS("http://www.w3.org/2000/svg", "foobar", "defaultblah"), QString("true")); +} + +void tst_QWebElement::listAttributes() +{ + QString content = "<html xmlns=\"http://www.w3.org/1999/xhtml/" " + "xmlns:svg=\"http://www.w3.org/2000/svg/">" + "<body><svg:svg foo=\"\" svg:bar=\"\">" + "</svg:svg></body></html>"; + + m_mainFrame->setContent(content.toUtf8(), "application/xhtml+xml"); + + QWebElement svg = m_mainFrame->findFirstElement("svg"); + QVERIFY(!svg.isNull()); + + QVERIFY(svg.attributeNames().contains("foo")); + QVERIFY(svg.attributeNames("http://www.w3.org/2000/svg").contains("bar")); + + svg.setAttributeNS("http://www.w3.org/2000/svg", "svg:foobar", "true"); + QVERIFY(svg.attributeNames().contains("foo")); + QStringList attributes = svg.attributeNames("http://www.w3.org/2000/svg"); + QCOMPARE(attributes.size(), 2); + QVERIFY(attributes.contains("bar")); + QVERIFY(attributes.contains("foobar")); +} + +void tst_QWebElement::classes() +{ + m_mainFrame->setHtml("<body><p class=\"a b c d a c\">Test"); + + QWebElement body = m_mainFrame->documentElement(); + QCOMPARE(body.classes().count(), 0); + + QWebElement p = m_mainFrame->documentElement().findAll("p").at(0); + QStringList classes = p.classes(); + QCOMPARE(classes.count(), 4); + QCOMPARE(classes[0], QLatin1String("a")); + QCOMPARE(classes[1], QLatin1String("b")); + QCOMPARE(classes[2], QLatin1String("c")); + QCOMPARE(classes[3], QLatin1String("d")); + QVERIFY(p.hasClass("a")); + QVERIFY(p.hasClass("b")); + QVERIFY(p.hasClass("c")); + QVERIFY(p.hasClass("d")); + QVERIFY(!p.hasClass("e")); + + p.addClass("f"); + QVERIFY(p.hasClass("f")); + p.addClass("a"); + QCOMPARE(p.classes().count(), 5); + QVERIFY(p.hasClass("a")); + QVERIFY(p.hasClass("b")); + QVERIFY(p.hasClass("c")); + QVERIFY(p.hasClass("d")); + + p.toggleClass("a"); + QVERIFY(!p.hasClass("a")); + QVERIFY(p.hasClass("b")); + QVERIFY(p.hasClass("c")); + QVERIFY(p.hasClass("d")); + QVERIFY(p.hasClass("f")); + QCOMPARE(p.classes().count(), 4); + p.toggleClass("f"); + QVERIFY(!p.hasClass("f")); + QCOMPARE(p.classes().count(), 3); + p.toggleClass("a"); + p.toggleClass("f"); + QVERIFY(p.hasClass("a")); + QVERIFY(p.hasClass("f")); + QCOMPARE(p.classes().count(), 5); + + p.removeClass("f"); + QVERIFY(!p.hasClass("f")); + QCOMPARE(p.classes().count(), 4); + p.removeClass("d"); + QVERIFY(!p.hasClass("d")); + QCOMPARE(p.classes().count(), 3); + p.removeClass("not-exist"); + QCOMPARE(p.classes().count(), 3); + p.removeClass("c"); + QVERIFY(!p.hasClass("c")); + QCOMPARE(p.classes().count(), 2); + p.removeClass("b"); + QVERIFY(!p.hasClass("b")); + QCOMPARE(p.classes().count(), 1); + p.removeClass("a"); + QVERIFY(!p.hasClass("a")); + QCOMPARE(p.classes().count(), 0); + p.removeClass("foobar"); + QCOMPARE(p.classes().count(), 0); +} + +void tst_QWebElement::namespaceURI() +{ + QString content = "<html xmlns=\"http://www.w3.org/1999/xhtml/" " + "xmlns:svg=\"http://www.w3.org/2000/svg/">" + "<body><svg:svg id=\"foobar\" width=\"400px\" height=\"300px\">" + "</svg:svg></body></html>"; + + m_mainFrame->setContent(content.toUtf8(), "application/xhtml+xml"); + QWebElement body = m_mainFrame->documentElement(); + QCOMPARE(body.namespaceUri(), QLatin1String("http://www.w3.org/1999/xhtml")); + + QWebElement svg = body.findAll("*#foobar").at(0); + QCOMPARE(svg.prefix(), QLatin1String("svg")); + QCOMPARE(svg.localName(), QLatin1String("svg")); + QCOMPARE(svg.tagName(), QLatin1String("svg:svg")); + QCOMPARE(svg.namespaceUri(), QLatin1String("http://www.w3.org/2000/svg")); + +} + +void tst_QWebElement::iteration() +{ + QString html = "<body><p>first para</p><p>second para</p></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + + QWebElementCollection paras = body.findAll("p"); + QList<QWebElement> referenceList = paras.toList(); + + QList<QWebElement> foreachList; + foreach(QWebElement p, paras) { + foreachList.append(p); + } + QVERIFY(foreachList.count() == 2); + QCOMPARE(foreachList.count(), referenceList.count()); + QCOMPARE(foreachList.at(0), referenceList.at(0)); + QCOMPARE(foreachList.at(1), referenceList.at(1)); + + QList<QWebElement> forLoopList; + for (int i = 0; i < paras.count(); ++i) { + forLoopList.append(paras.at(i)); + } + QVERIFY(foreachList.count() == 2); + QCOMPARE(foreachList.count(), referenceList.count()); + QCOMPARE(foreachList.at(0), referenceList.at(0)); + QCOMPARE(foreachList.at(1), referenceList.at(1)); + + for (int i = 0; i < paras.count(); ++i) { + QCOMPARE(paras.at(i), paras[i]); + } + + QCOMPARE(paras.at(0), paras.first()); + QCOMPARE(paras.at(1), paras.last()); +} + +void tst_QWebElement::nonConstIterator() +{ + QString html = "<body><p>first para</p><p>second para</p></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + QWebElementCollection paras = body.findAll("p"); + + QWebElementCollection::iterator it = paras.begin(); + QCOMPARE(*it, paras.at(0)); + ++it; + (*it).encloseWith("<div>"); + QCOMPARE(*it, paras.at(1)); + ++it; + QCOMPARE(it, paras.end()); +} + +void tst_QWebElement::constIterator() +{ + QString html = "<body><p>first para</p><p>second para</p></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + const QWebElementCollection paras = body.findAll("p"); + + QWebElementCollection::const_iterator it = paras.begin(); + QCOMPARE(*it, paras.at(0)); + ++it; + QCOMPARE(*it, paras.at(1)); + ++it; + QCOMPARE(it, paras.end()); +} + +void tst_QWebElement::foreachManipulation() +{ + QString html = "<body><p>first para</p><p>second para</p></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + + foreach(QWebElement p, body.findAll("p")) { + p.setInnerXml("<div>foo</div><div>bar</div>"); + } + + QCOMPARE(body.findAll("div").count(), 4); +} + +void tst_QWebElement::emptyCollection() +{ + QWebElementCollection emptyCollection; + QCOMPARE(emptyCollection.count(), 0); +} + +void tst_QWebElement::appendCollection() +{ + QString html = "<body><span class='a'>aaa</span><p>first para</p><div>foo</div>" + "<span class='b'>bbb</span><p>second para</p><div>bar</div></body>"; + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement(); + + QWebElementCollection collection = body.findAll("p"); + QCOMPARE(collection.count(), 2); + + collection.append(body.findAll("div")); + QCOMPARE(collection.count(), 4); + + collection += body.findAll("span.a"); + QCOMPARE(collection.count(), 5); + + QWebElementCollection all = collection + body.findAll("span.b"); + QCOMPARE(all.count(), 6); + QCOMPARE(collection.count(), 5); + + all += collection; + QCOMPARE(all.count(), 11); + + QCOMPARE(collection.count(), 5); + QWebElementCollection test; + test.append(collection); + QCOMPARE(test.count(), 5); + test.append(QWebElementCollection()); + QCOMPARE(test.count(), 5); +} + +void tst_QWebElement::evaluateJavaScript() +{ + QVariant result; + m_mainFrame->setHtml("<body><p>test"); + QWebElement para = m_mainFrame->findFirstElement("p"); + + result = para.evaluateJavaScript("this.tagName"); + QVERIFY(result.isValid()); + QVERIFY(result.type() == QVariant::String); + QCOMPARE(result.toString(), QLatin1String("P")); + + result = para.evaluateJavaScript("this.hasAttributes()"); + QVERIFY(result.isValid()); + QVERIFY(result.type() == QVariant::Bool); + QVERIFY(!result.toBool()); + + para.evaluateJavaScript("this.setAttribute('align', 'left');"); + QCOMPARE(para.attribute("align"), QLatin1String("left")); + + result = para.evaluateJavaScript("this.hasAttributes()"); + QVERIFY(result.isValid()); + QVERIFY(result.type() == QVariant::Bool); + QVERIFY(result.toBool()); +} + +void tst_QWebElement::documentElement() +{ + m_mainFrame->setHtml("<body><p>Test"); + + QWebElement para = m_mainFrame->documentElement().findAll("p").at(0); + QVERIFY(para.parent().parent() == m_mainFrame->documentElement()); + QVERIFY(para.document() == m_mainFrame->documentElement()); +} + +void tst_QWebElement::frame() +{ + m_mainFrame->setHtml("<body><p>test"); + + QWebElement doc = m_mainFrame->documentElement(); + QVERIFY(doc.webFrame() == m_mainFrame); + + m_mainFrame->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," + "<p>frame1\">" + "<frame src=\"data:text/html,<p>frame2\"></frameset>")); + + waitForSignal(m_page, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_mainFrame->childFrames().count(), 2); + + QWebFrame* firstFrame = m_mainFrame->childFrames().at(0); + QWebFrame* secondFrame = m_mainFrame->childFrames().at(1); + + QCOMPARE(firstFrame->toPlainText(), QString("frame1")); + QCOMPARE(secondFrame->toPlainText(), QString("frame2")); + + QWebElement firstPara = firstFrame->documentElement().findAll("p").at(0); + QWebElement secondPara = secondFrame->documentElement().findAll("p").at(0); + + QVERIFY(firstPara.webFrame() == firstFrame); + QVERIFY(secondPara.webFrame() == secondFrame); +} + +void tst_QWebElement::style() +{ + QString html = "<head>" + "<style type='text/css'>" + "p { color: green !important }" + "#idP { color: red }" + ".classP { color : yellow ! important }" + "</style>" + "</head>" + "<body>" + "<p id='idP' class='classP' style='color: blue;'>some text</p>" + "</body>"; + + m_mainFrame->setHtml(html); + + QWebElement p = m_mainFrame->documentElement().findAll("p").at(0); + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("blue")); + QVERIFY(p.styleProperty("cursor", QWebElement::InlineStyle).isEmpty()); + + p.setStyleProperty("color", "red"); + p.setStyleProperty("cursor", "auto"); + + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("red")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("yellow")); + QCOMPARE(p.styleProperty("cursor", QWebElement::InlineStyle), QLatin1String("auto")); + + p.setStyleProperty("color", "green !important"); + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("green")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("green")); + + p.setStyleProperty("color", "blue"); + // A current important InlineStyle shouldn't be overwritten by a non-important one. + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("green")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("green")); + + p.setStyleProperty("color", "blue !important"); + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("blue")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("blue")); + + QString html2 = "<head>" + "<style type='text/css'>" + "p { color: green }" + "#idP { color: red }" + ".classP { color: yellow }" + "</style>" + "</head>" + "<body>" + "<p id='idP' class='classP' style='color: blue;'>some text</p>" + "</body>"; + + m_mainFrame->setHtml(html2); + p = m_mainFrame->documentElement().findAll("p").at(0); + + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("blue")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("blue")); + + QString html3 = "<head>" + "<style type='text/css'>" + "p { color: green !important }" + "#idP { color: red !important}" + ".classP { color: yellow !important}" + "</style>" + "</head>" + "<body>" + "<p id='idP' class='classP' style='color: blue !important;'>some text</p>" + "</body>"; + + m_mainFrame->setHtml(html3); + p = m_mainFrame->documentElement().findAll("p").at(0); + + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("blue")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("blue")); + + QString html5 = "<head>" + "<style type='text/css'>" + "p { color: green }" + "#idP { color: red }" + ".classP { color: yellow }" + "</style>" + "</head>" + "<body>" + "<p id='idP' class='classP'>some text</p>" + "</body>"; + + m_mainFrame->setHtml(html5); + p = m_mainFrame->documentElement().findAll("p").at(0); + + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("red")); + + QString html6 = "<head>" + "<link rel='stylesheet' href='/service/qrc:/style.css' type='text/css' />" + "<style type='text/css'>" + "p { color: green }" + "#idP { color: red }" + ".classP { color: yellow ! important}" + "</style>" + "</head>" + "<body>" + "<p id='idP' class='classP' style='color: blue;'>some text</p>" + "</body>"; + + // in few seconds, the CSS should be completey loaded + m_mainFrame->setHtml(html6); + waitForSignal(m_page, SIGNAL(loadFinished(bool)), 200); + + p = m_mainFrame->documentElement().findAll("p").at(0); + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("blue")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("black")); + + QString html7 = "<head>" + "<style type='text/css'>" + "@import url(/service/qrc:/style2.css);" + "</style>" + "<link rel='stylesheet' href='/service/qrc:/style.css' type='text/css' />" + "</head>" + "<body>" + "<p id='idP' style='color: blue;'>some text</p>" + "</body>"; + + // in few seconds, the style should be completey loaded + m_mainFrame->setHtml(html7); + waitForSignal(m_page, SIGNAL(loadFinished(bool)), 200); + + p = m_mainFrame->documentElement().findAll("p").at(0); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("black")); + + QString html8 = "<body><p>some text</p></body>"; + + m_mainFrame->setHtml(html8); + p = m_mainFrame->documentElement().findAll("p").at(0); + + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("")); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("")); +} + +void tst_QWebElement::computedStyle() +{ + QString html = "<body><p>some text</p></body>"; + m_mainFrame->setHtml(html); + + QWebElement p = m_mainFrame->documentElement().findAll("p").at(0); + QCOMPARE(p.styleProperty("cursor", QWebElement::ComputedStyle), QLatin1String("auto")); + QVERIFY(!p.styleProperty("cursor", QWebElement::ComputedStyle).isEmpty()); + QVERIFY(p.styleProperty("cursor", QWebElement::InlineStyle).isEmpty()); + + p.setStyleProperty("cursor", "text"); + p.setStyleProperty("color", "red"); + + QCOMPARE(p.styleProperty("cursor", QWebElement::ComputedStyle), QLatin1String("text")); + QCOMPARE(p.styleProperty("color", QWebElement::ComputedStyle), QLatin1String("rgb(255, 0, 0)")); + QCOMPARE(p.styleProperty("color", QWebElement::InlineStyle), QLatin1String("red")); +} + +void tst_QWebElement::appendAndPrepend() +{ + QString html = "<body>" + "<p>" + "foo" + "</p>" + "<p>" + "bar" + "</p>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + + QCOMPARE(body.findAll("p").count(), 2); + body.appendInside(body.findFirst("p")); + QCOMPARE(body.findAll("p").count(), 2); + QCOMPARE(body.findFirst("p").toPlainText(), QString("bar")); + QCOMPARE(body.findAll("p").last().toPlainText(), QString("foo")); + + body.appendInside(body.findFirst("p").clone()); + QCOMPARE(body.findAll("p").count(), 3); + QCOMPARE(body.findFirst("p").toPlainText(), QString("bar")); + QCOMPARE(body.findAll("p").last().toPlainText(), QString("bar")); + + body.prependInside(body.findAll("p").at(1).clone()); + QCOMPARE(body.findAll("p").count(), 4); + QCOMPARE(body.findFirst("p").toPlainText(), QString("foo")); + + body.findFirst("p").appendInside("<div>booyakasha</div>"); + QCOMPARE(body.findAll("p div").count(), 1); + QCOMPARE(body.findFirst("p div").toPlainText(), QString("booyakasha")); + + body.findFirst("div").prependInside("<code>yepp</code>"); + QCOMPARE(body.findAll("p div code").count(), 1); + QCOMPARE(body.findFirst("p div code").toPlainText(), QString("yepp")); + + // Inserting HTML into an img tag is not allowed, but appending/prepending outside is. + body.findFirst("div").appendInside("<img src=\"test.png\">"); + QCOMPARE(body.findAll("p div img").count(), 1); + + QWebElement img = body.findFirst("img"); + QVERIFY(!img.isNull()); + img.appendInside("<p id=\"fail1\"></p>"); + QCOMPARE(body.findAll("p#fail1").count(), 0); + + img.appendOutside("<p id=\"success1\"></p>"); + QCOMPARE(body.findAll("p#success1").count(), 1); + + img.prependInside("<p id=\"fail2\"></p>"); + QCOMPARE(body.findAll("p#fail2").count(), 0); + + img.prependOutside("<p id=\"success2\"></p>"); + QCOMPARE(body.findAll("p#success2").count(), 1); + + +} + +void tst_QWebElement::insertBeforeAndAfter() +{ + QString html = "<body>" + "<p>" + "foo" + "</p>" + "<div>" + "yeah" + "</div>" + "<p>" + "bar" + "</p>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + QWebElement div = body.findFirst("div"); + + QCOMPARE(body.findAll("p").count(), 2); + QCOMPARE(body.findAll("div").count(), 1); + + div.prependOutside(body.findAll("p").last().clone()); + QCOMPARE(body.findAll("p").count(), 3); + QCOMPARE(body.findAll("p").at(0).toPlainText(), QString("foo")); + QCOMPARE(body.findAll("p").at(1).toPlainText(), QString("bar")); + QCOMPARE(body.findAll("p").at(2).toPlainText(), QString("bar")); + + div.appendOutside(body.findFirst("p").clone()); + QCOMPARE(body.findAll("p").count(), 4); + QCOMPARE(body.findAll("p").at(0).toPlainText(), QString("foo")); + QCOMPARE(body.findAll("p").at(1).toPlainText(), QString("bar")); + QCOMPARE(body.findAll("p").at(2).toPlainText(), QString("foo")); + QCOMPARE(body.findAll("p").at(3).toPlainText(), QString("bar")); + + div.prependOutside("<span>hey</span>"); + QCOMPARE(body.findAll("span").count(), 1); + + div.appendOutside("<span>there</span>"); + QCOMPARE(body.findAll("span").count(), 2); + QCOMPARE(body.findAll("span").at(0).toPlainText(), QString("hey")); + QCOMPARE(body.findAll("span").at(1).toPlainText(), QString("there")); +} + +void tst_QWebElement::remove() +{ + QString html = "<body>" + "<p>" + "foo" + "</p>" + "<div>" + "<p>yeah</p>" + "</div>" + "<p>" + "bar" + "</p>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + + QCOMPARE(body.findAll("div").count(), 1); + QCOMPARE(body.findAll("p").count(), 3); + + QWebElement div = body.findFirst("div"); + div.takeFromDocument(); + + QCOMPARE(div.isNull(), false); + QCOMPARE(body.findAll("div").count(), 0); + QCOMPARE(body.findAll("p").count(), 2); + + body.appendInside(div); + + QCOMPARE(body.findAll("div").count(), 1); + QCOMPARE(body.findAll("p").count(), 3); +} + +void tst_QWebElement::clear() +{ + QString html = "<body>" + "<p>" + "foo" + "</p>" + "<div>" + "<p>yeah</p>" + "</div>" + "<p>" + "bar" + "</p>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + + QCOMPARE(body.findAll("div").count(), 1); + QCOMPARE(body.findAll("p").count(), 3); + body.findFirst("div").removeAllChildren(); + QCOMPARE(body.findAll("div").count(), 1); + QCOMPARE(body.findAll("p").count(), 2); +} + + +void tst_QWebElement::replaceWith() +{ + QString html = "<body>" + "<p>" + "foo" + "</p>" + "<div>" + "yeah" + "</div>" + "<p>" + "<span>haba</span>" + "</p>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + + QCOMPARE(body.findAll("div").count(), 1); + QCOMPARE(body.findAll("span").count(), 1); + body.findFirst("div").replace(body.findFirst("span").clone()); + QCOMPARE(body.findAll("div").count(), 0); + QCOMPARE(body.findAll("span").count(), 2); + QCOMPARE(body.findAll("p").count(), 2); + + body.findFirst("span").replace("<p><code>wow</code></p>"); + QCOMPARE(body.findAll("p").count(), 3); + QCOMPARE(body.findAll("p code").count(), 1); + QCOMPARE(body.findFirst("p code").toPlainText(), QString("wow")); +} + +void tst_QWebElement::encloseContentsWith() +{ + QString html = "<body>" + "<div>" + "<i>" + "yeah" + "</i>" + "<i>" + "hello" + "</i>" + "</div>" + "<p>" + "<span>foo</span>" + "<span>bar</span>" + "</p>" + "<u></u>" + "<b></b>" + "<em>hey</em>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + + body.findFirst("p").encloseContentsWith(body.findFirst("b")); + QCOMPARE(body.findAll("p b span").count(), 2); + QCOMPARE(body.findFirst("p b span").toPlainText(), QString("foo")); + + body.findFirst("u").encloseContentsWith("<i></i>"); + QCOMPARE(body.findAll("u i").count(), 1); + QCOMPARE(body.findFirst("u i").toPlainText(), QString()); + + body.findFirst("div").encloseContentsWith("<span></span>"); + QCOMPARE(body.findAll("div span i").count(), 2); + QCOMPARE(body.findFirst("div span i").toPlainText(), QString("yeah")); + + QString snippet = "" + "<table>" + "<tbody>" + "<tr>" + "<td></td>" + "<td></td>" + "</tr>" + "<tr>" + "<td></td>" + "<td></td>" + "<tr>" + "</tbody>" + "</table>"; + + body.findFirst("em").encloseContentsWith(snippet); + QCOMPARE(body.findFirst("em table tbody tr td").toPlainText(), QString("hey")); +} + +void tst_QWebElement::encloseWith() +{ + QString html = "<body>" + "<p>" + "foo" + "</p>" + "<div>" + "yeah" + "</div>" + "<p>" + "<span>bar</span>" + "</p>" + "<em>hey</em>" + "<h1>hello</h1>" + "</body>"; + + m_mainFrame->setHtml(html); + QWebElement body = m_mainFrame->documentElement().findFirst("body"); + + body.findFirst("p").encloseWith("<br>"); + QCOMPARE(body.findAll("br").count(), 0); + + QCOMPARE(body.findAll("div").count(), 1); + body.findFirst("div").encloseWith(body.findFirst("span").clone()); + QCOMPARE(body.findAll("div").count(), 1); + QCOMPARE(body.findAll("span").count(), 2); + QCOMPARE(body.findAll("p").count(), 2); + + body.findFirst("div").encloseWith("<code></code>"); + QCOMPARE(body.findAll("code").count(), 1); + QCOMPARE(body.findAll("code div").count(), 1); + QCOMPARE(body.findFirst("code div").toPlainText(), QString("yeah")); + + QString snippet = "" + "<table>" + "<tbody>" + "<tr>" + "<td></td>" + "<td></td>" + "</tr>" + "<tr>" + "<td></td>" + "<td></td>" + "<tr>" + "</tbody>" + "</table>"; + + body.findFirst("em").encloseWith(snippet); + QCOMPARE(body.findFirst("table tbody tr td em").toPlainText(), QString("hey")); + + // Enclosing the contents of an img tag is not allowed, but enclosing the img tag itself is. + body.findFirst("td").appendInside("<img src=\"test.png\">"); + QCOMPARE(body.findAll("img").count(), 1); + + QWebElement img = body.findFirst("img"); + QVERIFY(!img.isNull()); + img.encloseWith("<p id=\"success\"></p>"); + QCOMPARE(body.findAll("p#success").count(), 1); + + img.encloseContentsWith("<p id=\"fail\"></p>"); + QCOMPARE(body.findAll("p#fail").count(), 0); + +} + +void tst_QWebElement::nullSelect() +{ + m_mainFrame->setHtml("<body><p>Test"); + + QWebElementCollection collection = m_mainFrame->findAllElements("invalid{syn(tax;;%#$f223e>>"); + QVERIFY(collection.count() == 0); +} + +void tst_QWebElement::firstChildNextSibling() +{ + m_mainFrame->setHtml("<body><!--comment--><p>Test</p><!--another comment--><table>"); + + QWebElement body = m_mainFrame->findFirstElement("body"); + QVERIFY(!body.isNull()); + QWebElement p = body.firstChild(); + QVERIFY(!p.isNull()); + QCOMPARE(p.tagName(), QString("P")); + QWebElement table = p.nextSibling(); + QVERIFY(!table.isNull()); + QCOMPARE(table.tagName(), QString("TABLE")); + QVERIFY(table.nextSibling().isNull()); +} + +void tst_QWebElement::lastChildPreviousSibling() +{ + m_mainFrame->setHtml("<body><!--comment--><p>Test</p><!--another comment--><table>"); + + QWebElement body = m_mainFrame->findFirstElement("body"); + QVERIFY(!body.isNull()); + QWebElement table = body.lastChild(); + QVERIFY(!table.isNull()); + QCOMPARE(table.tagName(), QString("TABLE")); + QWebElement p = table.previousSibling(); + QVERIFY(!p.isNull()); + QCOMPARE(p.tagName(), QString("P")); + QVERIFY(p.previousSibling().isNull()); +} + +void tst_QWebElement::hasSetFocus() +{ + m_mainFrame->setHtml("<html><body>" \ + "<input type='text' id='input1'/>" \ + "<br>"\ + "<input type='text' id='input2'/>" \ + "</body></html>"); + + QWebElementCollection inputs = m_mainFrame->documentElement().findAll("input"); + QWebElement input1 = inputs.at(0); + input1.setFocus(); + QVERIFY(input1.hasFocus()); + + QWebElement input2 = inputs.at(1); + input2.setFocus(); + QVERIFY(!input1.hasFocus()); + QVERIFY(input2.hasFocus()); +} + +void tst_QWebElement::render() +{ + QString html( "<html>" + "<head><style>" + "body, iframe { margin: 0px; border: none; background: white; }" + "</style></head>" + "<body><table width='300px' height='300px' border='1'>" + "<tr>" + "<td>test" + "</td>" + "<td><img src='/service/qrc:///image.png'>" + "</td>" + "</tr>" + "</table>" + "</body>" + "</html>" + ); + + QWebPage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + page.mainFrame()->setHtml(html); + + waitForSignal(&page, SIGNAL(loadFinished(bool))); + QCOMPARE(loadSpy.count(), 1); + + QSize size = page.mainFrame()->contentsSize(); + page.setViewportSize(size); + + QWebElementCollection imgs = page.mainFrame()->findAllElements("img"); + QCOMPARE(imgs.count(), 1); + + QImage resource(":/image.png"); + QRect imageRect(0, 0, resource.width(), resource.height()); + + QImage testImage(resource.width(), resource.height(), QImage::Format_ARGB32); + QPainter painter0(&testImage); + painter0.fillRect(imageRect, Qt::white); + // render() uses pixmaps internally, and pixmaps might have bit depths + // other than 32, giving different pixel values due to rounding. + QPixmap pix = QPixmap::fromImage(resource); + painter0.drawPixmap(0, 0, pix); + painter0.end(); + + QImage image1(resource.width(), resource.height(), QImage::Format_ARGB32); + QPainter painter1(&image1); + painter1.fillRect(imageRect, Qt::white); + imgs[0].render(&painter1); + painter1.end(); + + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=65243", Continue); + QVERIFY(image1 == testImage); + + // render image 2nd time to make sure that cached rendering works fine + QImage image2(resource.width(), resource.height(), QImage::Format_ARGB32); + QPainter painter2(&image2); + painter2.fillRect(imageRect, Qt::white); + imgs[0].render(&painter2); + painter2.end(); + + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=65243", Continue); + QVERIFY(image2 == testImage); + + // compare table rendered through QWebElement::render to whole page table rendering + QRect tableRect(0, 0, 300, 300); + QWebElementCollection tables = page.mainFrame()->findAllElements("table"); + QCOMPARE(tables.count(), 1); + + QImage image3(300, 300, QImage::Format_ARGB32); + QPainter painter3(&image3); + painter3.fillRect(tableRect, Qt::white); + tables[0].render(&painter3); + painter3.end(); + + QImage image4(300, 300, QImage::Format_ARGB32); + QPainter painter4(&image4); + page.mainFrame()->render(&painter4, tableRect); + painter4.end(); + + QVERIFY(image3 == image4); + + // Chunked render test reuses page rendered in image4 in previous test + const int chunkHeight = tableRect.height(); + const int chunkWidth = tableRect.width() / 3; + QImage chunk(chunkWidth, chunkHeight, QImage::Format_ARGB32); + QRect chunkRect(0, 0, chunkWidth, chunkHeight); + for (int x = 0; x < tableRect.width(); x += chunkWidth) { + QPainter painter(&chunk); + painter.fillRect(chunkRect, Qt::white); + QRect chunkPaintRect(x, 0, chunkWidth, chunkHeight); + tables[0].render(&painter, chunkPaintRect); + painter.end(); + + QVERIFY(chunk == image4.copy(chunkPaintRect)); + } +} + +void tst_QWebElement::addElementToHead() +{ + m_mainFrame->setHtml("<html><head></head><body></body></html>"); + QWebElement head = m_mainFrame->findFirstElement("head"); + QVERIFY(!head.isNull()); + QString append = "<script type=\"text/javascript\">var t = 0;</script>"; + head.appendInside(append); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=102234", Continue); + QCOMPARE(head.toInnerXml(), append); +} + +QTEST_MAIN(tst_QWebElement) +#include "tst_qwebelement.moc" diff --git a/tests/webkitwidgets/qwebelement/tst_qwebelement.qrc b/tests/webkitwidgets/qwebelement/tst_qwebelement.qrc new file mode 100644 index 000000000..7384c76e0 --- /dev/null +++ b/tests/webkitwidgets/qwebelement/tst_qwebelement.qrc @@ -0,0 +1,7 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> +<file alias="style.css">resources/style.css</file> +<file alias="style2.css">resources/style2.css</file> +<file alias="image.png">resources/image.png</file> +</qresource> +</RCC> diff --git a/tests/webkitwidgets/qwebframe/qwebframe.pro b/tests/webkitwidgets/qwebframe/qwebframe.pro new file mode 100644 index 000000000..f434ccbc1 --- /dev/null +++ b/tests/webkitwidgets/qwebframe/qwebframe.pro @@ -0,0 +1,3 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc + diff --git a/tests/webkitwidgets/qwebframe/resources/image.png b/tests/webkitwidgets/qwebframe/resources/image.png Binary files differnew file mode 100644 index 000000000..8d703640c --- /dev/null +++ b/tests/webkitwidgets/qwebframe/resources/image.png diff --git a/tests/webkitwidgets/qwebframe/resources/style.css b/tests/webkitwidgets/qwebframe/resources/style.css new file mode 100644 index 000000000..c05b747f1 --- /dev/null +++ b/tests/webkitwidgets/qwebframe/resources/style.css @@ -0,0 +1 @@ +#idP {color: red !important} diff --git a/tests/webkitwidgets/qwebframe/resources/test1.html b/tests/webkitwidgets/qwebframe/resources/test1.html new file mode 100644 index 000000000..b323f966e --- /dev/null +++ b/tests/webkitwidgets/qwebframe/resources/test1.html @@ -0,0 +1 @@ +<html><body><p>Some text 1</p></body></html> diff --git a/tests/webkitwidgets/qwebframe/resources/test2.html b/tests/webkitwidgets/qwebframe/resources/test2.html new file mode 100644 index 000000000..63ac1f6ec --- /dev/null +++ b/tests/webkitwidgets/qwebframe/resources/test2.html @@ -0,0 +1 @@ +<html><body> <p>Some text 2</p></body></html> diff --git a/tests/webkitwidgets/qwebframe/resources/testiframe.html b/tests/webkitwidgets/qwebframe/resources/testiframe.html new file mode 100644 index 000000000..ee0f64d1b --- /dev/null +++ b/tests/webkitwidgets/qwebframe/resources/testiframe.html @@ -0,0 +1,53 @@ +<html>
+<head>
+<title></title>
+<style type="text/css">
+<!--
+#header {
+ background: #0f0;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 800px;
+ height: 100px;
+}
+#content1 {
+ background: #ff0;
+ position: absolute;
+ top: 101px;
+ left: 0px;
+ width: 400px;
+ height: 400px;
+ overflow: scroll;
+}
+#content2 {
+ background: #ff7;
+ position: absolute;
+ top: 101px;
+ left: 401px;
+ width: 400px;
+ height: 400px;
+}
+#footer {
+ background: #0f0;
+ position: absolute;
+ top: 502px;
+ left: 0px;
+ width: 800px;
+ height: 200px;
+}
+-->
+</style>
+</head>
+<body>
+<div id="header"></div>
+<div id="content1">You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.</div>
+<iframe id="content2" name="control" src="http://code.qt.io/testiframe2.html"> </iframe>
+<div id="footer"></div>
+</body>
+</html>
\ No newline at end of file diff --git a/tests/webkitwidgets/qwebframe/resources/testiframe2.html b/tests/webkitwidgets/qwebframe/resources/testiframe2.html new file mode 100644 index 000000000..483e94e1d --- /dev/null +++ b/tests/webkitwidgets/qwebframe/resources/testiframe2.html @@ -0,0 +1,20 @@ +<html>
+<head>
+<title></title>
+<style type="text/css">
+<!--
+#content {
+ background: #fff;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 800px;
+ height: 800px;
+}
+-->
+</style>
+</head>
+<body>
+<div id="content"> </div>
+</body>
+</html>
\ No newline at end of file diff --git a/tests/webkitwidgets/qwebframe/tst_qwebframe.cpp b/tests/webkitwidgets/qwebframe/tst_qwebframe.cpp new file mode 100644 index 000000000..d635f8e2c --- /dev/null +++ b/tests/webkitwidgets/qwebframe/tst_qwebframe.cpp @@ -0,0 +1,1601 @@ +/* + Copyright (C) 2008,2009 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include <QtTest/QtTest> + +#include <qwebpage.h> +#include <qwebelement.h> +#include <qwebview.h> +#include <qwebframe.h> +#include <qwebhistory.h> +#include <QAbstractItemView> +#include <QApplication> +#include <QComboBox> +#include <QPaintEngine> +#include <QPicture> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QTextCodec> +#ifndef QT_NO_OPENSSL +#include <qsslerror.h> +#endif +#include "../util.h" + +class tst_QWebFrame : public QObject +{ + Q_OBJECT + +public: + bool eventFilter(QObject* watched, QEvent* event); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void horizontalScrollAfterBack(); + void symmetricUrl(); + void progressSignal(); + void urlChange(); + void requestedUrl(); + void requestedUrlAfterSetAndLoadFailures(); + void javaScriptWindowObjectCleared_data(); + void javaScriptWindowObjectCleared(); + void javaScriptWindowObjectClearedOnEvaluate(); + void setHtml(); + void setHtmlWithImageResource(); + void setHtmlWithStylesheetResource(); + void setHtmlWithBaseURL(); + void setHtmlWithJSAlert(); + void ipv6HostEncoding(); + void metaData(); +#if !defined(QT_NO_COMBOBOX) + void popupFocus(); +#endif + void inputFieldFocus(); + void hitTestContent(); + void baseUrl_data(); + void baseUrl(); + void hasSetFocus(); + void renderGeometry(); + void renderHints(); + void scrollPosition(); + void scrollToAnchor(); + void scrollbarsOff(); + void evaluateWillCauseRepaint(); + void setContent_data(); + void setContent(); + void setCacheLoadControlAttribute(); + void setUrlWithPendingLoads(); + void setUrlWithFragment_data(); + void setUrlWithFragment(); + void setUrlToEmpty(); + void setUrlToInvalid(); + void setUrlHistory(); + void setUrlUsingStateObject(); + void setUrlSameUrl(); + void setUrlThenLoads_data(); + void setUrlThenLoads(); + void loadFinishedAfterNotFoundError(); + void signalsDuringErrorHandling(); + void loadInSignalHandlers_data(); + void loadInSignalHandlers(); + +private: + QWebView* m_view { nullptr }; + QWebPage* m_page { nullptr }; + QWebView* m_inputFieldsTestView { nullptr }; + int m_inputFieldTestPaintCount { 0 }; +}; + +bool tst_QWebFrame::eventFilter(QObject* watched, QEvent* event) +{ + // used on the inputFieldFocus test + if (watched == m_inputFieldsTestView) { + if (event->type() == QEvent::Paint) + m_inputFieldTestPaintCount++; + } + return QObject::eventFilter(watched, event); +} + +void tst_QWebFrame::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebFrame::cleanup() +{ + delete m_view; +} + +void tst_QWebFrame::symmetricUrl() +{ + QVERIFY(m_view->url().isEmpty()); + + QCOMPARE(m_view->history()->count(), 0); + + QUrl dataUrl("data:text/html,<h1>Test"); + + m_view->setUrl(dataUrl); + QCOMPARE(m_view->url(), dataUrl); + QCOMPARE(m_view->history()->count(), 0); + + // loading is _not_ immediate, so the text isn't set just yet. + QVERIFY(m_view->page()->mainFrame()->toPlainText().isEmpty()); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_view->history()->count(), 1); + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test")); + + QUrl dataUrl2("data:text/html,<h1>Test2"); + QUrl dataUrl3("data:text/html,<h1>Test3"); + + m_view->setUrl(dataUrl2); + m_view->setUrl(dataUrl3); + + QCOMPARE(m_view->url(), dataUrl3); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_view->history()->count(), 2); + + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test3")); +} + +void tst_QWebFrame::progressSignal() +{ + QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QVERIFY(progressSpy.size() >= 1); + QCOMPARE(progressSpy.last().first().toInt(), 100); +} + +void tst_QWebFrame::urlChange() +{ + QSignalSpy urlSpy(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 1); + + QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>"); + m_view->setUrl(dataUrl2); + + ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 2); +} + +class FakeReply : public QNetworkReply { + Q_OBJECT + +public: + static const QUrl urlFor404ErrorWithoutContents; + + FakeReply(const QNetworkRequest& request, QObject* parent = 0) + : QNetworkReply(parent) + { + setOperation(QNetworkAccessManager::GetOperation); + setRequest(request); + setUrl(request.url()); + if (request.url() == QUrl("qrc:/test1.html")) { + setHeader(QNetworkRequest::LocationHeader, QString("qrc:/test2.html")); + setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html")); + QTimer::singleShot(0, this, SLOT(continueRedirect())); + } +#ifndef QT_NO_OPENSSL + else if (request.url() == QUrl("qrc:/fake-ssl-error.html")) { + setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error!")); + QTimer::singleShot(0, this, SLOT(continueError())); + } +#endif + else if (request.url().host() == QLatin1String("abcdef.abcdef")) { + setError(QNetworkReply::HostNotFoundError, tr("Invalid URL")); + QTimer::singleShot(0, this, SLOT(continueError())); + } else if (request.url() == FakeReply::urlFor404ErrorWithoutContents) { + setError(QNetworkReply::ContentNotFoundError, "Not found"); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404); + QTimer::singleShot(0, this, SLOT(continueError())); + } + + open(QIODevice::ReadOnly); + } + ~FakeReply() + { + close(); + } + virtual void abort() {} + virtual void close() {} + +protected: + qint64 readData(char*, qint64) + { + return 0; + } + +private Q_SLOTS: + void continueRedirect() + { + emit metaDataChanged(); + emit finished(); + } + + void continueError() + { + emit error(this->error()); + emit finished(); + } +}; + +const QUrl FakeReply::urlFor404ErrorWithoutContents = QUrl("http://this.will/return-http-404-error-without-contents.html"); + +class FakeNetworkManager : public QNetworkAccessManager { + Q_OBJECT + +public: + FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { } + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData) + { + QString url = request.url().toString(); + if (op == QNetworkAccessManager::GetOperation) { +#ifndef QT_NO_OPENSSL + if (url == "qrc:/fake-ssl-error.html") { + FakeReply* reply = new FakeReply(request, this); + QList<QSslError> errors; + emit sslErrors(reply, errors << QSslError(QSslError::UnspecifiedError)); + return reply; + } +#endif + if (url == "qrc:/test1.html" || url == "http://abcdef.abcdef/" || request.url() == FakeReply::urlFor404ErrorWithoutContents) + return new FakeReply(request, this); + } + + return QNetworkAccessManager::createRequest(op, request, outgoingData); + } +}; + +void tst_QWebFrame::requestedUrl() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completely loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + frame->setUrl(QUrl("qrc:/test1.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/test1.html")); + QCOMPARE(frame->url(), QUrl("qrc:/test2.html")); + + frame->setUrl(QUrl("qrc:/non-existent.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 2); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/non-existent.html")); + QCOMPARE(frame->url(), QUrl("qrc:/non-existent.html")); + + frame->setUrl(QUrl("http://abcdef.abcdef/")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 3); + QCOMPARE(frame->requestedUrl(), QUrl("http://abcdef.abcdef/")); + QCOMPARE(frame->url(), QUrl("http://abcdef.abcdef/")); + +#ifndef QT_NO_OPENSSL + qRegisterMetaType<QList<QSslError> >("QList<QSslError>"); + qRegisterMetaType<QNetworkReply* >("QNetworkReply*"); + + QSignalSpy spy2(page.networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + frame->setUrl(QUrl("qrc:/fake-ssl-error.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy2.count(), 1); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/fake-ssl-error.html")); + QCOMPARE(frame->url(), QUrl("qrc:/fake-ssl-error.html")); +#endif +} + +void tst_QWebFrame::requestedUrlAfterSetAndLoadFailures() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + const QUrl first("http://abcdef.abcdef/"); + frame->setUrl(first); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), first); + QCOMPARE(frame->requestedUrl(), first); + QVERIFY(!spy.at(0).first().toBool()); + + const QUrl second("http://abcdef.abcdef/another_page.html"); + QVERIFY(first != second); + + page.settings()->setAttribute(QWebSettings::ErrorPageEnabled, false); + + frame->load(second); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), first); + QCOMPARE(frame->requestedUrl(), second); + QVERIFY(!spy.at(1).first().toBool()); + + page.settings()->setAttribute(QWebSettings::ErrorPageEnabled, true); + + frame->load(second); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), second); + QCOMPARE(frame->requestedUrl(), second); + QVERIFY(!spy.at(2).first().toBool()); +} + +void tst_QWebFrame::javaScriptWindowObjectCleared_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<int>("signalCount"); + QTest::newRow("with <script>") << "<html><body><script>i=0</script><p>hello world</p></body></html>" << 1; + // NOTE: Empty scripts no longer cause this signal to be emitted. + QTest::newRow("with empty <script>") << "<html><body><script></script><p>hello world</p></body></html>" << 0; + QTest::newRow("without <script>") << "<html><body><p>hello world</p></body></html>" << 0; +} + +void tst_QWebFrame::javaScriptWindowObjectCleared() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared())); + QFETCH(QString, html); + frame->setHtml(html); + + QFETCH(int, signalCount); + QCOMPARE(spy.count(), signalCount); +} + +void tst_QWebFrame::javaScriptWindowObjectClearedOnEvaluate() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared())); + frame->setHtml("<html></html>"); + QCOMPARE(spy.count(), 0); + frame->evaluateJavaScript("var a = 'a';"); + QCOMPARE(spy.count(), 1); + // no new clear for a new script: + frame->evaluateJavaScript("var a = 1;"); + QCOMPARE(spy.count(), 1); +} + +void tst_QWebFrame::setHtml() +{ + QString html("<html><head></head><body><p>hello world</p></body></html>"); + QSignalSpy spy(m_view->page(), SIGNAL(loadFinished(bool))); + m_view->page()->mainFrame()->setHtml(html); + QCOMPARE(m_view->page()->mainFrame()->toHtml(), html); + QCOMPARE(spy.count(), 1); +} + +void tst_QWebFrame::setHtmlWithImageResource() +{ + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to get a proper origin and load the local image. + + QLatin1String html("<html><body><p>hello world</p><img src='/service/qrc:/image.png'/></body></html>"); + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + frame->setHtml(html, QUrl(QLatin1String("file:///path/to/file"))); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128); + + // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources. + + frame->setHtml(html); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 0); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 0); +} + +void tst_QWebFrame::setHtmlWithStylesheetResource() +{ + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to be able to download the local stylesheet. + + const char* htmlData = + "<html>" + "<head>" + "<link rel='stylesheet' href='/service/qrc:/style.css' type='text/css' />" + "</head>" + "<body>" + "<p id='idP'>some text</p>" + "</body>" + "</html>"; + QLatin1String html(htmlData); + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QWebElement webElement; + + frame->setHtml(html, QUrl(QLatin1String("qrc:///file"))); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + webElement = frame->documentElement().findFirst("p"); + QCOMPARE(webElement.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("red")); + + // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources. + + frame->setHtml(html, QUrl(QLatin1String("http://www.example.com/"))); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + webElement = frame->documentElement().findFirst("p"); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(webElement.styleProperty("color", QWebElement::CascadedStyle), QString()); +} + +void tst_QWebFrame::setHtmlWithBaseURL() +{ + // This tests if baseUrl is indeed affecting the relative paths from resources. + // As we are using a local file as baseUrl, its security origin should be able to load local resources. + + if (!QDir(TESTS_SOURCE_DIR).exists()) + QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QString html("<html><body><p>hello world</p><img src='http://code.qt.io/qwebframe/resources/image.png'/></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completey loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + frame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128); + + // no history item has to be added. + QCOMPARE(m_view->page()->history()->count(), 0); +} + +class MyPage : public QWebPage +{ +public: + MyPage() : QWebPage(), alerts(0) {} + int alerts; + +protected: + virtual void javaScriptAlert(QWebFrame*, const QString& msg) + { + alerts++; + QCOMPARE(msg, QString("foo")); + // Should not be enough to trigger deferred loading, since we've upped the HTML + // tokenizer delay in the Qt frameloader. See HTMLTokenizer::continueProcessing() + QTest::qWait(1000); + } +}; + +void tst_QWebFrame::setHtmlWithJSAlert() +{ + QString html("<html><head></head><body><script>alert('foo');</script><p>hello world</p></body></html>"); + MyPage page; + m_view->setPage(&page); + page.mainFrame()->setHtml(html); + QCOMPARE(page.alerts, 1); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118663", Continue); + QCOMPARE(m_view->page()->mainFrame()->toHtml(), html); +} + +class TestNetworkManager : public QNetworkAccessManager +{ +public: + TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} + + QList<QUrl> requestedUrls; + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) { + requestedUrls.append(request.url()); + QNetworkRequest redirectedRequest = request; + redirectedRequest.setUrl(QUrl("data:text/html,<p>hello")); + return QNetworkAccessManager::createRequest(op, redirectedRequest, outgoingData); + } +}; + +void tst_QWebFrame::ipv6HostEncoding() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requestedUrls.clear(); + + QUrl baseUrl = QUrl::fromEncoded("http://[::1]/index.html"); + m_view->setHtml("<p>Hi", baseUrl); + m_view->page()->mainFrame()->evaluateJavaScript("var r = new XMLHttpRequest();" + "r.open('GET', 'http://code.qt.io/service/http://[::1]/test.xml', false);" + "r.send(null);" + ); + QCOMPARE(networkManager->requestedUrls.count(), 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl::fromEncoded("http://[::1]/test.xml")); +} + +void tst_QWebFrame::metaData() +{ + m_view->setHtml("<html>" + " <head>" + " <meta name=\"description\" content=\"Test description\">" + " <meta name=\"keywords\" content=\"HTML, JavaScript, Css\">" + " </head>" + "</html>"); + + QMultiMap<QString, QString> metaData = m_view->page()->mainFrame()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QCOMPARE(metaData.value("description"), QString("Test description")); + QCOMPARE(metaData.value("keywords"), QString("HTML, JavaScript, Css")); + QCOMPARE(metaData.value("nonexistant"), QString()); + + m_view->setHtml("<html>" + " <head>" + " <meta name=\"samekey\" content=\"FirstValue\">" + " <meta name=\"samekey\" content=\"SecondValue\">" + " </head>" + "</html>"); + + metaData = m_view->page()->mainFrame()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QStringList values = metaData.values("samekey"); + QCOMPARE(values.count(), 2); + + QVERIFY(values.contains("FirstValue")); + QVERIFY(values.contains("SecondValue")); + + QCOMPARE(metaData.value("nonexistant"), QString()); +} + +#if !defined(QT_NO_COMBOBOX) +void tst_QWebFrame::popupFocus() +{ + QWebView view; + view.setHtml("<html>" + " <body>" + " <select name=\"select\">" + " <option>1</option>" + " <option>2</option>" + " </select>" + " <input type=\"text\"> </input>" + " <textarea name=\"text_area\" rows=\"3\" cols=\"40\">" + "This test checks whether showing and hiding a popup" + "takes the focus away from the webpage." + " </textarea>" + " </body>" + "</html>"); + view.resize(400, 100); + // Call setFocus before show to work around http://bugreports.qt.nokia.com/browse/QTBUG-14762 + view.setFocus(); + view.show(); + QTest::qWaitForWindowExposed(&view); + view.activateWindow(); + QTRY_VERIFY(view.hasFocus()); + + // open the popup by clicking. check if focus is on the popup + const QWebElement webCombo = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("select[name=select]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center()); + + QComboBox* combo = view.findChild<QComboBox*>(); + QVERIFY(combo != 0); + QTRY_VERIFY(!view.hasFocus() && combo->view()->hasFocus()); // Focus should be on the popup + + // hide the popup and check if focus is on the page + combo->hidePopup(); + QTRY_VERIFY(view.hasFocus()); // Focus should be back on the WebView +} +#endif + +void tst_QWebFrame::inputFieldFocus() +{ + QWebView view; + view.setHtml("<html><body><input type=\"text\"></input></body></html>"); + view.resize(400, 100); + view.show(); + QTest::qWaitForWindowExposed(&view); + view.activateWindow(); + view.setFocus(); + QTRY_VERIFY(view.hasFocus()); + + // double the flashing time, should at least blink once already + int delay = qApp->cursorFlashTime() * 2; + + // focus the lineedit and check if it blinks + bool autoSipEnabled = qApp->autoSipEnabled(); + qApp->setAutoSipEnabled(false); + const QWebElement inputElement = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, inputElement.geometry().center()); + m_inputFieldsTestView = &view; + view.installEventFilter( this ); + QTest::qWait(delay); + QVERIFY2(m_inputFieldTestPaintCount >= 3, + "The input field should have a blinking caret"); + qApp->setAutoSipEnabled(autoSipEnabled); +} + +void tst_QWebFrame::hitTestContent() +{ + QString html("<html><body><p>A paragraph</p><br/><br/><br/><a href=\"about:blank\" target=\"_foo\" id=\"link\">link text</a></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->setHtml(html); + page.setViewportSize(QSize(200, 0)); //no height so link is not visible + const QWebElement linkElement = frame->documentElement().findFirst(QLatin1String("a#link")); + QWebHitTestResult result = frame->hitTestContent(linkElement.geometry().center()); + QCOMPARE(result.linkText(), QString("link text")); + QWebElement link = result.linkElement(); + QCOMPARE(link.attribute("target"), QString("_foo")); + QCOMPARE(result.element().tagName(), QString("A")); +} + +void tst_QWebFrame::baseUrl_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QUrl>("loadUrl"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("null") << QString() << QUrl() + << QUrl("about:blank") << QUrl("about:blank"); + + QTest::newRow("foo") << QString() << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/"); + + QString html = "<html>" + "<head>" + "<base href=\"http://foobaz.bar//" />" + "</head>" + "</html>"; + QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/"); +} + +void tst_QWebFrame::baseUrl() +{ + QFETCH(QString, html); + QFETCH(QUrl, loadUrl); + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + + m_page->mainFrame()->setHtml(html, loadUrl); + QEXPECT_FAIL("null", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(m_page->mainFrame()->url(), url); + QEXPECT_FAIL("null", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(m_page->mainFrame()->baseUrl(), baseUrl); +} + +void tst_QWebFrame::hasSetFocus() +{ + QString html("<html><body><p>top</p>" \ + "<iframe width='80%' height='30%'/>" \ + "</body></html>"); + + QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); + m_page->mainFrame()->setHtml(html); + + waitForSignal(m_page->mainFrame(), SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.size(), 1); + + QList<QWebFrame*> children = m_page->mainFrame()->childFrames(); + QWebFrame* frame = children.at(0); + QString innerHtml("<html><body><p>another iframe</p>" \ + "<iframe width='80%' height='30%'/>" \ + "</body></html>"); + frame->setHtml(innerHtml); + + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.size(), 2); + + m_page->mainFrame()->setFocus(); + QTRY_VERIFY(m_page->mainFrame()->hasFocus()); + + for (int i = 0; i < children.size(); ++i) { + children.at(i)->setFocus(); + QTRY_VERIFY(children.at(i)->hasFocus()); + QVERIFY(!m_page->mainFrame()->hasFocus()); + } + + m_page->mainFrame()->setFocus(); + QTRY_VERIFY(m_page->mainFrame()->hasFocus()); +} + +void tst_QWebFrame::renderGeometry() +{ + QString html("<html>" \ + "<head><style>" \ + "body, iframe { margin: 0px; border: none; }" \ + "</style></head>" \ + "<body><iframe width='100px' height='100px'/></body>" \ + "</html>"); + + QWebPage page; + page.mainFrame()->setHtml(html); + + QList<QWebFrame*> frames = page.mainFrame()->childFrames(); + QWebFrame *frame = frames.at(0); + QString innerHtml("<body style='margin: 0px;'><img src='/service/qrc:/image.png'/></body>"); + + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to get a proper origin. + frame->setHtml(innerHtml, QUrl("file:///path/to/file")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + + QPicture picture; + + QSize size = page.mainFrame()->contentsSize(); + page.setViewportSize(size); + + // render contents layer only (the iframe is smaller than the image, so it will have scrollbars) + QPainter painter1(&picture); + frame->render(&painter1, QWebFrame::ContentsLayer); + painter1.end(); + + QCOMPARE(size.width(), picture.boundingRect().width() + frame->scrollBarGeometry(Qt::Vertical).width()); + QCOMPARE(size.height(), picture.boundingRect().height() + frame->scrollBarGeometry(Qt::Horizontal).height()); + + // render everything, should be the size of the iframe + QPainter painter2(&picture); + frame->render(&painter2, QWebFrame::AllLayers); + painter2.end(); + + QCOMPARE(size.width(), picture.boundingRect().width()); // width: 100px + QCOMPARE(size.height(), picture.boundingRect().height()); // height: 100px +} + + +class DummyPaintEngine: public QPaintEngine { +public: + + DummyPaintEngine() + : QPaintEngine(QPaintEngine::AllFeatures) + , renderHints(0) + { + } + + bool begin(QPaintDevice*) + { + setActive(true); + return true; + } + + bool end() + { + setActive(false); + return false; + } + + void updateState(const QPaintEngineState& state) + { + renderHints = state.renderHints(); + } + + void drawPath(const QPainterPath&) { } + void drawPixmap(const QRectF&, const QPixmap&, const QRectF&) { } + + QPaintEngine::Type type() const + { + return static_cast<QPaintEngine::Type>(QPaintEngine::User + 2); + } + + QPainter::RenderHints renderHints; +}; + +class DummyPaintDevice: public QPaintDevice { +public: + DummyPaintDevice() + : QPaintDevice() + , m_engine(new DummyPaintEngine) + { + } + + ~DummyPaintDevice() + { + delete m_engine; + } + + QPaintEngine* paintEngine() const + { + return m_engine; + } + + QPainter::RenderHints renderHints() const + { + return m_engine->renderHints; + } + +protected: + int metric(PaintDeviceMetric metric) const; + +private: + DummyPaintEngine* m_engine; + friend class DummyPaintEngine; +}; + + +int DummyPaintDevice::metric(PaintDeviceMetric metric) const +{ + switch (metric) { + case PdmWidth: + return 400; + break; + + case PdmHeight: + return 200; + break; + + case PdmNumColors: + return INT_MAX; + break; + + case PdmDepth: + return 32; + break; + + default: + break; + } + return 0; +} + +void tst_QWebFrame::renderHints() +{ + QString html("<html><body><p>Hello, world!</p></body></html>"); + + QWebPage page; + page.mainFrame()->setHtml(html); + page.setViewportSize(page.mainFrame()->contentsSize()); + + // We will call frame->render and trap the paint engine state changes + // to ensure that GraphicsContext does not clobber the render hints. + DummyPaintDevice buffer; + QPainter painter(&buffer); + + painter.setRenderHint(QPainter::TextAntialiasing, false); + page.mainFrame()->render(&painter); + QVERIFY(!(buffer.renderHints() & QPainter::TextAntialiasing)); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::TextAntialiasing, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::HighQualityAntialiasing, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(buffer.renderHints() & QPainter::HighQualityAntialiasing); +} + +void tst_QWebFrame::scrollPosition() +{ + // enlarged image in a small viewport, to provoke the scrollbars to appear + QString html("<html><body><img src='/service/qrc:/image.png' height=500 width=500/></body></html>"); + + QWebPage page; + page.setViewportSize(QSize(200, 200)); + + QWebFrame* frame = page.mainFrame(); + frame->setHtml(html); + frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + // try to set the scroll offset programmatically + frame->setScrollPosition(QPoint(23, 29)); + QCOMPARE(frame->scrollPosition().x(), 23); + QCOMPARE(frame->scrollPosition().y(), 29); + + int x = frame->evaluateJavaScript("window.scrollX").toInt(); + int y = frame->evaluateJavaScript("window.scrollY").toInt(); + QCOMPARE(x, 23); + QCOMPARE(y, 29); +} + +void tst_QWebFrame::scrollToAnchor() +{ + QWebPage page; + page.setViewportSize(QSize(480, 800)); + QWebFrame* frame = page.mainFrame(); + + QString html("<html><body><p style=\"margin-bottom: 1500px;\">Hello.</p>" + "<p><a id=\"foo\">This</a> is an anchor</p>" + "<p style=\"margin-bottom: 1500px;\"><a id=\"bar\">This</a> is another anchor</p>" + "</body></html>"); + frame->setHtml(html); + frame->setScrollPosition(QPoint(0, 0)); + QCOMPARE(frame->scrollPosition().x(), 0); + QCOMPARE(frame->scrollPosition().y(), 0); + + QWebElement fooAnchor = frame->findFirstElement("a[id=foo]"); + + frame->scrollToAnchor("foo"); + QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top()); + + frame->scrollToAnchor("bar"); + frame->scrollToAnchor("foo"); + QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top()); + + frame->scrollToAnchor("top"); + QCOMPARE(frame->scrollPosition().y(), 0); + + frame->scrollToAnchor("bar"); + frame->scrollToAnchor("notexist"); + QVERIFY(frame->scrollPosition().y() != 0); +} + + +void tst_QWebFrame::scrollbarsOff() +{ + QWebView view; + QWebFrame* mainFrame = view.page()->mainFrame(); + + mainFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + mainFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + QString html("<script>" \ + " function checkScrollbar() {" \ + " if (innerWidth === document.documentElement.offsetWidth)" \ + " document.getElementById('span1').innerText = 'SUCCESS';" \ + " else" \ + " document.getElementById('span1').innerText = 'FAIL';" \ + " }" \ + "</script>" \ + "<body>" \ + " <div style='margin-top:1000px ; margin-left:1000px'>" \ + " <a id='offscreen' href='http://code.qt.io/a'>End</a>" \ + " </div>" \ + "<span id='span1'></span>" \ + "</body>"); + + + QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); + view.setHtml(html); + ::waitForSignal(&view, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.count(), 1); + + mainFrame->evaluateJavaScript("checkScrollbar();"); + QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS")); +} + +void tst_QWebFrame::horizontalScrollAfterBack() +{ + QWebView view; + QWebFrame* frame = view.page()->mainFrame(); + QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); + + view.page()->settings()->setMaximumPagesInCache(2); + frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded); + frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded); + + view.load(QUrl("qrc:/testiframe2.html")); + view.resize(200, 200); + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height()); + + view.load(QUrl("qrc:/testiframe.html")); + QTRY_COMPARE(loadSpy.count(), 2); + + view.page()->triggerAction(QWebPage::Back); + QTRY_COMPARE(loadSpy.count(), 3); + QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height()); +} + +void tst_QWebFrame::evaluateWillCauseRepaint() +{ + QWebView view; + QString html("<html><body>top<div id=\"junk\" style=\"display: block;\">" + "junk</div>bottom</body></html>"); + view.setHtml(html); + view.show(); + + QTest::qWaitForWindowExposed(&view); + view.page()->mainFrame()->evaluateJavaScript( + "document.getElementById('junk').style.display = 'none';"); + + ::waitForSignal(view.page(), SIGNAL(repaintRequested(QRect))); +} + +void tst_QWebFrame::setContent_data() +{ + QTest::addColumn<QString>("mimeType"); + QTest::addColumn<QByteArray>("testContents"); + QTest::addColumn<QString>("expected"); + + QString str = QString::fromUtf8("ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει"); + QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str; + + QTextCodec *utf16 = QTextCodec::codecForName("UTF-16"); + if (utf16) + QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str; + + str = QString::fromUtf8("Une chaîne de caractères à sa façon."); + QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str; + + +} + +void tst_QWebFrame::setContent() +{ + QFETCH(QString, mimeType); + QFETCH(QByteArray, testContents); + QFETCH(QString, expected); + m_view->setContent(testContents, mimeType); + QWebFrame* mainFrame = m_view->page()->mainFrame(); + QCOMPARE(expected , mainFrame->toPlainText()); +} + +class CacheNetworkAccessManager : public QNetworkAccessManager { +public: + CacheNetworkAccessManager(QObject* parent = 0) + : QNetworkAccessManager(parent) + , m_lastCacheLoad(QNetworkRequest::PreferNetwork) + { + } + + virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*) + { + QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute); + if (cacheLoad.isValid()) + m_lastCacheLoad = static_cast<QNetworkRequest::CacheLoadControl>(cacheLoad.toUInt()); + else + m_lastCacheLoad = QNetworkRequest::PreferNetwork; // default value + return new FakeReply(request, this); + } + + QNetworkRequest::CacheLoadControl lastCacheLoad() const + { + return m_lastCacheLoad; + } + +private: + QNetworkRequest::CacheLoadControl m_lastCacheLoad; +}; + +void tst_QWebFrame::setCacheLoadControlAttribute() +{ + QWebPage page; + CacheNetworkAccessManager* manager = new CacheNetworkAccessManager(&page); + page.setNetworkAccessManager(manager); + QWebFrame* frame = page.mainFrame(); + + QNetworkRequest request(QUrl("http://abcdef.abcdef/")); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysNetwork); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferNetwork); +} + +void tst_QWebFrame::setUrlWithPendingLoads() +{ + QWebPage page; + page.mainFrame()->setHtml("<img src='/service/dummy:'/>"); + page.mainFrame()->setUrl(QUrl("about:blank")); +} + +void tst_QWebFrame::setUrlWithFragment_data() +{ + QTest::addColumn<QUrl>("previousUrl"); + QTest::newRow("empty") << QUrl(); + QTest::newRow("same URL no fragment") << QUrl("qrc:/test1.html"); + // See comments in setUrlSameUrl about using setUrl() with the same url(). + QTest::newRow("same URL with same fragment") << QUrl("qrc:/test1.html#"); + QTest::newRow("same URL with different fragment") << QUrl("qrc:/test1.html#anotherFragment"); + QTest::newRow("another URL") << QUrl("qrc:/test2.html"); +} + +// Based on bug report https://bugs.webkit.org/show_bug.cgi?id=32723 +void tst_QWebFrame::setUrlWithFragment() +{ + QFETCH(QUrl, previousUrl); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + if (!previousUrl.isEmpty()) { + frame->load(previousUrl); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), previousUrl); + } + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + const QUrl url("/service/qrc:/test1.html#"); + QVERIFY(!url.fragment().isNull()); + + frame->setUrl(url); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), 1); + QVERIFY(!frame->toPlainText().isEmpty()); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->url(), url); +} + +void tst_QWebFrame::setUrlToEmpty() +{ + int expectedLoadFinishedCount = 0; + const QUrl aboutBlank("about:blank"); + const QUrl url("/service/qrc:/test2.html"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QCOMPARE(frame->url(), QUrl()); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), QUrl()); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + // Set existing url + frame->setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), url); + + // Set empty url + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), aboutBlank); + + // Set existing url + frame->setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), url); + + // Load empty url + frame->load(QUrl()); + expectedLoadFinishedCount++; + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), aboutBlank); +} + +void tst_QWebFrame::setUrlToInvalid() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + const QUrl invalidUrl("http:/example.com"); + QVERIFY(!invalidUrl.isEmpty()); + QVERIFY(invalidUrl != QUrl()); + + // QWebFrame will do its best to accept the URL, possible converting it to a valid equivalent URL. + const QUrl validUrl("http://example.com/"); + frame->setUrl(invalidUrl); + QCOMPARE(frame->url(), validUrl); + QCOMPARE(frame->requestedUrl(), validUrl); + QCOMPARE(frame->baseUrl(), validUrl); + + // QUrls equivalent to QUrl() will be treated as such. + const QUrl aboutBlank("about:blank"); + const QUrl anotherInvalidUrl("1http://bugs.webkit.org"); + QVERIFY(!anotherInvalidUrl.isEmpty()); // and they are not necessarily empty. + QVERIFY(!anotherInvalidUrl.isValid()); + QCOMPARE(anotherInvalidUrl.toEncoded(), QUrl().toEncoded()); + + frame->setUrl(anotherInvalidUrl); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl().toEncoded(), anotherInvalidUrl.toEncoded()); + QCOMPARE(frame->baseUrl(), aboutBlank); +} + +void tst_QWebFrame::setUrlHistory() +{ + const QUrl aboutBlank("about:blank"); + QUrl url; + int expectedLoadFinishedCount = 0; + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_page->history()->count(), 0); + + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("http://non.existant/"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 1); + + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 2); + + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(m_page->history()->count(), 2); + + // Loading same page as current in history, so history count doesn't change. + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 2); + + url = QUrl("qrc:/test2.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 3); +} + +void tst_QWebFrame::setUrlUsingStateObject() +{ + const QUrl aboutBlank("about:blank"); + QUrl url; + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl))); + int expectedUrlChangeCount = 0; + + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), url); + QCOMPARE(m_page->history()->count(), 1); + + frame->evaluateJavaScript("window.history.pushState(null,'push', 'navigate/to/here')"); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), QUrl("qrc:/navigate/to/here")); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(m_page->history()->canGoBack()); + + frame->evaluateJavaScript("window.history.replaceState(null,'replace', 'another/location')"); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), QUrl("qrc:/navigate/to/another/location")); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(!m_page->history()->canGoForward()); + QVERIFY(m_page->history()->canGoBack()); + + frame->evaluateJavaScript("window.history.back()"); + QTest::qWait(100); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), QUrl("qrc:/test1.html")); + QVERIFY(m_page->history()->canGoForward()); + QVERIFY(!m_page->history()->canGoBack()); +} + +void tst_QWebFrame::setUrlSameUrl() +{ + const QUrl url1("qrc:/test1.html"); + const QUrl url2("qrc:/test2.html"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QVERIFY(frame->url() != url1); // Nota bene: our QNAM redirects url1 to url2 + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 1); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QVERIFY(frame->url() != url1); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 2); + + // Now a case without redirect. The existing behavior we have for setUrl() + // is more like a "clear(); load()", so the page will be loaded again, even + // if urlToBeLoaded == url(). This test should be changed if we want to + // make setUrl() early return in this case. + frame->setUrl(url2); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 3); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 4); +} + +static inline QUrl extractBaseUrl(const QUrl& url) +{ + return url.resolved(QUrl()); +} + +void tst_QWebFrame::setUrlThenLoads_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("resource file") << QUrl("qrc:/test1.html") << extractBaseUrl(QUrl("qrc:/test1.html")); + QTest::newRow("base specified in HTML") << QUrl("data:text/html,<head><base href=\"http://different.base//"></head>") << QUrl("http://different.base/"); +} + +void tst_QWebFrame::setUrlThenLoads() +{ + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl))); + QSignalSpy startedSpy(frame, SIGNAL(loadStarted())); + QSignalSpy finishedSpy(frame, SIGNAL(loadFinished(bool))); + + frame->setUrl(url); + QCOMPARE(startedSpy.count(), 1); + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 1); + QVERIFY(finishedSpy.at(0).first().toBool()); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), baseUrl); + + const QUrl urlToLoad1("qrc:/test2.html"); + const QUrl urlToLoad2("qrc:/test1.html"); + + // Just after first load. URL didn't changed yet. + frame->load(urlToLoad1); + QCOMPARE(startedSpy.count(), 2); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), urlToLoad1); + QCOMPARE(frame->baseUrl(), baseUrl); + + // After first URL changed. + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 2); + QVERIFY(finishedSpy.at(1).first().toBool()); + QCOMPARE(frame->url(), urlToLoad1); + QCOMPARE(frame->requestedUrl(), urlToLoad1); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1)); + + // Just after second load. URL didn't changed yet. + frame->load(urlToLoad2); + QCOMPARE(startedSpy.count(), 3); + QCOMPARE(frame->url(), urlToLoad1); + QCOMPARE(frame->requestedUrl(), urlToLoad2); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1)); + + // After second URL changed. + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 3); + QVERIFY(finishedSpy.at(2).first().toBool()); + QCOMPARE(frame->url(), urlToLoad2); + QCOMPARE(frame->requestedUrl(), urlToLoad2); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad2)); +} + +void tst_QWebFrame::loadFinishedAfterNotFoundError() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + frame->setUrl(FakeReply::urlFor404ErrorWithoutContents); + QTRY_COMPARE(spy.count(), 1); + const bool wasLoadOk = spy.at(0).at(0).toBool(); + QVERIFY(!wasLoadOk); +} + +void tst_QWebFrame::signalsDuringErrorHandling() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + QSignalSpy loadStartedSpy(frame, &QWebFrame::loadStarted); + QSignalSpy loadFinishedSpy(frame, &QWebFrame::loadFinished); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + frame->setUrl(FakeReply::urlFor404ErrorWithoutContents); + QTRY_COMPARE(loadStartedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + bool wasLoadOk = loadFinishedSpy.at(0).at(0).toBool(); + QVERIFY(!wasLoadOk); + + frame->load(QUrl("http://example.com/")); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(loadStartedSpy.count(), 2); + QCOMPARE(loadFinishedSpy.count(), 2); + wasLoadOk = loadFinishedSpy.at(1).at(0).toBool(); + QVERIFY(wasLoadOk); +} + +class URLSetter : public QObject { + Q_OBJECT + +public: + enum Signal { + LoadStarted, + LoadFinished, + ProvisionalLoad + }; + + enum Type { + UseLoad, + UseSetUrl + }; + + URLSetter(QWebFrame*, Signal, Type, const QUrl&); + +public Q_SLOTS: + void execute(); + +Q_SIGNALS: + void finished(); + +private: + QWebFrame* m_frame; + QUrl m_url; + Type m_type; +}; + +Q_DECLARE_METATYPE(URLSetter::Signal) +Q_DECLARE_METATYPE(URLSetter::Type) + +URLSetter::URLSetter(QWebFrame* frame, Signal signal, URLSetter::Type type, const QUrl& url) + : m_frame(frame), m_http://code.qt.io/url(url), m_type(type) +{ + if (signal == LoadStarted) + connect(m_frame, SIGNAL(loadStarted()), SLOT(execute())); + else if (signal == LoadFinished) + connect(m_frame, SIGNAL(loadFinished(bool)), SLOT(execute())); + else + connect(m_frame, SIGNAL(provisionalLoad()), SLOT(execute())); +} + +void URLSetter::execute() +{ + // We track only the first emission. + m_frame->disconnect(this); + if (m_type == URLSetter::UseLoad) + m_frame->load(m_url); + else + m_frame->setUrl(m_url); + connect(m_frame, SIGNAL(loadFinished(bool)), SIGNAL(finished())); +} + +void tst_QWebFrame::loadInSignalHandlers_data() +{ + QTest::addColumn<URLSetter::Type>("type"); + QTest::addColumn<URLSetter::Signal>("signal"); + QTest::addColumn<QUrl>("url"); + + const QUrl validUrl("qrc:/test2.html"); + const QUrl invalidUrl("qrc:/invalid"); + + QTest::newRow("call load() in loadStarted() after valid url") << URLSetter::UseLoad << URLSetter::LoadStarted << validUrl; + QTest::newRow("call load() in loadStarted() after invalid url") << URLSetter::UseLoad << URLSetter::LoadStarted << invalidUrl; + QTest::newRow("call load() in loadFinished() after valid url") << URLSetter::UseLoad << URLSetter::LoadFinished << validUrl; + QTest::newRow("call load() in loadFinished() after invalid url") << URLSetter::UseLoad << URLSetter::LoadFinished << invalidUrl; + QTest::newRow("call load() in provisionalLoad() after valid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << validUrl; + QTest::newRow("call load() in provisionalLoad() after invalid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << invalidUrl; + + QTest::newRow("call setUrl() in loadStarted() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << validUrl; + QTest::newRow("call setUrl() in loadStarted() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << invalidUrl; + QTest::newRow("call setUrl() in loadFinished() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << validUrl; + QTest::newRow("call setUrl() in loadFinished() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << invalidUrl; + QTest::newRow("call setUrl() in provisionalLoad() after valid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << validUrl; + QTest::newRow("call setUrl() in provisionalLoad() after invalid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << invalidUrl; +} + +void tst_QWebFrame::loadInSignalHandlers() +{ + QFETCH(URLSetter::Type, type); + QFETCH(URLSetter::Signal, signal); + QFETCH(QUrl, url); + + QWebFrame* frame = m_page->mainFrame(); + const QUrl urlForSetter("qrc:/test1.html"); + URLSetter setter(frame, signal, type, urlForSetter); + + frame->load(url); + waitForSignal(&setter, SIGNAL(finished()), 200); + QCOMPARE(frame->url(), urlForSetter); +} + +QTEST_MAIN(tst_QWebFrame) +#include "tst_qwebframe.moc" diff --git a/tests/webkitwidgets/qwebframe/tst_qwebframe.qrc b/tests/webkitwidgets/qwebframe/tst_qwebframe.qrc new file mode 100644 index 000000000..2a7d0b9c2 --- /dev/null +++ b/tests/webkitwidgets/qwebframe/tst_qwebframe.qrc @@ -0,0 +1,10 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> +<file alias="image.png">resources/image.png</file> +<file alias="style.css">resources/style.css</file> +<file alias="test1.html">resources/test1.html</file> +<file alias="test2.html">resources/test2.html</file> +<file alias="testiframe.html">resources/testiframe.html</file> +<file alias="testiframe2.html">resources/testiframe2.html</file> +</qresource> +</RCC> diff --git a/tests/webkitwidgets/qwebhistory/qwebhistory.pro b/tests/webkitwidgets/qwebhistory/qwebhistory.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/qwebhistory.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebhistory/resources/page1.html b/tests/webkitwidgets/qwebhistory/resources/page1.html new file mode 100644 index 000000000..82fa4aff1 --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/resources/page1.html @@ -0,0 +1 @@ +<title>page1</title><body><h1>page1</h1></body> diff --git a/tests/webkitwidgets/qwebhistory/resources/page2.html b/tests/webkitwidgets/qwebhistory/resources/page2.html new file mode 100644 index 000000000..5307bdcfd --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/resources/page2.html @@ -0,0 +1 @@ +<title>page2</title><body><h1>page2</h1></body> diff --git a/tests/webkitwidgets/qwebhistory/resources/page3.html b/tests/webkitwidgets/qwebhistory/resources/page3.html new file mode 100644 index 000000000..4e5547c7e --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/resources/page3.html @@ -0,0 +1 @@ +<title>page3</title><body><h1>page3</h1></body> diff --git a/tests/webkitwidgets/qwebhistory/resources/page4.html b/tests/webkitwidgets/qwebhistory/resources/page4.html new file mode 100644 index 000000000..3c57aeddc --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/resources/page4.html @@ -0,0 +1 @@ +<title>page4</title><body><h1>page4</h1></body> diff --git a/tests/webkitwidgets/qwebhistory/resources/page5.html b/tests/webkitwidgets/qwebhistory/resources/page5.html new file mode 100644 index 000000000..859355279 --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/resources/page5.html @@ -0,0 +1 @@ +<title>page5</title><body><h1>page5</h1></body> diff --git a/tests/webkitwidgets/qwebhistory/resources/page6.html b/tests/webkitwidgets/qwebhistory/resources/page6.html new file mode 100644 index 000000000..c5bbc6f79 --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/resources/page6.html @@ -0,0 +1 @@ +<title>page6</title><body><h1>page6</h1></body> diff --git a/tests/webkitwidgets/qwebhistory/tst_qwebhistory.cpp b/tests/webkitwidgets/qwebhistory/tst_qwebhistory.cpp new file mode 100644 index 000000000..8df3d26d6 --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/tst_qwebhistory.cpp @@ -0,0 +1,528 @@ +/* + Copyright (C) 2008 Holger Hans Peter Freyther + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <QtTest/QtTest> +#include <QAction> + +#include "../util.h" +#include "qwebpage.h" +#include "qwebview.h" +#include "qwebframe.h" +#include "qwebhistory.h" +#include "qdebug.h" + +class tst_QWebHistory : public QObject +{ + Q_OBJECT + +public: + tst_QWebHistory(); + virtual ~tst_QWebHistory(); + +protected : + void loadPage(int nr) + { + frame->load(QUrl("qrc:/resources/page" + QString::number(nr) + ".html")); + loadFinishedBarrier->ensureSignalEmitted(); + } + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void title(); + void count(); + void back(); + void forward(); + void itemAt(); + void goToItem(); + void items(); + void serialize_1(); //QWebHistory countity + void serialize_2(); //QWebHistory index + void serialize_3(); //QWebHistoryItem + // Those tests shouldn't crash + void saveAndRestore_crash_1(); + void saveAndRestore_crash_2(); + void saveAndRestore_crash_3(); + void saveAndRestore_crash_4(); + + void popPushState_data(); + void popPushState(); + void clear(); + void restoreIncompatibleVersion1(); + + +private: + QWebPage* page { nullptr }; + QWebFrame* frame { nullptr }; + QWebHistory* hist { nullptr }; + QScopedPointer<SignalBarrier> loadFinishedBarrier; + int histsize {0}; +}; + +tst_QWebHistory::tst_QWebHistory() +{ +} + +tst_QWebHistory::~tst_QWebHistory() +{ +} + +void tst_QWebHistory::init() +{ + page = new QWebPage(this); + frame = page->mainFrame(); + loadFinishedBarrier.reset(new SignalBarrier(frame, SIGNAL(loadFinished(bool)))); + + for (int i = 1;i < 6;i++) { + loadPage(i); + } + hist = page->history(); + histsize = 5; +} + +void tst_QWebHistory::cleanup() +{ + loadFinishedBarrier.reset(); + delete page; +} + +/** + * Check QWebHistoryItem::title() method + */ +void tst_QWebHistory::title() +{ + QCOMPARE(hist->currentItem().title(), QString("page5")); +} + +/** + * Check QWebHistory::count() method + */ +void tst_QWebHistory::count() +{ + QCOMPARE(hist->count(), histsize); +} + +/** + * Check QWebHistory::back() method + */ +void tst_QWebHistory::back() +{ + for (int i = histsize;i > 1;i--) { + QCOMPARE(page->mainFrame()->toPlainText(), QStringLiteral("page%1").arg(i)); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + } + //try one more time (too many). crash test + hist->back(); + QCOMPARE(page->mainFrame()->toPlainText(), QString("page1")); +} + +/** + * Check QWebHistory::forward() method + */ +void tst_QWebHistory::forward() +{ + //rewind history :-) + while (hist->canGoBack()) { + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + } + + for (int i = 1;i < histsize;i++) { + QCOMPARE(page->mainFrame()->toPlainText(), QStringLiteral("page%1").arg(i)); + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + } + //try one more time (too many). crash test + hist->forward(); + QCOMPARE(page->mainFrame()->toPlainText(), QStringLiteral("page%1").arg(histsize)); +} + +/** + * Check QWebHistory::itemAt() method + */ +void tst_QWebHistory::itemAt() +{ + for (int i = 1;i < histsize;i++) { + QCOMPARE(hist->itemAt(i - 1).title(), QStringLiteral("page%1").arg(i)); + QVERIFY(hist->itemAt(i - 1).isValid()); + } + //check out of range values + QVERIFY(!hist->itemAt(-1).isValid()); + QVERIFY(!hist->itemAt(histsize).isValid()); +} + +/** + * Check QWebHistory::goToItem() method + */ +void tst_QWebHistory::goToItem() +{ + QWebHistoryItem current = hist->currentItem(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + QVERIFY(hist->currentItem().title() != current.title()); + hist->goToItem(current); + loadFinishedBarrier->ensureSignalEmitted(); + QCOMPARE(hist->currentItem().title(), current.title()); +} + +/** + * Check QWebHistory::items() method + */ +void tst_QWebHistory::items() +{ + QList<QWebHistoryItem> items = hist->items(); + //check count + QCOMPARE(histsize, items.count()); + + //check order + for (int i = 1;i <= histsize;i++) { + QCOMPARE(items.at(i - 1).title(), QStringLiteral("page%1").arg(i)); + } +} + +/** + * Check history state after serialization (pickle, persistent..) method + * Checks history size, history order + */ +void tst_QWebHistory::serialize_1() +{ + QByteArray tmp; //buffer + QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved + QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded + + save << *hist; + QVERIFY(save.status() == QDataStream::Ok); + QCOMPARE(hist->count(), histsize); + + //check size of history + //load next page to find differences + loadPage(6); + QCOMPARE(hist->count(), histsize + 1); + load >> *hist; + QVERIFY(load.status() == QDataStream::Ok); + QCOMPARE(hist->count(), histsize); + + //check order of historyItems + QList<QWebHistoryItem> items = hist->items(); + for (int i = 1;i <= histsize;i++) { + QCOMPARE(items.at(i - 1).title(), QStringLiteral("page%1").arg(i)); + } +} + +/** + * Check history state after serialization (pickle, persistent..) method + * Checks history currentIndex value + */ +void tst_QWebHistory::serialize_2() +{ + QByteArray tmp; //buffer + QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved + QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded + + // Force a "same document" navigation. + frame->load(QUrl(frame->url().toString() + QLatin1String("#dummyAnchor"))); + + int initialCurrentIndex = hist->currentItemIndex(); + + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + //check if current index was changed (make sure that it is not last item) + QVERIFY(hist->currentItemIndex() != initialCurrentIndex); + //save current index + int oldCurrentIndex = hist->currentItemIndex(); + + save << *hist; + QVERIFY(save.status() == QDataStream::Ok); + load >> *hist; + QVERIFY(load.status() == QDataStream::Ok); + + //check current index + QCOMPARE(hist->currentItemIndex(), oldCurrentIndex); + + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + QCOMPARE(hist->currentItemIndex(), initialCurrentIndex); +} + +/** + * Check history state after serialization (pickle, persistent..) method + * Checks QWebHistoryItem public property after serialization + */ +void tst_QWebHistory::serialize_3() +{ + QByteArray tmp; //buffer + QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved + QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded + + //prepare two different history items + QWebHistoryItem a = hist->currentItem(); + a.setUserData("A - user data"); + + //check properties BEFORE serialization + QString title(a.title()); + QDateTime lastVisited(a.lastVisited()); + QUrl originalUrl(a.originalUrl()); + QUrl url(http://code.qt.io/a.url()); + QVariant userData(a.userData()); + + save << *hist; + QVERIFY(save.status() == QDataStream::Ok); + QVERIFY(!load.atEnd()); + hist->clear(); + QVERIFY(hist->count() == 1); + load >> *hist; + QVERIFY(load.status() == QDataStream::Ok); + QWebHistoryItem b = hist->currentItem(); + + //check properties AFTER serialization + QCOMPARE(b.title(), title); + QCOMPARE(b.lastVisited(), lastVisited); + QCOMPARE(b.originalUrl(), originalUrl); + QCOMPARE(b.url(), url); + QCOMPARE(b.userData(), userData); + + //Check if all data was read + QVERIFY(load.atEnd()); +} + +static void saveHistory(QWebHistory* history, QByteArray* in) +{ + in->clear(); + QDataStream save(in, QIODevice::WriteOnly); + save << *history; +} + +static void restoreHistory(QWebHistory* history, QByteArray* out) +{ + QDataStream load(out, QIODevice::ReadOnly); + load >> *history; +} + +void tst_QWebHistory::saveAndRestore_crash_1() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + for (unsigned i = 0; i < 5; i++) { + restoreHistory(hist, &buffer); + saveHistory(hist, &buffer); + } +} + +void tst_QWebHistory::saveAndRestore_crash_2() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + QWebPage* page2 = new QWebPage(this); + QWebHistory* hist2 = page2->history(); + for (unsigned i = 0; i < 5; i++) { + restoreHistory(hist2, &buffer); + saveHistory(hist2, &buffer); + } + delete page2; +} + +void tst_QWebHistory::saveAndRestore_crash_3() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + QWebPage* page2 = new QWebPage(this); + QWebHistory* hist1 = hist; + QWebHistory* hist2 = page2->history(); + for (unsigned i = 0; i < 5; i++) { + restoreHistory(hist1, &buffer); + restoreHistory(hist2, &buffer); + QVERIFY(hist1->count() == hist2->count()); + QVERIFY(hist1->count() == histsize); + hist2->back(); + saveHistory(hist2, &buffer); + hist2->clear(); + } + delete page2; +} + +void tst_QWebHistory::saveAndRestore_crash_4() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + + QWebPage* page2 = new QWebPage(this); + // The initial crash was in PageCache. + page2->settings()->setMaximumPagesInCache(3); + + // Load the history in a new page, waiting for the load to finish. + QEventLoop waitForLoadFinished; + QObject::connect(page2, SIGNAL(loadFinished(bool)), &waitForLoadFinished, SLOT(quit()), Qt::QueuedConnection); + QDataStream load(&buffer, QIODevice::ReadOnly); + load >> *page2->history(); + waitForLoadFinished.exec(); + + delete page2; + // Give some time for the PageCache cleanup 0-timer to fire. + QTest::qWait(50); +} + +void tst_QWebHistory::popPushState_data() +{ + QTest::addColumn<QString>("script"); + QTest::newRow("pushState") << "history.pushState(123, \"foo\");"; + QTest::newRow("replaceState") << "history.replaceState(\"a\", \"b\");"; + QTest::newRow("back") << "history.back();"; + QTest::newRow("forward") << "history.forward();"; + QTest::newRow("clearState") << "history.clearState();"; +} + +/** Crash test, WebKit bug 38840 (https://bugs.webkit.org/show_bug.cgi?id=38840) */ +void tst_QWebHistory::popPushState() +{ + QFETCH(QString, script); + QWebPage page; + page.mainFrame()->setHtml("<html><body>long live Qt!</body></html>"); + page.mainFrame()->evaluateJavaScript(script); +} + +/** ::clear */ +void tst_QWebHistory::clear() +{ + QByteArray buffer; + + QAction* actionBack = page->action(QWebPage::Back); + QVERIFY(actionBack->isEnabled()); + saveHistory(hist, &buffer); + QVERIFY(hist->count() > 1); + hist->clear(); + QVERIFY(hist->count() == 1); // Leave current item. + QVERIFY(!actionBack->isEnabled()); + + QWebPage* page2 = new QWebPage(this); + QWebHistory* hist2 = page2->history(); + QVERIFY(hist2->count() == 0); + hist2->clear(); + QVERIFY(hist2->count() == 0); // Do not change anything. + delete page2; +} + +// static void dumpCurrentVersion(QWebHistory* history) +// { +// QByteArray buffer; +// saveHistory(history, &buffer); +// printf(" static const char version1Dump[] = {"); +// for (int i = 0; i < buffer.size(); ++i) { +// bool newLine = !(i % 15); +// bool last = i == buffer.size() - 1; +// printf("%s0x%.2x%s", newLine ? "\n " : "", (unsigned char)buffer[i], last ? "" : ", "); +// } +// printf("};\n"); +// } + +void tst_QWebHistory::restoreIncompatibleVersion1() +{ + // Uncomment this code to generate a dump similar to the one below with the current stream version. + // dumpCurrentVersion(hist); + static const unsigned char version1Dump[] = { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, + 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x68, + 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, + 0x67, 0x00, 0x65, 0x00, 0x31, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, + 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, + 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, + 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, + 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x68, 0x00, + 0x74, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, + 0x00, 0x65, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, + 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x32, + 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, + 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, + 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, + 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, + 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, + 0x65, 0x00, 0x33, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, + 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, + 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x33, 0x00, + 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, + 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, + 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, + 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, + 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, + 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x34, 0x00, 0x2e, + 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, + 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, + 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, + 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, + 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, + 0x35, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, + 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x35, 0x00, 0x2e, 0x00, + 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + QByteArray version1(reinterpret_cast<const char*>(version1Dump), sizeof(version1Dump)); + QDataStream stream(&version1, QIODevice::ReadOnly); + + // This should fail to load, the history should be cleared and the stream should be broken. + stream >> *hist; + QVERIFY(!hist->canGoBack()); + QVERIFY(!hist->canGoForward()); + QVERIFY(stream.status() == QDataStream::ReadCorruptData); +} + +QTEST_MAIN(tst_QWebHistory) +#include "tst_qwebhistory.moc" diff --git a/tests/webkitwidgets/qwebhistory/tst_qwebhistory.qrc b/tests/webkitwidgets/qwebhistory/tst_qwebhistory.qrc new file mode 100644 index 000000000..6e2f50a95 --- /dev/null +++ b/tests/webkitwidgets/qwebhistory/tst_qwebhistory.qrc @@ -0,0 +1,11 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/page1.html</file> + <file>resources/page2.html</file> + <file>resources/page3.html</file> + <file>resources/page4.html</file> + <file>resources/page5.html</file> + <file>resources/page6.html</file> +</qresource> +</RCC> + diff --git a/tests/webkitwidgets/qwebhistoryinterface/qwebhistoryinterface.pro b/tests/webkitwidgets/qwebhistoryinterface/qwebhistoryinterface.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebhistoryinterface/qwebhistoryinterface.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebhistoryinterface/tst_qwebhistoryinterface.cpp b/tests/webkitwidgets/qwebhistoryinterface/tst_qwebhistoryinterface.cpp new file mode 100644 index 000000000..91d1c997f --- /dev/null +++ b/tests/webkitwidgets/qwebhistoryinterface/tst_qwebhistoryinterface.cpp @@ -0,0 +1,92 @@ +/* + Copyright (C) 2008 Holger Hans Peter Freyther + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include <QtTest/QtTest> + +#include <qwebpage.h> +#include <qwebview.h> +#include <qwebframe.h> +#include <qwebelement.h> +#include <qwebhistoryinterface.h> + +class tst_QWebHistoryInterface : public QObject +{ + Q_OBJECT + +public: + tst_QWebHistoryInterface(); + virtual ~tst_QWebHistoryInterface(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void visitedLinks(); + +private: + QWebView* m_view { nullptr }; + QWebPage* m_page { nullptr }; +}; + +tst_QWebHistoryInterface::tst_QWebHistoryInterface() +{ +} + +tst_QWebHistoryInterface::~tst_QWebHistoryInterface() +{ +} + +void tst_QWebHistoryInterface::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebHistoryInterface::cleanup() +{ + delete m_view; +} + +class FakeHistoryImplementation : public QWebHistoryInterface { +public: + void addHistoryEntry(const QString&) {} + bool historyContains(const QString& url) const { + return url == QLatin1String("http://www.trolltech.com/"); + } +}; + + +/* + * Test that visited links are properly colored. http://www.trolltech.com is marked + * as visited, so the below website should have exactly one element in the a:visited + * state. + */ +void tst_QWebHistoryInterface::visitedLinks() +{ + QWebHistoryInterface::setDefaultInterface(new FakeHistoryImplementation); + m_view->setHtml("<html><style>:link{color:green}:visited{color:red}</style><body><a href='http://www.trolltech.com/' id='vlink'>Trolltech</a></body></html>"); + QWebElement anchor = m_view->page()->mainFrame()->findFirstElement("a[id=vlink]"); + QString linkColor = anchor.styleProperty("color", QWebElement::ComputedStyle); + QCOMPARE(linkColor, QString::fromLatin1("rgb(255, 0, 0)")); +} + +QTEST_MAIN(tst_QWebHistoryInterface) +#include "tst_qwebhistoryinterface.moc" diff --git a/tests/webkitwidgets/qwebinspector/qwebinspector.pro b/tests/webkitwidgets/qwebinspector/qwebinspector.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebinspector/qwebinspector.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebinspector/tst_qwebinspector.cpp b/tests/webkitwidgets/qwebinspector/tst_qwebinspector.cpp new file mode 100644 index 000000000..37e62f67b --- /dev/null +++ b/tests/webkitwidgets/qwebinspector/tst_qwebinspector.cpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <QtTest/QtTest> + +#include <qdir.h> +#include <qwebinspector.h> +#include <qwebpage.h> +#include <qwebsettings.h> + +class tst_QWebInspector : public QObject { + Q_OBJECT + +private Q_SLOTS: + void attachAndDestroyPageFirst(); + void attachAndDestroyInspectorFirst(); + void attachAndDestroyInternalInspector(); +}; + +void tst_QWebInspector::attachAndDestroyPageFirst() +{ + // External inspector + manual destruction of page first + QWebPage* page = new QWebPage(); + page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + QWebInspector* inspector = new QWebInspector(); + inspector->setPage(page); + page->updatePositionDependentActions(QPoint(0, 0)); + page->triggerAction(QWebPage::InspectElement); + + delete page; + delete inspector; +} + +void tst_QWebInspector::attachAndDestroyInspectorFirst() +{ + // External inspector + manual destruction of inspector first + QWebPage* page = new QWebPage(); + page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + QWebInspector* inspector = new QWebInspector(); + inspector->setPage(page); + page->updatePositionDependentActions(QPoint(0, 0)); + page->triggerAction(QWebPage::InspectElement); + + delete inspector; + delete page; +} + +void tst_QWebInspector::attachAndDestroyInternalInspector() +{ + // Internal inspector + QWebPage page; + page.settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + page.updatePositionDependentActions(QPoint(0, 0)); + page.triggerAction(QWebPage::InspectElement); +} + +QTEST_MAIN(tst_QWebInspector) + +#include "tst_qwebinspector.moc" diff --git a/tests/webkitwidgets/qwebpage/qwebpage.pro b/tests/webkitwidgets/qwebpage/qwebpage.pro new file mode 100644 index 000000000..e56bbe8f7 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/qwebpage.pro @@ -0,0 +1,3 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc +QT *= core-private gui-private diff --git a/tests/webkitwidgets/qwebpage/resources/content.html b/tests/webkitwidgets/qwebpage/resources/content.html new file mode 100644 index 000000000..823a98306 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/content.html @@ -0,0 +1,6 @@ +<html> +<body> +<a>This is test content</a> +</body> +</html> + diff --git a/tests/webkitwidgets/qwebpage/resources/frame_a.html b/tests/webkitwidgets/qwebpage/resources/frame_a.html new file mode 100644 index 000000000..9ff68f13a --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/frame_a.html @@ -0,0 +1,2 @@ +<a href="http://google.com/" target="frame_b"><img src="" width=100 height=100 alt="Google"></a> +<a href="http://yahoo.com/" target="frame_b"><img src="" width=100 height=100 alt="Yahoo"></a> diff --git a/tests/webkitwidgets/qwebpage/resources/frame_c.html b/tests/webkitwidgets/qwebpage/resources/frame_c.html new file mode 100644 index 000000000..eba9ca0eb --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/frame_c.html @@ -0,0 +1 @@ +<a href="http://code.qt.io/content.html" target="frame_b"><img src="" width=100 height=100 alt="Google"></a> diff --git a/tests/webkitwidgets/qwebpage/resources/framedindex.html b/tests/webkitwidgets/qwebpage/resources/framedindex.html new file mode 100644 index 000000000..be4500483 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/framedindex.html @@ -0,0 +1,6 @@ +<html> +<frameset cols="25%,75%"> + <frame src="http://code.qt.io/frame_c.html" name="frame_c"> + <frame src="http://code.qt.io/frame_b.html" name="frame_b"> +</frameset> +</html> diff --git a/tests/webkitwidgets/qwebpage/resources/iframe.html b/tests/webkitwidgets/qwebpage/resources/iframe.html new file mode 100644 index 000000000..f17027c7a --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/iframe.html @@ -0,0 +1,6 @@ +<html> +<body> +<p>top</p> +<iframe src="http://code.qt.io/iframe2.html" width="80%" height="30%"/> +</body> +</html> diff --git a/tests/webkitwidgets/qwebpage/resources/iframe2.html b/tests/webkitwidgets/qwebpage/resources/iframe2.html new file mode 100644 index 000000000..501743597 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/iframe2.html @@ -0,0 +1,7 @@ +<html> +<body> +<p>another iframe</p> +<iframe src="http://code.qt.io/iframe3.html" width="80%" height="30%"></iframe> +</body> +</html> + diff --git a/tests/webkitwidgets/qwebpage/resources/iframe3.html b/tests/webkitwidgets/qwebpage/resources/iframe3.html new file mode 100644 index 000000000..ed6ac5b94 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/iframe3.html @@ -0,0 +1,5 @@ +<html> +<body> +<p>inner</p> +</body> +</html> diff --git a/tests/webkitwidgets/qwebpage/resources/index.html b/tests/webkitwidgets/qwebpage/resources/index.html new file mode 100644 index 000000000..638df364e --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/index.html @@ -0,0 +1,6 @@ +<html> +<frameset cols="25%,75%"> + <frame src="http://code.qt.io/frame_a.html" name="frame_a"> + <frame src="http://code.qt.io/frame_b.html" name="frame_b"> +</frameset> +</html> diff --git a/tests/webkitwidgets/qwebpage/resources/script.html b/tests/webkitwidgets/qwebpage/resources/script.html new file mode 100644 index 000000000..ede986415 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/script.html @@ -0,0 +1,3 @@ +<html><head> +<script language="javascript" type="text/javascript" src="http://code.qt.io/does_not_exist.js"></script> +</head></html> diff --git a/tests/webkitwidgets/qwebpage/resources/user.css b/tests/webkitwidgets/qwebpage/resources/user.css new file mode 100644 index 000000000..4ccb2f0fc --- /dev/null +++ b/tests/webkitwidgets/qwebpage/resources/user.css @@ -0,0 +1,3 @@ +p { + background-image: url('http://does.not/exist.png'); +}
\ No newline at end of file diff --git a/tests/webkitwidgets/qwebpage/tst_qwebpage.cpp b/tests/webkitwidgets/qwebpage/tst_qwebpage.cpp new file mode 100644 index 000000000..eb50384e7 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/tst_qwebpage.cpp @@ -0,0 +1,3518 @@ +/* + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + Copyright (C) 2009 Girish Ramakrishnan <[email protected]> + Copyright (C) 2010 Holger Hans Peter Freyther + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "../util.h" +#include <QClipboard> +#include <QDir> +#include <QGraphicsWidget> +#include <QLineEdit> +#include <QMainWindow> +#include <QMenu> +#include <QMimeDatabase> +#include <QPushButton> +#include <QRegExp> +#include <QStateMachine> +#include <QStyle> +#include <QtTest/QtTest> +#include <QTextCharFormat> +#include <private/qinputmethod_p.h> +#include <qgraphicsscene.h> +#include <qgraphicsview.h> +#include <qgraphicswebview.h> +#include <qnetworkcookiejar.h> +#include <qnetworkreply.h> +#include <qnetworkrequest.h> +#include <qpa/qplatforminputcontext.h> +#include <qwebdatabase.h> +#include <qwebelement.h> +#include <qwebframe.h> +#include <qwebhistory.h> +#include <qwebpage.h> +#include <qwebsecurityorigin.h> +#include <qwebview.h> +#include <qimagewriter.h> + +#ifdef HAVE_QTTESTSUPPORT +#include "../WebCoreSupport/DumpRenderTreeSupportQt.h" +#endif + +static void removeRecursive(const QString& dirname) +{ + QDir dir(dirname); + QFileInfoList entries(dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)); + for (int i = 0; i < entries.count(); ++i) + if (entries[i].isDir()) + removeRecursive(entries[i].filePath()); + else + dir.remove(entries[i].fileName()); + QDir().rmdir(dirname); +} + +class TestInputContext : public QPlatformInputContext +{ +public: + TestInputContext() + : m_visible(false) + { + QInputMethodPrivate* inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod()); + inputMethodPrivate->testContext = this; + } + + ~TestInputContext() + { + QInputMethodPrivate* inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod()); + inputMethodPrivate->testContext = 0; + } + + virtual void showInputPanel() + { + m_visible = true; + } + virtual void hideInputPanel() + { + m_visible = false; + } + virtual bool isInputPanelVisible() const + { + return m_visible; + } + + bool m_visible; +}; + +class tst_QWebPage : public QObject +{ + Q_OBJECT + +public: + tst_QWebPage(); + virtual ~tst_QWebPage(); + +public Q_SLOTS: + void init(); + void cleanup(); + void cleanupFiles(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + +#ifdef HAVE_QTTESTSUPPORT + void thirdPartyCookiePolicy(); +#endif + + void contextMenuCopy(); + void contextMenuPopulatedOnce(); + void acceptNavigationRequest(); + void domainSpecificKeyEvent(); + void geolocationRequestJS(); + void loadFinished(); + void actionStates(); + void popupFormSubmission(); + void acceptNavigationRequestWithNewWindow(); + void userStyleSheet(); + void userStyleSheetFromLocalFileUrl(); + void userStyleSheetFromQrcUrl(); + void loadHtml5Video(); + void modified(); + void contextMenuCrash(); + void updatePositionDependentActionsCrash(); + void database(); + void createPluginWithPluginsEnabled(); + void createPluginWithPluginsDisabled(); + void destroyPlugin_data(); + void destroyPlugin(); + void createViewlessPlugin_data(); + void createViewlessPlugin(); + void graphicsWidgetPlugin(); + +#ifdef HAVE_QTTESTSUPPORT + void multiplePageGroupsAndLocalStorage(); +#endif + + void cursorMovements(); + void textSelection(); + void textEditing(); + void backActionUpdate(); + void frameAt(); + void requestCache(); + void loadCachedPage(); + +#ifdef HAVE_QTTESTSUPPORT + void protectBindingsRuntimeObjectsFromCollector(); +#endif + + void localURLSchemes(); + void testOptionalJSObjects(); + void testLocalStorageVisibility(); + void testEnablePersistentStorage(); + void consoleOutput(); + void inputMethods_data(); + void inputMethods(); + void inputMethodsTextFormat_data(); + void inputMethodsTextFormat(); + void defaultTextEncoding(); + void errorPageExtension(); + void errorPageExtensionInIFrames(); + void errorPageExtensionInFrameset(); + void userAgentApplicationName(); + void userAgentNewlineStripping(); + void undoActionHaveCustomText(); + + void viewModes(); + + void crashTests_LazyInitializationOfMainFrame(); + + void screenshot_data(); + void screenshot(); + + void changeVisibilityState(); + +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL + void acceleratedWebGLScreenshotWithoutView(); + void unacceleratedWebGLScreenshotWithoutView(); +#endif + + void originatingObjectInNetworkRequests(); + void networkReplyParentDidntChange(); + void destroyQNAMBeforeAbortDoesntCrash(); + void testJSPrompt(); + void showModalDialog(); + void testStopScheduledPageRefresh(); + void findText(); + void supportedContentType(); + // [Qt] tst_QWebPage::infiniteLoopJS() timeouts with DFG JIT + // https://bugs.webkit.org/show_bug.cgi?id=79040 + // void infiniteLoopJS(); + void navigatorCookieEnabled(); + void deleteQWebViewTwice(); + void renderOnRepaintRequestedShouldNotRecurse(); + void loadSignalsOrder_data(); + void loadSignalsOrder(); + void openWindowDefaultSize(); + void cssMediaTypeGlobalSetting(); + void cssMediaTypePageSetting(); + +#ifdef Q_OS_MACOS + void macCopyUnicodeToClipboard(); +#endif + +private: + QWebView* m_view { nullptr }; + QWebPage* m_page { nullptr }; + QString tmpDirPath() const + { + static QString tmpd = QDir::tempPath() + "/tst_qwebpage-" + + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddhhmmss")); + return tmpd; + } +}; + +tst_QWebPage::tst_QWebPage() +{ +} + +tst_QWebPage::~tst_QWebPage() +{ +} + +void tst_QWebPage::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebPage::cleanup() +{ + delete m_view; +} + +void tst_QWebPage::cleanupFiles() +{ + removeRecursive(tmpDirPath()); +} + +void tst_QWebPage::initTestCase() +{ + cleanupFiles(); // In case there are old files from previous runs +} + +void tst_QWebPage::cleanupTestCase() +{ + cleanupFiles(); // Be nice +} + +class NavigationRequestOverride : public QWebPage +{ +public: + NavigationRequestOverride(QWebView* parent, bool initialValue) : QWebPage(parent), m_acceptNavigationRequest(initialValue) {} + + bool m_acceptNavigationRequest; +protected: + virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, QWebPage::NavigationType type) { + Q_UNUSED(frame); + Q_UNUSED(request); + Q_UNUSED(type); + + return m_acceptNavigationRequest; + } +}; + +void tst_QWebPage::acceptNavigationRequest() +{ + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + NavigationRequestOverride* newPage = new NavigationRequestOverride(m_view, false); + m_view->setPage(newPage); + + m_view->setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>" + "<input type='text'><input type='submit'></form></body></html>"), QUrl()); + QTRY_COMPARE(loadSpy.count(), 1); + + m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();"); + + newPage->m_acceptNavigationRequest = true; + m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();"); + QTRY_COMPARE(loadSpy.count(), 2); + + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("foo?")); + + // Restore default page + m_view->setPage(0); +} + +void tst_QWebPage::domainSpecificKeyEvent() +{ + QWebView webView; + webView.show(); + QTest::qWaitForWindowExposed(&webView); + + webView.setHtml(QLatin1String("<html><head>" + "<script>" + "var receivedKeyArray = new Array();" + "function keyEvent(e) {" + "receivedKeyArray.push(e.type+':'+e.keyCode+':'+e.charCode);" + "};" + "window.onkeyup = keyEvent; window.onkeypress = keyEvent; window.onkeydown = keyEvent;" + "</script></head><body>test</body></html>")); + + // Enable settings to use nativeVirtualKey as DOM key value. + webView.page()->setProperty("_q_useNativeVirtualKeyAsDOMKey", true); + // Simulate domain specific keyevent to WebKit by passing it as nativeVirtualKey in QKeyEvent. + // Qt::Key_Pause --> 0x51 + QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + keyEvent = QKeyEvent(QEvent::KeyRelease, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + const QLatin1String expectedReceivedKeyArray1("keydown:81:0,keyup:81:0"); + QString receivedKeyArray = webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray")).toStringList().join(","); + QVERIFY(receivedKeyArray == expectedReceivedKeyArray1); + + // Normal PC keyboard key converstion flow shouldn't be affected when sending nativeVirtual key as 0. + // Qt::Key_Pause --> VK_PAUSE(0x13) + webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray = new Array()")); // Reset + keyEvent = QKeyEvent(QEvent::KeyPress, Qt::Key_Pause, Qt::NoModifier, 0, 0, 0); + QApplication::sendEvent(&webView, &keyEvent); + keyEvent = QKeyEvent(QEvent::KeyRelease, Qt::Key_Pause, Qt::NoModifier, 0, 0, 0); + QApplication::sendEvent(&webView, &keyEvent); + const QLatin1String expectedReceivedKeyArray2("keydown:19:0,keyup:19:0"); + receivedKeyArray = webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray")).toStringList().join(","); + QVERIFY(receivedKeyArray == expectedReceivedKeyArray2); + + // Negative case. + // Disable settings to use nativeVirtualKey as DOM key value. + webView.page()->setProperty("_q_useNativeVirtualKeyAsDOMKey", false); + // Qt::Key_Pause --> VK_PAUSE(0x13) + webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray = new Array()")); // Reset + keyEvent = QKeyEvent(QEvent::KeyPress, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + keyEvent = QKeyEvent(QEvent::KeyRelease, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + const QLatin1String expectedReceivedKeyArray3("keydown:19:0,keyup:19:0"); + receivedKeyArray = webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray")).toStringList().join(","); + QVERIFY(receivedKeyArray == expectedReceivedKeyArray3); +} + +class JSTestPage : public QWebPage +{ +Q_OBJECT +public: + JSTestPage(QObject* parent = 0) + : QWebPage(parent) {} + + virtual bool shouldInterruptJavaScript() + { + return true; + } +public Q_SLOTS: + void requestPermission(QWebFrame* frame, QWebPage::Feature feature) + { + if (m_allowGeolocation) + setFeaturePermission(frame, feature, PermissionGrantedByUser); + else + setFeaturePermission(frame, feature, PermissionDeniedByUser); + } + +public: + void setGeolocationPermission(bool allow) + { + m_allowGeolocation = allow; + } + +private: + bool m_allowGeolocation { false }; +}; + +// [Qt] tst_QWebPage::infiniteLoopJS() timeouts with DFG JIT +// https://bugs.webkit.org/show_bug.cgi?id=79040 +/* +void tst_QWebPage::infiniteLoopJS() +{ + JSTestPage* newPage = new JSTestPage(m_view); + m_view->setPage(newPage); + m_view->setHtml(QString("<html><body>test</body></html>"), QUrl()); + m_view->page()->mainFrame()->evaluateJavaScript("var run = true;var a = 1;while(run){a++;}"); + delete newPage; +} +*/ + +void tst_QWebPage::geolocationRequestJS() +{ + JSTestPage* newPage = new JSTestPage(m_view); + + if (newPage->mainFrame()->evaluateJavaScript(QLatin1String("!navigator.geolocation")).toBool()) { + delete newPage; + QSKIP("Geolocation is not supported.", SkipSingle); + } + + connect(newPage, SIGNAL(featurePermissionRequested(QWebFrame*, QWebPage::Feature)), + newPage, SLOT(requestPermission(QWebFrame*, QWebPage::Feature))); + + newPage->setGeolocationPermission(false); + m_view->setPage(newPage); + m_view->setHtml(QString("<html><body>test</body></html>"), QUrl()); + m_view->page()->mainFrame()->evaluateJavaScript("var errorCode = 0; function error(err) { errorCode = err.code; } function success(pos) { } navigator.geolocation.getCurrentPosition(success, error)"); + QTest::qWait(2000); + QVariant empty = m_view->page()->mainFrame()->evaluateJavaScript("errorCode"); + + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=102235", Continue); + QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 0); + + newPage->setGeolocationPermission(true); + m_view->page()->mainFrame()->evaluateJavaScript("errorCode = 0; navigator.geolocation.getCurrentPosition(success, error);"); + empty = m_view->page()->mainFrame()->evaluateJavaScript("errorCode"); + + //http://dev.w3.org/geo/api/spec-source.html#position + //PositionError: const unsigned short PERMISSION_DENIED = 1; + QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 1); + delete newPage; +} + +void tst_QWebPage::loadFinished() +{ + qRegisterMetaType<QWebFrame*>("QWebFrame*"); + qRegisterMetaType<QNetworkRequest*>("QNetworkRequest*"); + QSignalSpy spyLoadStarted(m_view, SIGNAL(loadStarted())); + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + + m_view->page()->mainFrame()->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," + "<head><meta http-equiv='refresh' content='1'></head>foo \">" + "<frame src=\"data:text/html,bar\"></frameset>")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + + QTRY_VERIFY(spyLoadStarted.count() > 1); + QTRY_VERIFY(spyLoadFinished.count() > 1); + + spyLoadFinished.clear(); + + m_view->page()->mainFrame()->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," + "foo \"><frame src=\"data:text/html,bar\"></frameset>")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + QCOMPARE(spyLoadFinished.count(), 1); +} + +void tst_QWebPage::actionStates() +{ + QWebPage* page = m_view->page(); + + page->mainFrame()->load(QUrl("qrc:///resources/script.html")); + + QAction* reloadAction = page->action(QWebPage::Reload); + QAction* stopAction = page->action(QWebPage::Stop); + + QTRY_VERIFY(reloadAction->isEnabled()); + QTRY_VERIFY(!stopAction->isEnabled()); +} + +class ConsolePage : public QWebPage +{ +public: + ConsolePage(QObject* parent = 0) : QWebPage(parent) {} + + virtual void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID) + { + messages.append(message); + lineNumbers.append(lineNumber); + sourceIDs.append(sourceID); + } + + QStringList messages; + QList<int> lineNumbers; + QStringList sourceIDs; +}; + +void tst_QWebPage::consoleOutput() +{ + ConsolePage page; + page.mainFrame()->evaluateJavaScript("this is not valid JavaScript"); + QCOMPARE(page.messages.count(), 1); + QCOMPARE(page.lineNumbers.at(0), 1); +} + +class TestPage : public QWebPage { + Q_OBJECT +public: + TestPage(QObject* parent = 0) : QWebPage(parent) + { + connect(this, SIGNAL(geometryChangeRequested(QRect)), this, SLOT(slotGeometryChangeRequested(QRect))); + } + + struct Navigation { + QPointer<QWebFrame> frame; + QNetworkRequest request; + NavigationType type; + }; + + QList<Navigation> navigations; + QList<TestPage*> createdWindows; + QRect requestedGeometry; + + virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, NavigationType type) { + Navigation n; + n.frame = frame; + n.request = request; + n.type = type; + navigations.append(n); + return true; + } + + virtual QWebPage* createWindow(WebWindowType) { + TestPage* page = new TestPage(this); + createdWindows.append(page); + return page; + } + +private Q_SLOTS: + void slotGeometryChangeRequested(const QRect& geom) { + requestedGeometry = geom; + } +}; + +void tst_QWebPage::popupFormSubmission() +{ + TestPage page; + page.settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); + page.mainFrame()->setHtml("<form name=form1 method=get action='' target=myNewWin>"\ + "<input type=hidden name=foo value='bar'>"\ + "</form>"); + page.mainFrame()->evaluateJavaScript("window.open('', 'myNewWin', 'width=500,height=300,toolbar=0')"); + page.mainFrame()->evaluateJavaScript("document.form1.submit();"); + + QTest::qWait(500); + // The number of popup created should be one. + QVERIFY(page.createdWindows.size() == 1); + + QString url = page.createdWindows.takeFirst()->mainFrame()->url().toString(); + // Check if the form submission was OK. + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118597", Continue); + QVERIFY(url.contains("?foo=bar")); +} + +void tst_QWebPage::acceptNavigationRequestWithNewWindow() +{ + TestPage* page = new TestPage(m_view); + page->settings()->setAttribute(QWebSettings::LinksIncludedInFocusChain, true); + m_page = page; + m_view->setPage(m_page); + + m_view->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>")); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QFocusEvent fe(QEvent::FocusIn); + m_page->event(&fe); + + QVERIFY(m_page->focusNextPrevChild(/*next*/ true)); + + QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); + m_page->event(&keyEnter); + + QCOMPARE(page->navigations.count(), 2); + + TestPage::Navigation n = page->navigations.at(1); + QVERIFY(n.frame.isNull()); + QCOMPARE(n.request.url().toString(), QString("data:text/html,Reached")); + QVERIFY(n.type == QWebPage::NavigationTypeLinkClicked); + + QCOMPARE(page->createdWindows.count(), 1); +} + +class TestNetworkManager : public QNetworkAccessManager +{ +public: + TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} + + QList<QUrl> requestedUrls; + QList<QNetworkRequest> requests; + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) { + requests.append(request); + requestedUrls.append(request.url()); + return QNetworkAccessManager::createRequest(op, request, outgoingData); + } +}; + +void tst_QWebPage::userStyleSheet() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + + m_page->settings()->setUserStyleSheetUrl(QUrl(QString::fromLatin1("data:text/css;charset=utf-8;base64," + + QByteArray("p { background-image: url('http://does.not/exist.png');}").toBase64()))); + m_view->setHtml("<p>hello world</p>"); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(networkManager->requestedUrls.count() >= 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); +} + +void tst_QWebPage::userStyleSheetFromLocalFileUrl() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + + QUrl styleSheetUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebpage/resources/user.css")); + m_page->settings()->setUserStyleSheetUrl(styleSheetUrl); + m_view->setHtml("<p>hello world</p>"); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(networkManager->requestedUrls.count() >= 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); +} + +void tst_QWebPage::userStyleSheetFromQrcUrl() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + + m_page->settings()->setUserStyleSheetUrl(QUrl("qrc:///resources/user.css")); + m_view->setHtml("<p>hello world</p>"); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(networkManager->requestedUrls.count() >= 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); +} + +void tst_QWebPage::loadHtml5Video() +{ +#if defined(USE_QT_MULTIMEDIA) && USE_QT_MULTIMEDIA + QByteArray url("http://does.not/exist?a=1%2Cb=2"); + m_view->setHtml("<p><video id ='video' src='" + url + "' autoplay/></p>"); + QTest::qWait(2000); + QUrl mUrl = DumpRenderTreeSupportQt::mediaContentUrlByElementId(m_page->mainFrame()->handle(), "video"); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=65452", Continue); + QCOMPARE(mUrl.toEncoded(), url); +#else + QSKIP("This test requires Qt Multimedia", SkipAll); +#endif +} + +void tst_QWebPage::viewModes() +{ + m_view->setHtml("<body></body>"); + m_page->setProperty("_q_viewMode", "minimized"); + + QVariant empty = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode)\")"); + QVERIFY(empty.type() == QVariant::Bool && empty.toBool()); + + QVariant minimized = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode: minimized)\")"); + QVERIFY(minimized.type() == QVariant::Bool && minimized.toBool()); + + QVariant maximized = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode: maximized)\")"); + QVERIFY(maximized.type() == QVariant::Bool && !maximized.toBool()); +} + +void tst_QWebPage::modified() +{ + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>blub")); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body id=foo contenteditable>blah")); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(!m_page->isModified()); + +// m_page->mainFrame()->evaluateJavaScript("alert(document.getElementById('foo'))"); + m_page->mainFrame()->evaluateJavaScript("document.getElementById('foo').focus()"); + m_page->mainFrame()->evaluateJavaScript("document.execCommand('InsertText', true, 'Test');"); + + QVERIFY(m_page->isModified()); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('Undo', true);"); + + QVERIFY(!m_page->isModified()); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('Redo', true);"); + + QVERIFY(m_page->isModified()); + + QVERIFY(m_page->history()->canGoBack()); + QVERIFY(!m_page->history()->canGoForward()); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(m_page->history()->backItem().isValid()); + QVERIFY(!m_page->history()->forwardItem().isValid()); + + m_page->history()->back(); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(!m_page->history()->canGoBack()); + QVERIFY(m_page->history()->canGoForward()); + + QVERIFY(!m_page->isModified()); + + QVERIFY(m_page->history()->currentItemIndex() == 0); + + m_page->history()->setMaximumItemCount(3); + QVERIFY(m_page->history()->maximumItemCount() == 3); + + QVariant variant("string test"); + m_page->history()->currentItem().setUserData(variant); + QVERIFY(m_page->history()->currentItem().userData().toString() == "string test"); + + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is second page")); + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is third page")); + QVERIFY(m_page->history()->count() == 2); + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fourth page")); + QVERIFY(m_page->history()->count() == 2); + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fifth page")); + QVERIFY(::waitForSignal(m_page, SIGNAL(saveFrameStateRequested(QWebFrame*,QWebHistoryItem*)))); +} + +// https://bugs.webkit.org/show_bug.cgi?id=51331 +void tst_QWebPage::updatePositionDependentActionsCrash() +{ + QWebView view; + view.setHtml("<p>test"); + QPoint pos(0, 0); + view.page()->updatePositionDependentActions(pos); + QMenu* contextMenu = 0; + foreach (QObject* child, view.children()) { + contextMenu = qobject_cast<QMenu*>(child); + if (contextMenu) + break; + } + QVERIFY(!contextMenu); +} + +// https://bugs.webkit.org/show_bug.cgi?id=20357 +void tst_QWebPage::contextMenuCrash() +{ + QWebView view; + view.setHtml("<p>test"); + QPoint pos(0, 0); + QContextMenuEvent event(QContextMenuEvent::Mouse, pos); + view.page()->swallowContextMenuEvent(&event); + view.page()->updatePositionDependentActions(pos); + QMenu* contextMenu = 0; + foreach (QObject* child, view.children()) { + contextMenu = qobject_cast<QMenu*>(child); + if (contextMenu) + break; + } + QVERIFY(contextMenu); + delete contextMenu; +} + +void tst_QWebPage::database() +{ + QString path = tmpDirPath(); + m_page->settings()->setOfflineStoragePath(path); + QVERIFY(m_page->settings()->offlineStoragePath() == path); + + QWebSettings::setOfflineStorageDefaultQuota(1024 * 1024); + QVERIFY(QWebSettings::offlineStorageDefaultQuota() == 1024 * 1024); + + m_page->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); + m_page->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true); + + QString dbFileName = path + "Databases.db"; + + if (QFile::exists(dbFileName)) + QFile::remove(dbFileName); + + qRegisterMetaType<QWebFrame*>("QWebFrame*"); + QSignalSpy spy(m_page, SIGNAL(databaseQuotaExceeded(QWebFrame*,QString))); + m_view->setHtml(QString("<html><head><script>var db; db=openDatabase('testdb', '1.0', 'test database API', 50000); </script></head><body><div></div></body></html>"), QUrl("http://www.myexample.com/")); + QTRY_COMPARE(spy.count(), 1); + m_page->mainFrame()->evaluateJavaScript("var db2; db2=openDatabase('testdb', '1.0', 'test database API', 50000);"); + QTRY_COMPARE(spy.count(),1); + + m_page->mainFrame()->evaluateJavaScript("localStorage.test='This is a test for local storage';"); + m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com/")); + + QVariant s1 = m_page->mainFrame()->evaluateJavaScript("localStorage.test"); + QCOMPARE(s1.toString(), QString("This is a test for local storage")); + + m_page->mainFrame()->evaluateJavaScript("sessionStorage.test='This is a test for session storage';"); + m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com/")); + QVariant s2 = m_page->mainFrame()->evaluateJavaScript("sessionStorage.test"); + QCOMPARE(s2.toString(), QString("This is a test for session storage")); + + m_view->setHtml(QString("<html><head></head><body><div></div></body></html>"), QUrl("http://www.myexample.com/")); + m_page->mainFrame()->evaluateJavaScript("var db3; db3=openDatabase('testdb', '1.0', 'test database API', 50000);db3.transaction(function(tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS Test (text TEXT)', []); }, function(tx, result) { }, function(tx, error) { });"); + QTest::qWait(200); + + // Remove all databases. + QWebSecurityOrigin origin = m_page->mainFrame()->securityOrigin(); + QList<QWebDatabase> dbs = origin.databases(); + for (int i = 0; i < dbs.count(); i++) { + QString fileName = dbs[i].fileName(); + QVERIFY(QFile::exists(fileName)); + QWebDatabase::removeDatabase(dbs[i]); + QVERIFY(!QFile::exists(fileName)); + } + QVERIFY(!origin.databases().size()); + // Remove removed test :-) + QWebDatabase::removeAllDatabases(); + QVERIFY(!origin.databases().size()); +} + +class PluginPage : public QWebPage +{ +public: + PluginPage(QObject *parent = 0) + : QWebPage(parent) {} + + struct CallInfo + { + CallInfo(const QString &c, const QUrl &u, + const QStringList &pn, const QStringList &pv, + QObject *r) + : classid(c), http://code.qt.io/url(u), paramNames(pn), + paramValues(pv), returnValue(r) + {} + QString classid; + QUrl url; + QStringList paramNames; + QStringList paramValues; + QObject *returnValue; + }; + + QList<CallInfo> calls; + +protected: + virtual QObject *createPlugin(const QString &classid, const QUrl &url, + const QStringList ¶mNames, + const QStringList ¶mValues) + { + QObject *result = 0; + if (classid == "pushbutton") + result = new QPushButton(); +#ifndef QT_NO_INPUTDIALOG + else if (classid == "lineedit") + result = new QLineEdit(); +#endif + else if (classid == "graphicswidget") + result = new QGraphicsWidget(); + if (result) + result->setObjectName(classid); + calls.append(CallInfo(classid, url, paramNames, paramValues, result)); + return result; + } +}; + +static void createPlugin(QWebView *view) +{ + QSignalSpy loadSpy(view, SIGNAL(loadFinished(bool))); + + PluginPage* newPage = new PluginPage(view); + view->setPage(newPage); + + // type has to be application/x-qt-plugin + view->setHtml(QString("<html><body><object type='application/x-foobarbaz' classid='pushbutton' id='mybutton'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(newPage->calls.count(), 0); + + view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 2); + QCOMPARE(newPage->calls.count(), 1); + { + PluginPage::CallInfo ci = newPage->calls.takeFirst(); + QCOMPARE(ci.classid, QString::fromLatin1("pushbutton")); + QCOMPARE(ci.url, QUrl()); + QCOMPARE(ci.paramNames.count(), 3); + QCOMPARE(ci.paramValues.count(), 3); + QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); + QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); + QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); + QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton")); + QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); + QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton")); + QVERIFY(ci.returnValue != 0); + QVERIFY(ci.returnValue->inherits("QPushButton")); + } + // test JS bindings + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mybutton').toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.objectName").toString(), + QString::fromLatin1("string")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.objectName").toString(), + QString::fromLatin1("pushbutton")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.clicked").toString(), + QString::fromLatin1("function")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.clicked.toString()").toString(), + QString::fromLatin1("function clicked() {\n [native code]\n}")); + + view->setHtml(QString("<html><body><table>" + "<tr><object type='application/x-qt-plugin' classid='lineedit' id='myedit'/></tr>" + "<tr><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></tr>" + "</table></body></html>"), QUrl("http://foo.bar.baz/")); + QTRY_COMPARE(loadSpy.count(), 3); + QCOMPARE(newPage->calls.count(), 2); + { + PluginPage::CallInfo ci = newPage->calls.takeFirst(); + QCOMPARE(ci.classid, QString::fromLatin1("lineedit")); + QCOMPARE(ci.url, QUrl()); + QCOMPARE(ci.paramNames.count(), 3); + QCOMPARE(ci.paramValues.count(), 3); + QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); + QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); + QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); + QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("lineedit")); + QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); + QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("myedit")); + QVERIFY(ci.returnValue != 0); + QVERIFY(ci.returnValue->inherits("QLineEdit")); + } + { + PluginPage::CallInfo ci = newPage->calls.takeFirst(); + QCOMPARE(ci.classid, QString::fromLatin1("pushbutton")); + QCOMPARE(ci.url, QUrl()); + QCOMPARE(ci.paramNames.count(), 3); + QCOMPARE(ci.paramValues.count(), 3); + QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); + QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); + QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); + QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton")); + QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); + QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton")); + QVERIFY(ci.returnValue != 0); + QVERIFY(ci.returnValue->inherits("QPushButton")); + } +} + +void tst_QWebPage::graphicsWidgetPlugin() +{ + m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + QGraphicsWebView webView; + + QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); + + PluginPage* newPage = new PluginPage(&webView); + webView.setPage(newPage); + + // type has to be application/x-qt-plugin + webView.setHtml(QString("<html><body><object type='application/x-foobarbaz' classid='graphicswidget' id='mygraphicswidget'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(newPage->calls.count(), 0); + + webView.setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='graphicswidget' id='mygraphicswidget'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 2); + QCOMPARE(newPage->calls.count(), 1); + { + PluginPage::CallInfo ci = newPage->calls.takeFirst(); + QCOMPARE(ci.classid, QString::fromLatin1("graphicswidget")); + QCOMPARE(ci.url, QUrl()); + QCOMPARE(ci.paramNames.count(), 3); + QCOMPARE(ci.paramValues.count(), 3); + QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); + QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); + QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); + QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("graphicswidget")); + QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); + QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mygraphicswidget")); + QVERIFY(ci.returnValue); + QVERIFY(ci.returnValue->inherits("QGraphicsWidget")); + } + // test JS bindings + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mygraphicswidget').toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mygraphicswidget.objectName").toString(), + QString::fromLatin1("string")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.objectName").toString(), + QString::fromLatin1("graphicswidget")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mygraphicswidget.geometryChanged").toString(), + QString::fromLatin1("function")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.geometryChanged.toString()").toString(), + QString::fromLatin1("function geometryChanged() {\n [native code]\n}")); +} + +void tst_QWebPage::createPluginWithPluginsEnabled() +{ + m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + createPlugin(m_view); +} + +void tst_QWebPage::createPluginWithPluginsDisabled() +{ + // Qt Plugins should be loaded by QtWebKit even when PluginsEnabled is + // false. The client decides whether a Qt plugin is enabled or not when + // it decides whether or not to instantiate it. + m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, false); + createPlugin(m_view); +} + +// Standard base class for template PluginTracerPage. In tests it is used as interface. +class PluginCounterPage : public QWebPage { +public: + int m_count; + QPointer<QObject> m_widget; + QObject* m_pluginParent; + PluginCounterPage(QObject* parent = 0) + : QWebPage(parent) + , m_count(0) + , m_pluginParent(0) + { + settings()->setAttribute(QWebSettings::PluginsEnabled, true); + } + ~PluginCounterPage() + { + if (m_pluginParent) + m_pluginParent->deleteLater(); + } +}; + +template<class T> +class PluginTracerPage : public PluginCounterPage { +public: + PluginTracerPage(QObject* parent = 0) + : PluginCounterPage(parent) + { + // this is a dummy parent object for the created plugin + m_pluginParent = new T; + } + virtual QObject* createPlugin(const QString&, const QUrl&, const QStringList&, const QStringList&) + { + m_count++; + m_widget = new T; + // need a cast to the specific type, as QObject::setParent cannot be called, + // because it is not virtual. Instead it is necesary to call QWidget::setParent, + // which also takes a QWidget* instead of a QObject*. Therefore we need to + // upcast to T*, which is a QWidget. + static_cast<T*>(m_widget.data())->setParent(static_cast<T*>(m_pluginParent)); + return m_widget.data(); + } +}; + +class PluginFactory { +public: + enum FactoredType {QWidgetType, QGraphicsWidgetType}; + static PluginCounterPage* create(FactoredType type, QObject* parent = 0) + { + PluginCounterPage* result = 0; + switch (type) { + case QWidgetType: + result = new PluginTracerPage<QWidget>(parent); + break; + case QGraphicsWidgetType: + result = new PluginTracerPage<QGraphicsWidget>(parent); + break; + default: {/*Oops*/}; + } + return result; + } + + static void prepareTestData() + { + QTest::addColumn<int>("type"); + QTest::newRow("QWidget") << (int)PluginFactory::QWidgetType; + QTest::newRow("QGraphicsWidget") << (int)PluginFactory::QGraphicsWidgetType; + } +}; + +void tst_QWebPage::destroyPlugin_data() +{ + PluginFactory::prepareTestData(); +} + +void tst_QWebPage::destroyPlugin() +{ + QFETCH(int, type); + PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type, m_view); + m_view->setPage(page); + + // we create the plugin, so the widget should be constructed + QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>"); + m_view->setHtml(content); + QVERIFY(page->m_widget); + QCOMPARE(page->m_count, 1); + + // navigate away, the plugin widget should be destructed + m_view->setHtml("<html><body>Hi</body></html>"); + QTestEventLoop::instance().enterLoop(1); + QVERIFY(!page->m_widget); +} + +void tst_QWebPage::createViewlessPlugin_data() +{ + PluginFactory::prepareTestData(); +} + +void tst_QWebPage::createViewlessPlugin() +{ + QFETCH(int, type); + PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type); + QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>"); + page->mainFrame()->setHtml(content); + QCOMPARE(page->m_count, 1); + QVERIFY(page->m_widget); + QVERIFY(page->m_pluginParent); + QVERIFY(page->m_widget.data()->parent() == page->m_pluginParent); + delete page; + +} + +#ifdef HAVE_QTTESTSUPPORT +void tst_QWebPage::multiplePageGroupsAndLocalStorage() +{ + QDir dir(tmpDirPath()); + dir.mkdir("path1"); + dir.mkdir("path2"); + + QWebView view1; + QWebView view2; + + view1.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); + view1.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(tmpDirPath() + "/path1")); + DumpRenderTreeSupportQt::webPageSetGroupName(view1.page()->handle(), "group1"); + view2.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); + view2.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(tmpDirPath() + "/path2")); + DumpRenderTreeSupportQt::webPageSetGroupName(view2.page()->handle(), "group2"); + QCOMPARE(DumpRenderTreeSupportQt::webPageGroupName(view1.page()->handle()), QString("group1")); + QCOMPARE(DumpRenderTreeSupportQt::webPageGroupName(view2.page()->handle()), QString("group2")); + + + view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com/")); + view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com/")); + + view1.page()->mainFrame()->evaluateJavaScript("localStorage.test='value1';"); + view2.page()->mainFrame()->evaluateJavaScript("localStorage.test='value2';"); + + view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com/")); + view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com/")); + + QVariant s1 = view1.page()->mainFrame()->evaluateJavaScript("localStorage.test"); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(s1.toString(), QString("value1")); + + QVariant s2 = view2.page()->mainFrame()->evaluateJavaScript("localStorage.test"); + QCOMPARE(s2.toString(), QString("value2")); + + QTest::qWait(1000); + + QFile::remove(QDir::toNativeSeparators(tmpDirPath() + "/path1/http_www.myexample.com_0.localstorage")); + QFile::remove(QDir::toNativeSeparators(tmpDirPath() + "/path2/http_www.myexample.com_0.localstorage")); + dir.rmdir(QDir::toNativeSeparators("./path1")); + dir.rmdir(QDir::toNativeSeparators("./path2")); +} +#endif + +class CursorTrackedPage : public QWebPage +{ +public: + + CursorTrackedPage(QWidget *parent = 0): QWebPage(parent) { + setViewportSize(QSize(1024, 768)); // big space + } + + QString selectedText() { + return mainFrame()->evaluateJavaScript("window.getSelection().toString()").toString(); + } + + int selectionStartOffset() { + return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).startOffset").toInt(); + } + + int selectionEndOffset() { + return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).endOffset").toInt(); + } + + // true if start offset == end offset, i.e. no selected text + int isSelectionCollapsed() { + return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).collapsed").toBool(); + } +}; + +void tst_QWebPage::cursorMovements() +{ + CursorTrackedPage* page = new CursorTrackedPage; + QString content("<html><body><p id=one>The quick brown fox</p><p id=two>jumps over the lazy dog</p><p>May the source<br/>be with you!</p></body></html>"); + page->mainFrame()->setHtml(content); + + // this will select the first paragraph + QString script = "var range = document.createRange(); " \ + "var node = document.getElementById(\"one\"); " \ + "range.selectNode(node); " \ + "getSelection().addRange(range);"; + page->mainFrame()->evaluateJavaScript(script); + QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); + + QRegExp regExp(" style=\".*\""); + regExp.setMinimal(true); + QCOMPARE(page->selectedHtml().trimmed().replace(regExp, ""), QString::fromLatin1("<p id=\"one\">The quick brown fox</p>")); + + // these actions must exist + QVERIFY(page->action(QWebPage::MoveToNextChar) != 0); + QVERIFY(page->action(QWebPage::MoveToPreviousChar) != 0); + QVERIFY(page->action(QWebPage::MoveToNextWord) != 0); + QVERIFY(page->action(QWebPage::MoveToPreviousWord) != 0); + QVERIFY(page->action(QWebPage::MoveToNextLine) != 0); + QVERIFY(page->action(QWebPage::MoveToPreviousLine) != 0); + QVERIFY(page->action(QWebPage::MoveToStartOfLine) != 0); + QVERIFY(page->action(QWebPage::MoveToEndOfLine) != 0); + QVERIFY(page->action(QWebPage::MoveToStartOfBlock) != 0); + QVERIFY(page->action(QWebPage::MoveToEndOfBlock) != 0); + QVERIFY(page->action(QWebPage::MoveToStartOfDocument) != 0); + QVERIFY(page->action(QWebPage::MoveToEndOfDocument) != 0); + + // right now they are disabled because contentEditable is false + QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), false); + + // make it editable before navigating the cursor + page->setContentEditable(true); + + // here the actions are enabled after contentEditable is true + QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), true); + + // cursor will be before the word "jump" + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be between 'j' and 'u' in the word "jump" + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 1); + + // cursor will be between 'u' and 'm' in the word "jump" + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 2); + + // cursor will be after the word "jump" + page->triggerAction(QWebPage::MoveToNextWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 5); + + // cursor will be after the word "lazy" + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 19); + + // cursor will be between 'z' and 'y' in "lazy" + page->triggerAction(QWebPage::MoveToPreviousChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 18); + + // cursor will be between 'a' and 'z' in "lazy" + page->triggerAction(QWebPage::MoveToPreviousChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 17); + + // cursor will be before the word "lazy" + page->triggerAction(QWebPage::MoveToPreviousWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 15); + + // cursor will be before the word "quick" + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 4); + + // cursor will be between 'p' and 's' in the word "jumps" + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 4); + + // cursor will be before the word "jumps" + page->triggerAction(QWebPage::MoveToStartOfLine); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be after the word "dog" + page->triggerAction(QWebPage::MoveToEndOfLine); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 23); + + // cursor will be between 'w' and 'n' in "brown" + page->triggerAction(QWebPage::MoveToStartOfLine); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 14); + + // cursor will be after the word "fox" + page->triggerAction(QWebPage::MoveToEndOfLine); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 19); + + // cursor will be before the word "The" + page->triggerAction(QWebPage::MoveToStartOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be after the word "you!" + page->triggerAction(QWebPage::MoveToEndOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + + // cursor will be before the word "be" + page->triggerAction(QWebPage::MoveToStartOfBlock); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be after the word "you!" + page->triggerAction(QWebPage::MoveToEndOfBlock); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + + // try to move before the document start + page->triggerAction(QWebPage::MoveToStartOfDocument); + page->triggerAction(QWebPage::MoveToPreviousChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + page->triggerAction(QWebPage::MoveToStartOfDocument); + page->triggerAction(QWebPage::MoveToPreviousWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // try to move past the document end + page->triggerAction(QWebPage::MoveToEndOfDocument); + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + page->triggerAction(QWebPage::MoveToEndOfDocument); + page->triggerAction(QWebPage::MoveToNextWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + + delete page; +} + +void tst_QWebPage::textSelection() +{ + CursorTrackedPage* page = new CursorTrackedPage; + QString content("<html><body><p id=one>The quick brown fox</p>" \ + "<p id=two>jumps over the lazy dog</p>" \ + "<p>May the source<br/>be with you!</p></body></html>"); + page->mainFrame()->setHtml(content); + + // these actions must exist + QVERIFY(page->action(QWebPage::SelectAll) != 0); + QVERIFY(page->action(QWebPage::SelectNextChar) != 0); + QVERIFY(page->action(QWebPage::SelectPreviousChar) != 0); + QVERIFY(page->action(QWebPage::SelectNextWord) != 0); + QVERIFY(page->action(QWebPage::SelectPreviousWord) != 0); + QVERIFY(page->action(QWebPage::SelectNextLine) != 0); + QVERIFY(page->action(QWebPage::SelectPreviousLine) != 0); + QVERIFY(page->action(QWebPage::SelectStartOfLine) != 0); + QVERIFY(page->action(QWebPage::SelectEndOfLine) != 0); + QVERIFY(page->action(QWebPage::SelectStartOfBlock) != 0); + QVERIFY(page->action(QWebPage::SelectEndOfBlock) != 0); + QVERIFY(page->action(QWebPage::SelectStartOfDocument) != 0); + QVERIFY(page->action(QWebPage::SelectEndOfDocument) != 0); + + // right now they are disabled because contentEditable is false and + // there isn't an existing selection to modify + QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), false); + + // ..but SelectAll is awalys enabled + QCOMPARE(page->action(QWebPage::SelectAll)->isEnabled(), true); + + // Verify hasSelection returns false since there is no selection yet... + QCOMPARE(page->hasSelection(), false); + + // this will select the first paragraph + QString selectScript = "var range = document.createRange(); " \ + "var node = document.getElementById(\"one\"); " \ + "range.selectNode(node); " \ + "getSelection().addRange(range);"; + page->mainFrame()->evaluateJavaScript(selectScript); + QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); + QRegExp regExp(" style=\".*\""); + regExp.setMinimal(true); + QCOMPARE(page->selectedHtml().trimmed().replace(regExp, ""), QString::fromLatin1("<p id=\"one\">The quick brown fox</p>")); + + // Make sure hasSelection returns true, since there is selected text now... + QCOMPARE(page->hasSelection(), true); + + // here the actions are enabled after a selection has been created + QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true); + + // make it editable before navigating the cursor + page->setContentEditable(true); + + // cursor will be before the word "The", this makes sure there is a charet + page->triggerAction(QWebPage::MoveToStartOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // here the actions are enabled after contentEditable is true + QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true); + + delete page; +} + +void tst_QWebPage::textEditing() +{ + CursorTrackedPage* page = new CursorTrackedPage; + QString content("<html><body><p id=one>The quick brown fox</p>" \ + "<p id=two>jumps over the lazy dog</p>" \ + "<p>May the source<br/>be with you!</p></body></html>"); + page->mainFrame()->setHtml(content); + + // these actions must exist + QVERIFY(page->action(QWebPage::Cut) != 0); + QVERIFY(page->action(QWebPage::Copy) != 0); + QVERIFY(page->action(QWebPage::Paste) != 0); + QVERIFY(page->action(QWebPage::DeleteStartOfWord) != 0); + QVERIFY(page->action(QWebPage::DeleteEndOfWord) != 0); + QVERIFY(page->action(QWebPage::SetTextDirectionDefault) != 0); + QVERIFY(page->action(QWebPage::SetTextDirectionLeftToRight) != 0); + QVERIFY(page->action(QWebPage::SetTextDirectionRightToLeft) != 0); + QVERIFY(page->action(QWebPage::ToggleBold) != 0); + QVERIFY(page->action(QWebPage::ToggleItalic) != 0); + QVERIFY(page->action(QWebPage::ToggleUnderline) != 0); + QVERIFY(page->action(QWebPage::InsertParagraphSeparator) != 0); + QVERIFY(page->action(QWebPage::InsertLineSeparator) != 0); + QVERIFY(page->action(QWebPage::PasteAndMatchStyle) != 0); + QVERIFY(page->action(QWebPage::RemoveFormat) != 0); + QVERIFY(page->action(QWebPage::ToggleStrikethrough) != 0); + QVERIFY(page->action(QWebPage::ToggleSubscript) != 0); + QVERIFY(page->action(QWebPage::ToggleSuperscript) != 0); + QVERIFY(page->action(QWebPage::InsertUnorderedList) != 0); + QVERIFY(page->action(QWebPage::InsertOrderedList) != 0); + QVERIFY(page->action(QWebPage::Indent) != 0); + QVERIFY(page->action(QWebPage::Outdent) != 0); + QVERIFY(page->action(QWebPage::AlignCenter) != 0); + QVERIFY(page->action(QWebPage::AlignJustified) != 0); + QVERIFY(page->action(QWebPage::AlignLeft) != 0); + QVERIFY(page->action(QWebPage::AlignRight) != 0); + + // right now they are disabled because contentEditable is false + QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), false); + + // Select everything + page->triggerAction(QWebPage::SelectAll); + + // make sure it is enabled since there is a selection + QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), true); + + // make it editable before navigating the cursor + page->setContentEditable(true); + + // clear the selection + page->triggerAction(QWebPage::MoveToStartOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // make sure it is disabled since there isn't a selection + QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), false); + + // here the actions are enabled after contentEditable is true + QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), true); + + // make sure these are disabled since there isn't a selection + QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false); + + // make sure everything is selected + page->triggerAction(QWebPage::SelectAll); + + // this is only true if there is an editable selection + QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), true); + + delete page; +} + +void tst_QWebPage::requestCache() +{ + TestPage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>")); + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(page.navigations.count(), 1); + + page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me2</a>")); + QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE(page.navigations.count(), 2); + + page.triggerAction(QWebPage::Stop); + QVERIFY(page.history()->canGoBack()); + page.triggerAction(QWebPage::Back); + + QTRY_COMPARE(loadSpy.count(), 3); + QTRY_COMPARE(page.navigations.count(), 3); + QCOMPARE(page.navigations.at(0).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(), + (int)QNetworkRequest::PreferNetwork); + QCOMPARE(page.navigations.at(1).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(), + (int)QNetworkRequest::PreferNetwork); + QCOMPARE(page.navigations.at(2).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(), + (int)QNetworkRequest::PreferCache); +} + +void tst_QWebPage::loadCachedPage() +{ + TestPage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + page.settings()->setMaximumPagesInCache(3); + + page.mainFrame()->load(QUrl("data:text/html,This is first page")); + + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(page.navigations.count(), 1); + + QUrl firstPageUrl = page.mainFrame()->url(); + page.mainFrame()->load(QUrl("data:text/html,This is second page")); + + QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE(page.navigations.count(), 2); + + page.triggerAction(QWebPage::Stop); + QVERIFY(page.history()->canGoBack()); + + QSignalSpy urlSpy(page.mainFrame(), SIGNAL(urlChanged(QUrl))); + QVERIFY(urlSpy.isValid()); + + page.triggerAction(QWebPage::Back); + ::waitForSignal(page.mainFrame(), SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlSpy.size(), 1); + + QList<QVariant> arguments1 = urlSpy.takeFirst(); + QCOMPARE(arguments1.at(0).toUrl(), firstPageUrl); + +} +void tst_QWebPage::backActionUpdate() +{ + QWebView view; + QWebPage *page = view.page(); + QAction *action = page->action(QWebPage::Back); + QVERIFY(!action->isEnabled()); + QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool))); + QUrl url = QUrl("qrc:///resources/framedindex.html"); + page->mainFrame()->load(url); + QTRY_COMPARE(loadSpy.count(), 1); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QVERIFY(!action->isEnabled()); + QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10)); + QTRY_COMPARE(loadSpy.count(), 2); + + QVERIFY(action->isEnabled()); +} + +void frameAtHelper(QWebPage* webPage, QWebFrame* webFrame, QPoint framePosition) +{ + if (!webFrame) + return; + + framePosition += QPoint(webFrame->pos()); + QList<QWebFrame*> children = webFrame->childFrames(); + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->childFrames().size() > 0) + frameAtHelper(webPage, children.at(i), framePosition); + + QRect frameRect(children.at(i)->pos() + framePosition, children.at(i)->geometry().size()); + QVERIFY(children.at(i) == webPage->frameAt(frameRect.topLeft())); + } +} + +void tst_QWebPage::frameAt() +{ + QWebView webView; + QWebPage* webPage = webView.page(); + QSignalSpy loadSpy(webPage, SIGNAL(loadFinished(bool))); + QUrl url = QUrl("qrc:///resources/iframe.html"); + webPage->mainFrame()->load(url); + QTRY_COMPARE(loadSpy.count(), 1); + frameAtHelper(webPage, webPage->mainFrame(), webPage->mainFrame()->pos()); +} + +void tst_QWebPage::inputMethods_data() +{ + QTest::addColumn<QString>("viewType"); + QTest::newRow("QWebView") << "QWebView"; + QTest::newRow("QGraphicsWebView") << "QGraphicsWebView"; +} + +static Qt::InputMethodHints inputMethodHints(QObject* object) +{ + if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object)) + return o->inputMethodHints(); + if (QWidget* w = qobject_cast<QWidget*>(object)) + return w->inputMethodHints(); + return Qt::InputMethodHints(); +} + +static bool inputMethodEnabled(QObject* object) +{ + if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object)) + return o->flags() & QGraphicsItem::ItemAcceptsInputMethod; + if (QWidget* w = qobject_cast<QWidget*>(object)) + return w->testAttribute(Qt::WA_InputMethodEnabled); + return false; +} + +static void clickOnPage(QWebPage* page, const QPoint& position) +{ + QMouseEvent evpres(QEvent::MouseButtonPress, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + page->event(&evpres); + QMouseEvent evrel(QEvent::MouseButtonRelease, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + page->event(&evrel); +} + +void tst_QWebPage::inputMethods() +{ + QFETCH(QString, viewType); + QWebPage* page = new QWebPage; + QObject* view = 0; + QObject* container = 0; + if (viewType == "QWebView") { + QWebView* wv = new QWebView; + wv->setPage(page); + view = wv; + container = view; + } else if (viewType == "QGraphicsWebView") { + QGraphicsWebView* wv = new QGraphicsWebView; + wv->setPage(page); + view = wv; + + QGraphicsView* gv = new QGraphicsView; + QGraphicsScene* scene = new QGraphicsScene(gv); + gv->setScene(scene); + scene->addItem(wv); + wv->setGeometry(QRect(0, 0, 500, 500)); + + container = gv; + } else + QVERIFY2(false, "Unknown view type"); + + page->settings()->setFontFamily(QWebSettings::SerifFont, page->settings()->fontFamily(QWebSettings::FixedFont)); + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/><br>" \ + "<input type='password'/>" \ + "</body></html>"); + page->mainFrame()->setFocus(); + + TestInputContext testContext; + + QWebElementCollection inputs = page->mainFrame()->documentElement().findAll("input"); + QPoint textInputCenter = inputs.at(0).geometry().center(); + + clickOnPage(page, textInputCenter); + + // This part of the test checks if the SIP (Software Input Panel) is triggered, + // which normally happens on mobile platforms, when a user input form receives + // a mouse click. + int inputPanel = 0; + if (viewType == "QWebView") { + if (QWebView* wv = qobject_cast<QWebView*>(view)) + inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel); + } else if (viewType == "QGraphicsWebView") { + if (QGraphicsWebView* wv = qobject_cast<QGraphicsWebView*>(view)) + inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel); + } + + // For non-mobile platforms RequestSoftwareInputPanel event is not called + // because there is no SIP (Software Input Panel) triggered. In the case of a + // mobile platform, an input panel, e.g. virtual keyboard, is usually invoked + // and the RequestSoftwareInputPanel event is called. For these two situations + // this part of the test can verified as the checks below. + if (inputPanel) + QVERIFY(testContext.isInputPanelVisible()); + else + QVERIFY(!testContext.isInputPanelVisible()); + testContext.hideInputPanel(); + + clickOnPage(page, textInputCenter); + QVERIFY(testContext.isInputPanelVisible()); + + //ImMicroFocus + QVariant variant = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(inputs.at(0).geometry().contains(variant.toRect().topLeft())); + + // We assigned the serif font famility to be the same as the fixef font family. + // Then test ImFont on a serif styled element, we should get our fixef font family. + variant = page->inputMethodQuery(Qt::ImFont); + QFont font = variant.value<QFont>(); + QCOMPARE(page->settings()->fontFamily(QWebSettings::FixedFont), font.family()); + + QList<QInputMethodEvent::Attribute> inputAttributes; + + //Insert text. + { + QInputMethodEvent eventText("QtWebKit", inputAttributes); + QSignalSpy signalSpy(page, SIGNAL(microFocusChanged())); + page->event(&eventText); + QCOMPARE(signalSpy.count(), 0); + } + + { + QInputMethodEvent eventText("", inputAttributes); + eventText.setCommitString(QString("QtWebKit"), 0, 0); + page->event(&eventText); + } + + //ImMaximumTextLength + variant = page->inputMethodQuery(Qt::ImMaximumTextLength); + QCOMPARE(20, variant.toInt()); + + //Set selection + inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 3, 2, QVariant()); + QInputMethodEvent eventSelection("",inputAttributes); + page->event(&eventSelection); + + //ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + int anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 3); + + //ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + int cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 5); + + //ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + QString selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("eb")); + + //Set selection with negative length + inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 6, -5, QVariant()); + QInputMethodEvent eventSelection3("",inputAttributes); + page->event(&eventSelection3); + + //ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 1); + + //ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 6); + + //ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("tWebK")); + + //ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + QString value = variant.value<QString>(); + QCOMPARE(value, QString("QtWebKit")); + + { + QList<QInputMethodEvent::Attribute> attributes; + // Clear the selection, so the next test does not clear any contents. + QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant()); + attributes.append(newSelection); + QInputMethodEvent event("composition", attributes); + page->event(&event); + } + + // A ongoing composition should not change the surrounding text before it is committed. + variant = page->inputMethodQuery(Qt::ImSurroundingText); + value = variant.value<QString>(); + QCOMPARE(value, QString("QtWebKit")); + + // Cancel current composition first + inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 0, QVariant()); + QInputMethodEvent eventSelection4("", inputAttributes); + page->event(&eventSelection4); + + // START - Tests for Selection when the Editor is NOT in Composition mode + + // LEFT to RIGHT selection + // Deselect the selection by sending MouseButtonPress events + // This moves the current cursor to the end of the text + clickOnPage(page, textInputCenter); + + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event(QString(), attributes); + event.setCommitString("XXX", 0, 0); + page->event(&event); + event.setCommitString(QString(), -2, 2); // Erase two characters. + page->event(&event); + event.setCommitString(QString(), -1, 1); // Erase one character. + page->event(&event); + variant = page->inputMethodQuery(Qt::ImSurroundingText); + value = variant.value<QString>(); + QCOMPARE(value, QString("QtWebKit")); + } + + //Move to the start of the line + page->triggerAction(QWebPage::MoveToStartOfLine); + + QKeyEvent keyRightEventPress(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier); + QKeyEvent keyRightEventRelease(QEvent::KeyRelease, Qt::Key_Right, Qt::NoModifier); + + //Move 2 characters RIGHT + for (int j = 0; j < 2; ++j) { + page->event(&keyRightEventPress); + page->event(&keyRightEventRelease); + } + + //Select to the end of the line + page->triggerAction(QWebPage::SelectEndOfLine); + + //ImAnchorPosition QtWebKit + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 2); + + //ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 8); + + //ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("WebKit")); + + //RIGHT to LEFT selection + //Deselect the selection (this moves the current cursor to the end of the text) + clickOnPage(page, textInputCenter); + + //ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 8); + + //ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 8); + + //ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + QKeyEvent keyLeftEventPress(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier); + QKeyEvent keyLeftEventRelease(QEvent::KeyRelease, Qt::Key_Left, Qt::NoModifier); + + //Move 2 characters LEFT + for (int i = 0; i < 2; ++i) { + page->event(&keyLeftEventPress); + page->event(&keyLeftEventRelease); + } + + //Select to the start of the line + page->triggerAction(QWebPage::SelectStartOfLine); + + //ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 6); + + //ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 0); + + //ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("QtWebK")); + + //END - Tests for Selection when the Editor is not in Composition mode + + //ImhHiddenText + QPoint passwordInputCenter = inputs.at(1).geometry().center(); + clickOnPage(page, passwordInputCenter); + + QVERIFY(inputMethodEnabled(view)); + QVERIFY(inputMethodHints(view) & Qt::ImhHiddenText); + + clickOnPage(page, textInputCenter); + QVERIFY(!(inputMethodHints(view) & Qt::ImhHiddenText)); + + page->mainFrame()->setHtml("<html><body><p>nothing to input here"); + testContext.hideInputPanel(); + + QWebElement para = page->mainFrame()->findFirstElement("p"); + clickOnPage(page, para.geometry().center()); + + QVERIFY(!testContext.isInputPanelVisible()); + + //START - Test for sending empty QInputMethodEvent + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input3' value='QtWebKit2'/>" \ + "</body></html>"); + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input3'); inputEle.focus(); inputEle.select();"); + + //Send empty QInputMethodEvent + QInputMethodEvent emptyEvent; + page->event(&emptyEvent); + + QString inputValue = page->mainFrame()->evaluateJavaScript("document.getElementById('input3').value").toString(); + QCOMPARE(inputValue, QString("QtWebKit2")); + //END - Test for sending empty QInputMethodEvent + + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input4' value='QtWebKit inputMethod'/>" \ + "</body></html>"); + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input4'); inputEle.focus(); inputEle.select();"); + + // Clear the selection, also cancel the ongoing composition if there is one. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant()); + attributes.append(newSelection); + QInputMethodEvent event("", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + variant = page->inputMethodQuery(Qt::ImSurroundingText); + QString surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("QtWebKit inputMethod")); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 0); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 0); + + // 1. Insert a character to the begining of the line. + // Send temporary text, which makes the editor has composition 'm'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("m", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("QtWebKit inputMethod")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 0); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 0); + + // Send temporary text, which makes the editor has composition 'n'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("n", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("QtWebKit inputMethod")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 0); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 0); + + // Send commit text, which makes the editor conforms composition. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("o"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oQtWebKit inputMethod")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 1); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 1); + + // 2. insert a character to the middle of the line. + // Send temporary text, which makes the editor has composition 'd'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("d", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oQtWebKit inputMethod")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 1); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 1); + + // Send commit text, which makes the editor conforms composition. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("e"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethod")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 2); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 2); + + // 3. Insert a character to the end of the line. + page->triggerAction(QWebPage::MoveToEndOfLine); + + // Send temporary text, which makes the editor has composition 't'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("t", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethod")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 22); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 22); + + // Send commit text, which makes the editor conforms composition. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("t"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethodt")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 23); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 23); + + // 4. Replace the selection. + page->triggerAction(QWebPage::SelectPreviousWord); + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("inputMethodt")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethodt")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 11); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 23); + + // Send temporary text, which makes the editor has composition 'w'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("w", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit ")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 11); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 11); + + // Send commit text, which makes the editor conforms composition. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("2"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit 2")); + + // ImCursorPosition + variant = page->inputMethodQuery(Qt::ImCursorPosition); + cursorPosition = variant.toInt(); + QCOMPARE(cursorPosition, 12); + + // ImAnchorPosition + variant = page->inputMethodQuery(Qt::ImAnchorPosition); + anchorPosition = variant.toInt(); + QCOMPARE(anchorPosition, 12); + + // Check sending RequestSoftwareInputPanel event + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input5' value='QtWebKit inputMethod'/>" \ + "<div id='btnDiv' onclick='i=document.getElementById("input5"); i.focus();'>abc</div>"\ + "</body></html>"); + QWebElement inputElement = page->mainFrame()->findFirstElement("div"); + clickOnPage(page, inputElement.geometry().center()); + + QVERIFY(!testContext.isInputPanelVisible()); + + // START - Newline test for textarea + qApp->processEvents(); + page->mainFrame()->setHtml("<html><body>" \ + "<textarea rows='5' cols='1' id='input5' value=''/>" \ + "</body></html>"); + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.focus(); inputEle.select();"); + + // Enter Key without key text + QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); + page->event(&keyEnter); + QList<QInputMethodEvent::Attribute> attribs; + + QInputMethodEvent eventText(QString(), attribs); + eventText.setCommitString("\n"); + page->event(&eventText); + + QInputMethodEvent eventText2(QString(), attribs); + eventText2.setCommitString("third line"); + page->event(&eventText2); + qApp->processEvents(); + + QString inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Enter Key with key text '\r' + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyEnterWithCarriageReturn(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier, "\r"); + page->event(&keyEnterWithCarriageReturn); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Enter Key with key text '\n' + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyEnterWithLineFeed(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier, "\n"); + page->event(&keyEnterWithLineFeed); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Enter Key with key text "\n\r" + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyEnterWithLFCR(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier, "\n\r"); + page->event(&keyEnterWithLFCR); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Return Key without key text + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyReturn(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); + page->event(&keyReturn); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // END - Newline test for textarea + + delete container; +} + +void tst_QWebPage::inputMethodsTextFormat_data() +{ + QTest::addColumn<QString>("string"); + QTest::addColumn<int>("start"); + QTest::addColumn<int>("length"); + + QTest::newRow("") << QString("") << 0 << 0; + QTest::newRow("Q") << QString("Q") << 0 << 1; + QTest::newRow("Qt") << QString("Qt") << 0 << 1; + QTest::newRow("Qt") << QString("Qt") << 0 << 2; + QTest::newRow("Qt") << QString("Qt") << 1 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 0 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 1 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 2 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 2 << -1; + QTest::newRow("Qt ") << QString("Qt ") << -2 << 3; + QTest::newRow("Qt ") << QString("Qt ") << 0 << 3; + QTest::newRow("Qt by") << QString("Qt by") << 0 << 1; + QTest::newRow("Qt by Nokia") << QString("Qt by Nokia") << 0 << 1; +} + + +void tst_QWebPage::inputMethodsTextFormat() +{ + QWebPage* page = new QWebPage; + QWebView* view = new QWebView; + view->setPage(page); + page->settings()->setFontFamily(QWebSettings::SerifFont, "FooSerifFont"); + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/>"); + page->mainFrame()->evaluateJavaScript("document.getElementById('input1').focus()"); + page->mainFrame()->setFocus(); + view->show(); + + QFETCH(QString, string); + QFETCH(int, start); + QFETCH(int, length); + + QList<QInputMethodEvent::Attribute> attrs; + QTextCharFormat format; + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + format.setUnderlineColor(Qt::red); + attrs.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, length, format)); + QInputMethodEvent im(string, attrs); + page->event(&im); + + QTest::qWait(1000); + + delete view; +} + +#ifdef HAVE_QTTESTSUPPORT +void tst_QWebPage::protectBindingsRuntimeObjectsFromCollector() +{ + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + PluginPage* newPage = new PluginPage(m_view); + m_view->setPage(newPage); + + m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + + m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='lineedit' id='mylineedit'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + + newPage->mainFrame()->evaluateJavaScript("function testme(text) { var lineedit = document.getElementById('mylineedit'); lineedit.setText(text); lineedit.selectAll(); }"); + + newPage->mainFrame()->evaluateJavaScript("testme('foo')"); + + DumpRenderTreeSupportQt::garbageCollectorCollect(); + + // don't crash! + newPage->mainFrame()->evaluateJavaScript("testme('bar')"); +} +#endif + +void tst_QWebPage::localURLSchemes() +{ + int i = QWebSecurityOrigin::localSchemes().size(); + + QWebSecurityOrigin::removeLocalScheme("file"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + QWebSecurityOrigin::addLocalScheme("file"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + + QWebSecurityOrigin::removeLocalScheme("qrc"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i - 1); + QWebSecurityOrigin::addLocalScheme("qrc"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + + QString myscheme = "myscheme"; + QWebSecurityOrigin::addLocalScheme(myscheme); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i + 1); + QVERIFY(QWebSecurityOrigin::localSchemes().contains(myscheme)); + QWebSecurityOrigin::removeLocalScheme(myscheme); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + QWebSecurityOrigin::removeLocalScheme(myscheme); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); +} + +static inline bool testFlag(QWebPage& webPage, QWebSettings::WebAttribute settingAttribute, const QString& jsObjectName, bool settingValue) +{ + webPage.settings()->setAttribute(settingAttribute, settingValue); + return webPage.mainFrame()->evaluateJavaScript(QString("(window.%1 != undefined)").arg(jsObjectName)).toBool(); +} + +void tst_QWebPage::testOptionalJSObjects() +{ + // Once a feature is enabled and the JS object is accessed turning off the setting will not turn off + // the visibility of the JS object any more. For this reason this test uses two QWebPage instances. + // Part of the test is to make sure that the QWebPage instances do not interfere with each other so turning on + // a feature for one instance will not turn it on for another. + + QWebPage webPage1; + QWebPage webPage2; + + webPage1.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); + webPage2.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); + + QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue); + QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", true), true); + QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue); + QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), true); + + QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", true), true); + QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", false), true); +} + +static inline bool checkLocalStorageVisibility(QWebPage& webPage, bool localStorageEnabled) +{ + webPage.settings()->setAttribute(QWebSettings::LocalStorageEnabled, localStorageEnabled); + return webPage.mainFrame()->evaluateJavaScript(QString("(window.localStorage != undefined)")).toBool(); +} + +void tst_QWebPage::testLocalStorageVisibility() +{ + // Local storage's visibility depends on its security origin, which depends on base url. + // Initially, it will test it with base urls that get a globally unique origin, which may not + // be able to use local storage even if the feature is enabled. Then later the same test is + // done but with urls that would get a valid origin, so local storage could be used. + // Before every test case it checks if local storage is not already visible. + + QWebPage webPage; + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl()); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("invalid")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("://misparsed.com")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("about:blank")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("data:text/html,test")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("file:///")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("https://www.example.com/")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("ftp://files.example.com")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("file:///path/to/index.html")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); +} + +void tst_QWebPage::testEnablePersistentStorage() +{ + QWebPage webPage; + + // By default all persistent options should be disabled + QCOMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), false); + QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), false); + QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), false); + QVERIFY(webPage.settings()->iconDatabasePath().isEmpty()); + + QWebSettings::enablePersistentStorage(); + + + QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), true); + QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), true); + QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), true); + + QTRY_VERIFY(!webPage.settings()->offlineStoragePath().isEmpty()); + QTRY_VERIFY(!webPage.settings()->offlineWebApplicationCachePath().isEmpty()); + QTRY_VERIFY(!webPage.settings()->iconDatabasePath().isEmpty()); +} + +void tst_QWebPage::defaultTextEncoding() +{ + QWebFrame* mainFrame = m_page->mainFrame(); + + QString defaultCharset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QVERIFY(!defaultCharset.isEmpty()); + QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), defaultCharset); + + m_page->settings()->setDefaultTextEncoding(QString("utf-8")); + QString charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QCOMPARE(charset, QString("utf-8")); + QCOMPARE(m_page->settings()->defaultTextEncoding(), charset); + + m_page->settings()->setDefaultTextEncoding(QString()); + charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QVERIFY(!charset.isEmpty()); + QCOMPARE(charset, defaultCharset); + + QWebSettings::globalSettings()->setDefaultTextEncoding(QString("utf-8")); + charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QCOMPARE(charset, QString("utf-8")); + QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), charset); +} + +class ErrorPage : public QWebPage +{ +public: + + ErrorPage(QWidget* parent = 0): QWebPage(parent) + { + } + + virtual bool supportsExtension(Extension extension) const + { + return extension == ErrorPageExtension; + } + + virtual bool extension(Extension, const ExtensionOption* option, ExtensionReturn* output) + { + ErrorPageExtensionReturn* errorPage = static_cast<ErrorPageExtensionReturn*>(output); + + errorPage->contentType = "text/html"; + errorPage->content = "error"; + return true; + } +}; + +void tst_QWebPage::errorPageExtension() +{ + ErrorPage page; + m_view->setPage(&page); + + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + + m_view->setUrl(QUrl("data:text/html,foo")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + + page.mainFrame()->setUrl(QUrl("http://non.existent/url")); + QTRY_COMPARE(spyLoadFinished.count(), 2); + QCOMPARE(page.mainFrame()->toPlainText(), QString("error")); + QCOMPARE(page.history()->count(), 2); + QCOMPARE(page.history()->currentItem().url(), QUrl("http://non.existent/url")); + QCOMPARE(page.history()->canGoBack(), true); + QCOMPARE(page.history()->canGoForward(), false); + + page.triggerAction(QWebPage::Back); + QTRY_COMPARE(page.history()->canGoBack(), false); + QTRY_COMPARE(page.history()->canGoForward(), true); + + page.triggerAction(QWebPage::Forward); + QTRY_COMPARE(page.history()->canGoBack(), true); + QTRY_COMPARE(page.history()->canGoForward(), false); + + page.triggerAction(QWebPage::Back); + QTRY_COMPARE(page.history()->canGoBack(), false); + QTRY_COMPARE(page.history()->canGoForward(), true); + QTRY_COMPARE(page.history()->currentItem().url(), QUrl("data:text/html,foo")); + + m_view->setPage(0); +} + +void tst_QWebPage::errorPageExtensionInIFrames() +{ + ErrorPage page; + m_view->setPage(&page); + + m_view->page()->mainFrame()->load(QUrl( + "data:text/html," + "<h1>h1</h1>" + "<iframe src='data:text/html,<p/>p'></iframe>" + "<iframe src='http://non.existent/url'></iframe>")); + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + QTRY_COMPARE(spyLoadFinished.count(), 1); + + QCOMPARE(page.mainFrame()->childFrames()[1]->toPlainText(), QString("error")); + + m_view->setPage(0); +} + +void tst_QWebPage::errorPageExtensionInFrameset() +{ + ErrorPage page; + m_view->setPage(&page); + + m_view->load(QUrl("qrc:///resources/index.html")); + + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + QTRY_COMPARE(spyLoadFinished.count(), 1); + QCOMPARE(page.mainFrame()->childFrames().count(), 2); + QCOMPARE(page.mainFrame()->childFrames()[1]->toPlainText(), QString("error")); + + m_view->setPage(0); +} + +class FriendlyWebPage : public QWebPage +{ +public: + friend class tst_QWebPage; +}; + +void tst_QWebPage::userAgentApplicationName() +{ + const QString oldApplicationName = QCoreApplication::applicationName(); + FriendlyWebPage page; + + const QString applicationNameMarker = QString::fromUtf8("StrangeName\342\210\236"); + QCoreApplication::setApplicationName(applicationNameMarker); + QVERIFY(page.userAgentForUrl(QUrl()).contains(applicationNameMarker)); + + QCoreApplication::setApplicationName(oldApplicationName); +} + +class CustomUserAgentWebPage : public QWebPage +{ +public: + static const QLatin1String filteredUserAgent; +protected: + virtual QString userAgentForUrl(const QUrl& url) const + { + return QString("My User Agent\nX-New-Http-Header: Oh Noes!"); + } +}; +const QLatin1String CustomUserAgentWebPage::filteredUserAgent("My User AgentX-New-Http-Header: Oh Noes!"); + +void tst_QWebPage::userAgentNewlineStripping() +{ + CustomUserAgentWebPage page; + QWebFrame* mainFrame = page.mainFrame(); + mainFrame->setHtml("<html><body></body></html>"); + QCOMPARE(mainFrame->evaluateJavaScript("navigator.userAgent").toString(), CustomUserAgentWebPage::filteredUserAgent); +} + +void tst_QWebPage::crashTests_LazyInitializationOfMainFrame() +{ + { + QWebPage webPage; + } + { + QWebPage webPage; + webPage.selectedText(); + } + { + QWebPage webPage; + webPage.selectedHtml(); + } + { + QWebPage webPage; + webPage.triggerAction(QWebPage::Back, true); + } + { + QWebPage webPage; + QPoint pos(10,10); + webPage.updatePositionDependentActions(pos); + } +} + +static void takeScreenshot(QWebPage* page) +{ + QWebFrame* mainFrame = page->mainFrame(); + page->setViewportSize(mainFrame->contentsSize()); + QImage image(page->viewportSize(), QImage::Format_ARGB32); + QPainter painter(&image); + mainFrame->render(&painter); + painter.end(); +} + +void tst_QWebPage::screenshot_data() +{ + QTest::addColumn<QString>("html"); + QTest::newRow("WithoutPlugin") << "<html><body id='b'>text</body></html>"; + QTest::newRow("WindowedPlugin") << QString("<html><body id='b'>text<embed src='http://code.qt.io/resources/test.swf'></embed></body></html>"); + QTest::newRow("WindowlessPlugin") << QString("<html><body id='b'>text<embed src='http://code.qt.io/resources/test.swf' wmode='transparent'></embed></body></html>"); +} + +void tst_QWebPage::screenshot() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QFETCH(QString, html); + QWebPage* page = new QWebPage; + page->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + QWebFrame* mainFrame = page->mainFrame(); + mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + ::waitForSignal(mainFrame, SIGNAL(loadFinished(bool)), 2000); + + // take screenshot without a view + takeScreenshot(page); + + QWebView* view = new QWebView; + view->setPage(page); + + // take screenshot when attached to a view + takeScreenshot(page); + + delete page; + delete view; + + QDir::setCurrent(QApplication::applicationDirPath()); +} + +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL +// https://bugs.webkit.org/show_bug.cgi?id=54138 +static void webGLScreenshotWithoutView(bool accelerated) +{ + QWebPage page; + page.settings()->setAttribute(QWebSettings::WebGLEnabled, true); + page.settings()->setAttribute(QWebSettings::AcceleratedCompositingEnabled, accelerated); + QWebFrame* mainFrame = page.mainFrame(); + mainFrame->setHtml("<html><body>" + "<canvas id='webgl' width='300' height='300'></canvas>" + "<script>document.getElementById('webgl').getContext('experimental-webgl')</script>" + "</body></html>"); + + takeScreenshot(&page); +} + +void tst_QWebPage::acceleratedWebGLScreenshotWithoutView() +{ + webGLScreenshotWithoutView(true); +} + +void tst_QWebPage::unacceleratedWebGLScreenshotWithoutView() +{ + webGLScreenshotWithoutView(false); +} +#endif + +void tst_QWebPage::originatingObjectInNetworkRequests() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requests.clear(); + + m_view->setHtml(QString("<frameset cols=\"25%,75%\"><frame src=\"qrc:///frame_c.html\">" + "<frame src=\"qrc:///frame_b.html\"></frameset>"), QUrl()); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QCOMPARE(networkManager->requests.count(), 2); + + QList<QWebFrame*> childFrames = m_page->mainFrame()->childFrames(); + QCOMPARE(childFrames.count(), 2); + + for (int i = 0; i < 2; ++i) + QVERIFY(qobject_cast<QWebFrame*>(networkManager->requests.at(i).originatingObject()) == childFrames.at(i)); +} + +void tst_QWebPage::networkReplyParentDidntChange() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requests.clear(); + + // Trigger a load and check that pending QNetworkReplies haven't been reparented before returning to the event loop. + m_view->load(QUrl("qrc:///resources/content.html")); + + QVERIFY(networkManager->requests.count() > 0); + QVERIFY(networkManager->findChildren<QNetworkReply*>().size() > 0); +} + +void tst_QWebPage::destroyQNAMBeforeAbortDoesntCrash() +{ + QNetworkAccessManager* networkManager = new QNetworkAccessManager; + m_page->setNetworkAccessManager(networkManager); + + m_view->load(QUrl("qrc:///resources/content.html")); + delete networkManager; + // This simulates what PingLoader does with its QNetworkReply when it times out. + // PingLoader isn't attached to a QWebPage and can be kept alive + // for 60000 seconds (~16.7 hours) to then cancel its ResourceHandle. + m_view->stop(); +} + +/** + * Test fixups for https://bugs.webkit.org/show_bug.cgi?id=30914 + * + * From JS we test the following conditions. + * + * OK + QString() => SUCCESS, empty string (but not null) + * OK + "text" => SUCCESS, "text" + * CANCEL + QString() => CANCEL, null string + * CANCEL + "text" => CANCEL, null string + */ +class JSPromptPage : public QWebPage { + Q_OBJECT +public: + JSPromptPage() + {} + + bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result) + { + if (msg == QLatin1String("test1")) { + *result = QString(); + return true; + } else if (msg == QLatin1String("test2")) { + *result = QLatin1String("text"); + return true; + } else if (msg == QLatin1String("test3")) { + *result = QString(); + return false; + } else if (msg == QLatin1String("test4")) { + *result = QLatin1String("text"); + return false; + } + + qFatal("Unknown msg."); + return QWebPage::javaScriptPrompt(frame, msg, defaultValue, result); + } +}; + +void tst_QWebPage::testJSPrompt() +{ + JSPromptPage page; + bool res; + + // OK + QString() + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test1');" + "retval=='' && retval.length == 0;").toBool(); + QVERIFY(res); + + // OK + "text" + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test2');" + "retval=='text' && retval.length == 4;").toBool(); + QVERIFY(res); + + // Cancel + QString() + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test3');" + "retval===null;").toBool(); + QVERIFY(res); + + // Cancel + "text" + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test4');" + "retval===null;").toBool(); + QVERIFY(res); +} + +class TestModalPage : public QWebPage +{ + Q_OBJECT +public: + TestModalPage(QObject* parent = 0) : QWebPage(parent) { + } + virtual QWebPage* createWindow(WebWindowType) { + QWebPage* page = new TestModalPage(); + connect(page, SIGNAL(windowCloseRequested()), page, SLOT(deleteLater())); + return page; + } +}; + +void tst_QWebPage::showModalDialog() +{ + TestModalPage page; + page.settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); + page.mainFrame()->setHtml(QString("<html></html>")); + QString res = page.mainFrame()->evaluateJavaScript("window.showModalDialog('javascript:window.returnValue=dialogArguments; window.close();', 'This is a test');").toString(); + QCOMPARE(res, QString("This is a test")); +} + +void tst_QWebPage::testStopScheduledPageRefresh() +{ + // Without QWebPage::StopScheduledPageRefresh + QWebPage page1; + page1.setNetworkAccessManager(new TestNetworkManager(&page1)); + page1.mainFrame()->setHtml("<html><head>" + "<meta http-equiv=\"refresh\"content=\"0;URL=qrc:///resources/index.html\">" + "</head><body><h1>Page redirects immediately...</h1>" + "</body></html>"); + QVERIFY(::waitForSignal(&page1, SIGNAL(loadFinished(bool)))); + QTest::qWait(500); + QCOMPARE(page1.mainFrame()->url(), QUrl(QLatin1String("qrc:///resources/index.html"))); + + // With QWebPage::StopScheduledPageRefresh + QWebPage page2; + page2.setNetworkAccessManager(new TestNetworkManager(&page2)); + page2.mainFrame()->setHtml("<html><head>" + "<meta http-equiv=\"refresh\"content=\"1;URL=qrc:///resources/index.html\">" + "</head><body><h1>Page redirect test with 1 sec timeout...</h1>" + "</body></html>"); + page2.triggerAction(QWebPage::StopScheduledPageRefresh); + QTest::qWait(1500); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118673", Continue); + QCOMPARE(page2.mainFrame()->url().toString(), QLatin1String("about:blank")); +} + +void tst_QWebPage::findText() +{ + m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); + m_page->triggerAction(QWebPage::SelectAll); + QVERIFY(!m_page->selectedText().isEmpty()); + QVERIFY(!m_page->selectedHtml().isEmpty()); + m_page->findText(""); + QVERIFY(m_page->selectedText().isEmpty()); + QVERIFY(m_page->selectedHtml().isEmpty()); + QStringList words = (QStringList() << "foo" << "bar"); + foreach (QString subString, words) { + m_page->findText(subString, QWebPage::FindWrapsAroundDocument); + QCOMPARE(m_page->selectedText(), subString); + QVERIFY(m_page->selectedHtml().contains(subString)); + m_page->findText(""); + QVERIFY(m_page->selectedText().isEmpty()); + QVERIFY(m_page->selectedHtml().isEmpty()); + } +} + +void tst_QWebPage::supportedContentType() +{ + QStringList contentTypes; + + // Add supported non image types... + contentTypes << "text/html" << "text/xml" << "text/xsl" << "text/plain" << "text/" + << "application/xml" << "application/xhtml+xml" << "application/vnd.wap.xhtml+xml" + << "application/rss+xml" << "application/atom+xml" << "application/json" + // Add JPEG MIME type + << "image/jpeg"; + +#if ENABLE_MHTML + contentTypes << "application/x-mimearchive"; +#endif + + // Get the mime types supported by webkit... + const QStringList supportedContentTypes = m_page->supportedContentTypes(); + + Q_FOREACH(const QString& mimeType, contentTypes) + QVERIFY2(supportedContentTypes.contains(mimeType), QString("'%1' is not a supported content type!").arg(mimeType).toLatin1()); + + Q_FOREACH(const QString& mimeType, contentTypes) + QVERIFY2(m_page->supportsContentType(mimeType), QString("Cannot handle content types '%1'!").arg(mimeType).toLatin1()); +} + + +void tst_QWebPage::navigatorCookieEnabled() +{ + m_page->networkAccessManager()->setCookieJar(0); + QVERIFY(!m_page->networkAccessManager()->cookieJar()); + QVERIFY(!m_page->mainFrame()->evaluateJavaScript("navigator.cookieEnabled").toBool()); + + m_page->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); + QVERIFY(m_page->networkAccessManager()->cookieJar()); + QVERIFY(m_page->mainFrame()->evaluateJavaScript("navigator.cookieEnabled").toBool()); +} + +#ifdef HAVE_QTTESTSUPPORT +void tst_QWebPage::thirdPartyCookiePolicy() +{ + QWebSettings::globalSettings()->setThirdPartyCookiePolicy(QWebSettings::AlwaysBlockThirdPartyCookies); + m_page->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); + QVERIFY(m_page->networkAccessManager()->cookieJar()); + + // These are all first-party cookies, so should pass. + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.com/"), QUrl("http://example.com/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.com/"), QUrl("http://doc.example.com/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://aaa.www.example.com/"), QUrl("http://doc.example.com/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.com/"), QUrl("http://www.example.com/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.co.uk/"), QUrl("http://example.co.uk/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.co.uk/"), QUrl("http://doc.example.co.uk/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://aaa.www.example.co.uk/"), QUrl("http://doc.example.co.uk/"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.co.uk/"), QUrl("http://www.example.co.uk/"))); + + // These are all third-party cookies, so should fail. + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.com/"), QUrl("http://slashdot.org/"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.com/"), QUrl("http://anotherexample.com/"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://anotherexample.com/"), QUrl("http://example.com/"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.co.uk/"), QUrl("http://slashdot.co.uk/"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.co.uk/"), QUrl("http://anotherexample.co.uk/"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://anotherexample.co.uk/"), QUrl("http://example.co.uk/"))); +} +#endif + +#ifdef Q_OS_MACOS +void tst_QWebPage::macCopyUnicodeToClipboard() +{ + QString unicodeText = QString::fromUtf8("αβγδεζηθικλμπ"); + m_page->mainFrame()->setHtml(QString("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>%1</body></html>").arg(unicodeText)); + m_page->triggerAction(QWebPage::SelectAll); + m_page->triggerAction(QWebPage::Copy); + + QString clipboardData = QString::fromUtf8(QApplication::clipboard()->mimeData()->data(QLatin1String("text/html"))); + + QVERIFY(clipboardData.contains(QLatin1String("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"))); + QVERIFY(clipboardData.contains(unicodeText)); +} +#endif + +void tst_QWebPage::changeVisibilityState() +{ + QVariant stateBool, stateString, cpt; + + m_page->mainFrame()->setHtml("<html><body></body></html>"); + m_page->mainFrame()->evaluateJavaScript("var stateBool = undefined, stateString = undefined, cpt = 0; var visibilityCallBack = function () {stateBool = document['hidden']; stateString = document['visibilityState']; cpt++;};document.addEventListener('visibilitychange', visibilityCallBack, false);"); + + // The visibility state should be initialised to visible. + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateVisible); + stateBool = m_page->mainFrame()->evaluateJavaScript("document['hidden']"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("document['visibilityState']"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + + // Try to change with the same value. + m_page->setVisibilityState(QWebPage::VisibilityStateVisible); + QVERIFY(m_page->visibilityState() == QWebPage::VisibilityStateVisible); + // We check that there isn't any JS event that has been fired and + // visibility properties are still equals to visible. + stateBool = m_page->mainFrame()->evaluateJavaScript("document['hidden']"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("document['visibilityState']"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QVERIFY(cpt.type() == QVariant::Double && !cpt.toDouble()); + + // Try to change to with different values then check if a JS event has been fired + // and visibility properties have been updated correctly. + m_page->setVisibilityState(QWebPage::VisibilityStatePrerender); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStatePrerender); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("prerender")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QVERIFY(cpt.type() == QVariant::Double && cpt.toDouble() == 1); + + m_page->setVisibilityState(QWebPage::VisibilityStateUnloaded); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateUnloaded); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QCOMPARE(stateString.type(), QVariant::String); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(stateString.toString(), QString("unloaded")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 2.0); + + m_page->setVisibilityState(QWebPage::VisibilityStateVisible); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateVisible); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 3.0); + + m_page->setVisibilityState(QWebPage::VisibilityStateHidden); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateHidden); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("hidden")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 4.0); + + m_page->setVisibilityState(QWebPage::VisibilityStateVisible); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateVisible); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 5.0); +} + +void tst_QWebPage::contextMenuCopy() +{ + QWebView view; + + view.setHtml("<a href=\"http://www.google.com/">You cant miss this</a>"); + + view.page()->triggerAction(QWebPage::SelectAll); + QVERIFY(!view.page()->selectedText().isEmpty()); + + QWebElement link = view.page()->mainFrame()->findFirstElement("a"); + QPoint pos(link.geometry().center()); + QContextMenuEvent event(QContextMenuEvent::Mouse, pos); + view.page()->swallowContextMenuEvent(&event); + view.page()->updatePositionDependentActions(pos); + + QList<QMenu*> contextMenus = view.findChildren<QMenu*>(); + QVERIFY(!contextMenus.isEmpty()); + QMenu* contextMenu = contextMenus.first(); + QVERIFY(contextMenu); + + QList<QAction *> list = contextMenu->actions(); + int index = list.indexOf(view.page()->action(QWebPage::Copy)); + QVERIFY(index != -1); +} + +// https://bugs.webkit.org/show_bug.cgi?id=62139 +void tst_QWebPage::contextMenuPopulatedOnce() +{ + QWebView view; + + view.setHtml("<input type=\"text\">"); + + QWebElement link = view.page()->mainFrame()->findFirstElement("input"); + QPoint pos(link.geometry().center()); + QContextMenuEvent event(QContextMenuEvent::Mouse, pos); + view.page()->swallowContextMenuEvent(&event); + view.page()->updatePositionDependentActions(pos); + + QList<QMenu*> contextMenus = view.findChildren<QMenu*>(); + QVERIFY(!contextMenus.isEmpty()); + QMenu* contextMenu = contextMenus.first(); + QVERIFY(contextMenu); + + QList<QAction *> list = contextMenu->actions(); + QStringList entries; + while (!list.isEmpty()) { + const QAction* action = list.takeFirst(); + if (!action->isSeparator()) { + QString entry = action->text(); + QVERIFY(!entries.contains(entry)); + entries << entry; + } + } +} + +void tst_QWebPage::deleteQWebViewTwice() +{ + for (int i = 0; i < 2; ++i) { + QMainWindow mainWindow; + QWebView* webView = new QWebView(&mainWindow); + mainWindow.setCentralWidget(webView); + webView->load(QUrl("qrc:///resources/frame_a.html")); + mainWindow.show(); + QVERIFY(::waitForSignal(webView, SIGNAL(loadFinished(bool)))); + } +} + +class RepaintRequestedRenderer : public QObject { + Q_OBJECT +public: + RepaintRequestedRenderer(QWebPage* page, QPainter* painter) + : m_page(page) + , m_painter(painter) + , m_recursionCount(0) + { + connect(m_page, SIGNAL(repaintRequested(QRect)), this, SLOT(onRepaintRequested(QRect))); + } + +Q_SIGNALS: + void finished(); + +private Q_SLOTS: + void onRepaintRequested(const QRect& rect) + { + QCOMPARE(m_recursionCount, 0); + + m_recursionCount++; + m_page->mainFrame()->render(m_painter, rect); + m_recursionCount--; + + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + } + +private: + QWebPage* m_page; + QPainter* m_painter; + int m_recursionCount; +}; + +void tst_QWebPage::renderOnRepaintRequestedShouldNotRecurse() +{ + QSize viewportSize(720, 576); + QWebPage page; + + QImage image(viewportSize, QImage::Format_ARGB32); + QPainter painter(&image); + + page.setPreferredContentsSize(viewportSize); + page.setViewportSize(viewportSize); + RepaintRequestedRenderer r(&page, &painter); + + page.mainFrame()->setHtml("zalan loves trunk", QUrl()); + + QVERIFY(::waitForSignal(&r, SIGNAL(finished()))); +} + +class SpyForLoadSignalsOrder : public QStateMachine { + Q_OBJECT +public: + SpyForLoadSignalsOrder(QWebPage* page, QObject* parent = 0) + : QStateMachine(parent) + { + connect(page, SIGNAL(loadProgress(int)), SLOT(onLoadProgress(int))); + + QState* waitingForLoadStarted = new QState(this); + QState* waitingForLastLoadProgress = new QState(this); + QState* waitingForLoadFinished = new QState(this); + QFinalState* final = new QFinalState(this); + + waitingForLoadStarted->addTransition(page, SIGNAL(loadStarted()), waitingForLastLoadProgress); + waitingForLastLoadProgress->addTransition(this, SIGNAL(lastLoadProgress()), waitingForLoadFinished); + waitingForLoadFinished->addTransition(page, SIGNAL(loadFinished(bool)), final); + + setInitialState(waitingForLoadStarted); + start(); + } + bool isFinished() const + { + return !isRunning(); + } +public Q_SLOTS: + void onLoadProgress(int progress) + { + if (progress == 100) + emit lastLoadProgress(); + } +Q_SIGNALS: + void lastLoadProgress(); +}; + +void tst_QWebPage::loadSignalsOrder_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::newRow("inline data") << QUrl("data:text/html,This is first page"); + QTest::newRow("simple page") << QUrl("qrc:///resources/content.html"); + QTest::newRow("frameset page") << QUrl("qrc:///resources/index.html"); +} + +void tst_QWebPage::loadSignalsOrder() +{ + QFETCH(QUrl, url); + QWebPage page; + SpyForLoadSignalsOrder loadSpy(&page); + waitForSignal(&loadSpy, SIGNAL(started())); + page.mainFrame()->load(url); + QTRY_VERIFY(loadSpy.isFinished()); +} + +void tst_QWebPage::undoActionHaveCustomText() +{ + m_page->mainFrame()->setHtml("<div id=test contenteditable></div>"); + m_page->mainFrame()->evaluateJavaScript("document.getElementById('test').focus()"); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('insertText', true, 'Test');"); + QString typingActionText = m_page->action(QWebPage::Undo)->text(); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('indent', true);"); + QString alignActionText = m_page->action(QWebPage::Undo)->text(); + + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QVERIFY(typingActionText != alignActionText); +} + +void tst_QWebPage::openWindowDefaultSize() +{ + TestPage page; + page.settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); + // Open a default window. + page.mainFrame()->evaluateJavaScript("window.open()"); + // Open a too small window. + page.mainFrame()->evaluateJavaScript("window.open('', '', 'width=10,height=10')"); + + QTest::qWait(500); + // The number of popups created should be two. + QVERIFY(page.createdWindows.size() == 2); + + QRect requestedGeometry = page.createdWindows[0]->requestedGeometry; + // Check default size has been requested. + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(requestedGeometry.width(), 0); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(requestedGeometry.height(), 0); + + requestedGeometry = page.createdWindows[1]->requestedGeometry; + // Check minimum size has been requested. + QCOMPARE(requestedGeometry.width(), 100); + QCOMPARE(requestedGeometry.height(), 100); +} + +void tst_QWebPage::cssMediaTypeGlobalSetting() +{ + QString testHtml("<style>@media tv {body{background-color:red;}}@media handheld {body{background-color:green;}}@media screen {body{background-color:blue;}}</style>"); + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + QWebSettings::globalSettings()->setCSSMediaType("tv"); + // Clear page specific setting to read from global setting + m_view->page()->settings()->setCSSMediaType(QString()); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('tv').matches == true").toBool()); + QVERIFY(QWebSettings::globalSettings()->cssMediaType() == "tv"); + + QWebSettings::globalSettings()->setCSSMediaType("handheld"); + // Clear page specific setting to read from global setting + m_view->page()->settings()->setCSSMediaType(QString()); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 2); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('handheld').matches == true").toBool()); + QVERIFY(QWebSettings::globalSettings()->cssMediaType() == "handheld"); + + QWebSettings::globalSettings()->setCSSMediaType("screen"); + // Clear page specific setting to read from global setting + m_view->page()->settings()->setCSSMediaType(QString()); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 3); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('screen').matches == true").toBool()); + QVERIFY(QWebSettings::globalSettings()->cssMediaType() == "screen"); +} + +void tst_QWebPage::cssMediaTypePageSetting() +{ + QString testHtml("<style>@media tv {body{background-color:red;}}@media handheld {body{background-color:green;}}@media screen {body{background-color:blue;}}</style>"); + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + m_view->page()->settings()->setCSSMediaType("tv"); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('tv').matches == true").toBool()); + QVERIFY(m_view->page()->settings()->cssMediaType() == "tv"); + + m_view->page()->settings()->setCSSMediaType("handheld"); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 2); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('handheld').matches == true").toBool()); + QVERIFY(m_view->page()->settings()->cssMediaType() == "handheld"); + + m_view->page()->settings()->setCSSMediaType("screen"); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 3); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('screen').matches == true").toBool()); + QVERIFY(m_view->page()->settings()->cssMediaType() == "screen"); +} + +QTEST_MAIN(tst_QWebPage) +#include "tst_qwebpage.moc" diff --git a/tests/webkitwidgets/qwebpage/tst_qwebpage.qrc b/tests/webkitwidgets/qwebpage/tst_qwebpage.qrc new file mode 100644 index 000000000..ab78dfa60 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/tst_qwebpage.qrc @@ -0,0 +1,15 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/index.html</file> + <file>resources/frame_a.html</file> + <file>resources/frame_c.html</file> + <file>resources/iframe.html</file> + <file>resources/iframe2.html</file> + <file>resources/iframe3.html</file> + <file>resources/framedindex.html</file> + <file>resources/content.html</file> + <file>resources/script.html</file> + <file>resources/user.css</file> +</qresource> +</RCC> + diff --git a/tests/webkitwidgets/qwebplugindatabase/qwebplugindatabase.pro b/tests/webkitwidgets/qwebplugindatabase/qwebplugindatabase.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebplugindatabase/qwebplugindatabase.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebplugindatabase/tst_qwebplugindatabase.cpp b/tests/webkitwidgets/qwebplugindatabase/tst_qwebplugindatabase.cpp new file mode 100644 index 000000000..1d524e24a --- /dev/null +++ b/tests/webkitwidgets/qwebplugindatabase/tst_qwebplugindatabase.cpp @@ -0,0 +1,437 @@ +/* + Copyright (C) 2009 Jakub Wieczorek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <QtTest/QtTest> + +#include <qdir.h> +#include <qwebframe.h> +#include <qwebpage.h> +#include <qwebplugindatabase.h> +#include <qwebsettings.h> +#include <qvariant.h> + +class tst_QWebPluginDatabase : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void installedPlugins(); + void searchPaths(); + void null_data(); + void null(); + void pluginForMimeType(); + void enabled(); + void operatorequal_data(); + void operatorequal(); + void preferredPlugin(); + void operatorassign_data(); + void operatorassign(); +}; + +typedef QWebPluginInfo::MimeType MimeType; + +void tst_QWebPluginDatabase::installedPlugins() +{ + QWebPage page; + page.settings()->setAttribute(QWebSettings::PluginsEnabled, true); + QWebFrame* frame = page.mainFrame(); + + QVariantMap jsPluginsMap = frame->evaluateJavaScript("window.navigator.plugins").toMap(); + QList<QWebPluginInfo> plugins = QWebSettings::pluginDatabase()->plugins(); + QCOMPARE(plugins, QWebSettings::pluginDatabase()->plugins()); + + int length = jsPluginsMap["length"].toInt(); + QCOMPARE(length, plugins.count()); + + for (int i = 0; i < length; ++i) { + QWebPluginInfo plugin = plugins.at(i); + + QVariantMap jsPlugin = frame->evaluateJavaScript(QString("window.navigator.plugins[%1]").arg(i)).toMap(); + QString name = jsPlugin["name"].toString(); + QString description = jsPlugin["description"].toString(); + QString fileName = jsPlugin["filename"].toString(); + + QCOMPARE(plugin.name(), name); + QCOMPARE(plugin.description(), description); + QCOMPARE(QFileInfo(plugin.path()).fileName(), fileName); + + QList<MimeType> mimeTypes; + int mimeTypesCount = jsPlugin["length"].toInt(); + + for (int j = 0; j < mimeTypesCount; ++j) { + QVariantMap jsMimeType = frame->evaluateJavaScript(QString("window.navigator.plugins[%1][%2]").arg(i).arg(j)).toMap(); + + MimeType mimeType; + mimeType.name = jsMimeType["type"].toString(); + mimeType.description = jsMimeType["description"].toString(); + mimeType.fileExtensions = jsMimeType["suffixes"].toString().split(',', QString::SkipEmptyParts); + + mimeTypes.append(mimeType); + QVERIFY(plugin.supportsMimeType(mimeType.name)); + } + + QCOMPARE(plugin.mimeTypes(), mimeTypes); + + QVERIFY(!plugin.isNull()); + QVERIFY(plugin.isEnabled()); + } +} + +void tst_QWebPluginDatabase::searchPaths() +{ + QWebPluginDatabase* database = QWebSettings::pluginDatabase(); + QList<QWebPluginInfo> plugins = database->plugins(); + QStringList directories = database->searchPaths(); + QCOMPARE(QWebPluginDatabase::defaultSearchPaths(), directories); + + database->setSearchPaths(directories); + QCOMPARE(QWebPluginDatabase::defaultSearchPaths(), directories); + QCOMPARE(database->searchPaths(), directories); + QCOMPARE(database->plugins(), plugins); + database->refresh(); + QCOMPARE(database->plugins(), plugins); + + database->setSearchPaths(QStringList()); + QCOMPARE(QWebPluginDatabase::defaultSearchPaths(), directories); + QCOMPARE(database->searchPaths(), QStringList()); + QCOMPARE(database->plugins().count(), 0); + + database->setSearchPaths(directories); + QCOMPARE(database->searchPaths(), directories); + database->addSearchPath(QDir::tempPath()); + QCOMPARE(database->searchPaths().count(), directories.count() + 1); + QVERIFY(database->searchPaths().contains(QDir::tempPath())); + directories.append(QDir::tempPath()); + QCOMPARE(database->searchPaths(), directories); + + // As an empty set of search paths has been set, the database has been rebuilt + // from scratch after bringing the old path set back. + // Because the QWebPlugins no longer point to the same PluginPackages, + // the list is also no longer equal to the older one, even though it contains + // the same information. + QCOMPARE(database->plugins().count(), plugins.count()); + plugins = database->plugins(); + QCOMPARE(database->plugins(), plugins); + + for (int i = (directories.count() - 1); i >= 0; --i) { + QDir directory(directories.at(i)); + if (!directory.exists() || !directory.count()) + directories.removeAt(i); + } + + database->setSearchPaths(directories); + QCOMPARE(database->plugins(), plugins); + database->refresh(); + QCOMPARE(database->plugins(), plugins); + + database->setSearchPaths(QWebPluginDatabase::defaultSearchPaths()); + directories = QWebPluginDatabase::defaultSearchPaths(); + QCOMPARE(QWebPluginDatabase::defaultSearchPaths(), directories); + QCOMPARE(database->searchPaths(), directories); + QCOMPARE(database->plugins(), plugins); +} + +Q_DECLARE_METATYPE(QWebPluginInfo) +void tst_QWebPluginDatabase::null_data() +{ + QTest::addColumn<QWebPluginInfo>("plugin"); + QTest::addColumn<bool>("null"); + + QTest::newRow("null") << QWebPluginInfo() << true; + QTest::newRow("foo") << QWebSettings::pluginDatabase()->pluginForMimeType("foobarbaz") << true; + + QList<QWebPluginInfo> plugins = QWebSettings::pluginDatabase()->plugins(); + for (int i = 0; i < plugins.count(); ++i) + QTest::newRow(QString::number(i).toUtf8().constData()) << plugins.at(i) << false; +} + +void tst_QWebPluginDatabase::null() +{ + QFETCH(QWebPluginInfo, plugin); + QFETCH(bool, null); + + QCOMPARE(plugin.isNull(), null); +} + +void tst_QWebPluginDatabase::pluginForMimeType() +{ + QMultiMap<QString, QWebPluginInfo> pluginsMap; + QWebPluginDatabase* database = QWebSettings::pluginDatabase(); + QList<QWebPluginInfo> plugins = database->plugins(); + + for (int i = 0; i < plugins.count(); ++i) { + QWebPluginInfo plugin = plugins.at(i); + + QList<MimeType> mimeTypes = plugin.mimeTypes(); + for (int j = 0; j < mimeTypes.count(); ++j) { + QString mimeType = mimeTypes.at(j).name; + pluginsMap.insert(mimeType, plugin); + QVERIFY(plugin.supportsMimeType(mimeType)); + } + } + + for (int i = 0; i < plugins.count(); ++i) { + QWebPluginInfo plugin = plugins.at(i); + + QList<MimeType> mimeTypes = plugin.mimeTypes(); + for (int j = 0; j < mimeTypes.count(); ++j) { + QString mimeType = mimeTypes.at(j).name; + + QVERIFY(pluginsMap.count(mimeType) > 0); + if (pluginsMap.count(mimeType) > 1) + continue; + + QWebPluginInfo pluginForMimeType = database->pluginForMimeType(mimeType); + QCOMPARE(pluginForMimeType, plugin); + database->setSearchPaths(database->searchPaths()); + QCOMPARE(pluginForMimeType, plugin); + QCOMPARE(pluginForMimeType, database->pluginForMimeType(mimeType.toUpper())); + QCOMPARE(pluginForMimeType, database->pluginForMimeType(mimeType.toLower())); + QVERIFY(plugin.supportsMimeType(mimeType)); + QVERIFY(!pluginForMimeType.isNull()); + QVERIFY(!plugin.isNull()); + } + } +} + +void tst_QWebPluginDatabase::enabled() +{ + QMultiMap<QString, QWebPluginInfo> pluginsMap; + QWebPluginDatabase* database = QWebSettings::pluginDatabase(); + QList<QWebPluginInfo> plugins = database->plugins(); + + for (int i = 0; i < plugins.count(); ++i) { + QWebPluginInfo plugin = plugins.at(i); + + QList<MimeType> mimeTypes = plugin.mimeTypes(); + for (int j = 0; j < mimeTypes.count(); ++j) { + QString mimeType = mimeTypes.at(j).name; + pluginsMap.insert(mimeType, plugin); + QVERIFY(plugin.supportsMimeType(mimeType)); + } + } + + QMultiMap<QString, QWebPluginInfo>::iterator it = pluginsMap.begin(); + while (it != pluginsMap.end()) { + QString mimeType = it.key(); + QWebPluginInfo plugin = it.value(); + QWebPluginInfo pluginForMimeType = database->pluginForMimeType(mimeType); + + QVERIFY(pluginsMap.count(mimeType) > 0); + + if (pluginsMap.count(mimeType) == 1) { + QCOMPARE(plugin, pluginForMimeType); + + QVERIFY(plugin.isEnabled()); + QVERIFY(pluginForMimeType.isEnabled()); + plugin.setEnabled(false); + QVERIFY(!plugin.isEnabled()); + QVERIFY(!pluginForMimeType.isEnabled()); + } else { + QVERIFY(plugin.isEnabled()); + QVERIFY(pluginForMimeType.isEnabled()); + plugin.setEnabled(false); + QVERIFY(!plugin.isEnabled()); + } + + QVERIFY(!plugin.isNull()); + QVERIFY(!pluginForMimeType.isNull()); + + QWebPluginInfo pluginForMimeType2 = database->pluginForMimeType(mimeType); + if (pluginsMap.count(mimeType) == 1) { + QVERIFY(pluginForMimeType2 != plugin); + QVERIFY(pluginForMimeType2.isNull()); + } else { + QVERIFY(pluginForMimeType2 != plugin); + QVERIFY(!pluginForMimeType2.isNull()); + } + + plugin.setEnabled(true); + + ++it; + } +} + +void tst_QWebPluginDatabase::operatorequal_data() +{ + QTest::addColumn<QWebPluginInfo>("first"); + QTest::addColumn<QWebPluginInfo>("second"); + QTest::addColumn<bool>("equal"); + + QWebPluginDatabase* database = QWebSettings::pluginDatabase(); + QTest::newRow("null") << QWebPluginInfo() << QWebPluginInfo() << true; + QTest::newRow("application/x-shockwave-flash") << database->pluginForMimeType("application/x-shockwave-flash") + << database->pluginForMimeType("application/x-shockwave-flash") << true; + QTest::newRow("foo/bar-baz") << database->pluginForMimeType("foo/bar-baz") + << database->pluginForMimeType("foo/bar-baz") << true; + + QList<QWebPluginInfo> plugins = database->plugins(); + for (int i = 0; i < (plugins.count() - 1); ++i) { + QWebPluginInfo first = plugins.at(i); + QWebPluginInfo second = plugins.at(i + 1); + + QTest::newRow(QString("%1==%2").arg(first.name(), second.name()).toUtf8().constData()) + << first << second << false; + } +} + +void tst_QWebPluginDatabase::operatorequal() +{ + QFETCH(QWebPluginInfo, first); + QFETCH(QWebPluginInfo, second); + QFETCH(bool, equal); + + QCOMPARE(first == second, equal); +} + +void tst_QWebPluginDatabase::preferredPlugin() +{ + QMultiMap<QString, QWebPluginInfo> pluginsMap; + QWebPluginDatabase* database = QWebSettings::pluginDatabase(); + QList<QWebPluginInfo> plugins = database->plugins(); + + for (int i = 0; i < plugins.count(); ++i) { + QWebPluginInfo plugin = plugins.at(i); + + QList<MimeType> mimeTypes = plugin.mimeTypes(); + for (int j = 0; j < mimeTypes.count(); ++j) { + QString mimeType = mimeTypes.at(j).name; + pluginsMap.insert(mimeType, plugin); + } + } + + QMultiMap<QString, QWebPluginInfo>::iterator it = pluginsMap.begin(); + while (it != pluginsMap.end()) { + QString mimeType = it.key(); + + if (pluginsMap.count(mimeType) > 1) { + QList<QWebPluginInfo> pluginsForMimeType = pluginsMap.values(mimeType); + QWebPluginInfo plugin = database->pluginForMimeType(mimeType); + QVERIFY(plugin.supportsMimeType(mimeType)); + + pluginsForMimeType.removeAll(plugin); + for (int i = 0; i < pluginsForMimeType.count(); ++i) { + QWebPluginInfo anotherPlugin = pluginsForMimeType.at(i); + QVERIFY(plugin.supportsMimeType(mimeType)); + QVERIFY(plugin != anotherPlugin); + + QCOMPARE(database->pluginForMimeType(mimeType), plugin); + database->setPreferredPluginForMimeType(mimeType, anotherPlugin); + QCOMPARE(database->pluginForMimeType(mimeType), anotherPlugin); + + anotherPlugin.setEnabled(false); + QCOMPARE(database->pluginForMimeType(mimeType), plugin); + + anotherPlugin.setEnabled(true); + QCOMPARE(database->pluginForMimeType(mimeType), anotherPlugin); + database->setSearchPaths(database->searchPaths()); + QCOMPARE(database->pluginForMimeType(mimeType), anotherPlugin); + + database->setPreferredPluginForMimeType(mimeType, QWebPluginInfo()); + QCOMPARE(database->pluginForMimeType(mimeType), plugin); + } + } else { + QWebPluginInfo plugin = database->pluginForMimeType(mimeType); + QCOMPARE(pluginsMap.value(mimeType), plugin); + + database->setPreferredPluginForMimeType(mimeType, plugin); + QCOMPARE(database->pluginForMimeType(mimeType), plugin); + + plugin.setEnabled(false); + QCOMPARE(database->pluginForMimeType(mimeType), QWebPluginInfo()); + plugin.setEnabled(true); + + database->setPreferredPluginForMimeType(mimeType, QWebPluginInfo()); + QCOMPARE(database->pluginForMimeType(mimeType), plugin); + } + + ++it; + } + + if (pluginsMap.keys().count() >= 2) { + QStringList mimeTypes = pluginsMap.uniqueKeys(); + + QString mimeType1 = mimeTypes.at(0); + QString mimeType2 = mimeTypes.at(1); + QWebPluginInfo plugin1 = database->pluginForMimeType(mimeType1); + QWebPluginInfo plugin2 = database->pluginForMimeType(mimeType2); + + int i = 2; + while (plugin2.supportsMimeType(mimeType1) + && !mimeType2.isEmpty() + && i < mimeTypes.count()) { + mimeType2 = mimeTypes.at(i); + plugin2 = database->pluginForMimeType(mimeType2); + ++i; + } + + plugin1 = database->pluginForMimeType(mimeType1); + QVERIFY(plugin1.supportsMimeType(mimeType1)); + QVERIFY(!plugin1.isNull()); + plugin2 = database->pluginForMimeType(mimeType2); + QVERIFY(plugin2.supportsMimeType(mimeType2)); + QVERIFY(!plugin2.isNull()); + + database->setPreferredPluginForMimeType(mimeType2, plugin1); + QVERIFY(!plugin1.supportsMimeType(mimeType2)); + QCOMPARE(database->pluginForMimeType(mimeType2), plugin2); + + database->setPreferredPluginForMimeType(mimeType1, plugin1); + QVERIFY(!plugin2.supportsMimeType(mimeType1)); + QCOMPARE(database->pluginForMimeType(mimeType2), plugin2); + } +} + +void tst_QWebPluginDatabase::operatorassign_data() +{ + QTest::addColumn<QWebPluginInfo>("first"); + QTest::addColumn<QWebPluginInfo>("second"); + + QWebPluginDatabase* database = QWebSettings::pluginDatabase(); + QTest::newRow("null") << QWebPluginInfo() << QWebPluginInfo(); + + QList<QWebPluginInfo> plugins = database->plugins(); + for (int i = 0; i < (plugins.count() - 1); ++i) { + QWebPluginInfo first = plugins.at(i); + QWebPluginInfo second = plugins.at(i + 1); + + QTest::newRow(QString("%1=%2").arg(first.name(), second.name()).toUtf8().constData()) << first << second; + } +} + +void tst_QWebPluginDatabase::operatorassign() +{ + QFETCH(QWebPluginInfo, first); + QFETCH(QWebPluginInfo, second); + + QWebPluginInfo info; + QCOMPARE(info.mimeTypes(), QList<MimeType>()); + QCOMPARE(info = first, first); + QCOMPARE(info, first); + QCOMPARE(info.mimeTypes(), first.mimeTypes()); + QCOMPARE(info = second, second); + QCOMPARE(info, second); + QCOMPARE(info.mimeTypes(), second.mimeTypes()); + QCOMPARE(info = QWebPluginInfo(), QWebPluginInfo()); + QCOMPARE(info.mimeTypes(), QList<MimeType>()); +} + +QTEST_MAIN(tst_QWebPluginDatabase) + +#include "tst_qwebplugindatabase.moc" diff --git a/tests/webkitwidgets/qwebsecurityorigin/qwebsecurityorigin.pro b/tests/webkitwidgets/qwebsecurityorigin/qwebsecurityorigin.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebsecurityorigin/qwebsecurityorigin.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebsecurityorigin/resources/test.html b/tests/webkitwidgets/qwebsecurityorigin/resources/test.html new file mode 100644 index 000000000..43e25f26d --- /dev/null +++ b/tests/webkitwidgets/qwebsecurityorigin/resources/test.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>QWebSecurityOrigin test page</title> + <script Language="JavaScript"> + function runTest(url) + { + var result = ""; + function trace(point) + { + var el = document.createElement("P"); + el.innerHTML = point + ": Result is:\"" + result + "\"" + document.getElementById("Console").appendChild(el); + } + try { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET',url, false); + xmlhttp.onreadystatechange = done; + xmlhttp.onerror = error; + xmlhttp.send(null); + result = xmlhttp.responseText; + } catch (e) { + if (result == "") { + result = e; + trace("Catch1"); + } else + trace("Catch2"); + } + + function done() + { + if (result.length < 5) { + result = "" + xmlhttp.readyState; + trace("Done1"); + } else + trace("Done2"); + } + function error() + { + result = "FAILED"; + trace("Error"); + } + trace("Exit"); + return result == "Test"; + } + </script> + </head> + <body> + <input type="button" onclick="javascript:alert(runTest('http://www.google.com/'))" value="Run Test"/><br/> + <div id="Console"/> + </body> + </html>
\ No newline at end of file diff --git a/tests/webkitwidgets/qwebsecurityorigin/tst_qwebsecurityorigin.cpp b/tests/webkitwidgets/qwebsecurityorigin/tst_qwebsecurityorigin.cpp new file mode 100644 index 000000000..225c42d0a --- /dev/null +++ b/tests/webkitwidgets/qwebsecurityorigin/tst_qwebsecurityorigin.cpp @@ -0,0 +1,191 @@ +/* + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + Copyright (C) 2013 Cisco Systems, Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include <QNetworkReply> +#include <QtTest> +#include <qwebframe.h> +#include <qwebpage.h> +#include <qwebsecurityorigin.h> +#include <qwebview.h> + +class tst_QWebSecurityOrigin : public QObject { + Q_OBJECT +public: + tst_QWebSecurityOrigin(); + virtual ~tst_QWebSecurityOrigin(); + +private slots: + void init(); + void cleanup(); + void whiteList_data(); + void whiteList(); +private: + QWebView* m_view { nullptr }; + QWebPage* m_page { nullptr }; +}; + +tst_QWebSecurityOrigin::tst_QWebSecurityOrigin() +{ +} + +tst_QWebSecurityOrigin::~tst_QWebSecurityOrigin() +{ +} + +void tst_QWebSecurityOrigin::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebSecurityOrigin::cleanup() +{ + delete m_view; +} + +void tst_QWebSecurityOrigin::whiteList_data() +{ + QTest::addColumn<QString>("source"); + QTest::addColumn<QString>("scheme"); + QTest::addColumn<QString>("host"); + QTest::addColumn<bool>("includeSubDomains"); + QTest::addColumn<QString>("testUrl"); + QTest::addColumn<bool>("successBeforeAdd"); + QTest::addColumn<bool>("successAfterAdd"); + QTest::addColumn<bool>("successAfterRemove"); + + QTest::newRow("scheme") << "http://www.source.com/" << "https" << "www.target.com" << false << "https://www.target.com/other" << false << true << false; + QTest::newRow("schemeFail") << "http://www.source.com/" << "https" << "www.target.com" << false << "http://www.target.com/other" << false << false << false; + QTest::newRow("schemeSubDom") << "http://www.source.com/" << "https" << "target.com" << true << "https://www.target.com/other" << false << true << false; + QTest::newRow("schemeSubDomFail") << "http://www.source.com/" << "https" << "target.com" << true << "https://wwwtarget.com/other" << false << false << false; + QTest::newRow("schemeSubDomFail") << "http://www.source.com/" << "https" << "target.com" << true << "http://www.target.com/other" << false << false << false; + QTest::newRow("host") << "http://www.source.com/" << "http" << "www.target.com" << false << "http://www.target.com/target" << false << true << false; + QTest::newRow("hostFail") << "http://www.source.com/" << "http" << "www.target.com" << false << "http://www.newtarget.com/" << false << false << false; + QTest::newRow("hostSubDom") << "http://www.source.com/" << "http" << "target.com" << true << "http://www.new.target.com/" << false << true << false; + QTest::newRow("hostSubDomFail") << "http://www.source.com/" << "http" << "target.com" << false << "http://www.new.target.com/" << false << false << false; + QTest::newRow("hostSubDomFailCountry") << "http://www.source.com/" << "http" << "target.com" << true << "http://www.target.com.tw/" << false << false << false; +} + +class CannedResponseNetworkReply: public QNetworkReply { + Q_OBJECT +public: + CannedResponseNetworkReply(QObject* parent, QNetworkAccessManager::Operation op, const QNetworkRequest& req, const QBuffer& body): QNetworkReply(parent) + { + setRequest(req); + setUrl(req.url()); + setOperation(op); + m_buffer.setData(body.data()); + connect(&m_buffer, SIGNAL(readyRead()), SLOT(emitReadyRead())); + connect(&m_buffer, SIGNAL(readChannelFinished()), SLOT(emitReadChannelFinished())); + connect(&m_buffer, SIGNAL(readChannelFinished()), SLOT(emitReadChannelFinished())); + m_buffer.open(QIODevice::ReadOnly); + open(QIODevice::ReadOnly); + QTimer::singleShot(10, this, SLOT(update())); + } +protected: + qint64 readData(char * data, qint64 maxSize) + { + qint64 result = m_buffer.read(data, maxSize); + if (!m_buffer.bytesAvailable()) + QTimer::singleShot(10, this, SLOT(emitReadChannelFinished())); + return result; + } + + virtual qint64 bytesAvailable() const + { + return m_buffer.bytesAvailable(); + } + +private slots: + void emitReadyRead() + { + emit readyRead(); + } + + void emitReadChannelFinished() + { + emit readChannelFinished(); + emit finished(); + } + void abort() { }; + + void update() + { + setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + setHeader(QNetworkRequest::ContentLengthHeader, m_buffer.size()); + setError(QNetworkReply::NoError, ""); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, QString("Ok").toLatin1()); + emit metaDataChanged(); + emit readyRead(); + } +public: + QBuffer m_buffer; +}; + +class CannedResponseNetworkAccessManager: public QNetworkAccessManager { + Q_OBJECT +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& req, QIODevice* outgoingData = 0) + { + return new CannedResponseNetworkReply(this, op, req, *m_buffer); + }; +public: + QBuffer* m_buffer; +}; + +static const char cannedResponse[] = "Test"; + +void tst_QWebSecurityOrigin::whiteList() +{ + QFETCH(QString, source); + QFETCH(QString, scheme); + QFETCH(QString, host); + QFETCH(bool, includeSubDomains); + QFETCH(QString, testUrl); + QFETCH(bool, successBeforeAdd); + QFETCH(bool, successAfterAdd); + QFETCH(bool, successAfterRemove); + + QWebSecurityOrigin* origin = new QWebSecurityOrigin(source); + CannedResponseNetworkAccessManager manager; + QBuffer buffer; + QWebSecurityOrigin::SubdomainSetting subdomainSetting = includeSubDomains ? QWebSecurityOrigin::AllowSubdomains : QWebSecurityOrigin::DisallowSubdomains; + buffer.setData(cannedResponse, sizeof(cannedResponse)-1); + manager.m_buffer = &buffer; + QFile testPageFile(":/resources/test.html"); + QVERIFY(testPageFile.open(QIODevice::ReadOnly)); + uchar* testPage = testPageFile.map(0, testPageFile.size()); + QVERIFY(testPage); + m_view->setHtml(QString((const char*)testPage), QUrl(source)); + m_view->page()->setNetworkAccessManager(&manager); + QString testJS="runTest(\"" + testUrl + "\")"; + QCOMPARE(m_view->page()->mainFrame()->evaluateJavaScript(testJS), QVariant(successBeforeAdd)); + origin->addAccessWhitelistEntry(scheme, host, subdomainSetting); + QCOMPARE(m_view->page()->mainFrame()->evaluateJavaScript(testJS), QVariant(successAfterAdd)); + origin->removeAccessWhitelistEntry(scheme, host, subdomainSetting); + QCOMPARE(m_view->page()->mainFrame()->evaluateJavaScript(testJS), QVariant(successAfterRemove)); + m_view->page()->setNetworkAccessManager(0); +} + +QTEST_MAIN(tst_QWebSecurityOrigin) +#include "tst_qwebsecurityorigin.moc" + diff --git a/tests/webkitwidgets/qwebsecurityorigin/tst_qwebsecurityorigin.qrc b/tests/webkitwidgets/qwebsecurityorigin/tst_qwebsecurityorigin.qrc new file mode 100644 index 000000000..ec8d9c02d --- /dev/null +++ b/tests/webkitwidgets/qwebsecurityorigin/tst_qwebsecurityorigin.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/test.html</file> +</qresource> +</RCC> + diff --git a/tests/webkitwidgets/qwebview/qwebview.pro b/tests/webkitwidgets/qwebview/qwebview.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/webkitwidgets/qwebview/qwebview.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/webkitwidgets/qwebview/resources/frame_a.html b/tests/webkitwidgets/qwebview/resources/frame_a.html new file mode 100644 index 000000000..9ff68f13a --- /dev/null +++ b/tests/webkitwidgets/qwebview/resources/frame_a.html @@ -0,0 +1,2 @@ +<a href="http://google.com/" target="frame_b"><img src="" width=100 height=100 alt="Google"></a> +<a href="http://yahoo.com/" target="frame_b"><img src="" width=100 height=100 alt="Yahoo"></a> diff --git a/tests/webkitwidgets/qwebview/resources/index.html b/tests/webkitwidgets/qwebview/resources/index.html new file mode 100644 index 000000000..c53ad09a7 --- /dev/null +++ b/tests/webkitwidgets/qwebview/resources/index.html @@ -0,0 +1,4 @@ +<frameset cols="25%,75%"> + <frame src="http://code.qt.io/frame_a.html" name="frame_a"> + <frame src="http://code.qt.io/frame_b.html" name="frame_b"> +</frameset> diff --git a/tests/webkitwidgets/qwebview/resources/input_types.html b/tests/webkitwidgets/qwebview/resources/input_types.html new file mode 100644 index 000000000..2e893afae --- /dev/null +++ b/tests/webkitwidgets/qwebview/resources/input_types.html @@ -0,0 +1,9 @@ +<html><body> +<input type='text' maxlength='20' style='position: absolute; left: 10px; top: 0px; height: 50px; width: 100px;'/><br> +<input type='password' style='position: absolute; left: 10px; top: 50px; height: 50px; width: 100px;'/><br> +<input type='tel' style='position: absolute; left: 10px; top: 100px; height: 50px; width: 100px;'/><br> +<input type='number' style='position: absolute; left: 10px; top: 150px; height: 50px; width: 100px;'/><br> +<input type='email' style='position: absolute; left: 10px; top: 200px; height: 50px; width: 100px;'/><br> +<input type='url' style='position: absolute; left: 10px; top: 250px; height: 50px; width: 100px;'/><br> +<textarea style='position: absolute; left: 10px; top: 310px; height: 50px; width: 100px;' rows="2" cols="20">blah blah blah blah</textarea><br> +</body></html> diff --git a/tests/webkitwidgets/qwebview/resources/scrolltest_page.html b/tests/webkitwidgets/qwebview/resources/scrolltest_page.html new file mode 100644 index 000000000..18fcbbebe --- /dev/null +++ b/tests/webkitwidgets/qwebview/resources/scrolltest_page.html @@ -0,0 +1,6 @@ +<html> +<head><title>Scrolling test</title></head> +<body> + <div style="width: 1000px; height: 1000px; background-color: green"/> +</body> +</html> diff --git a/tests/webkitwidgets/qwebview/tst_qwebview.cpp b/tests/webkitwidgets/qwebview/tst_qwebview.cpp new file mode 100644 index 000000000..e793d3fbf --- /dev/null +++ b/tests/webkitwidgets/qwebview/tst_qwebview.cpp @@ -0,0 +1,548 @@ +/* + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + Copyright (C) 2009 Torch Mobile Inc. + Copyright (C) 2009 Girish Ramakrishnan <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <qtest.h> +#include "../util.h" + +#include <qpainter.h> +#include <qwebview.h> +#include <qwebpage.h> +#include <qnetworkrequest.h> +#include <qdiriterator.h> +#include <qwebelement.h> +#include <qwebframe.h> + +#define VERIFY_INPUTMETHOD_HINTS(actual, expect) \ + QVERIFY(actual == expect); + +class tst_QWebView : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + +private Q_SLOTS: + void renderingAfterMaxAndBack(); + void renderHints(); + void getWebKitVersion(); + + void reusePage_data(); + void reusePage(); + void microFocusCoordinates(); + void focusInputTypes(); + void horizontalScrollbarTest(); + + void crashTests(); +#if !(defined(USE_QT_MOBILE_THEME) && USE_QT_MOBILE_THEME) + void setPalette_data(); + void setPalette(); +#endif + void innerOuterRect(); +}; + +// This will be called before the first test function is executed. +// It is only called once. +void tst_QWebView::initTestCase() +{ +} + +// This will be called after the last test function is executed. +// It is only called once. +void tst_QWebView::cleanupTestCase() +{ +} + +// This will be called before each test function is executed. +void tst_QWebView::init() +{ +} + +// This will be called after every test function. +void tst_QWebView::cleanup() +{ +} + +void tst_QWebView::renderHints() +{ + QWebView webView; + + // default is only text antialiasing + smooth pixmap transform + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::Antialiasing, true); + QVERIFY(webView.renderHints() & QPainter::Antialiasing); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::Antialiasing, false); + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::SmoothPixmapTransform, true); + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::SmoothPixmapTransform, false); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +} + +void tst_QWebView::getWebKitVersion() +{ + QVERIFY(qWebKitVersion().toDouble() > 0); +} + +void tst_QWebView::reusePage_data() +{ + QTest::addColumn<QString>("html"); + QTest::newRow("WithoutPlugin") << "<html><body id='b'>text</body></html>"; + QTest::newRow("WindowedPlugin") << QString("<html><body id='b'>text<embed src='http://code.qt.io/resources/test.swf'></embed></body></html>"); + QTest::newRow("WindowlessPlugin") << QString("<html><body id='b'>text<embed src='http://code.qt.io/resources/test.swf' wmode=\"transparent\"></embed></body></html>"); +} + +void tst_QWebView::reusePage() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QFETCH(QString, html); + QWebView* view1 = new QWebView; + QPointer<QWebPage> page = new QWebPage; + view1->setPage(page.data()); + page.data()->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + QWebFrame* mainFrame = page.data()->mainFrame(); + mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + if (html.contains("</embed>")) { + // some reasonable time for the PluginStream to feed test.swf to flash and start painting + waitForSignal(view1, SIGNAL(loadFinished(bool)), 2000); + } + + view1->show(); + QTest::qWaitForWindowExposed(view1); + delete view1; + QVERIFY(page != 0); // deleting view must not have deleted the page, since it's not a child of view + + QWebView *view2 = new QWebView; + view2->setPage(page.data()); + view2->show(); // in Windowless mode, you should still be able to see the plugin here + QTest::qWaitForWindowExposed(view2); + delete view2; + + delete page.data(); // must not crash + + QDir::setCurrent(QApplication::applicationDirPath()); +} + +// Class used in crashTests +class WebViewCrashTest : public QObject { + Q_OBJECT + QWebView* m_view; +public: + bool m_executed; + + + WebViewCrashTest(QWebView* view) + : m_view(view) + , m_executed(false) + { + view->connect(view, SIGNAL(loadProgress(int)), this, SLOT(loading(int))); + } + +private Q_SLOTS: + void loading(int progress) + { + if (progress >= 20 && progress < 90) { + QVERIFY(!m_executed); + m_view->stop(); + m_executed = true; + } + } +}; + + +// Should not crash. +void tst_QWebView::crashTests() +{ + QSKIP("https://github.com/qtwebkit/qtwebkit/issues/913"); + + // Test if loading can be stopped in loadProgress handler without crash. + // Test page should have frames. + QWebView view; + WebViewCrashTest tester(&view); + QUrl url("/service/qrc:///resources/index.html"); + view.load(url); + QTRY_VERIFY(tester.m_executed); // If fail it means that the test wasn't executed. +} + +void tst_QWebView::microFocusCoordinates() +{ + QWebPage* page = new QWebPage; + QWebView* webView = new QWebView; + webView->setPage( page ); + + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font--family: serif' value='' maxlength='20'/><br>" \ + "<canvas id='canvas1' width='500' height='500'></canvas>" \ + "<input type='password'/><br>" \ + "<canvas id='canvas2' width='500' height='500'></canvas>" \ + "</body></html>"); + + page->mainFrame()->setFocus(); + + QVariant initialMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(initialMicroFocus.isValid()); + + page->mainFrame()->scroll(0,50); + + QVariant currentMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(currentMicroFocus.isValid()); + + QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-50)), currentMicroFocus.toRect()); +} + +void tst_QWebView::focusInputTypes() +{ + QWebView webView; + webView.show(); + QTest::qWaitForWindowExposed(&webView); + + QUrl url("/service/qrc:///resources/input_types.html"); + QWebFrame* const mainFrame = webView.page()->mainFrame(); + mainFrame->load(url); + mainFrame->setFocus(); + + QVERIFY(waitForSignal(&webView, SIGNAL(loadFinished(bool)))); + + // 'text' type + QWebElement inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + QVERIFY(webView.inputMethodHints() == Qt::ImhNone); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'password' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'tel' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=tel]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhDialableCharactersOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'number' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=number]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhDigitsOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'email' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=email]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhEmailCharactersOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'url' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=url]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhUrlCharactersOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'password' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'text' type + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + QVERIFY(webView.inputMethodHints() == Qt::ImhNone); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'password' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'text area' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("textarea")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + QVERIFY(webView.inputMethodHints() == Qt::ImhNone); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); +} + +void tst_QWebView::horizontalScrollbarTest() +{ + QWebView webView; + webView.resize(600, 600); + webView.show(); + QTest::qWaitForWindowExposed(&webView); + + QUrl url("/service/qrc:///resources/scrolltest_page.html"); + QWebFrame* const mainFrame = webView.page()->mainFrame(); + mainFrame->load(url); + mainFrame->setFocus(); + + QVERIFY(waitForSignal(&webView, SIGNAL(loadFinished(bool)))); + + QVERIFY(webView.page()->mainFrame()->scrollPosition() == QPoint(0, 0)); + + // Note: The test below assumes that the layout direction is Qt::LeftToRight. + QTest::mouseClick(&webView, Qt::LeftButton, 0, QPoint(550, 595)); + QVERIFY(webView.page()->mainFrame()->scrollPosition().x() > 0); + + // Note: The test below assumes that the layout direction is Qt::LeftToRight. + QTest::mouseClick(&webView, Qt::LeftButton, 0, QPoint(20, 595)); + QVERIFY(webView.page()->mainFrame()->scrollPosition() == QPoint(0, 0)); +} + + +#if !(defined(USE_QT_MOBILE_THEME) && USE_QT_MOBILE_THEME) +void tst_QWebView::setPalette_data() +{ + QTest::addColumn<bool>("active"); + QTest::addColumn<bool>("background"); + QTest::newRow("activeBG") << true << true; + QTest::newRow("activeFG") << true << false; + QTest::newRow("inactiveBG") << false << true; + QTest::newRow("inactiveFG") << false << false; +} + +// Render a QWebView to a QImage twice, each time with a different palette set, +// verify that images rendered are not the same, confirming WebCore usage of +// custom palette on selections. +void tst_QWebView::setPalette() +{ + QString html = "<html><head></head>" + "<body>" + "Some text here" + "</body>" + "</html>"; + + QFETCH(bool, active); + QFETCH(bool, background); + + QWidget* activeView = 0; + + // Use controlView to manage active/inactive state of test views by raising + // or lowering their position in the window stack. + QWebView controlView; + controlView.setHtml(html); + + QWebView view1; + + QPalette palette1; + QBrush brush1(Qt::red); + brush1.setStyle(Qt::SolidPattern); + if (active && background) { + // Rendered image must have red background on an active QWebView. + palette1.setBrush(QPalette::Active, QPalette::Highlight, brush1); + } else if (active && !background) { + // Rendered image must have red foreground on an active QWebView. + palette1.setBrush(QPalette::Active, QPalette::HighlightedText, brush1); + } else if (!active && background) { + // Rendered image must have red background on an inactive QWebView. + palette1.setBrush(QPalette::Inactive, QPalette::Highlight, brush1); + } else if (!active && !background) { + // Rendered image must have red foreground on an inactive QWebView. + palette1.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush1); + } + + view1.setPalette(palette1); + view1.setHtml(html); + view1.page()->setViewportSize(view1.page()->currentFrame()->contentsSize()); + view1.show(); + + QTest::qWaitForWindowExposed(&view1); + + if (!active) { + controlView.show(); + QTest::qWaitForWindowExposed(&controlView); + activeView = &controlView; + controlView.activateWindow(); + } else { + view1.activateWindow(); + activeView = &view1; + } + + QTRY_COMPARE(QApplication::activeWindow(), activeView); + + view1.page()->triggerAction(QWebPage::SelectAll); + + QImage img1(view1.page()->viewportSize(), QImage::Format_ARGB32); + QPainter painter1(&img1); + view1.page()->currentFrame()->render(&painter1); + painter1.end(); + view1.close(); + controlView.close(); + + QWebView view2; + + QPalette palette2; + QBrush brush2(Qt::blue); + brush2.setStyle(Qt::SolidPattern); + if (active && background) { + // Rendered image must have blue background on an active QWebView. + palette2.setBrush(QPalette::Active, QPalette::Highlight, brush2); + } else if (active && !background) { + // Rendered image must have blue foreground on an active QWebView. + palette2.setBrush(QPalette::Active, QPalette::HighlightedText, brush2); + } else if (!active && background) { + // Rendered image must have blue background on an inactive QWebView. + palette2.setBrush(QPalette::Inactive, QPalette::Highlight, brush2); + } else if (!active && !background) { + // Rendered image must have blue foreground on an inactive QWebView. + palette2.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush2); + } + + view2.setPalette(palette2); + view2.setHtml(html); + view2.page()->setViewportSize(view2.page()->currentFrame()->contentsSize()); + view2.show(); + + QTest::qWaitForWindowExposed(&view2); + + if (!active) { + controlView.show(); + QTest::qWaitForWindowExposed(&controlView); + activeView = &controlView; + controlView.activateWindow(); + } else { + view2.activateWindow(); + activeView = &view2; + } + + QTRY_COMPARE(QApplication::activeWindow(), activeView); + + view2.page()->triggerAction(QWebPage::SelectAll); + + QImage img2(view2.page()->viewportSize(), QImage::Format_ARGB32); + QPainter painter2(&img2); + view2.page()->currentFrame()->render(&painter2); + painter2.end(); + + view2.close(); + controlView.close(); + + QVERIFY(img1 != img2); +} +#endif + +void tst_QWebView::renderingAfterMaxAndBack() +{ + QUrl url = QUrl("data:text/html,<html><head></head>" + "<body width=1024 height=768 bgcolor=red>" + "</body>" + "</html>"); + + QWebView view; + view.page()->mainFrame()->load(url); + QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool)))); + view.show(); + + view.page()->settings()->setMaximumPagesInCache(3); + + QTest::qWaitForWindowExposed(&view); + + QPixmap reference(view.page()->viewportSize()); + reference.fill(Qt::red); + + QPixmap image(view.page()->viewportSize()); + QPainter painter(&image); + view.page()->currentFrame()->render(&painter); + + QCOMPARE(image, reference); + + QUrl url2 = QUrl("data:text/html,<html><head></head>" + "<body width=1024 height=768 bgcolor=blue>" + "</body>" + "</html>"); + view.page()->mainFrame()->load(url2); + + QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool)))); + + view.showMaximized(); + + QTest::qWaitForWindowExposed(&view); + + QPixmap reference2(view.page()->viewportSize()); + reference2.fill(Qt::blue); + + QPixmap image2(view.page()->viewportSize()); + QPainter painter2(&image2); + view.page()->currentFrame()->render(&painter2); + + QCOMPARE(image2, reference2); + + view.back(); + + QPixmap reference3(view.page()->viewportSize()); + reference3.fill(Qt::red); + QPixmap image3(view.page()->viewportSize()); + QPainter painter3(&image3); + view.page()->currentFrame()->render(&painter3); + + QCOMPARE(image3, reference3); +} + +void tst_QWebView::innerOuterRect() +{ + QUrl url = QUrl("data:text/html,<html><head></head>" + "<body bgcolor=red>" + "</body>" + "</html>"); + QWebView view; + view.page()->mainFrame()->load(url); + QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool)))); + view.showMaximized(); + const QRect frameGeometry = view.frameGeometry(); + const QRect geometry = view.geometry(); + QVariant outerWidth = view.page()->mainFrame()->evaluateJavaScript("window.outerWidth;"); + QCOMPARE(outerWidth.toInt(), frameGeometry.width()); + QVariant innerWidth = view.page()->mainFrame()->evaluateJavaScript("window.innerWidth;"); + QCOMPARE(innerWidth.toInt(), geometry.width()); + QVariant outerHeight = view.page()->mainFrame()->evaluateJavaScript("window.outerHeight;"); + QCOMPARE(outerHeight.toInt(), frameGeometry.height()); + QVariant innerHeight = view.page()->mainFrame()->evaluateJavaScript("window.innerHeight;"); + QCOMPARE(innerHeight.toInt(), geometry.height()); +} + +QTEST_MAIN(tst_QWebView) +#include "tst_qwebview.moc" + diff --git a/tests/webkitwidgets/qwebview/tst_qwebview.qrc b/tests/webkitwidgets/qwebview/tst_qwebview.qrc new file mode 100644 index 000000000..e4b9ad776 --- /dev/null +++ b/tests/webkitwidgets/qwebview/tst_qwebview.qrc @@ -0,0 +1,9 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/index.html</file> + <file>resources/frame_a.html</file> + <file>resources/input_types.html</file> + <file>resources/scrolltest_page.html</file> +</qresource> +</RCC> + diff --git a/tests/webkitwidgets/resources/image2.png b/tests/webkitwidgets/resources/image2.png Binary files differnew file mode 100644 index 000000000..8d703640c --- /dev/null +++ b/tests/webkitwidgets/resources/image2.png diff --git a/tests/webkitwidgets/tests.pri b/tests/webkitwidgets/tests.pri new file mode 100644 index 000000000..b48806286 --- /dev/null +++ b/tests/webkitwidgets/tests.pri @@ -0,0 +1,22 @@ +TEMPLATE = app + +VPATH += $$_PRO_FILE_PWD_ +TARGET = tst_$$TARGET + +# Load mobilityconfig if Qt Mobility is available +load(mobilityconfig, true) +contains(MOBILITY_CONFIG, multimedia) { + # This define is used by tests depending on Qt Multimedia + DEFINES -= WTF_USE_QT_MULTIMEDIA=0 + DEFINES += WTF_USE_QT_MULTIMEDIA=1 +} + +SOURCES += $${TARGET}.cpp +INCLUDEPATH += \ + $$PWD \ + $$PWD/../Api + +QT += testlib network webkitwidgets widgets + +# This define is used by some tests to look up resources in the source tree +DEFINES += TESTS_SOURCE_DIR=\\\"$$PWD/\\\" diff --git a/tests/webkitwidgets/util.h b/tests/webkitwidgets/util.h new file mode 100644 index 000000000..19955b810 --- /dev/null +++ b/tests/webkitwidgets/util.h @@ -0,0 +1,79 @@ +/* + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +// Functions and macros that really need to be in QTestLib + +#if 0 +#pragma qt_no_master_include +#endif + +#include <QEventLoop> +#include <QSignalSpy> +#include <QTimer> + +#if !defined(TESTS_SOURCE_DIR) +#define TESTS_SOURCE_DIR "" +#endif + +/** + * Starts an event loop that runs until the given signal is received. + * Optionally the event loop + * can return earlier on a timeout. + * + * \return \p true if the requested signal was received + * \p false on timeout + */ +static inline bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000) +{ + QEventLoop loop; + QObject::connect(obj, signal, &loop, SLOT(quit())); + QTimer timer; + QSignalSpy timeoutSpy(&timer, SIGNAL(timeout())); + if (timeout > 0) { + QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.setSingleShot(true); + timer.start(timeout); + } + loop.exec(); + return timeoutSpy.isEmpty(); +} + +/** + * Just like QSignalSpy but facilitates sync and async + * signal emission. For example if you want to verify that + * page->foo() emitted a signal, it could be that the + * implementation decides to emit the signal asynchronously + * - in which case we want to spin a local event loop until + * emission - or that the call to foo() emits it right away. + */ +class SignalBarrier : private QSignalSpy +{ +public: + SignalBarrier(const QObject* obj, const char* aSignal) + : QSignalSpy(obj, aSignal) + { } + + bool ensureSignalEmitted() + { + bool result = count() > 0; + if (!result) + result = wait(); + clear(); + return result; + } +}; diff --git a/tests/webkitwidgets/webkitwidgets.pro b/tests/webkitwidgets/webkitwidgets.pro new file mode 100644 index 000000000..8b900139f --- /dev/null +++ b/tests/webkitwidgets/webkitwidgets.pro @@ -0,0 +1,16 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS += \ + cmake \ + qobjectbridge \ + qwebframe \ + qwebpage \ + qwebelement \ + qgraphicswebview \ + qwebhistoryinterface \ + qwebview \ + qwebhistory \ + qwebinspector \ + qwebsecurityorigin \ + hybridPixmap |