diff options
Diffstat (limited to 'webkit/tools')
110 files changed, 18734 insertions, 0 deletions
diff --git a/webkit/tools/layout_tests/layout_package/__init__.py b/webkit/tools/layout_tests/layout_package/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/__init__.py diff --git a/webkit/tools/layout_tests/layout_package/compare_failures.py b/webkit/tools/layout_tests/layout_package/compare_failures.py new file mode 100644 index 0000000..5931ef4 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/compare_failures.py @@ -0,0 +1,170 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""A helper class for comparing the failures and crashes between layout test +runs. The results from the last test run are stored in expected-failures.txt +and expected-crashes.txt in the layout test results directory.""" + +import errno +import os + +import path_utils +import test_failures +import test_expectations + + +def PrintFilesFromSet(filenames, header_text): + """A helper method to print a list of files to stdout. + + Args: + filenames: a list of absolute filenames + header_text: a string to display before the list of filenames + """ + if not len(filenames): + return + + filenames = list(filenames) + filenames.sort() + print + print header_text, "(%d):" % len(filenames) + for filename in filenames: + print " %s" % path_utils.RelativeTestFilename(filename) + + +class CompareFailures: + # A list of which TestFailure classes count as a failure vs a crash. + FAILURE_TYPES = (test_failures.FailureTextMismatch, + test_failures.FailureImageHashMismatch) + CRASH_TYPES = (test_failures.FailureCrash,) + HANG_TYPES = (test_failures.FailureTimeout,) + MISSING_TYPES = (test_failures.FailureMissingResult, + test_failures.FailureMissingImageHash) + + + def __init__(self, test_files, test_failures, expectations): + """Read the past layout test run's failures from disk. + + Args: + test_files is a set of the filenames of all the test cases we ran + test_failures is a dictionary mapping the test filename to a list of + TestFailure objects if the test failed + expectations is a TestExpectations object representing the + current test status + """ + self._test_files = test_files + self._test_failures = test_failures + self._expectations = expectations + self._CalculateRegressions() + + + def PrintRegressions(self): + """Print the regressions computed by _CalculateRegressions() to stdout. """ + + print "-" * 78 + + # Print unexpected passes by category. + passes = self._regressed_passes + PrintFilesFromSet(passes & self._expectations.GetFixableFailures(), + "Expected to fail, but passed") + PrintFilesFromSet(passes & self._expectations.GetFixableTimeouts(), + "Expected to timeout, but passed") + PrintFilesFromSet(passes & self._expectations.GetFixableCrashes(), + "Expected to crash, but passed") + + PrintFilesFromSet(passes & self._expectations.GetIgnoredFailures(), + "Expected to fail (ignored), but passed") + PrintFilesFromSet(passes & self._expectations.GetIgnoredTimeouts(), + "Expected to timeout (ignored), but passed") + + PrintFilesFromSet(passes & self._expectations.GetFixableDeferredFailures(), + "Expected to fail (deferred), but passed") + PrintFilesFromSet(passes & self._expectations.GetFixableDeferredTimeouts(), + "Expected to timeout (deferred), but passed") + + # Print real regressions. + PrintFilesFromSet(self._regressed_failures, + "Regressions: Unexpected failures") + PrintFilesFromSet(self._regressed_hangs, + "Regressions: Unexpected timeouts") + PrintFilesFromSet(self._regressed_crashes, + "Regressions: Unexpected crashes") + PrintFilesFromSet(self._missing, "Missing expected results") + + + def _CalculateRegressions(self): + """Calculate regressions from this run through the layout tests.""" + worklist = self._test_files.copy() + + passes = set() + crashes = set() + hangs = set() + missing = set() + failures = set() + + for test, failure_types in self._test_failures.iteritems(): + # Although each test can have multiple test_failures, we only put them + # into one list (either the crash list or the failure list). We give + # priority to a crash/timeout over others, and to missing results over + # a text mismatch. + is_crash = [True for f in failure_types if type(f) in self.CRASH_TYPES] + is_hang = [True for f in failure_types if type(f) in self.HANG_TYPES] + is_missing = [True for f in failure_types + if type(f) in self.MISSING_TYPES] + is_failure = [True for f in failure_types + if type(f) in self.FAILURE_TYPES] + expectations = self._expectations.GetExpectations(test) + if is_crash: + if not test_expectations.CRASH in expectations: crashes.add(test) + elif is_hang: + if not test_expectations.TIMEOUT in expectations: hangs.add(test) + elif is_missing: + missing.add(test) + elif is_failure: + if not test_expectations.FAIL in expectations: failures.add(test) + worklist.remove(test) + + for test in worklist: + # Check that all passing tests are expected to pass. + expectations = self._expectations.GetExpectations(test) + if not test_expectations.PASS in expectations: passes.add(test) + + self._regressed_passes = passes + self._regressed_crashes = crashes + self._regressed_hangs = hangs + self._missing = missing + self._regressed_failures = failures + + + def GetRegressions(self): + """Returns a set of regressions from the test expectations. This is + used to determine which tests to list in results.html and the + right script exit code for the build bots. The list does not + include the unexpected passes.""" + return (self._regressed_failures | self._regressed_hangs | + self._regressed_crashes | self._missing) diff --git a/webkit/tools/layout_tests/layout_package/compare_failures_unittest.py b/webkit/tools/layout_tests/layout_package/compare_failures_unittest.py new file mode 100644 index 0000000..a33b5fc --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/compare_failures_unittest.py @@ -0,0 +1,305 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Unittests to make sure we generate and update the expected-*.txt files +properly after running layout tests.""" + +import os +import shutil +import tempfile +import unittest + +import compare_failures +import path_utils +import test_failures + +class CompareFailuresUnittest(unittest.TestCase): + def setUp(self): + """Makes a temporary results directory and puts expected-failures.txt and + expected-crashes.txt into it.""" + self._tempdir = tempfile.mkdtemp() + # copy over expected-*.txt files + testdatadir = self.GetTestDataDir() + filenames = ("expected-passing.txt", "expected-failures.txt", + "expected-crashes.txt") + for filename in filenames: + # copyfile doesn't copy file permissions so we can delete the files later + shutil.copyfile(os.path.join(testdatadir, filename), + os.path.join(self._tempdir, filename)) + + def tearDown(self): + """Remove temp directory.""" + shutil.rmtree(self._tempdir) + self._tempdir = None + + ########################################################################### + # Tests + def testGenerateNewBaseline(self): + """Test the generation of new expected-*.txt files when either they don't + exist or the user explicitly asks to make new files.""" + failures = self.GetTestFailures() + + # Test to make sure we generate baseline files if the file doesn't exist. + os.remove(os.path.join(self.GetTmpDir(), 'expected-passing.txt')) + os.remove(os.path.join(self.GetTmpDir(), 'expected-failures.txt')) + os.remove(os.path.join(self.GetTmpDir(), 'expected-crashes.txt')) + + # Test force generation of new baseline files with a new failure and one + # less passing. + pass_file = os.path.join(path_utils.LayoutDataDir(), 'fast', 'pass1.html') + failures[pass_file] = [test_failures.FailureTextMismatch(None)] + + cf = compare_failures.CompareFailures(self.GetTestFiles(), failures, + set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing-new-baseline.txt', + 'expected-failures-added.txt', + 'expected-crashes.txt') + + def testPassingToFailure(self): + """When there's a new failure, we don't add it to the baseline.""" + failures = self.GetTestFailures() + + # Test case where we don't update new baseline. We have a new failure, + # but it shouldn't be added to the expected-failures.txt file. + pass_file = os.path.join(path_utils.LayoutDataDir(), 'fast', 'pass1.html') + failures[pass_file] = [test_failures.FailureTextMismatch(None)] + self.CheckNoChanges(failures) + + # Same thing as before: pass -> crash + failures[pass_file] = [test_failures.FailureCrash()] + self.CheckNoChanges(failures) + + def testFailureToCrash(self): + """When there's a new crash, we don't add it to the baseline or remove it + from the failure list.""" + failures = self.GetTestFailures() + + # Test case where we don't update new baseline. A failure moving to a + # crash shouldn't be added to the expected-crashes.txt file. + failure_file = os.path.join(path_utils.LayoutDataDir(), + 'fast', 'foo', 'fail1.html') + failures[failure_file] = [test_failures.FailureCrash()] + self.CheckNoChanges(failures) + + def testFailureToPassing(self): + """This is better than before, so we should update the failure list.""" + failures = self.GetTestFailures() + + # Remove one of the failing test cases from the failures dictionary. This + # makes failure_file considered to be passing. + failure_file = os.path.join(path_utils.LayoutDataDir(), + 'fast', 'bar', 'fail2.html') + del failures[failure_file] + + cf = compare_failures.CompareFailures(self.GetTestFiles(), failures, + set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing-new-passing2.txt', + 'expected-failures-new-passing.txt', + 'expected-crashes.txt') + + def testCrashToPassing(self): + """This is better than before, so we update the crashes file.""" + failures = self.GetTestFailures() + + crash_file = os.path.join(path_utils.LayoutDataDir(), + 'fast', 'bar', 'betz', 'crash3.html') + del failures[crash_file] + cf = compare_failures.CompareFailures(self.GetTestFiles(), failures, + set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing-new-passing.txt', + 'expected-failures.txt', + 'expected-crashes-new-passing.txt') + + def testCrashToFailure(self): + """This is better than before, so we should update both lists.""" + failures = self.GetTestFailures() + + crash_file = os.path.join(path_utils.LayoutDataDir(), + 'fast', 'bar', 'betz', 'crash3.html') + failures[crash_file] = [test_failures.FailureTextMismatch(None)] + cf = compare_failures.CompareFailures(self.GetTestFiles(), failures, + set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing.txt', + 'expected-failures-new-crash.txt', + 'expected-crashes-new-passing.txt') + + def testNewTestPass(self): + """After a merge, we need to update new passing tests properly.""" + files = self.GetTestFiles() + new_test_file = os.path.join(path_utils.LayoutDataDir(), "new-test.html") + files.add(new_test_file) + failures = self.GetTestFailures() + + # New test file passing + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing-new-test.txt', + 'expected-failures.txt', + 'expected-crashes.txt') + + def testNewTestFail(self): + """After a merge, we need to update new failing tests properly.""" + files = self.GetTestFiles() + new_test_file = os.path.join(path_utils.LayoutDataDir(), "new-test.html") + files.add(new_test_file) + failures = self.GetTestFailures() + + # New test file failing + failures[new_test_file] = [test_failures.FailureTextMismatch(None)] + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing.txt', + 'expected-failures-new-test.txt', + 'expected-crashes.txt') + + def testNewTestCrash(self): + """After a merge, we need to update new crashing tests properly.""" + files = self.GetTestFiles() + new_test_file = os.path.join(path_utils.LayoutDataDir(), "new-test.html") + files.add(new_test_file) + failures = self.GetTestFailures() + + # New test file crashing + failures[new_test_file] = [test_failures.FailureCrash()] + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing.txt', + 'expected-failures.txt', + 'expected-crashes-new-test.txt') + + def testHasNewFailures(self): + files = self.GetTestFiles() + failures = self.GetTestFailures() + + # no changes, no new failures + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + self.failUnless(not cf.HasNewFailures()) + + # test goes from passing to failing + pass_file = os.path.join(path_utils.LayoutDataDir(), 'fast', 'pass1.html') + failures[pass_file] = [test_failures.FailureTextMismatch(None)] + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + self.failUnless(cf.HasNewFailures()) + + # Failing to passing + failures = self.GetTestFailures() + failure_file = os.path.join(path_utils.LayoutDataDir(), + 'fast', 'bar', 'fail2.html') + del failures[failure_file] + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + self.failUnless(not cf.HasNewFailures()) + + # A new test that fails, this doesn't count as a new failure. + new_test_file = os.path.join(path_utils.LayoutDataDir(), "new-test.html") + files.add(new_test_file) + failures = self.GetTestFailures() + failures[new_test_file] = [test_failures.FailureCrash()] + cf = compare_failures.CompareFailures(files, failures, set(), set(), + self.GetTmpDir(), False) + self.failUnless(not cf.HasNewFailures()) + + + ########################################################################### + # Helper methods + def CheckOutputEqualsExpectedFile(self, output, expected): + """Compares a file in our output dir against a file from the testdata + directory.""" + output = os.path.join(self.GetTmpDir(), output) + expected = os.path.join(self.GetTestDataDir(), expected) + self.failUnlessEqual(open(output).read(), open(expected).read()) + + def CheckOutputWithExpectedFiles(self, passing, failing, crashing): + """Compare all three output files against three provided expected + files.""" + self.CheckOutputEqualsExpectedFile('expected-passing.txt', passing) + self.CheckOutputEqualsExpectedFile('expected-failures.txt', failing) + self.CheckOutputEqualsExpectedFile('expected-crashes.txt', crashing) + + def CheckNoChanges(self, failures): + """Verify that none of the expected-*.txt files have changed.""" + cf = compare_failures.CompareFailures(self.GetTestFiles(), failures, + set(), set(), + self.GetTmpDir(), False) + cf.UpdateFailuresOnDisk() + self.CheckOutputWithExpectedFiles('expected-passing.txt', + 'expected-failures.txt', + 'expected-crashes.txt') + + def GetTestDataDir(self): + return os.path.abspath('testdata') + + def GetTmpDir(self): + return self._tempdir + + def GetTestFiles(self): + """Get a set of files that includes the expected crashes and failures + along with two passing tests.""" + layout_dir = path_utils.LayoutDataDir() + files = [ + 'fast\\pass1.html', + 'fast\\foo\\pass2.html', + 'fast\\foo\\crash1.html', + 'fast\\bar\\crash2.html', + 'fast\\bar\\betz\\crash3.html', + 'fast\\foo\\fail1.html', + 'fast\\bar\\fail2.html', + 'fast\\bar\\betz\\fail3.html', + ] + + return set([os.path.join(layout_dir, f) for f in files]) + + def GetTestFailures(self): + """Get a dictionary representing the crashes and failures in the + expected-*.txt files.""" + failures = {} + for filename in self.GetTestFiles(): + if filename.find('crash') != -1: + failures[filename] = [test_failures.FailureCrash()] + elif filename.find('fail') != -1: + failures[filename] = [test_failures.FailureTextMismatch(None)] + + return failures + +if '__main__' == __name__: + unittest.main() diff --git a/webkit/tools/layout_tests/layout_package/http_server.py b/webkit/tools/layout_tests/layout_package/http_server.py new file mode 100644 index 0000000..aac1cca --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/http_server.py @@ -0,0 +1,219 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""A class to help start/stop the lighttpd server used by layout tests.""" + + +import logging +import optparse +import os +import subprocess +import sys +import time +import urllib + +import google.path_utils + +# This will be a native path to the directory this file resides in. +# It can either be relative or absolute depending how it's executed. +THISDIR = os.path.dirname(os.path.abspath(__file__)) + +def PathFromBase(*pathies): + return google.path_utils.FindUpward(THISDIR, *pathies) + +class HttpdNotStarted(Exception): + pass + +class Lighttpd: + # Webkit tests + _webkit_tests = PathFromBase('webkit', 'data', 'layout_tests', + 'LayoutTests', 'http', 'tests') + # New tests for Chrome + _pending_tests = PathFromBase('webkit', 'data', 'layout_tests', + 'pending', 'http', 'tests') + # Path where we can access all of the tests + _all_tests = PathFromBase('webkit', 'data', 'layout_tests') + # Self generated certificate for SSL server (for client cert get + # <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt) + _pem_file = PathFromBase('tools', 'python', 'google', 'httpd_config', + 'httpd2.pem') + VIRTUALCONFIG = [ + # Three mappings (one with SSL enabled) for LayoutTests http tests + {'port': 8000, 'docroot': _webkit_tests}, + {'port': 8080, 'docroot': _webkit_tests}, + {'port': 8443, 'docroot': _webkit_tests, 'sslcert': _pem_file}, + # Three similar mappings (one with SSL enabled) for pending http tests + {'port': 9000, 'docroot': _pending_tests}, + {'port': 9080, 'docroot': _pending_tests}, + {'port': 9443, 'docroot': _pending_tests, 'sslcert': _pem_file}, + # One mapping where we can get to everything + {'port': 8081, 'docroot': _all_tests} + ] + + def __init__(self, output_dir, background=False): + """Args: + output_dir: the absolute path to the layout test result directory + """ + self._output_dir = output_dir + self._process = None + + def IsRunning(self): + return self._process != None + + def Start(self): + if self.IsRunning(): + raise 'Lighttpd already running' + + base_conf_file = os.path.join(THISDIR, 'lighttpd.conf') + out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf') + access_log = os.path.join(self._output_dir, 'access.log.txt') + error_log = os.path.join(self._output_dir, 'error.log.txt') + + # Write out the config + f = file(base_conf_file, 'rb') + base_conf = f.read() + f.close() + + f = file(out_conf_file, 'wb') + f.write(base_conf) + + # Write out our cgi handlers. Run perl through env so that it processes + # the #! line and runs perl with the proper command line arguments. + # Emulate apache's mod_asis with a cat cgi handler. + f.write(('cgi.assign = ( ".cgi" => "/usr/bin/env",\n' + ' ".pl" => "/usr/bin/env",\n' + ' ".asis" => "/usr/bin/cat",\n' + ' ".php" => "%s" )\n\n') % + PathFromBase('third_party', 'lighttpd', 'php5', 'php-cgi.exe')) + + # Setup log files + f.write(('server.errorlog = "%s"\n' + 'accesslog.filename = "%s"\n\n') % (error_log, access_log)) + + # dump out of virtual host config at the bottom. + for mapping in self.VIRTUALCONFIG: + ssl_setup = '' + if 'sslcert' in mapping: + ssl_setup = (' ssl.engine = "enable"\n' + ' ssl.pemfile = "%s"\n' % mapping['sslcert']) + + f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n' + ' server.document-root = "%s"\n' + + ssl_setup + + '}\n\n') % (mapping['port'], mapping['docroot'])) + f.close() + + start_cmd = [ PathFromBase('third_party', 'lighttpd', 'LightTPD.exe'), + # Newly written config file + '-f', PathFromBase(self._output_dir, 'lighttpd.conf'), + # Where it can find it's module dynamic libraries + '-m', PathFromBase('third_party', 'lighttpd', 'lib'), + # Don't background + '-D' ] + + # Put the cygwin directory first in the path to find cygwin1.dll + env = os.environ + env['PATH'] = '%s;%s' % ( + PathFromBase('third_party', 'cygwin', 'bin'), env['PATH']) + + logging.info('Starting http server') + self._process = subprocess.Popen(start_cmd, env=env) + + # Ensure that the server is running on all the desired ports. + for mapping in self.VIRTUALCONFIG: + url = 'http%s://127.0.0.1:%d/' % ('sslcert' in mapping and 's' or '', + mapping['port']) + if not self._UrlIsAlive(url): + raise HttpdNotStarted('Failed to start httpd on port %s' % str(port)) + + # Our process terminated already + if self._process.returncode != None: + raise HttpdNotStarted('Failed to start httpd.') + + def _UrlIsAlive(self, url): + """Checks to see if we get an http response from |url|. + We poll the url 5 times with a 1 second delay. If we don't + get a reply in that time, we give up and assume the httpd + didn't start properly. + + Args: + url: The URL to check. + Return: + True if the url is alive. + """ + wait_time = 5 + while wait_time > 0: + try: + response = urllib.urlopen(url) + # Server is up and responding. + return True + except IOError: + pass + wait_time -= 1 + # Wait a second and try again. + time.sleep(1) + + return False + + # TODO(deanm): Find a nicer way to shutdown cleanly. Our log files are + # probably not being flushed, etc... why doesn't our python have os.kill ? + def Stop(self, force=False): + if not force and not self.IsRunning(): + return + + logging.info('Shutting down http server') + + subprocess.Popen(('taskkill.exe', '/f', '/im', 'LightTPD.exe'), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).wait() + + if self._process: + self._process.wait() + self._process = None + + # Wait a bit to make sure the ports are free'd up + time.sleep(2) + + +if '__main__' == __name__: + # Provide some command line params for starting/stopping the http server + # manually. + option_parser = optparse.OptionParser() + option_parser.add_option('-k', '--server', help='Server action (start|stop)') + options, args = option_parser.parse_args() + + if not options.server: + print "Usage: %s --server {start|stop} [--apache2]" % sys.argv[0] + else: + httpd = Lighttpd('c:/cygwin/tmp') + if 'start' == options.server: + httpd.Start() + else: + httpd.Stop(force=True) diff --git a/webkit/tools/layout_tests/layout_package/lighttpd.conf b/webkit/tools/layout_tests/layout_package/lighttpd.conf new file mode 100644 index 0000000..2b53d78 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/lighttpd.conf @@ -0,0 +1,74 @@ +server.tag = "LightTPD/1.4.19 (Win32)" +server.modules = ( "mod_accesslog", + "mod_cgi", + "mod_rewrite" ) + +# default document root required +server.document-root = "." + +# files to check for if .../ is requested +index-file.names = ( "index.php", "index.pl", "index.cgi", + "index.html", "index.htm", "default.htm" ) +# mimetype mapping +mimetype.assign = ( + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".xhtml" => "application/xhtml+xml", + ".js" => "text/javascript", + ".log" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ) + +# Use the "Content-Type" extended attribute to obtain mime type if possible +mimetype.use-xattr = "enable" + +## +# which extensions should not be handle via static-file transfer +# +# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi +static-file.exclude-extensions = ( ".php", ".pl", ".cgi" ) + +server.bind = "localhost" +server.port = 8001 + +## virtual directory listings +dir-listing.activate = "enable" +#dir-listing.encoding = "iso-8859-2" +#dir-listing.external-css = "style/oldstyle.css" + +## enable debugging +#debug.log-request-header = "enable" +#debug.log-response-header = "enable" +#debug.log-request-handling = "enable" +#debug.log-file-not-found = "enable" + +#### SSL engine +#ssl.engine = "enable" +#ssl.pemfile = "server.pem" + +# Rewrite rule for utf-8 path test (LayoutTests/http/tests/uri/utf8-path.html) +# See the apache rewrite rule at LayoutTests/http/tests/uri/intercept/.htaccess +url.rewrite-once = ( "^/uri/intercept/(.*)" => "/uri/resources/print-uri.php" ) + +# LayoutTests/http/tests/xmlhttprequest/response-encoding.html uses an htaccess +# to override charset for reply2.txt, reply2.xml, and reply4.txt. +$HTTP["url"] =~ "^/xmlhttprequest/resources/reply2.(txt|xml)" { + mimetype.assign = ( + ".txt" => "text/plain; charset=windows-1251", + ".xml" => "text/xml; charset=windows-1251" + ) +} +$HTTP["url"] =~ "^/xmlhttprequest/resources/reply4.txt" { + mimetype.assign = ( ".txt" => "text/plain; charset=koi8-r" ) +} + +# Autogenerated test-specific config follows. diff --git a/webkit/tools/layout_tests/layout_package/path_utils.py b/webkit/tools/layout_tests/layout_package/path_utils.py new file mode 100644 index 0000000..292d298 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/path_utils.py @@ -0,0 +1,190 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Some utility methods for getting paths used by run_webkit_tests.py. +""" + +import errno +import os +import subprocess +import sys + +import google.path_utils + + +class PathNotFound(Exception): pass + +# Save some paths here so we don't keep re-evaling. +_webkit_root = None +_layout_data_dir = None +_expected_results_dir = None +_platform_results_dirs = {} + +# TODO this should probably be moved into path_utils as ToUnixPath(). +def WinPathToUnix(path): + """Convert a windows path to use unix-style path separators (a/b/c).""" + return path.replace('\\', '/') + +def WebKitRoot(): + """Returns the full path to the directory containing webkit.sln. Raises + PathNotFound if we're unable to find webkit.sln.""" + global _webkit_root + if _webkit_root: + return _webkit_root + webkit_sln_path = google.path_utils.FindUpward(google.path_utils.ScriptDir(), + 'webkit.sln') + _webkit_root = os.path.dirname(webkit_sln_path) + return _webkit_root + +def LayoutDataDir(): + """Gets the full path to the tests directory. Raises PathNotFound if + we're unable to find it.""" + global _layout_data_dir + if _layout_data_dir: + return _layout_data_dir + _layout_data_dir = google.path_utils.FindUpward(WebKitRoot(), 'webkit', + 'data', 'layout_tests') + return _layout_data_dir + +def ExpectedResultsDir(): + """Gets the full path to the custom_results directory. Raises + PathNotFound if we're unable to find it.""" + global _expected_results_dir + if _expected_results_dir: + return _expected_results_dir + _expected_results_dir = google.path_utils.FindUpward(WebKitRoot(), 'webkit', + 'data', + 'layout_test_results') + return _expected_results_dir + +def CustomExpectedResultsDir(custom_id): + """Gets the full path to the directory in which custom expected results for + this app and build type are located. + + Args: + custom_id: a string specifying the particular set of results to use (e.g., + 'v8' or 'kjs') + """ + return os.path.join(ExpectedResultsDir(), custom_id) + +def PlatformResultsDir(name): + """Gets the full path to a platform-specific results directory. Raises + PathNotFound if we're unable to find it.""" + global _platform_results_dirs + if _platform_results_dirs.get(name): + return _platform_results_dirs[name] + _platform_results_dirs[name] = google.path_utils.FindUpward(WebKitRoot(), + 'webkit', 'data', 'layout_tests', 'LayoutTests', 'platform', name) + return _platform_results_dirs[name] + +def ExpectedFilename(filename, suffix, custom_result_id): + """Given a test name, returns an absolute filename to the most specific + applicable file of expected results. + + Args: + filename: absolute filename to test file + suffix: file suffix of the expected results, including dot; e.g. '.txt' + or '.png'. This should not be None, but may be an empty string. + custom_result_id: Tells us where to look for custom results. Currently + this is either kjs or v8. + + Return: + If a file named <testname>-expected<suffix> exists in the subdirectory + of the ExpectedResultsDir() specified by this platform's identifier, + return its absolute path. Otherwise, return a path to a + <testname>-expected<suffix> file under the MacExpectedResultsDir() or + <testname>-expected<suffix> file under the MacLeopardExpectedResultsDir() + or (if not found there) in the same directory as the test file (even if + that default file does not exist). + """ + testname = os.path.splitext(RelativeTestFilename(filename))[0] + results_filename = testname + '-expected' + suffix + results_dirs = [ + CustomExpectedResultsDir(custom_result_id), + CustomExpectedResultsDir('common'), + LayoutDataDir() + ] + + for results_dir in results_dirs: + platform_file = os.path.join(results_dir, results_filename) + if os.path.exists(platform_file): + return platform_file + + # for 'base' tests, we need to look for mac-specific results + if testname.startswith('LayoutTests'): + layout_test_results_dirs = [ + PlatformResultsDir('mac'), + PlatformResultsDir('mac-leopard'), + PlatformResultsDir('mac-tiger') + ] + rel_testname = testname[len('LayoutTests') + 1:] + rel_filename = rel_testname + '-expected' + suffix + for results_dir in layout_test_results_dirs: + platform_file = os.path.join(results_dir, rel_filename) + if os.path.exists(platform_file): + return platform_file + + # Failed to find the results anywhere, return default path anyway + return os.path.join(results_dirs[0], results_filename) + +def TestShellBinary(): + """Returns the name of the test_shell executable.""" + return 'test_shell.exe' + +def TestShellBinaryPath(target): + """Gets the full path to the test_shell binary for the target build + configuration. Raises PathNotFound if the file doesn't exist""" + full_path = os.path.join(WebKitRoot(), target, TestShellBinary()) + if not os.path.exists(full_path): + # try chrome's output directory in case test_shell was built by chrome.sln + full_path = google.path_utils.FindUpward(WebKitRoot(), 'chrome', target, + TestShellBinary()) + if not os.path.exists(full_path): + raise PathNotFound('unable to find test_shell at %s' % full_path) + return full_path + +def RelativeTestFilename(filename): + """Provide the filename of the test relative to the layout data + directory as a unix style path (a/b/c).""" + return WinPathToUnix(filename[len(LayoutDataDir()) + 1:]) + +# Map platform specific path utility functions. We do this as a convenience +# so importing path_utils will get all path related functions even if they are +# platform specific. +def GetAbsolutePath(path): + # Avoid circular import by delaying it. + import layout_package.platform_utils + platform_util = layout_package.platform_utils.PlatformUtility(WebKitRoot()) + return platform_util.GetAbsolutePath(path) + +def FilenameToUri(path): + # Avoid circular import by delaying it. + import layout_package.platform_utils + platform_util = layout_package.platform_utils.PlatformUtility(WebKitRoot()) + return platform_util.FilenameToUri(path) diff --git a/webkit/tools/layout_tests/layout_package/platform_utils.py b/webkit/tools/layout_tests/layout_package/platform_utils.py new file mode 100644 index 0000000..1fcb182 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/platform_utils.py @@ -0,0 +1,46 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Platform-specific utilities and pseudo-constants + +Any functions whose implementations or values differ from one platform to +another should be defined in their respective platform_utils_<platform>.py +modules. The appropriate one of those will be imported into this module to +provide callers with a common, platform-independent interface. +""" + +import sys + +# We may not support the version of Python that a user has installed (Cygwin +# especially has had problems), but we'll allow the platform utils to be +# included in any case so we don't get an import error. +if sys.platform in ('cygwin', 'win32'): + from platform_utils_win import * + diff --git a/webkit/tools/layout_tests/layout_package/platform_utils_win.py b/webkit/tools/layout_tests/layout_package/platform_utils_win.py new file mode 100644 index 0000000..542533a --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/platform_utils_win.py @@ -0,0 +1,168 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Platform specific utility methods. This file contains methods that are +specific to running the layout tests on windows. + +This file constitutes a complete wrapper for google.platform_utils_win, +implementing or mapping all needed functions from there. Layout-test scripts +should be able to import only this file (via platform_utils.py), with no need +to fall back to the base functions. +""" + +import os +import re +import subprocess + +import google.httpd_utils +import google.path_utils +import google.platform_utils_win + +import layout_package.path_utils + + +class PlatformUtility(google.platform_utils_win.PlatformUtility): + """Overrides base PlatformUtility methods as needed for layout tests.""" + + LAYOUTTEST_HTTP_DIR = "LayoutTests/http/tests/" + PENDING_HTTP_DIR = "pending/http/tests/" + + def FilenameToUri(self, full_path): + relative_path = layout_package.path_utils.RelativeTestFilename(full_path) + port = None + use_ssl = False + + # LayoutTests/http/tests/ run off port 8000 and ssl/ off 8443 + if relative_path.startswith(self.LAYOUTTEST_HTTP_DIR): + relative_path = relative_path[len(self.LAYOUTTEST_HTTP_DIR):] + port = 8000 + # pending/http/tests/ run off port 9000 and ssl/ off 9443 + elif relative_path.startswith(self.PENDING_HTTP_DIR): + relative_path = relative_path[len(self.PENDING_HTTP_DIR):] + port = 9000 + # chrome/http/tests run off of port 8081 with the full path + elif relative_path.find("/http/") >= 0: + print relative_path + port = 8081 + + # We want to run off of the http server + if port: + if relative_path.startswith("ssl/"): + port += 443 + use_ssl = True + return google.platform_utils_win.PlatformUtility.FilenameToUri(self, + relative_path, + use_http=True, + use_ssl=use_ssl, + port=port) + + # Run off file:// + return google.platform_utils_win.PlatformUtility.FilenameToUri( + self, full_path) + + def KillAllTestShells(self): + """Kills all instances of the test_shell binary currently running.""" + subprocess.Popen(('taskkill.exe', '/f', '/im', + layout_package.path_utils.TestShellBinary()), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).wait() + + def _GetVirtualHostConfig(self, document_root, port, ssl=False): + """Returns a <VirtualHost> directive block for an httpd.conf file. It will + listen to 127.0.0.1 on each of the given port. + """ + cygwin_document_root = google.platform_utils_win.GetCygwinPath( + document_root) + + return '\n'.join(('<VirtualHost 127.0.0.1:%s>' % port, + 'DocumentRoot %s' % cygwin_document_root, + ssl and 'SSLEngine On' or '', + '</VirtualHost>', '')) + + def GetStartHttpdCommand(self, output_dir, apache2=False): + """Prepares the config file and output directory to start an httpd server. + Returns a list of strings containing the server's command line+args. + + Creates the test output directory and generates an httpd.conf (or + httpd2.conf for Apache 2 if apache2 is True) file in it that contains + the necessary <VirtualHost> directives for running all the http tests. + + WebKit http tests expect the DocumentRoot to be in LayoutTests/http/tests/, + but that prevents us from running http tests in chrome/ or pending/. So we + run two virtual hosts, one on ports 8000 and 8080 for WebKit, and one on + port 8081 with a much broader DocumentRoot for everything else. (Note that + WebKit http tests that have been modified and are temporarily in pending/ + will still fail, if they expect the DocumentRoot to be located as described + above.) + + Args: + output_dir: the path to the test output directory. It will be created. + apache2: boolean if true will cause this function to return start + command for Apache 2.x instead of Apache 1.3.x + """ + layout_dir = google.platform_utils_win.GetCygwinPath( + layout_package.path_utils.LayoutDataDir()) + main_document_root = os.path.join(layout_dir, "LayoutTests", + "http", "tests") + pending_document_root = os.path.join(layout_dir, "pending", + "http", "tests") + chrome_document_root = layout_dir + apache_config_dir = google.httpd_utils.ApacheConfigDir(self._base_dir) + mime_types_path = os.path.join(apache_config_dir, "mime.types") + + conf_file_name = "httpd.conf" + if apache2: + conf_file_name = "httpd2.conf" + # Make the test output directory and place the generated httpd.conf in it. + orig_httpd_conf_path = os.path.join(apache_config_dir, conf_file_name) + + httpd_conf_path = os.path.join(output_dir, conf_file_name) + google.path_utils.MaybeMakeDirectory(output_dir) + httpd_conf = open(orig_httpd_conf_path).read() + httpd_conf = (httpd_conf + + self._GetVirtualHostConfig(main_document_root, 8000) + + self._GetVirtualHostConfig(main_document_root, 8080) + + self._GetVirtualHostConfig(pending_document_root, 9000) + + self._GetVirtualHostConfig(pending_document_root, 9080) + + self._GetVirtualHostConfig(chrome_document_root, 8081)) + if apache2: + httpd_conf += self._GetVirtualHostConfig(main_document_root, 8443, + ssl=True) + httpd_conf += self._GetVirtualHostConfig(pending_document_root, 9443, + ssl=True) + f = open(httpd_conf_path, 'wb') + f.write(httpd_conf) + f.close() + + return google.platform_utils_win.PlatformUtility.GetStartHttpdCommand( + self, + output_dir, + httpd_conf_path, + mime_types_path, + apache2=apache2) diff --git a/webkit/tools/layout_tests/layout_package/test_expectations.py b/webkit/tools/layout_tests/layout_package/test_expectations.py new file mode 100644 index 0000000..7ea0fb1 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/test_expectations.py @@ -0,0 +1,368 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""A helper class for reading in and dealing with tests expectations +for layout tests. """ + +import os +import re +import path_utils +import compare_failures + + +# Test expectation constants. +PASS = 0 +FAIL = 1 +TIMEOUT = 2 +CRASH = 3 + + +class TestExpectations: + FIXABLE = "tests_fixable.txt" + IGNORED = "tests_ignored.txt" + + def __init__(self, tests, directory, build_type): + """Reads the test expectations files from the given directory.""" + self._tests = tests + self._directory = directory + self._build_type = build_type + self._ReadFiles() + self._ValidateLists() + + def GetFixable(self): + return (self._fixable.GetTests() - + self._fixable.GetNonSkippedDeferred() - + self._fixable.GetSkippedDeferred()) + + def GetFixableSkipped(self): + return self._fixable.GetSkipped() + + def GetFixableSkippedDeferred(self): + return self._fixable.GetSkippedDeferred() + + def GetFixableFailures(self): + return (self._fixable.GetTestsExpectedTo(FAIL) - + self._fixable.GetTestsExpectedTo(TIMEOUT) - + self._fixable.GetTestsExpectedTo(CRASH) - + self._fixable.GetNonSkippedDeferred()) + + def GetFixableTimeouts(self): + return (self._fixable.GetTestsExpectedTo(TIMEOUT) - + self._fixable.GetTestsExpectedTo(CRASH) - + self._fixable.GetNonSkippedDeferred()) + + def GetFixableCrashes(self): + return self._fixable.GetTestsExpectedTo(CRASH) + + def GetFixableDeferred(self): + return self._fixable.GetNonSkippedDeferred() + + def GetFixableDeferredFailures(self): + return (self._fixable.GetNonSkippedDeferred() & + self._fixable.GetTestsExpectedTo(FAIL)) + + def GetFixableDeferredTimeouts(self): + return (self._fixable.GetNonSkippedDeferred() & + self._fixable.GetTestsExpectedTo(TIMEOUT)) + + def GetIgnored(self): + return self._ignored.GetTests() + + def GetIgnoredSkipped(self): + return self._ignored.GetSkipped() + + def GetIgnoredFailures(self): + return (self._ignored.GetTestsExpectedTo(FAIL) - + self._ignored.GetTestsExpectedTo(TIMEOUT)) + + def GetIgnoredTimeouts(self): + return self._ignored.GetTestsExpectedTo(TIMEOUT) + + def GetExpectations(self, test): + if self._fixable.Contains(test): return self._fixable.GetExpectations(test) + if self._ignored.Contains(test): return self._ignored.GetExpectations(test) + # If the test file is not listed in any of the expectations lists + # we expect it to pass (and nothing else). + return set([PASS]) + + def IsFixable(self, test): + return (self._fixable.Contains(test) and + test not in self._fixable.GetNonSkippedDeferred()) + + def IsDeferred(self, test): + return (self._fixable.Contains(test) and + test in self._fixable.GetNonSkippedDeferred()) + + def IsIgnored(self, test): + return self._ignored.Contains(test) + + def _ReadFiles(self): + self._fixable = self._GetExpectationsFile(self.FIXABLE) + self._ignored = self._GetExpectationsFile(self.IGNORED) + skipped = self.GetFixableSkipped() | self.GetIgnoredSkipped() + self._fixable.PruneSkipped(skipped) + self._ignored.PruneSkipped(skipped) + + def _GetExpectationsFile(self, filename): + """Read the expectation files for the given filename and return a single + expectations file with the merged results. + """ + + path = os.path.join(self._directory, filename) + return TestExpectationsFile(path, self._tests, self._build_type) + + def _ValidateLists(self): + # Make sure there's no overlap between the tests in the two files. + overlap = self._fixable.GetTests() & self._ignored.GetTests() + message = "Files contained in both " + self.FIXABLE + " and " + self.IGNORED + compare_failures.PrintFilesFromSet(overlap, message) + assert(len(overlap) == 0) + # Make sure there are no ignored tests expected to crash. + assert(len(self._ignored.GetTestsExpectedTo(CRASH)) == 0) + + +def StripComments(line): + """Strips comments from a line and return None if the line is empty + or else the contents of line with leading and trailing spaces removed + and all other whitespace collapsed""" + + commentIndex = line.find('//') + if commentIndex is -1: + commentIndex = len(line) + + line = re.sub(r'\s+', ' ', line[:commentIndex].strip()) + if line == '': return None + else: return line + + +class TestExpectationsFile: + """Test expectation files consist of lines with specifications of what + to expect from layout test cases. The test cases can be directories + in which case the expectations apply to all test cases in that + directory and any subdirectory. The format of the file is along the + lines of: + + KJS # LayoutTests/fast/js/fixme.js = FAIL + V8 # LayoutTests/fast/js/flaky.js = FAIL | PASS + V8 | KJS # LayoutTests/fast/js/crash.js = CRASH | TIMEOUT | FAIL | PASS + ... + + In case you want to skip tests completely, add a SKIP: + V8 | KJS # SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT | PASS + + If you want the test to not count in our statistics for the current release, + add a DEFER: + V8 | KJS # DEFER : LayoutTests/fast/js/no-good.js = TIMEOUT | PASS + + And you can skip + defer a test: + V8 | KJS # DEFER | SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT | PASS + + You can also have different expecations for V8 and KJS + V8 # LayoutTests/fast/js/no-good.js = TIMEOUT | PASS + KJS # DEFER | SKIP : LayoutTests/fast/js/no-good.js = FAIL + + A test can be included twice, but not via the same path. If a test is included + twice, then the more precise path wins. + """ + + EXPECTATIONS = { 'pass': PASS, + 'fail': FAIL, + 'timeout': TIMEOUT, + 'crash': CRASH } + + BUILD_TYPES = [ 'kjs', 'v8' ] + + + def __init__(self, path, full_test_list, build_type): + """path is the path to the expectation file. An error is thrown if a test + is listed more than once for a given build_type. + full_test_list is the list of all tests to be run pending processing of the + expections for those tests. + build_type is used to filter out tests that only have expectations for + a different build_type. + + """ + + self._full_test_list = full_test_list + self._skipped = set() + self._skipped_deferred = set() + self._non_skipped_deferred = set() + self._expectations = {} + self._test_list_paths = {} + self._tests = {} + for expectation in self.EXPECTATIONS.itervalues(): + self._tests[expectation] = set() + self._Read(path, build_type) + + def GetSkipped(self): + return self._skipped + + def GetNonSkippedDeferred(self): + return self._non_skipped_deferred + + def GetSkippedDeferred(self): + return self._skipped_deferred + + def GetExpectations(self, test): + return self._expectations[test] + + def GetTests(self): + return set(self._expectations.keys()) + + def GetTestsExpectedTo(self, expectation): + return self._tests[expectation] + + def Contains(self, test): + return test in self._expectations + + def PruneSkipped(self, skipped): + for test in skipped: + if not test in self._expectations: continue + for expectation in self._expectations[test]: + self._tests[expectation].remove(test) + del self._expectations[test] + + def _Read(self, path, build_type): + """For each test in an expectations file, generate the expectations for it. + + """ + + lineno = 0 + for line in open(path): + lineno += 1 + line = StripComments(line) + if not line: continue + + parts = line.split('#') + if len(parts) is not 2: + self._ReportSyntaxError(path, lineno, "Test must have build types") + + if build_type not in self._GetOptionsList(parts[0]): continue + + parts = parts[1].split(':') + + if len(parts) is 2: + test_and_expectations = parts[1] + skip_defer_options = self._GetOptionsList(parts[0]) + is_skipped = 'skip' in skip_defer_options + is_deferred = 'defer' in skip_defer_options + else: + test_and_expectations = parts[0] + is_skipped = False + is_deferred = False + + tests_and_expecation_parts = test_and_expectations.split('=') + if (len(tests_and_expecation_parts) is not 2): + self._ReportSyntaxError(path, lineno, "Test is missing expectations") + + test_list_path = tests_and_expecation_parts[0].strip() + tests = self._ExpandTests(test_list_path) + + if is_skipped: + self._AddSkippedTests(tests, is_deferred) + else: + try: + self._AddTests(tests, + self._ParseExpectations(tests_and_expecation_parts[1]), + test_list_path, + is_deferred) + except SyntaxError, err: + self._ReportSyntaxError(path, lineno, str(err)) + + def _GetOptionsList(self, listString): + return [part.strip().lower() for part in listString.split('|')] + + def _ParseExpectations(self, string): + result = set() + for part in self._GetOptionsList(string): + if not part in self.EXPECTATIONS: + raise SyntaxError('Unsupported expectation: ' + part) + expectation = self.EXPECTATIONS[part] + result.add(expectation) + return result + + def _ExpandTests(self, test_list_path): + # Convert the test specification to an absolute, normalized + # path and make sure directories end with the OS path separator. + path = os.path.join(path_utils.LayoutDataDir(), test_list_path) + path = os.path.normpath(path) + if os.path.isdir(path): path = os.path.join(path, '') + # This is kind of slow - O(n*m) - since this is called for all + # entries in the test lists. It has not been a performance + # issue so far. Maybe we should re-measure the time spent reading + # in the test lists? + result = [] + for test in self._full_test_list: + if test.startswith(path): result.append(test) + return result + + def _AddTests(self, tests, expectations, test_list_path, is_deferred): + # Do not add tests that we expect only to pass to the lists. + # This makes it easier to account for tests that we expect to + # consistently pass, because they'll never be represented in + # any of the lists. + if len(expectations) == 1 and PASS in expectations: return + # Traverse all tests and add them with the given expectations. + + for test in tests: + if test in self._test_list_paths: + prev_base_path = self._test_list_paths[test] + if (prev_base_path == os.path.normpath(test_list_path)): + raise SyntaxError('Already seen expectations for path ' + test) + if prev_base_path.startswith(test_list_path): + # already seen a more precise path + continue + + # Remove prexisiting expectations for this test. + if test in self._test_list_paths: + if test in self._non_skipped_deferred: + self._non_skipped_deferred.remove(test) + + for expectation in self.EXPECTATIONS.itervalues(): + if test in self._tests[expectation]: + self._tests[expectation].remove(test) + + # Now add the new expectations. + self._expectations[test] = expectations + self._test_list_paths[test] = os.path.normpath(test_list_path) + + if is_deferred: + self._non_skipped_deferred.add(test) + + for expectation in expectations: + self._tests[expectation].add(test) + + def _AddSkippedTests(self, tests, is_deferred): + for test in tests: + self._skipped.add(test) + if is_deferred: + self._skipped_deferred.add(test) + + def _ReportSyntaxError(self, path, lineno, message): + raise SyntaxError(path + ':' + str(lineno) + ': ' + message) diff --git a/webkit/tools/layout_tests/layout_package/test_failures.py b/webkit/tools/layout_tests/layout_package/test_failures.py new file mode 100644 index 0000000..685c591 --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/test_failures.py @@ -0,0 +1,215 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Classes for failures that occur during tests.""" + +import path_utils + +class FailureSort(object): + """A repository for failure sort orders and tool to facilitate sorting.""" + + # Each failure class should have an entry in this dictionary. Sort order 1 + # will be sorted first in the list. Failures with the same numeric sort + # order will be sorted alphabetically by Message(). + SORT_ORDERS = { + 'FailureTextMismatch': 1, + 'FailureSimplifiedTextMismatch': 2, + 'FailureImageHashMismatch': 3, + 'FailureTimeout': 4, + 'FailureCrash': 5, + 'FailureMissingImageHash': 6, + 'FailureMissingImage': 7, + 'FailureMissingResult': 8, + } + + @staticmethod + def SortOrder(failure_type): + """Returns a tuple of the class's numeric sort order and its message.""" + order = FailureSort.SORT_ORDERS.get(failure_type.__name__, -1) + return (order, failure_type.Message()) + + +class TestFailure(object): + """Abstract base class that defines the failure interface.""" + @staticmethod + def Message(): + """Returns a string describing the failure in more detail.""" + raise NotImplemented + + def ResultHtmlOutput(self, filename): + """Returns an HTML string to be included on the results.html page.""" + raise NotImplemented + + def ShouldKillTestShell(self): + """Returns True if we should kill the test shell before the next test.""" + return False + + +class FailureWithType(TestFailure): + """Base class that produces standard HTML output based on the test type. + + Subclasses may commonly choose to override the ResultHtmlOutput, but still + use the standard OutputLinks. + """ + def __init__(self, test_type): + TestFailure.__init__(self) + self._test_type = test_type + + # Filename suffixes used by ResultHtmlOutput. + OUT_FILENAMES = [] + + def OutputLinks(self, filename, out_names): + """Returns a string holding all applicable output file links. + + Args: + filename: the test filename, used to construct the result file names + out_names: list of filename suffixes for the files. If three or more + suffixes are in the list, they should be [actual, expected, diff]. + Two suffixes should be [actual, expected], and a single item is the + [actual] filename suffix. If out_names is empty, returns the empty + string. + """ + links = [''] + uris = [self._test_type.RelativeOutputFilename(filename, fn) + for fn in out_names] + if len(uris) > 1: + links.append("<a href='%s'>expected</a>" % uris[1]) + if len(uris) > 0: + links.append("<a href='%s'>actual</a>" % uris[0]) + if len(uris) > 2: + links.append("<a href='%s'>diff</a>" % uris[2]) + return ' '.join(links) + + def ResultHtmlOutput(self, filename): + return self.Message() + self.OutputLinks(filename, self.OUT_FILENAMES) + + +class FailureTimeout(TestFailure): + """Test timed out. We also want to restart the test shell if this + happens.""" + @staticmethod + def Message(): + return "Test timed out" + + def ResultHtmlOutput(self, filename): + return "<strong>%s</strong>" % self.Message() + + def ShouldKillTestShell(self): + return True + + +class FailureCrash(TestFailure): + """Test shell crashed.""" + @staticmethod + def Message(): + return "Test shell crashed" + + def ResultHtmlOutput(self, filename): + # TODO(tc): create a link to the minidump file + return "<strong>%s</strong>" % self.Message() + + def ShouldKillTestShell(self): + return True + + +class FailureMissingResult(FailureWithType): + """Expected result was missing.""" + OUT_FILENAMES = ["-actual-win.txt"] + + @staticmethod + def Message(): + return "No expected results found" + + def ResultHtmlOutput(self, filename): + return ("<strong>%s</strong>" % self.Message() + + self.OutputLinks(filename, self.OUT_FILENAMES)) + + +class FailureTextMismatch(FailureWithType): + """Text diff output failed.""" + # Filename suffixes used by ResultHtmlOutput. + OUT_FILENAMES = ["-actual-win.txt", "-expected.txt", "-diff-win.txt"] + + @staticmethod + def Message(): + return "Text diff mismatch" + + +class FailureSimplifiedTextMismatch(FailureTextMismatch): + """Simplified text diff output failed. + + The results.html output format is basically the same as regular diff + failures (links to expected, actual and diff text files) so we share code + with the FailureTextMismatch class. + """ + + OUT_FILENAMES = ["-simp-actual-win.txt", "-simp-expected.txt", + "-simp-diff-win.txt"] + + @staticmethod + def Message(): + return "Simplified text diff mismatch" + + +class FailureMissingImageHash(FailureWithType): + """Actual result hash was missing.""" + # Chrome doesn't know to display a .checksum file as text, so don't bother + # putting in a link to the actual result. + OUT_FILENAMES = [] + + @staticmethod + def Message(): + return "No expected image hash found" + + def ResultHtmlOutput(self, filename): + return "<strong>%s</strong>" % self.Message() + + +class FailureMissingImage(FailureWithType): + """Actual result image was missing.""" + OUT_FILENAMES = ["-actual-win.png"] + + @staticmethod + def Message(): + return "No expected image found" + + def ResultHtmlOutput(self, filename): + return ("<strong>%s</strong>" % self.Message() + + self.OutputLinks(filename, self.OUT_FILENAMES)) + + +class FailureImageHashMismatch(FailureWithType): + """Image hashes didn't match.""" + OUT_FILENAMES = ["-actual-win.png", "-expected.png"] + + @staticmethod + def Message(): + # We call this a simple image mismatch to avoid confusion, since we link + # to the PNGs rather than the checksums. + return "Image mismatch" diff --git a/webkit/tools/layout_tests/layout_package/test_shell_thread.py b/webkit/tools/layout_tests/layout_package/test_shell_thread.py new file mode 100644 index 0000000..e28a30e --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/test_shell_thread.py @@ -0,0 +1,301 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""A Thread object for running the test shell and processing URLs from a +shared queue. + +Each thread runs a separate instance of the test_shell binary and validates +the output. When there are no more URLs to process in the shared queue, the +thread exits. +""" + +import copy +import logging +import os +import Queue +import subprocess +import thread +import threading + +import path_utils +import platform_utils +import test_failures + +# The per-test timeout in milliseconds, if no --time-out-ms option was given to +# run_webkit_tests. This should correspond to the default timeout in +# test_shell.exe. +DEFAULT_TEST_TIMEOUT_MS = 10 * 1000 + +def ProcessOutput(proc, filename, test_uri, test_types, test_args): + """Receives the output from a test_shell process, subjects it to a number + of tests, and returns a list of failure types the test produced. + + Args: + proc: an active test_shell process + filename: path of the test file being run + test_types: list of test types to subject the output to + test_args: arguments to be passed to each test + + Returns: a list of failure objects for the test being processed + """ + outlines = [] + failures = [] + crash_or_timeout = False + + # Some test args, such as the image hash, may be added or changed on a + # test-by-test basis. + local_test_args = copy.copy(test_args) + + line = proc.stdout.readline() + while line.rstrip() != "#EOF": + # Make sure we haven't crashed. + if line == '' and proc.poll() is not None: + failures.append(test_failures.FailureCrash()) + + # This is hex code 0xc000001d, which is used for abrupt termination. + # This happens if we hit ctrl+c from the prompt and we happen to + # be waiting on the test_shell. + if -1073741510 == proc.returncode: + raise KeyboardInterrupt + crash_or_timeout = True + break + + # Don't include #URL lines in our output + if line.startswith("#URL:"): + url = line.rstrip()[5:] + if url != test_uri: + logging.fatal("Test got out of sync:\n|%s|\n|%s|" % + (url, test_uri)) + raise AssertionError("test out of sync") + elif line.startswith("#MD5:"): + local_test_args.hash = line.rstrip()[5:] + elif line.startswith("#TEST_TIMED_OUT"): + # Test timed out, but we still need to read until #EOF. + crash_or_timeout = True + failures.append(test_failures.FailureTimeout()) + else: + outlines.append(line) + line = proc.stdout.readline() + + # Check the output and save the results. + for test_type in test_types: + new_failures = test_type.CompareOutput(filename, proc, + ''.join(outlines), + local_test_args) + # Don't add any more failures if we already have a crash or timeout, so + # we don't double-report those tests. + if not crash_or_timeout: + failures.extend(new_failures) + + return failures + + +def StartTestShell(binary, args): + """Returns the process for a new test_shell started in layout-tests mode.""" + cmd = [binary, '--layout-tests'] + args + return subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + +class SingleTestThread(threading.Thread): + """Thread wrapper for running a single test file.""" + def __init__(self, test_shell_binary, shell_args, test_uri, filename, + test_types, test_args): + """ + Args: + test_uri: full file:// or http:// URI of the test file to be run + filename: absolute local path to the test file + See TestShellThread for documentation of the remaining arguments. + """ + + threading.Thread.__init__(self) + self._binary = test_shell_binary + self._shell_args = shell_args + self._test_uri = test_uri + self._filename = filename + self._test_types = test_types + self._test_args = test_args + self._single_test_failures = [] + + def run(self): + proc = StartTestShell(self._binary, self._shell_args + [self._test_uri]) + self._single_test_failures = ProcessOutput(proc, + self._filename, + self._test_uri, + self._test_types, + self._test_args) + + def GetFailures(self): + return self._single_test_failures + + +class TestShellThread(threading.Thread): + + def __init__(self, filename_queue, test_shell_binary, test_types, + test_args, shell_args, options): + """Initialize all the local state for this test shell thread. + + Args: + filename_queue: A thread safe Queue class that contains tuples of + (filename, uri) pairs. + test_shell_binary: The path to test_shell.exe + test_types: A list of TestType objects to run the test output against. + test_args: A TestArguments object to pass to each TestType. + shell_args: Any extra arguments to be passed to test_shell.exe. + options: A property dictionary as produced by optparse. The command-line + options should match those expected by run_webkit_tests; they + are typically passed via the run_webkit_tests.TestRunner class. + """ + threading.Thread.__init__(self) + self._filename_queue = filename_queue + self._test_shell_binary = test_shell_binary + self._test_types = test_types + self._test_args = test_args + self._test_shell_proc = None + self._shell_args = shell_args + self._options = options + self._failures = {} + + if self._options.run_singly: + # When we're running one test per test_shell process, we can enforce + # a hard timeout. test_shell uses a default of 10 seconds if no + # time-out-ms is given, and the test_shell watchdog uses 2.5x the + # test_shell's value. We want to be larger than that. + if not self._options.time_out_ms: + self._options.time_out_ms = DEFAULT_TEST_TIMEOUT_MS + self._time_out_sec = int(self._options.time_out_ms) * 3.0 / 1000.0 + logging.info("Setting Python per-test timeout to %s ms (%s sec)" % + (1000 * self._time_out_sec, self._time_out_sec)) + + + def GetFailures(self): + """Returns a dictionary mapping test filename to a list of + TestFailures.""" + return self._failures + + def run(self): + """Main work entry point of the thread. Basically we pull urls from the + filename queue and run the tests until we run out of urls.""" + while True: + try: + filename, test_uri = self._filename_queue.get_nowait() + except Queue.Empty: + self._KillTestShell() + logging.debug("queue empty, quitting test shell thread") + return + + # we have a url, run tests + if self._options.run_singly: + failures = self._RunTestSingly(filename, test_uri) + else: + failures = self._RunTest(filename, test_uri) + if failures: + # Check and kill test shell if we need to + if len([1 for f in failures if f.ShouldKillTestShell()]): + self._KillTestShell() + # print the error message(s) + error_str = '\n'.join([' ' + f.Message() for f in failures]) + logging.error("%s failed:\n%s" % + (path_utils.RelativeTestFilename(filename), error_str)) + # Group the errors for reporting + self._failures[filename] = failures + else: + logging.debug(path_utils.RelativeTestFilename(filename) + " passed") + + + def _RunTestSingly(self, filename, test_uri): + """Run a test in a separate thread, enforcing a hard time limit. + + Since we can only detect the termination of a thread, not any internal + state or progress, we can only run per-test timeouts when running test + files singly. + """ + worker = SingleTestThread(self._test_shell_binary, + self._shell_args, + test_uri, + filename, + self._test_types, + self._test_args) + worker.start() + worker.join(self._time_out_sec) + if worker.isAlive(): + # If join() returned with the thread still running, the test_shell.exe is + # completely hung and there's nothing more we can do with it. We have + # to kill all the test_shells to free it up. If we're running more than + # one test_shell thread, we'll end up killing the other test_shells too, + # introducing spurious crashes. We accept that tradeoff in order to + # avoid losing the rest of this thread's results. + logging.error('Test thread hung: killing all test_shells') + # PlatformUtility() wants a base_dir, but it doesn't matter here. + platform_util = platform_utils.PlatformUtility('') + platform_util.KillAllTestShells() + + return worker.GetFailures() + + + def _RunTest(self, filename, test_uri): + """Run a single test file using a shared test_shell process. + + Args: + filename: The absolute filename of the test + test_uri: The URI version of the filename + + Return: + A list of TestFailure objects describing the error. + """ + self._EnsureTestShellIsRunning() + + # Ok, load the test URL... + self._test_shell_proc.stdin.write(test_uri + "\n") + self._test_shell_proc.stdin.flush() + + # ...and read the response + return ProcessOutput(self._test_shell_proc, filename, test_uri, + self._test_types, self._test_args) + + + def _EnsureTestShellIsRunning(self): + """Start the shared test shell, if it's not running. Not for use when + running tests singly, since those each start a separate test shell in + their own thread. + """ + if (not self._test_shell_proc or + self._test_shell_proc.poll() is not None): + self._test_shell_proc = StartTestShell(self._test_shell_binary, + self._shell_args) + + def _KillTestShell(self): + """Kill the test shell process if it's running.""" + if self._test_shell_proc: + self._test_shell_proc.stdin.close() + self._test_shell_proc.stdout.close() + self._test_shell_proc.stderr.close() + self._test_shell_proc = None diff --git a/webkit/tools/layout_tests/layout_package/test_types_unittest.py b/webkit/tools/layout_tests/layout_package/test_types_unittest.py new file mode 100644 index 0000000..dca5feb --- /dev/null +++ b/webkit/tools/layout_tests/layout_package/test_types_unittest.py @@ -0,0 +1,71 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Unittests that verify that the various test_types (e.g., simplified_diff) +are working.""" + +import difflib +import os +import unittest + +from test_types import simplified_text_diff + +class SimplifiedDiffUnittest(unittest.TestCase): + def testSimplifiedDiff(self): + """Compares actual output with expected output for some test cases. The + simplified diff of these test cases should be the same.""" + test_names = [ + 'null-offset-parent', + 'textAreaLineHeight', + 'form-element-geometry', + ] + differ = simplified_text_diff.SimplifiedTextDiff(None) + for prefix in test_names: + output_filename = os.path.join(self.GetTestDataDir(), + prefix + "-actual-win.txt") + expected_filename = os.path.join(self.GetTestDataDir(), + prefix + "-expected.txt") + output = differ._SimplifyText(open(output_filename).read()) + expected = differ._SimplifyText(open(expected_filename).read()) + + if output != expected: + lst = difflib.unified_diff(expected.splitlines(True), + output.splitlines(True), + 'expected', + 'actual') + for line in lst: + print line.rstrip() + self.failUnlessEqual(output, expected) + + def GetTestDataDir(self): + return os.path.join(os.path.abspath('testdata'), 'difftests') + +if '__main__' == __name__: + unittest.main() diff --git a/webkit/tools/layout_tests/run_webkit_tests.py b/webkit/tools/layout_tests/run_webkit_tests.py new file mode 100644 index 0000000..e2af53f --- /dev/null +++ b/webkit/tools/layout_tests/run_webkit_tests.py @@ -0,0 +1,653 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Run layout tests using the test_shell. + +This is a port of the existing webkit test script run-webkit-tests. + +The TestRunner class runs a series of tests (TestType interface) against a set +of test files. If a test file fails a TestType, it returns a list TestFailure +objects to the TestRunner. The TestRunner then aggregates the TestFailures to +create a final report. + +This script reads several files, if they exist in the test_lists subdirectory +next to this script itself. Each should contain a list of paths to individual +tests or entire subdirectories of tests, relative to the outermost test +directory. Entire lines starting with '//' (comments) will be ignored. + +For details of the files' contents and purposes, see test_lists/README. +""" + +import glob +import logging +import optparse +import os +import Queue +import shutil +import subprocess +import sys +import time + +import google.path_utils + +from layout_package import compare_failures +from layout_package import test_expectations +from layout_package import http_server +from layout_package import path_utils +from layout_package import test_failures +from layout_package import test_shell_thread +from test_types import image_diff +from test_types import test_type_base +from test_types import text_diff +from test_types import simplified_text_diff + + +# The test list files are found in this subdirectory, which must be a sibling +# to this script itself. +TEST_FILE_DIR = 'test_lists' + + +class TestRunner: + """A class for managing running a series of tests on a series of test + files.""" + + # When collecting test cases, we include any file with these extensions. + _supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.pl', + '.php', '.svg']) + # When collecting test cases, skip these directories + _skipped_directories = set(['.svn', '_svn', 'resources']) + + HTTP_SUBDIR = os.sep.join(['', 'http', '']) + + def __init__(self, options, paths): + """Collect a list of files to test. + + Args: + options: a dictionary of command line options + paths: a list of paths to crawl looking for test files + """ + self._options = options + + self._http_server = http_server.Lighttpd(options.results_directory) + # a list of TestType objects + self._test_types = [] + # a set of test files + self._test_files = set() + + if options.nosvg: + TestRunner._supported_file_extensions.remove('.svg') + TestRunner._skipped_directories.add('svg') + + self._GatherTestFiles(paths) + + def __del__(self): + sys.stdout.flush() + sys.stderr.flush() + # Stop the http server. + self._http_server.Stop() + + def _GatherTestFiles(self, paths): + """Generate a set of test files and place them in self._test_files, with + appropriate subsets in self._ignored_failures, self._fixable_failures, + and self._fixable_crashes. + + Args: + paths: a list of command line paths relative to the webkit/tests + directory. glob patterns are ok. + """ + paths_to_walk = set() + for path in paths: + # If there's an * in the name, assume it's a glob pattern. + path = os.path.join(path_utils.LayoutDataDir(), path) + if path.find('*') > -1: + filenames = glob.glob(path) + paths_to_walk.update(filenames) + else: + paths_to_walk.add(path) + + # Now walk all the paths passed in on the command line and get filenames + for path in paths_to_walk: + if os.path.isfile(path) and self._HasSupportedExtension(path): + self._test_files.add(os.path.normpath(path)) + continue + + for root, dirs, files in os.walk(path): + # don't walk skipped directories and sub directories + if os.path.basename(root) in TestRunner._skipped_directories: + del dirs[:] + continue + + for filename in files: + if self._HasSupportedExtension(filename): + filename = os.path.join(root, filename) + filename = os.path.normpath(filename) + self._test_files.add(filename) + + # Filter out http tests if we're not running them. + if self._options.nohttp: + for path in list(self._test_files): + if path.find(self.HTTP_SUBDIR) >= 0: + self._test_files.remove(path) + + # Filter and sort out files from the skipped, ignored, and fixable file + # lists. + saved_test_files = set() + if len(self._test_files) == 1: + # If there's only one test file, we don't want to skip it, but we do want + # to sort it. So we save it to add back to the list later. + saved_test_files = self._test_files + + file_dir = os.path.join(os.path.dirname(sys.argv[0]), TEST_FILE_DIR) + file_dir = path_utils.GetAbsolutePath(file_dir) + + expectations = test_expectations.TestExpectations(self._test_files, + file_dir, + self._options.build_type) + + # Remove skipped - both fixable and ignored - files from the + # top-level list of files to test. + skipped = expectations.GetFixableSkipped() | expectations.GetIgnoredSkipped() + self._test_files -= skipped + + # If there was only one test file, run the test even if it was skipped. + if len(saved_test_files): + self._test_files = saved_test_files + + logging.info('Run: %d tests' % len(self._test_files)) + logging.info('Skipped: %d tests' % len(skipped)) + logging.info('Skipped tests do not appear in any of the below numbers\n') + logging.info('Deferred: %d tests' % len(expectations.GetFixableDeferred())) + logging.info('Expected passes: %d tests' % + len(self._test_files - + expectations.GetFixable() - + expectations.GetIgnored())) + logging.info(('Expected failures: %d fixable, %d ignored ' + 'and %d deferred tests') % + (len(expectations.GetFixableFailures()), + len(expectations.GetIgnoredFailures()), + len(expectations.GetFixableDeferredFailures()))) + logging.info(('Expected timeouts: %d fixable, %d ignored ' + 'and %d deferred tests') % + (len(expectations.GetFixableTimeouts()), + len(expectations.GetIgnoredTimeouts()), + len(expectations.GetFixableDeferredTimeouts()))) + logging.info('Expected crashes: %d fixable tests' % + len(expectations.GetFixableCrashes())) + + # Store the expectations in this object to allow it to be used to + # track regressions and print results. + self._expectations = expectations + + def _HasSupportedExtension(self, filename): + """Return true if filename is one of the file extensions we want to run a + test on.""" + extension = os.path.splitext(filename)[1] + return extension in TestRunner._supported_file_extensions + + def AddTestType(self, test_type): + """Add a TestType to the TestRunner.""" + self._test_types.append(test_type) + + # We sort the tests so that tests using the http server will run first. We + # are seeing some flakiness, maybe related to apache getting swapped out, + # slow, or stuck after not serving requests for a while. + def TestFilesSort(self, x, y): + """Sort with http tests always first.""" + x_is_http = x.find(self.HTTP_SUBDIR) >= 0 + y_is_http = y.find(self.HTTP_SUBDIR) >= 0 + if x_is_http != y_is_http: + return cmp(y_is_http, x_is_http) + return cmp(x, y) + + def Run(self): + """Run all our tests on all our test files. + + For each test file, we run each test type. If there are any failures, we + collect them for reporting. + + Return: + We return nonzero if there are regressions compared to the last run. + """ + if not self._test_files: + return 0 + start_time = time.time() + logging.info("Starting tests") + + # Create the output directory if it doesn't already exist. + google.path_utils.MaybeMakeDirectory(self._options.results_directory) + + test_files = list(self._test_files) + test_files.sort(self.TestFilesSort) + # Create the thread safe queue of (test filenames, test URIs) tuples. Each + # TestShellThread pulls values from this queue. + filename_queue = Queue.Queue() + for test_file in test_files: + filename_queue.put((test_file, path_utils.FilenameToUri(test_file))) + + # If we have http tests, the first one will be an http test. + if test_files and test_files[0].find(self.HTTP_SUBDIR) >= 0: + self._http_server.Start() + + # Instantiate TestShellThreads and start them. + threads = [] + test_shell_binary = path_utils.TestShellBinaryPath(self._options.target) + for i in xrange(int(self._options.num_test_shells)): + shell_args = [] + test_args = test_type_base.TestArguments() + if self._options.pixel_tests: + png_path = os.path.join(self._options.results_directory, + "png_result%s.png" % i) + shell_args.append("--pixel-tests=" + png_path) + test_args.png_path = png_path + + if self._options.new_baseline: + test_args.new_baseline = self._options.new_baseline + if not self._options.pixel_tests: + test_args.text_baseline = True + + # Create separate TestTypes instances for each thread. + test_types = [] + for t in self._test_types: + test_types.append(t(self._options.build_type, + self._options.results_directory)) + + if self._options.startup_dialog: + shell_args.append('--testshell-startup-dialog') + + # larger timeout if page heap is enabled. + if self._options.time_out_ms: + shell_args.append('--time-out-ms=' + self._options.time_out_ms) + + thread = test_shell_thread.TestShellThread(filename_queue, + test_shell_binary, + test_types, + test_args, + shell_args, + self._options) + thread.start() + threads.append(thread) + + # Wait for the threads to finish and collect test failures. + test_failures = {} + for thread in threads: + thread.join() + test_failures.update(thread.GetFailures()) + + print + end_time = time.time() + logging.info("%f total testing time" % (end_time - start_time)) + + # Tests are done running. Compare failures with expected failures. + regressions = self._CompareFailures(test_failures) + + # Write summaries to stdout. + self._PrintResults(test_failures) + + # Write the summary to disk (results.html) and maybe open the test_shell + # to this file. + wrote_results = self._WriteResultsHtmlFile(test_failures, regressions) + if not self._options.noshow_results and wrote_results: + self._ShowResultsHtmlFile() + + sys.stdout.flush() + sys.stderr.flush() + return len(regressions) + + def _PrintResults(self, test_failures): + """Print a short summary to stdout about how many tests passed. + + Args: + test_failures is a dictionary mapping the test filename to a list of + TestFailure objects if the test failed + """ + + failure_counts = {} + deferred_counts = {} + fixable_counts = {} + non_ignored_counts = {} + fixable_failures = set() + deferred_failures = set() + non_ignored_failures = set() + + # Aggregate failures in a dictionary (TestFailure -> frequency), + # with known (fixable and ignored) failures separated out. + def AddFailure(dictionary, key): + if key in dictionary: + dictionary[key] += 1 + else: + dictionary[key] = 1 + + for test, failures in test_failures.iteritems(): + for failure in failures: + AddFailure(failure_counts, failure.__class__) + if self._expectations.IsFixable(test): + AddFailure(fixable_counts, failure.__class__) + fixable_failures.add(test) + if self._expectations.IsDeferred(test): + AddFailure(deferred_counts, failure.__class__) + deferred_failures.add(test) + if (not self._expectations.IsIgnored(test) and + not self._expectations.IsDeferred(test)): + AddFailure(non_ignored_counts, failure.__class__) + non_ignored_failures.add(test) + + # Print summaries. + print "-" * 78 + + # Print breakdown of tests we need to fix and want to pass. + # Include skipped fixable tests in the statistics. + skipped = (self._expectations.GetFixableSkipped() - + self._expectations.GetFixableSkippedDeferred()) + + self._PrintResultSummary("=> Tests to be fixed for the current release", + self._expectations.GetFixable(), + fixable_failures, + fixable_counts, + skipped) + + self._PrintResultSummary("=> Tests we want to pass for the current release", + (self._test_files - + self._expectations.GetIgnored() - + self._expectations.GetFixableDeferred()), + non_ignored_failures, + non_ignored_counts, + skipped) + + self._PrintResultSummary("=> Tests to be fixed for a future release", + self._expectations.GetFixableDeferred(), + deferred_failures, + deferred_counts, + self._expectations.GetFixableSkippedDeferred()) + + # Print breakdown of all tests including all skipped tests. + skipped |= self._expectations.GetIgnoredSkipped() + self._PrintResultSummary("=> All tests", + self._test_files, + test_failures, + failure_counts, + skipped) + print + + def _PrintResultSummary(self, heading, all, failed, failure_counts, skipped): + """Print a summary block of results for a particular category of test. + + Args: + heading: text to print before the block, followed by the total count + all: list of all tests in this category + failed: list of failing tests in this category + failure_counts: dictionary of (TestFailure -> frequency) + """ + total = len(all | skipped) + print "\n%s (%d):" % (heading, total) + skip_count = len(skipped) + pass_count = total - skip_count - len(failed) + self._PrintResultLine(pass_count, total, "Passed") + self._PrintResultLine(skip_count, total, "Skipped") + # Sort the failure counts and print them one by one. + sorted_keys = sorted(failure_counts.keys(), + key=test_failures.FailureSort.SortOrder) + for failure in sorted_keys: + self._PrintResultLine(failure_counts[failure], total, failure.Message()) + + def _PrintResultLine(self, count, total, message): + if count == 0: return + print ("%(count)d test case%(plural)s (%(percent).1f%%) %(message)s" % + { 'count' : count, + 'plural' : ('s', '')[count == 1], + 'percent' : float(count) * 100 / total, + 'message' : message }) + + def _CompareFailures(self, test_failures): + """Determine how the test failures from this test run differ from the + previous test run and print results to stdout and a file. + + Args: + test_failures is a dictionary mapping the test filename to a list of + TestFailure objects if the test failed + + Return: + A set of regressions (unexpected failures, hangs, or crashes) + """ + cf = compare_failures.CompareFailures(self._test_files, + test_failures, + self._expectations) + + if not self._options.nocompare_failures: cf.PrintRegressions() + return cf.GetRegressions() + + def _WriteResultsHtmlFile(self, test_failures, regressions): + """Write results.html which is a summary of tests that failed. + + Args: + test_failures: a dictionary mapping the test filename to a list of + TestFailure objects if the test failed + regressions: a set of test filenames that regressed + + Returns: + True if any results were written (since expected failures may be omitted) + """ + # test failures + if self._options.full_results_html: + test_files = test_failures.keys() + else: + test_files = list(regressions) + if not len(test_files): + return False + + out_filename = os.path.join(self._options.results_directory, + "results.html") + out_file = open(out_filename, 'w') + # header + if self._options.full_results_html: + h2 = "Test Failures" + else: + h2 = "Unexpected Test Failures" + out_file.write("<html><head><title>Layout Test Results (%(time)s)</title>" + "</head><body><h2>%(h2)s (%(time)s)</h2>\n" + % {'h2': h2, 'time': time.asctime()}) + + test_files.sort() + for test_file in test_files: + if test_file in test_failures: failures = test_failures[test_file] + else: failures = [] # unexpected passes + out_file.write("<p><a href='%s'>%s</a><br />\n" + % (path_utils.FilenameToUri(test_file), + path_utils.RelativeTestFilename(test_file))) + for failure in failures: + out_file.write(" %s<br/>" + % failure.ResultHtmlOutput( + path_utils.RelativeTestFilename(test_file))) + out_file.write("</p>\n") + + # footer + out_file.write("</body></html>\n") + return True + + def _ShowResultsHtmlFile(self): + """Launches the test shell open to the results.html page.""" + results_filename = os.path.join(self._options.results_directory, + "results.html") + subprocess.Popen([path_utils.TestShellBinaryPath(self._options.target), + path_utils.FilenameToUri(results_filename)]) + + +def ReadTestFiles(files): + tests = [] + for file in files: + for line in open(file): + line = test_expectations.StripComments(line) + if line: tests.append(line) + return tests + + +def main(options, args): + """Run the tests. Will call sys.exit when complete. + + Args: + options: a dictionary of command line options + args: a list of sub directories or files to test + """ + # Set up our logging format. + log_level = logging.INFO + if options.verbose: + log_level = logging.DEBUG + logging.basicConfig(level=log_level, + format='%(asctime)s %(filename)s:%(lineno)-3d' + ' %(levelname)s %(message)s', + datefmt='%y%m%d %H:%M:%S') + + if not options.target: + if options.debug: + options.target = "Debug" + else: + options.target = "Release" + + if (options.pixel_tests and + options.build_type != 'v8' and + not options.new_baseline): + logging.warn('Pixel tests disabled: no expected results for %s builds' % + options.build_type) + options.pixel_tests = False + + if options.results_directory.startswith("/"): + # Assume it's an absolute path and normalize + options.results_directory = path_utils.GetAbsolutePath( + options.results_directory) + else: + # If it's a relative path, make the output directory relative to Debug or + # Release. + basedir = path_utils.WebKitRoot() + basedir = os.path.join(basedir, options.target) + + options.results_directory = path_utils.GetAbsolutePath( + os.path.join(basedir, options.results_directory)) + + logging.info("Using expected results from %s" % + path_utils.CustomExpectedResultsDir(options.build_type)) + logging.info("Placing test results in %s" % options.results_directory) + logging.info("Using %s build at %s" % (options.target, + path_utils.TestShellBinaryPath(options.target))) + if options.pixel_tests: + logging.info("Running pixel tests") + + if 'cygwin' == sys.platform: + logging.warn("#" * 40) + logging.warn("# UNEXPECTED PYTHON VERSION") + logging.warn("# This script should be run using the version of python") + logging.warn("# in third_party/python_24/") + logging.warn("#" * 40) + sys.exit(1) + + # Delete the disk cache if any to ensure a clean test run. + cachedir = os.path.split(path_utils.TestShellBinaryPath(options.target))[0] + cachedir = os.path.join(cachedir, "cache") + if os.path.exists(cachedir): + shutil.rmtree(cachedir) + + # Include all tests if none are specified. + paths = [] + if args: + paths += args + if options.test_list: + paths += ReadTestFiles(options.test_list) + if not paths: + paths = ['.'] + test_runner = TestRunner(options, paths) + test_runner.AddTestType(text_diff.TestTextDiff) + test_runner.AddTestType(simplified_text_diff.SimplifiedTextDiff) + if options.pixel_tests: + test_runner.AddTestType(image_diff.ImageDiff) + has_new_failures = test_runner.Run() + logging.info("Exit status: %d" % has_new_failures) + sys.exit(has_new_failures) + +if '__main__' == __name__: + option_parser = optparse.OptionParser() + option_parser.add_option("", "--nohttp", action="store_true", default=False, + help="disable http tests") + option_parser.add_option("", "--nosvg", action="store_true", default=False, + help="disable svg tests") + option_parser.add_option("", "--pixel-tests", action="store_true", + default=False, + help="enable pixel-to-pixel PNG comparisons") + option_parser.add_option("", "--results-directory", + default="layout-test-results", + help="Output results directory source dir," + " relative to Debug or Release") + option_parser.add_option("", "--new-baseline", default=None, metavar="DIR", + help="save results as new baselines into this " + "directory (e.g. layout_test_results/v8), " + "overwriting whatever's already there. " + "If pixel tests are being run, only image " + "baselines will be saved, not text.") + option_parser.add_option("", "--noshow-results", action="store_true", + default=False, help="don't launch the test_shell" + " with results after the tests are done") + option_parser.add_option("", "--full-results-html", action="store_true", + default=False, help="show all failures in" + "results.html, rather than only regressions") + option_parser.add_option("", "--num-test-shells", + default=1, + help="The number of test shells to run in" + " parallel. EXPERIMENTAL.") + option_parser.add_option("", "--save-failures", action="store_true", + default=False, + help="Save lists of expected failures and crashes " + "and use them in computing regressions.") + option_parser.add_option("", "--nocompare-failures", action="store_true", + default=False, + help="Disable comparison to the last test run. " + "When enabled, show stats on how many tests " + "newly pass or fail.") + option_parser.add_option("", "--time-out-ms", + default=None, + help="Set the timeout for each test") + option_parser.add_option("", "--run-singly", action="store_true", + default=False, + help="run a separate test_shell for each test") + option_parser.add_option("", "--debug", action="store_true", default=False, + help="use the debug binary instead of the release " + "binary") + option_parser.add_option("", "--build-type", default="v8", + help="use these test lists and expected results " + "('kjs' or 'v8')") + option_parser.add_option("", "--target", default="", + help="Set the build target configuration (overrides" + "--debug)") + option_parser.add_option("-v", "--verbose", action="store_true", + default=False, help="include debug level logging") + option_parser.add_option("", "--startup-dialog", action="store_true", + default=False, + help="create a dialog on test_shell.exe startup") + option_parser.add_option("", "--test-list", action="append", + help="read list of tests to run from file", + metavar="FILE") + options, args = option_parser.parse_args() + main(options, args) diff --git a/webkit/tools/layout_tests/run_webkit_tests.sh b/webkit/tools/layout_tests/run_webkit_tests.sh new file mode 100755 index 0000000..6ac6696 --- /dev/null +++ b/webkit/tools/layout_tests/run_webkit_tests.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +system_root=`cygpath "$SYSTEMROOT"` +export PATH="/usr/bin:$system_root/system32:$system_root:$system_root/system32/WBEM" + +exec_dir=$(dirname $0) + +"$exec_dir/../../../third_party/python_24/python.exe" \ + "$exec_dir/run_webkit_tests.py" "$@" diff --git a/webkit/tools/layout_tests/test_lists/README b/webkit/tools/layout_tests/test_lists/README new file mode 100644 index 0000000..985ebe2 --- /dev/null +++ b/webkit/tools/layout_tests/test_lists/README @@ -0,0 +1,34 @@ +The files in this directory are read by the +run_webkit_tests.py script and used to determine which tests to run +and which failures to consider regressions. + +If we *never* expect to pass these tests (e.g. test for Apple's objective-c +API), then add them to tests_ignore.txt. If we expect that we might ever want to +pass the test, first file a bug, then add it to tests_fixable.txt. + +Tests can have the following metatdata associated with them (optional): +DEFER : We don't expect to pass them in the current release. +SKIP : We don't want to run them (perhaps because they take too long). + +Tests have a build-type (required): +V8 # The test only fails in V8 build. +KJS # The test only fails in KJS build. + +Tests can also have expections (required): +PASS +FAIL +TIMEOUT +CRASH + +The format of a test is as follows: +BUILD_TYPE # METADATA : TEST = EXPECTATIONS + +For example +V8 # DEFER : LayoutTests/media = PASS | FAIL + +The above means that in the V8 build, all the media tests are flaky and +we'll defer fixing them for a future release. + +Note that tests_fixable.txt and tests_ignored.txt aren't allowed to share any +lines. Except for skipped tests, all tests are run. In calculating success +metrics, we ignore tests in tests_ignored.txt and deferred tests. diff --git a/webkit/tools/layout_tests/test_lists/tests_fixable.txt b/webkit/tools/layout_tests/test_lists/tests_fixable.txt new file mode 100644 index 0000000..17ce386 --- /dev/null +++ b/webkit/tools/layout_tests/test_lists/tests_fixable.txt @@ -0,0 +1,718 @@ +// These tests are expected to fail until we get around to fixing +// them. + + +// ----------------------------------------------------------------- +// DEBUG ONLY FAILURES! +// ----------------------------------------------------------------- + +// Bug 1124548: Copying with no selection is sometimes supposed to work +// This test also crashes in debug due to an ASSERT. (see bug 1058654) +V8 | KJS # DEFER : LayoutTests/editing/execCommand/copy-without-selection.html = FAIL | CRASH + +// ----------------------------------------------------------------- +// HANGING TESTS +// ----------------------------------------------------------------- + +// onload race condition due to poorly designed test. +// Works fine when run stand-alone. Not needed for Beta. +V8 | KJS # DEFER : LayoutTests/fast/dom/frame-loading-via-document-write.html = FAIL + +// ----------------------------------------------------------------- +// FLAKY TESTS +// ----------------------------------------------------------------- + +// This test uses a -webkit-transition-duration and a set timeout, probably +// just a small race in our timers. Fails once in a while, only on v8. +V8 # DEFER : LayoutTests/fast/css/transition-color-unspecified.html = PASS | FAIL + +// Flaky tests, see bug 877986. +// WebKit's CSS counters are somewhat broken, thus expected results are failures +// Our high-precision timers make these tests flakey. +// We could fork these tests, but we'll just unfork them as soon as +// our high-precision timers are public. +V8 | KJS # DEFER : LayoutTests/css2.1/t1204-increment-00-c-o.html = FAIL | PASS +V8 | KJS # DEFER : LayoutTests/css2.1/t1204-increment-01-c-o.html = FAIL | PASS +V8 | KJS # DEFER : LayoutTests/css2.1/t1204-increment-02-c-o.html = FAIL | PASS +V8 | KJS # DEFER : LayoutTests/css2.1/t1204-reset-00-c-o.html = FAIL | PASS +V8 | KJS # DEFER : LayoutTests/css2.1/t1204-reset-01-c-o.html = FAIL | PASS + +// Bug 1143337 +// These tests are here because they fail on the buildbot, but not locally. +// Seems to be due to one or more timing issues in the tests. +// These only timeout on the v8-Debug buildbot it seems. (eseidel, 4/28) +// I'm not seeing this fail locally in v8-Debug or v8-Release (eseidel, 4/25) +V8 | KJS # LayoutTests/http/tests/security/dataURL/xss-DENIED-to-data-url-in-foreign-domain-subframe-location-change.html = PASS | FAIL +V8 | KJS # LayoutTests/http/tests/security/dataURL/xss-DENIED-from-data-url-in-foreign-domain-subframe.html = PASS | TIMEOUT + +// This are failing for different reasons under our new lighttpd configuration +// TODO(deanm): Address all of these via lighttpd if possible, otherwise fork. +// Maybe flaky and need to be forked? Bug 1234761. + +// Consistently fails on KJS only +KJS # LayoutTests/http/tests/security/cross-frame-access-call.html = FAIL +// Maybe flaky and need to be forked? +V8 | KJS # LayoutTests/http/tests/security/cross-frame-access-enumeration.html = TIMEOUT +// Difference in caching headers +V8 | KJS # LayoutTests/http/tests/xmlhttprequest/cache-override.html = FAIL +// LightTPD doesn't accept unknown HTTP methods +V8 | KJS # LayoutTests/http/tests/xmlhttprequest/methods-lower-case.html = TIMEOUT +V8 | KJS # LayoutTests/http/tests/xmlhttprequest/methods-async.html = TIMEOUT +// LightTPD doesn't accept unknown HTTP methods and passes CGIs a Content-Type +// even when a request didn't send the header. +V8 | KJS # LayoutTests/http/tests/xmlhttprequest/methods.html = FAIL + +// ----------------------------------------------------------------- +// TEXT +// ----------------------------------------------------------------- + +// This class of test fails because of size differences in text runs. +// Mostly this is because of international text rendering differences. + +// Bug 1124513: the max length is being applied correctly, but the over- and +// under-lines aren't placed properly over the "x". +// The under-lines are a cosmetic error which is not necessary to fix for Beta. (eseidel) +V8 | KJS # DEFER : LayoutTests/fast/forms/input-text-maxlength.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/forms/input-text-paste-maxlength.html = FAIL + +// Font differences, requiring overriden metrics, not a real bug, not fixing for Beta +V8 | KJS # DEFER : LayoutTests/fast/text/international/bidi-AN-after-L.html = FAIL + +// Bug: 1145880 +// Parethesis missing, metrics wrong. +V8 | KJS # DEFER : LayoutTests/fast/text/international/bidi-neutral-run.html = FAIL + +// Bug: 628529: complex text effects +// This is a real bug, but not one we're fixing for Beta. +V8 | KJS # DEFER : LayoutTests/fast/text/stroking-decorations.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/text/stroking.html = FAIL + +// Bug: 1124522 +// Incrorect results, in incorrect international font metrics. +// Fixing these overrides does not help us to Beta, deffering +V8 | KJS # DEFER : LayoutTests/fast/text/atsui-multiple-renderers.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/text/atsui-pointtooffset-calls-cg.html = FAIL + +// Bug: 1143381 +// This test checks that we hack around a bug in helvetica. We fail to. +V8 | KJS # DEFER : LayoutTests/fast/text/wide-zero-width-space.html = FAIL + +// Font-size differences in international text cause the wrong character +// to be under the (x,y) click location used by the test. See bug 850411 +// on faking international font sizes like we do for Latin fonts. +V8 | KJS # DEFER : LayoutTests/fast/text/atsui-rtl-override-selection.html = FAIL + +// Bug: 1124542 +// More missing international text overides, not needed for Beta. +// Capitalization results match Safari, even if "not fully correct" +V8 | KJS # DEFER : LayoutTests/fast/text/capitalize-boundaries.html = FAIL + +// Bug: 1145887 +// Different button line-heights, our behavior looks wrong. +V8 | KJS # DEFER : LayoutTests/fast/forms/control-restrict-line-height.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/replaced/table-percent-height.html = FAIL + +// Bug 992930: Unable to load file:/// URLs from data: URLs. +V8 | KJS # DEFER : LayoutTests/fast/events/standalone-image-drag-to-editable.html = FAIL + +// Bug 1187672. Two font faces should be identical but aren't. Punting SVG. +V8 | KJS # DEFER : LayoutTests/svg/custom/font-face-simple.svg = FAIL + +// ----------------------------------------------------------------- +// URL +// ----------------------------------------------------------------- + +// http://b/1089231: Form submission (GET) on non-standard url does not append a query. +// Defering these tests as the expected behavior is wonky, and shouldn't affect +// existing apps one way or another. + +// Implicit expectation in this test is that you can "set query" on a data URL, +// and it should replace the first "?" substring. This makes absolutely no sense. +V8 | KJS # DEFER : LayoutTests/fast/events/stopPropagation-submit.html = FAIL + +// Expected results has a terminal "?", since "set query" on about:blank is allowed. +// This is strange since query should have no meaning in non-standard urls +V8 | KJS # DEFER : LayoutTests/http/tests/navigation/onload-navigation-iframe-timeout.html = FAIL +V8 | KJS # DEFER : LayoutTests/http/tests/navigation/onload-navigation-iframe.html = FAIL + +// ----------------------------------------------------------------- +// Other +// ----------------------------------------------------------------- + +// Bug 865472, this should just need proper pixel test rebaselining. +V8 # LayoutTests/http/tests/navigation/javascriptlink-frames.html = FAIL + +// Bug 1235433, javascript can execute before stylesheets are completely loaded. +V8 | KJS # DEFER : LayoutTests/http/tests/local/stylesheet-and-script-load-order.html = FAIL + +// Bug 1195064, dragging into a textarea is less-ideal as it used to be. +V8 | KJS # DEFER : LayoutTests/fast/forms/drag-into-textarea.html = FAIL + +// Bug: 1143492 +// Window status should always return a string object +// WebKit does this to match IE, FF also fails this test. +// Obscure, not sure we care. DEFER for Beta +V8 | KJS # DEFER : LayoutTests/fast/dom/assign-to-window-status.html = FAIL + +// Bug 905894 +// Getting parseerror (probably wrong svn:eol-style) +// Will be fixed by upstream merge +V8 | KJS # DEFER : LayoutTests/fast/xsl/xslt-enc16.xml = FAIL +V8 | KJS # DEFER : LayoutTests/fast/xsl/xslt-enc16to16.xml = FAIL + +// Bug: 742182, 845388 +// Mac Safari under certain circumstances automatically places +// a caret in editable document even when none was requested programatically. +// We don't intend to copy this feature (at least not for Beta). +V8 | KJS # DEFER : LayoutTests/editing/selection/designmode-no-caret.html = FAIL + +// Bug: 742182, 845388, 960092 +// Platform-specific: simulates command-{arrow} input to modify selection +// Our Event-Sender isn't robust enough to support this. +// Not required for Beta. This may also be related to known home/end issues +// which are intended to be fixed for Beta. +V8 | KJS # DEFER : LayoutTests/editing/selection/move-begin-end.html = FAIL + +// Bug 845400 Miscellaneous layout issues under editing +V8 | KJS # DEFER : LayoutTests/editing/pasteboard/copy-standalone-image.html = FAIL + +// Bug 845400 +// The end result looks right, but the event messages differ. +// Not critical for Beta, DEFER +V8 | KJS # DEFER : LayoutTests/editing/pasteboard/paste-xml.xhtml = FAIL + +// Bug 849441 +// Directionality of mixed-direction text in selected choice should +// match that in the <select> option lists. +// Low priority, unclear if test expectations are correct (see bug) +V8 | KJS # DEFER : LayoutTests/fast/forms/select-writing-direction-natural.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/text/international/bidi-menulist.html = FAIL + +// Bug 850875 +// requires support for layoutTestController.encodeHostName() +// Not critical for Beta, DEFER +V8 | KJS # DEFER : LayoutTests/fast/encoding/idn-security.html = FAIL + +// Bug 852346 +// Tests link coloring and needs a history dataLayoutTests. +// This is a test tool problem, not a Chrome problem. +// Not critical for Beta. DEFER +V8 | KJS # DEFER : LayoutTests/fast/history/clicked-link-is-visited.html = FAIL + +// Bug 1027226 +// Bug 945322: test shell should dump text when +// layoutTestController.notifyDone() is called +// Not critical for beta. +V8 | KJS # DEFER : LayoutTests/editing/selection/drag-in-iframe.html = TIMEOUT + +// BUG 938563: occasionally times out (performs about 50 HTTP CGI requests) +V8 | KJS # DEFER : LayoutTests/http/tests/xmlhttprequest/supported-xml-content-types.html = PASS | TIMEOUT + +// Bug 849056 +// We don't support NPN_Enumerate, but don't know of any plugin +// which depends on that functionality. So we ignore this for beta. +V8 | KJS # DEFER : LayoutTests/plugins/netscape-enumerate.html = FAIL + +// Bug: 767628 +// This fails due to scrolling and event differences. +V8 | KJS # DEFER : LayoutTests/fast/forms/focus-selection-textarea.html = FAIL + +// This tests the screen's pixel depth, which we don't set on the buildbots +// so it depends on the users settings. Making this a broken test for us. +// The test must be fixed to not depend on user settings and rebaselined. post-beta. +V8 | KJS # DEFER : LayoutTests/fast/dom/Window/window-screen-properties.html = FAIL + +// Bug: 849060 +// Plugin creation is delayed until after first layout, so +// setwindow isn't being called. +V8 | KJS # LayoutTests/plugins/netscape-plugin-setwindow-size.html = FAIL + +// gc-6.html failed because the loop in the test didn't allocate enough +// garbage objects to trigger a GC. It was passing before. The code +// for triggering GC is unreliable. +// Not a useful test for us, DEFER +V8 | KJS # DEFER : LayoutTests/fast/dom/gc-6.html = TIMEOUT + +// Bug: 894476 +// It is flaky on V8 build, we need to investigate it. +// It fails on KJS build. It is not something for beta. +KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-put.html = FAIL +// It is flaky on V8 build. +V8 # LayoutTests/http/tests/security/cross-frame-access-put.html = PASS | FAIL + +// V8 specific test +KJS # SKIP | DEFER : chrome/fast/dom/script_lineno.html = FAIL + +// KJS doesn't support cross-domain setting of window.opener. +KJS # DEFER : chrome/http/tests/misc/set-window-opener-to-null.html = FAIL + +// Bug: 1135526 +// I've not seen the failing results, so it's difficult to guess what might be wrong +// This test flips back and forth on the bots, passes locally (eseidel, 4/28) +// This test uses setTimeout(foo, 1) to poll window.closed after window.close() is +// called to only continue once the window is fully closed. It's possible we're +// setting window.close too early (wild guess). +// only failing on KJS hence removing V8 (sandholm, 4/29) +// Defer until after beta since it only fails in KJS. +KJS # DEFER : LayoutTests/fast/dom/Window/new-window-opener.html = FAIL + +// Bug 982602 +// We don't support support window.resizeTo (nor is it planned for Beta) +V8 | KJS # DEFER : LayoutTests/fast/dom/Window/window-resize-and-move-arguments.html = FAIL + +// layoutTestController.setPopupBlockingEnabled() does not exist. +// Not critical for Beta. +V8 | KJS # DEFER : LayoutTests/fast/events/open-window-from-another-frame.html = FAIL + +// Test expects that when focus is in an iframe and page-up is hit, the parent +// document is also scrolled +// IE and FF also "fail" this test. DEFER +V8 | KJS # DEFER : LayoutTests/fast/frames/iframe-scroll-page-up-down.html = FAIL + +// Bug 1082426 +// document.write() pf plain text does not always flush +// This is a known WebKit bug, https://bugs.webkit.org/show_bug.cgi?id=8961 +V8 | KJS # DEFER : LayoutTests/editing/execCommand/delete-no-scroll.html = FAIL + +// TODO: investigate. Broken with webkit merge 28723:29478 +// KJS only, so we don't care for Beta. +KJS # DEFER : LayoutTests/http/tests/security/frameNavigation/xss-DENIED-plugin-navigation.html = FAIL + +// Bug: 879449 +// TODO(joshia): Need some changes to the test shell in order to support +// Java applet related unit tests. So disable the following for now. +// These tests should be fixed immediately after the Java applets work is done. +V8 | KJS # LayoutTests/fast/replaced/applet-disabled-positioned.html = FAIL +V8 | KJS # LayoutTests/fast/replaced/applet-rendering-java-disabled.html = FAIL + +// Bug 1198880 +V8 | KJS # DEFER : LayoutTests/svg/custom/svgsvgelement-ctm.xhtml = FAIL + +// Bug 1204878 +V8 | KJS # LayoutTests/http/tests/navigation/post-goback1.html = FAIL + +// ----------------------------------------------------------------- +// PENDING TESTS (forked to pending/, need to be sent upstream) +// ----------------------------------------------------------------- + +// BUG 792023. See http://bugs.webkit.org/show_bug.cgi?id=15690 and 16494 +// Fixed tests are in pending directory. (The originals still pass consistently +// in KJS.) +V8 # DEFER : LayoutTests/dom/html/level2/html/HTMLFrameElement09.html = FAIL +V8 # DEFER : LayoutTests/dom/html/level2/html/HTMLIFrameElement11.html = FAIL + +// Bug 972450: These tests don't work with fast timers due to setTimeout +// races. Pending versions have these fixed. +V8 | KJS # DEFER : LayoutTests/fast/history/history_reload.html = PASS | FAIL +V8 | KJS # DEFER : LayoutTests/fast/repaint/bugzilla-6473.html = PASS | FAIL + +// Bug 982608: test had a wrong result for one condition +V8 | KJS # DEFER : LayoutTests/plugins/destroy-stream-twice.html = FAIL + +// This test has been modified and placed in pending, so we ignore the original +// until we get our modification into WebKit. +V8 | KJS # DEFER : LayoutTests/security/block-test.html = PASS | FAIL + +// Bug 1124522 +// Test forked into pending and fixed. +V8 # DEFER : LayoutTests/fast/js/function-toString-parentheses.html = FAIL + +// Bug 1132721. Forked to pending/fast/encoding/script-in-head.html +// Should get this change pushed upstream and then unfork. +// Stopped failing +// V8 # DEFER : LayoutTests/fast/encoding/script-in-head.html = PASS | FAIL | TIMEOUT + +// Bug 1143337. Forked to pending/http/tests/security/. +V8 | KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-child-explicit-domain.html = PASS | FAIL | TIMEOUT +V8 | KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-parent-explicit-domain.html = PASS | FAIL | TIMEOUT +V8 | KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-port-explicit-domain.html = PASS | FAIL | TIMEOUT +V8 | KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-port.html = PASS | FAIL | TIMEOUT +V8 | KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-protocol-explicit-domain.html = PASS | FAIL | TIMEOUT +V8 | KJS # DEFER : LayoutTests/http/tests/security/cross-frame-access-protocol.html = PASS | FAIL | TIMEOUT +V8 | KJS # DEFER : LayoutTests/http/tests/security/protocol-compare-case-insensitive.html = PASS | FAIL | TIMEOUT + +// Bug 1199617. Forked to pending/svg/carto.net/window.svg. +// Test did not wait for all created functions to be called via +// setTimeout. +V8 | KJS # DEFER : LayoutTests/svg/carto.net/window.svg = PASS | FAIL + +// Bug 1064038. Image with border="1" drawn without the border. +V8 | KJS # DEFER : pending/fast/forms/image-border.html = FAIL + +// Bug 1055396. Vertical scrollbar created when there is no overflow. +V8 | KJS # DEFER : pending/fast/forms/textarea-scrollbar-height.html = FAIL + +// Bug 1059184. Dashed border-right isn't drawn on tall elements. +V8 | KJS # DEFER : pending/css/border-height.html = FAIL + +// ----------------------------------------------------------------- +// DEFERRED TESTS (deferred until a future release) +// ----------------------------------------------------------------- + +// Bug 1203341: Requires a working postMessage implementation +V8 # DEFER : LayoutTests/http/tests/security/cross-frame-access-delete.html = TIMEOUT +V8 # DEFER : LayoutTests/http/tests/security/cross-frame-access-history-put.html = TIMEOUT +V8 # DEFER : LayoutTests/http/tests/security/cross-frame-access-location-put.html = TIMEOUT +V8 # DEFER : LayoutTests/http/tests/security/postMessage/domain-and-uri-unaffected-by-base-tag.html = TIMEOUT +V8 # DEFER : LayoutTests/http/tests/security/postMessage/domain-unaffected-by-document-domain.html = TIMEOUT +V8 # DEFER : LayoutTests/http/tests/security/postMessage/javascript-page-still-sends-domain.html = TIMEOUT +V8 # DEFER : LayoutTests/http/tests/messaging/cross-domain-message-send.html = TIMEOUT + +// Bug 1135948: Fails because we cannot call plugins as functions. +V8 # DEFER : LayoutTests/plugins/bindings-test.html = FAIL + +// Bug 1124435: deal with the deletion UI in a later release. +V8 | KJS # DEFER : LayoutTests/editing/deleting/deletionUI-single-instance.html = FAIL + +// Bug 871718: This test loads data: URLs into frames and sets queries on then. +// This is totally broken. This layout test should be rewitten so that the +// subframes are not data URLs (probably we want files in the resources dir.). +V8 | KJS # DEFER : LayoutTests/fast/encoding/char-encoding.html = TIMEOUT + +// Bug 1130795: since we don't have Aqua-themed controls, don't ignore the +// box-shadow properties of controls that request Aqua theming. But since Aqua +// controls are rare on the web, defer this fix. +V8 | KJS # DEFER : LayoutTests/fast/forms/box-shadow-override.html = FAIL + +// These tests are not valid: the so-called expected results are not known to +// be correct. See bug 849072. +// TODO(ojan): They are *our* tests. +// It seems silly to skip our own tests when we can change/delete them. +// I'm marking them as deferred for now, but we should do something with them. +V8 | KJS # DEFER | SKIP : chrome/http/mime = PASS + +// Bug: 916857: These tests fail because of <audio> and <video>? +// Removed from the skip-list since they consistently fail quickly. +V8 | KJS # DEFER : LayoutTests/http/tests/media/video-play-stall.html = FAIL +V8 | KJS # DEFER : LayoutTests/http/tests/media/video-play-stall-seek.html = FAIL +V8 | KJS # DEFER : LayoutTests/http/tests/media/video-seekable-stall.html = FAIL +V8 | KJS # DEFER : LayoutTests/http/tests/media/remove-while-loading.html = FAIL + +// We don't support the storage APIs. Some of the them hang. +V8 | KJS # DEFER | SKIP : LayoutTests/storage = PASS + +// Fails due to storage APIs not implemented. See bug 1124568. Might be worth +// re-baselining temporarily so the rest of the conditions are still tested. +V8 | KJS # DEFER : LayoutTests/fast/dom/Window/window-function-name-getter-precedence.html = FAIL + +// These tests all time out, which makes running the suite take too long if +// they're included. See bug 916857 to investigate <audio> and <video>. +V8 | KJS # DEFER | SKIP : LayoutTests/media = TIMEOUT + +// Bug 850287: Need better Thai line-breaking. Not a high priority, because +// it's acceptable as it is. +V8 | KJS # DEFER : LayoutTests/fast/text/international/thai-line-breaks.html = FAIL + +// Bug 941049: Function arguments object is copied for each access. +V8 # DEFER : LayoutTests/fast/js/kde/function_arguments.html = FAIL + +// Bug 1155674: Test sometimes fails on V8 in debug mode, because it's very +// timing dependent. We should consider rewriting the test case to give us +// more wriggle room timing wise (especially for debug builds). Fixing the +// issue now is not going to improve product quality for beta. +V8 # DEFER : LayoutTests/fast/forms/search-event-delay.html = PASS | FAIL + + +// The following tests (up to the ---- line below) need to add new methods to +// layoutTestController in test_shell since WebKit add those methods to its +// layoutTestController in DumpRenderTree tools. + +// BUG 973468: Need a setAuthorAndUserStylesEnabled method in +// layoutTestController. Now we have preference to enable/disable user +// styles(not work now), we still need to add a preference to enable/disable +// styles of both author and user. +// Deferring, we don't support user-controlled UA stylesheets (in beta) +// Actually, gonna SKIP because it causes an additional error message in: +// LayoutTests/fast/css/display-none-inline-style-change-crash.html somehow +// the message is dumped after the #EOF, which causes an additional +// error in the header of the following test. +V8 | KJS # SKIP | DEFER : LayoutTests/fast/css/disabled-author-styles.html = FAIL + +// ------------------------------------------------------------------------- // + +// Bug: 924387, 1058654 +// Broken until we fix our port to support remote TTF fonts and SVG Fonts +// GDI @font-face support has been implemented upstream, but we don't plan +// to fork to add support for @font-face for Beta. +// upstream: http://trac.webkit.org/projects/webkit/changeset/31507 +V8 | KJS # DEFER : LayoutTests/fast/css/font-face-multiple-remote-sources.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/css/font-face-remote.html = FAIL +V8 | KJS # DEFER : LayoutTests/svg/custom/font-face-cascade-order.svg = FAIL + +// Bug: 1007391 +// These hit a not-implemented code-path in @font-face code +// Fixing this should not be required for beta. +V8 | KJS # DEFER : LayoutTests/fast/css/font-face-implicit-local-font.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/css/font-face-unicode-range.html = FAIL + +// Bug: 1110028 +// The v8 bindings allow shadowing of all properties on the global object. If you use +// 'var prop = value' you will get a new variable named prop that shadows builtin properties +// on the global object. This behavior is consistent and I'm reluctant to make the +// massive change that would be needed to implement the inconsistent handling of this +// that KJS has (some properties can be shadowed and others can't). This should have +// low priority. +// We currently match IE, the plan is to convince KJS to change post-beta. DEFER (eseidel, 4/25) +V8 # DEFER : LayoutTests/fast/dom/Window/window-property-shadowing-name.html = TIMEOUT +V8 # DEFER : LayoutTests/fast/js/var-declarations-shadowing.html = FAIL + +// This test expects weird behavior of __defineGetter__ on the +// window object. It expects variables introduced with 'var x = value' +// to behave differently from variables introduced with 'y = value'. +// This just seems wrong and should have very low priority. +// Agreed, not required for Beta, we can debate this with WebKit post Beta, (eseidel, 4/25) +V8 # DEFER : LayoutTests/fast/dom/getter-on-window-object2.html = FAIL + +// Bug: 1042653 +// We don't support WebKit-Editing-Delete-Button +// We've chosen not to for Beta. DEFER +V8 | KJS # DEFER : LayoutTests/editing/deleting/5408255.html = FAIL + +// Bug: 845337 +// Missing two callbacks: +// -EDITING DELEGATE: shouldBeginEditingInDOMRange:range ... +// -EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification +V8 | KJS # LayoutTests/editing/pasteboard/testcase-9507.html = FAIL + +// Bug: 1042838 +// User stylesheets not currently supported by chrome. +// Webkit supports them by doing filesystem operations directly. +// This is disallowed in a sandboxed renderer. The test fails in test_shell.exe because the +// necessary filesystem stubs are notImplemented(), and would need to be proxied through the browser +V8 | KJS # DEFER : LayoutTests/http/tests/security/local-user-CSS-from-remote.html = FAIL + +// Extra space at end of test results. Since this is a crash test, a +// FAIL here is just as good as running the test normally +// Not sure why it passes (frequently on V8, rarely on KJS). See bug 1126050. +// FAIL results from an extra newline at the top of the results +// the checked in results do not include the PASS text +// fixing this test file to be reliable does not help Beta. DEFERing +V8 | KJS # DEFER : LayoutTests/http/tests/navigation/changing-frame-hierarchy-in-onload.html = PASS | FAIL + +// ----------------------------------------------------------------- +// SVG TESTS +// ----------------------------------------------------------------- + +// BUG: 992321 +// SVG layout tests have just been enabled. The failures are listed +// below, and need to be investigated, categorized, and (one hopes) +// fixed. + +// Keep score here! +// +// P: Passes +// S: Pass simplified diff (probable nonbugs) +// F: Failures (in simplified as well as full text) +// X: Crashes/hangs +// T: Total # of tests +// +// (note 5-22-08: there should probably be a column for image diff +// failures as well. Some of the font tests seem to be flaky and +// return inconsistent image results.) +// +// P S F X T +// 1-18-08 473 87 125 3 688 +// 1-29-08 492 86 106 4 688 +// 2-05-08 513 97 83 1 694 +// 4-17-08 520 88 85 0 :) 693 +// 4-21-08 526 87 80 0 693 +// 4-22-08 540 138 29 0 707 +// 5-05-08 551 138 15 0 707 +// 5-09-08 554 139 16 0 712 +// 5-16-08 587 111 14 0 712 +// 5-22-08 641 58 7 0 712 +// + +// The following tests fail because SVG animation is not yet implemented +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-06-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-07-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-08-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-28-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-30-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-33-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-36-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-37-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-41-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-78-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-80-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-81-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-82-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/animate-elem-83-t.svg = FAIL + +// This test fails because SVG filters are not implemented +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/filters-example-01-b.svg = FAIL + +// These test fail full text diff (but not simplified diff) most likely due +// to differing implementations of SVG fonts. They may or may not represent real +// bugs which need fixin' +V8 | KJS # DEFER : LayoutTests/svg/batik/text/smallFonts.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/textBiDi.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/textGlyphOrientationHorizontal.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/textOnPath.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/textOnPath2.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/textOnPath3.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/textOnPathSpaces.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/verticalText.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/batik/text/verticalTextOnPath.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/custom/path-textPath-simulation.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/text/text-fonts-01-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/text/text-intro-05-t.svg = FAIL +V8 | KJS # DEFER : LayoutTests/svg/text/text-path-01-b.svg = FAIL + +// This test is failing because Apple's results appear to be bogus. Will let them know. +V8 | KJS # DEFER : LayoutTests/svg/custom/gradient-stop-style-change.svg = FAIL + +// This test fails because of an oddity in stroke width calculation. A +// line is stroked horizontally from Y=100 with stroke width 100, we consider +// it to be occupying the scan lines from Y=50 to Y=149 inclusive, which +// is a total of 100 lines. Apple expects it to be occupying Y=150 as +// well. The specification isn't very specific on which behavior is correct, +// but I feel like ours makes more sense. +V8 | KJS # DEFER : LayoutTests/svg/custom/stroke-width-click.svg = FAIL + +// These tests all pass simplified diff, but fail full text diffs due to +// positions and/or widths that deviate from Apple's expectations by varying +// degrees. These are almost certainly nonbugs, but won't be rebased until +// we can say for sure. +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/extend-namespace-01-f.svg = FAIL // 12 numbers differ, by an absolute total of 73.93 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/fonts-glyph-02-t.svg = FAIL // 1 numbers differ, by an absolute total of 10.00 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/metadata-example-01-b.svg = FAIL // 11 numbers differ, by an absolute total of 1.08 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/paths-data-01-t.svg = FAIL // 10 numbers differ, by an absolute total of 15.39 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/paths-data-02-t.svg = FAIL // 20 numbers differ, by an absolute total of 327.17 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/paths-data-03-f.svg = FAIL // 14 numbers differ, by an absolute total of 58.82 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/paths-data-10-t.svg = FAIL // 21 numbers differ, by an absolute total of 23.96 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/paths-data-12-t.svg = FAIL // 8 numbers differ, by an absolute total of 119.28 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/paths-data-15-t.svg = FAIL // 6 numbers differ, by an absolute total of 21.34 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/struct-group-03-t.svg = FAIL // 8 numbers differ, by an absolute total of 3.48 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/text-fonts-01-t.svg = FAIL // 3 numbers differ, by an absolute total of 42.00 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/text-intro-05-t.svg = FAIL // 18 numbers differ, by an absolute total of 237.00 +V8 | KJS # DEFER : LayoutTests/svg/W3C-SVG-1.1/text-path-01-b.svg = FAIL // 16 numbers differ, by an absolute total of 67.92 +V8 | KJS # DEFER : LayoutTests/svg/custom/control-points-for-S-and-T.svg = FAIL // 6 numbers differ, by an absolute total of 31.36 +V8 | KJS # DEFER : LayoutTests/svg/custom/dasharrayOrigin.svg = FAIL // 2 numbers differ, by an absolute total of 0.20 +V8 | KJS # DEFER : LayoutTests/svg/custom/linking-a-03-b-all.svg = FAIL // 4 numbers differ, by an absolute total of 0.06 +V8 | KJS # DEFER : LayoutTests/svg/custom/linking-a-03-b-viewBox-transform.svg = FAIL // 4 numbers differ, by an absolute total of 0.06 +V8 | KJS # DEFER : LayoutTests/svg/custom/linking-a-03-b-viewBox.svg = FAIL // 2 numbers differ, by an absolute total of 0.04 +V8 | KJS # DEFER : LayoutTests/svg/custom/marker-changes.svg = FAIL // 6 numbers differ, by an absolute total of 5.00 +V8 | KJS # DEFER : LayoutTests/svg/custom/use-css-events.svg = FAIL // 5 numbers differ, by an absolute total of 27.16 +V8 | KJS # DEFER : LayoutTests/svg/custom/use-on-symbol-inside-pattern.svg = FAIL // 11 numbers differ, by an absolute total of 4.69 +V8 | KJS # DEFER : LayoutTests/svg/hixie/perf/001.xml = FAIL // 274 numbers differ, by an absolute total of 157.52 +V8 | KJS # DEFER : LayoutTests/svg/hixie/perf/002.xml = FAIL // 274 numbers differ, by an absolute total of 157.52 +V8 | KJS # DEFER : LayoutTests/svg/hixie/perf/007.xml = FAIL // 745 numbers differ, by an absolute total of 37.97 +V8 | KJS # DEFER : LayoutTests/svg/hixie/shapes/path/001.xml = FAIL // 6 numbers differ, by an absolute total of 100.56 +V8 | KJS # DEFER : LayoutTests/svg/hixie/text/003.html = FAIL // 1 numbers differ, by an absolute total of 1.00 +V8 | KJS # DEFER : LayoutTests/svg/hixie/text/003a.xml = FAIL // 1 numbers differ, by an absolute total of 1.00 +V8 | KJS # DEFER : LayoutTests/svg/hixie/viewbox/preserveAspectRatio/001.xml = FAIL // 2 numbers differ, by an absolute total of 18.00 +V8 | KJS # DEFER : LayoutTests/svg/hixie/viewbox/preserveAspectRatio/002.xml = FAIL // 3 numbers differ, by an absolute total of 3.00 + +// This is an interesting one. +// The test has an error which causes it to output a message to the console. +// Apple's expected results include this error. Actually, Apple's expected +// results include the error twice, because (apparently unique among console +// debugging messages) it's generated during rendering. The problem is that +// we end up invoking the renderer twice when we're doing pixel tests, once +// otherwise, so we get different results depending on whether pixel tests are +// enabled. (Neither matches Apple's exactly... when we have pixel tests on, +// the duplicate error message ends up at the end of the file, while Apple's +// results have them both at the top.) But this test doesn't represent a real +// failure in any way and nothing needs to be done to fix it. I'm leaving it +// in tests_fixable in case we change the test shell's webview delegate to +// ignore console messages while doing a pixel dump +V8 | KJS # DEFER : LayoutTests/svg/custom/clip-path-referencing-use2.svg = FAIL + +// Bug 1107191. These flakily fail image diffs. Punting on SVG for now. +// Note that the "expected" images checked in are not necessarily correct. +V8 # DEFER : LayoutTests/svg/W3C-SVG-1.1/text-text-03-b.svg = PASS | FAIL +V8 # DEFER : LayoutTests/svg/batik/text/textDecoration2.svg = PASS | FAIL +V8 # DEFER : LayoutTests/svg/batik/text/textFeatures.svg = PASS | FAIL +V8 # DEFER : LayoutTests/svg/batik/text/textProperties.svg = PASS | FAIL +V8 # DEFER : LayoutTests/svg/batik/text/textStyles.svg = PASS | FAIL +V8 # DEFER : LayoutTests/svg/text/text-text-03-b.svg = PASS | FAIL +// These consistently fail with image diffs. +V8 # DEFER : LayoutTests/svg/text/text-deco-01-b.svg = FAIL +V8 # DEFER : LayoutTests/svg/W3C-SVG-1.1/text-deco-01-b.svg = FAIL + +// +// ----------------------------------------------------------------- +// End of SVG tests +// ----------------------------------------------------------------- + +// Bug: 1026885 +// Following tests are failing because Chrome does not allow file url +// to access non-file urls. +V8 | KJS # DEFER : LayoutTests/editing/selection/4960137.html = FAIL +V8 | KJS # DEFER : LayoutTests/editing/selection/cleared-by-relayout.html = FAIL +V8 | KJS # DEFER : LayoutTests/editing/selection/inactive-selection.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/dom/Element/offsetLeft-offsetTop-body-quirk.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/dom/HTMLObjectElement/object-as-frame.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/dom/clientWidthAfterDocumentIsRemoved.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/dom/wrapper-classes.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/frames/frame-src-attribute.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/leaks/002.html = FAIL +V8 | KJS # DEFER : LayoutTests/fast/dom/gc-7.html = TIMEOUT +V8 | KJS # DEFER : LayoutTests/fast/frames/frame-set-same-location.html = TIMEOUT +V8 | KJS # DEFER : LayoutTests/fast/frames/frame-set-same-src.html = TIMEOUT +V8 | KJS # DEFER : LayoutTests/fast/frames/hover-timer-crash.html = TIMEOUT + +// Bug: 1045048 +// We haven't implemented GCController in test shell +// Not critical for Beta. We have window.gc() for other tests. +V8 | KJS # DEFER : LayoutTests/fast/js/garbage-collect-after-string-appends.html = FAIL + +// Bug: 1115062 +// These fail the pixel tests in debug mode because they have +// unpainted space (filled red in Debug but not in Release). +// These have been filed upstream, and should be deferred from Beta +// https://bugs.webkit.org/show_bug.cgi?id=8423 +V8 # DEFER : LayoutTests/tables/mozilla_expected_failures/bugs/bug178855.xml = PASS | FAIL +V8 # DEFER : LayoutTests/fast/flexbox/flex-hang.html = PASS | FAIL + +// We expect this to fail because we can't force a GC on KJS. +// Not sure why it crashes. +// Once it stops crashing, move back to ignore list. +KJS # chrome/plugins/refcount-leaks.html = FAIL | CRASH + +// Bug: 1112288 +// This test fails in KJS release build. +// Since this failure is KJS only, we don't care for Beta. +KJS # DEFER : LayoutTests/fast/js/kde/Number.html = FAIL + +// Bug: 1010703 +// Fails only for KJS, so we don't care for Beta. +KJS # DEFER : chrome/fast/events/nested-window-event.html = FAIL + +// Fails only for KJS, so we don't care for Beta. +KJS # DEFER : chrome/fast/dom/xss-DENIED-javascript-variations.html = FAIL + +// Bug: 1093606 +// Crashes both Chrome and Safari. We don't care about KJS for beta. +KJS # DEFER : chrome/plugins/nested-plugin-objects.html = PASS | CRASH + +// Bug: 1155685 +// Flaky, but only on KJS. We don't care about KJS for beta. +KJS # DEFER : pending/fast/dom/DOMImplementation/singleton-modifications.html = FAIL | PASS + +// Bug: 1166260 +V8 # DEFER : LayoutTests/fast/canvas/gradient-empty-path.html = FAIL + +// Bug: 1166644 +// Fails on webkit windows as well. +V8 | KJS # DEFER : LayoutTests/fast/events/attempt-scroll-with-no-scrollbars.html = FAIL + +// Bug: 1170198 +// Intentionally failing to avoid XML External Entity exploit +// until https://bugs.webkit.org/show_bug.cgi?id=19199 is fixed. +V8 | KJS # DEFER : LayoutTests/fast/parser/external-entities-in-xslt.xml = FAIL +V8 | KJS # DEFER : LayoutTests/fast/xsl/dtd-in-source-document.xml = FAIL +V8 | KJS # DEFER : LayoutTests/fast/xsl/xslt-second-level-import.xml = FAIL + +// Bug 1226853: Fails on v8-latest after const changes (r123300). Test +// has been rebaselined (don't declare const x twice). +V8 # DEFER : LayoutTests/fast/js/const.html = FAIL + +// Bug 1237779 +// gc-2.html tests that a DOM tree out of document should be kept alive if +// one of nodes is reachable from JS side. V8 binding fails because it does +// not trace DOM objects not in a document. +// Also the way to trigger GC is not reliable, that's why sometimes it is +// passing and sometimes not. +V8 # DEFER : LayoutTests/fast/dom/gc-2.html = FAIL | PASS diff --git a/webkit/tools/layout_tests/test_lists/tests_ignored.txt b/webkit/tools/layout_tests/test_lists/tests_ignored.txt new file mode 100644 index 0000000..dc5de8e --- /dev/null +++ b/webkit/tools/layout_tests/test_lists/tests_ignored.txt @@ -0,0 +1,177 @@ +// These tests will be run (unless skipped), but do not expect ever to pass +// them. They use platform-specific conventions, or features we have decided +// never to support. + +// ----------------------------------------------------------------- +// SKIPPED TESTS +// ----------------------------------------------------------------- + +// XHTML tests. See bug 793944. These tests seem like they work, but +// only because the expected output expects to see JS errors. There is +// no point in running these tests, because they are giving us a false +// sense of testing that isn't really happening. Furthermore, since they +// appear to pass if we do try to run them, we can't even list them as +// permanently expected to fail. +V8 | KJS # SKIP : LayoutTests/fast/dom/xmlhttprequest-get.xhtml = PASS +V8 | KJS # SKIP : LayoutTests/fast/xpath/nsresolver-bad-object.xhtml = PASS +V8 | KJS # SKIP : LayoutTests/fast/xpath/nsresolver-exception.xhtml = PASS +V8 | KJS # SKIP : LayoutTests/fast/xpath/nsresolver-function.xhtml = PASS +V8 | KJS # SKIP : LayoutTests/fast/xpath/nsresolver-object.xhtml = PASS +V8 | KJS # SKIP : LayoutTests/dom/xhtml = PASS + +// Fails due to different window.close() rules. See bug 753420. We need +// to decide whether we ever expect to pass this. Now also timing out. +V8 | KJS # SKIP : LayoutTests/fast/dom/open-and-close-by-DOM.html = FAIL + +// Fails because we use MIME names for charset while webkit uses IANA names. +// Instead of this, we added the corresponding test in chrome with the +// MIME name (EUC-JP) in the expected result. +V8 | KJS # SKIP : LayoutTests/fast/encoding/hanarei-blog32-fc2-com.html = FAIL + +// Skip because of WebKit bug 18512. These bugs "poison" future tests, causing +// all SVG objects with fill="red" to be rendered in green. +V8 | KJS # SKIP : LayoutTests/svg/custom/fill-SVGPaint-interface.svg = PASS +V8 | KJS # SKIP : LayoutTests/svg/custom/getPresentationAttribute.svg = PASS + +// ----------------------------------------------------------------- +// FAILING TESTS +// ----------------------------------------------------------------- + +// Bug: 1137420 +// We don't intend to pass all of these cases, so this is an expected fail. +// Window resizing is not implemented in chrome. +V8 | KJS # LayoutTests/fast/dom/Window/window-resize.html = FAIL + +// Chrome uses different keyboard accelerators from those used by Safari, so +// these tests will always fail. +V8 | KJS # LayoutTests/editing/pasteboard/emacs-cntl-y-001.html = FAIL +V8 | KJS # LayoutTests/editing/pasteboard/emacs-ctrl-a-k-y.html = FAIL +V8 | KJS # LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001.html = FAIL +V8 | KJS # LayoutTests/editing/input/emacs-ctrl-o.html = FAIL + +// These tests check for very kjs-specific garbage collector behavior. Gc-8 +// tests behavior that makes no sense for us to implement. Gc-10 makes sense +// but would have to be implemented much differently to work in v8. +V8 | KJS # LayoutTests/fast/dom/gc-8.html = FAIL +V8 | KJS # LayoutTests/fast/dom/gc-10.html = FAIL + +// This fails because we're missing various useless apple-specific +// properties on the window object. +// This test also timeouts in Debug mode. See bug 1058654. +V8 | KJS # LayoutTests/fast/dom/Window/window-properties.html = FAIL | TIMEOUT + +// Safari specific test to ensure that JavaScript errors aren't logged when in +// private browsing mode. +V8 | KJS # LayoutTests/http/tests/security/cross-frame-access-private-browsing.html = FAIL + +// Chrome uses different keyboard accelerators from those used by Safari, so +// these tests will always fail. +// TODO(ericroman): can the following 2 tests be removed from this list, since they pass? +V8 | KJS # LayoutTests/fast/events/keydown-1.html = FAIL +V8 | KJS # LayoutTests/fast/events/option-tab.html = FAIL + +// Chrome does not support WebArchives (just like Safari for Windows). +// See bug 761653. +V8 | KJS # LayoutTests/webarchive = FAIL +V8 | KJS # LayoutTests/svg/webarchive = FAIL +V8 | KJS # LayoutTests/svg/custom/image-with-prefix-in-webarchive.svg = FAIL + +// Bug 932737 +V8 | KJS # LayoutTests/webarchive/loading/test-loading-archive.html = TIMEOUT + +// Mac-specific stuff +// Don't run the platform/mac* tests +V8 | KJS # LayoutTests/platform = FAIL | PASS + +// Ignored because we do not have OBJC bindings +V8 | KJS # LayoutTests/editing/pasteboard/paste-RTFD.html = FAIL +V8 | KJS # LayoutTests/editing/pasteboard/paste-TIFF.html = FAIL +V8 | KJS # LayoutTests/plugins/jsobjc-dom-wrappers.html = FAIL +V8 | KJS # LayoutTests/plugins/jsobjc-simple.html = FAIL +V8 | KJS # LayoutTests/plugins/root-object-premature-delete-crash.html = FAIL +V8 | KJS # LayoutTests/plugins/throw-on-dealloc.html = FAIL +V8 | KJS # LayoutTests/plugins/undefined-property-crash.html = FAIL + +// Uses __apple_runtime_object +V8 | KJS # LayoutTests/plugins/call-as-function-test.html = FAIL + +// Ignore test because it tries to load .pdf files in <img> tags. +V8 | KJS # LayoutTests/fast/images/pdf-as-image-landscape.html = FAIL +V8 | KJS # LayoutTests/fast/images/pdf-as-image.html = FAIL +V8 | KJS # LayoutTests/fast/replaced/pdf-as-image.html = FAIL + +// Uses Option-tab key to circle through form elements. Will not work on +// Windows. +V8 | KJS # LayoutTests/fast/events/frame-tab-focus.html = FAIL + +// Bug 853268: Chrome doesn't call the willCacheResponse callback (a method +// of ResourceHandleClient). That function is Mac-specific. +V8 | KJS # LayoutTests/http/tests/misc/willCacheResponse-delegate-callback.html = FAIL + +// Checks for very kjs-specific garbage collector +// behavior. Gc-9 is completely braindamaged; it tests that certain +// properties are reset by the garbage collector. It looks to pass recently. +V8 # LayoutTests/fast/dom/gc-9.html = PASS | FAIL + +// This test checks that ((new Error()).message is undefined, which is +// a direct contradiction of the javascript spec 15.11.4.3 which +// says that it must be a string. +V8 # LayoutTests/fast/js/kde/evil-n.html = FAIL + +// This test is broken. The regular expression used contains an error +// which kjs swallows and returns false, which is the expected result, +// but for which we issue a syntax error. +V8 # LayoutTests/fast/js/code-serialize-paren.html = FAIL + +// These tests check for a kjs-specific extension, that source file +// name and line numbers are available as properties on exception +// objects. We handle error positions differently. +V8 # LayoutTests/fast/js/exception-linenums-in-html-1.html = FAIL +V8 # LayoutTests/fast/js/exception-linenums-in-html-2.html = FAIL +V8 # LayoutTests/fast/js/exception-linenums.html = FAIL + +// These tests rely on specific details of decompilation of +// functions. V8 always returns the source code as written; there's +// no decompilation or pretty printing involved except for +// certain "native" functions where the V8 output does not include +// newline characters. This is working as intended and we don't care +// if the tests pass or fail. +V8 # LayoutTests/fast/js/function-names.html = FAIL | PASS + +// WebKit has moved/changed this test upstream +// https://bugs.webkit.org/show_bug.cgi?id=18681 +// We will pass the new one after we merge +KJS | V8 # LayoutTests/http/tests/incremental/slow-utf8-css.pl = FAIL + +// ----------------------------------------------------------------- +// CHROME REWRITTEN TESTS +// ----------------------------------------------------------------- + +// These tests have been rewritten, with the original being ignored, +// because they were written in ways which are not cross-browser. +// (e.g. they expect implementation-dependent strings in output) +V8 # LayoutTests/fast/js/date-proto-generic-invocation.html = FAIL +V8 # LayoutTests/fast/js/kde/function.html = FAIL +V8 # LayoutTests/fast/js/kde/inbuilt_function_tostring.html = FAIL + +// This test has been rewritten, with the original being ignored, +// because it depends on a specific enumeration order of properties +// on dom objects. +// +// The rewritten test chrome/fast/dom/domListEnumeration.html tests +// the exact same thing, but sorts the elements explicitly before +// comparing. +V8 # LayoutTests/fast/dom/domListEnumeration.html = FAIL + +// Bug 849085: we're taking a different approach on this test than +// Webkit does. +KJS | V8 # SKIP : LayoutTests/plugins/get-url-with-blank-target.html = FAIL + +// This test doesn't work on the bbot. Works locally. +KJS | V8 # SKIP : chrome/http/tests/plugins/get-file-url.html = FAIL | PASS | TIMEOUT + + +// These tests tests V8 bindings only, KJS needs to re-baseline the output. +// See Bug 1205552 +KJS # SKIP : chrome/fast/dom/set-document-body-no-crash.html = FAIL +KJS # SKIP : chrome/fast/dom/set-table-head-no-crash.html = FAIL diff --git a/webkit/tools/layout_tests/test_types/__init__.py b/webkit/tools/layout_tests/test_types/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/webkit/tools/layout_tests/test_types/__init__.py diff --git a/webkit/tools/layout_tests/test_types/image_diff.py b/webkit/tools/layout_tests/test_types/image_diff.py new file mode 100644 index 0000000..296a1d2 --- /dev/null +++ b/webkit/tools/layout_tests/test_types/image_diff.py @@ -0,0 +1,133 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Compares the image output of a test to the expected image output. + +Compares hashes for the generated and expected images. If the output doesn't +match, returns FailureImageHashMismatch and outputs both hashes into the layout +test results directory. +""" + +import errno +import os +import shutil + +from layout_package import path_utils +from layout_package import test_failures +from test_types import test_type_base + +class ImageDiff(test_type_base.TestTypeBase): + def _CopyOutputPNGs(self, filename, actual_png, expected_png): + """Copies result files into the output directory with appropriate names. + + Args: + filename: the test filename + actual_png: path to the actual result file + expected_png: path to the expected result file + """ + self._MakeOutputDirectory(filename) + actual_filename = self.OutputFilename(filename, "-actual-win.png") + expected_filename = self.OutputFilename(filename, "-expected.png") + + shutil.copyfile(actual_png, actual_filename) + try: + shutil.copyfile(expected_png, expected_filename) + except IOError, e: + # A missing expected PNG has already been recorded as an error. + if errno.ENOENT != e.errno: + raise + + def _SaveBaselineFiles(self, filename, dest_dir, png_path, checksum): + """Saves new baselines for the PNG and checksum. + + Args: + filename: test filename + dest_dir: outer directory into which the results should be saved. + png_path: path to the actual PNG result file + checksum: value of the actual checksum result + """ + png_file = open(png_path, "rb") + png_data = png_file.read() + png_file.close() + self._SaveBaselineData(filename, dest_dir, png_data, ".png") + self._SaveBaselineData(filename, dest_dir, checksum, ".checksum") + + def CompareOutput(self, filename, proc, output, test_args): + """Implementation of CompareOutput that checks the output image and + checksum against the expected files from the LayoutTest directory. + """ + failures = [] + + # If we didn't produce a hash file, this test must be text-only. + if test_args.hash is None: + return failures + + # If we're generating a new baseline, we pass. + if test_args.new_baseline: + self._SaveBaselineFiles(filename, test_args.new_baseline, + test_args.png_path, test_args.hash) + return failures + + # Compare hashes. + expected_hash_file = path_utils.ExpectedFilename(filename, + '.checksum', + self._custom_result_id) + + expected_png_file = path_utils.ExpectedFilename(filename, + '.png', + self._custom_result_id) + + try: + expected_hash = open(expected_hash_file, "r").read() + except IOError, e: + if errno.ENOENT != e.errno: + raise + expected_hash = '' + + if test_args.hash != expected_hash: + # TODO(pamg): If the hashes don't match, use the image_diff app to + # compare the actual images, and report a different error if those do + # match. + if expected_hash == '': + failures.append(test_failures.FailureMissingImageHash(self)) + else: + failures.append(test_failures.FailureImageHashMismatch(self)) + + # Also report a missing expected PNG file. + if not os.path.isfile(expected_png_file): + failures.append(test_failures.FailureMissingImage(self)) + + # If anything was wrong, write the output files. + if len(failures): + self.WriteOutputFiles(filename, '', '.checksum', test_args.hash, + expected_hash, diff=False) + self._CopyOutputPNGs(filename, test_args.png_path, + expected_png_file) + + return failures diff --git a/webkit/tools/layout_tests/test_types/simplified_text_diff.py b/webkit/tools/layout_tests/test_types/simplified_text_diff.py new file mode 100644 index 0000000..419a7c3 --- /dev/null +++ b/webkit/tools/layout_tests/test_types/simplified_text_diff.py @@ -0,0 +1,141 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Compares the text output to the expected text output while ignoring +positions and sizes of elements/text. + +If the output doesn't match, returns FailureSimplifiedTextMismatch and outputs +the diff files into the layout test results directory. +""" + +import difflib +import os.path +import re + +from layout_package import test_failures +from test_types import text_diff + +class SimplifiedTextDiff(text_diff.TestTextDiff): + def _SimplifyText(self, text): + """Removes position and size information from a render tree dump. This + also combines contiguous lines of text together so lines that wrap between + different words match. Returns the simplified text.""" + + # SVG render paths are a little complicated: we want to strip digits after + # a decimal point only for strings that begin with "RenderPath.*data". + def simplify_svg_path(match): + return match.group(1) + re.sub(r"([0-9]*)\.[0-9]{2}", "\\1", match.group(2)) + + # Regular expressions to remove or substitue text. + simplifications = ( + # Ignore TypeError and ReferenceError, V8 has different error text. + (re.compile(r"Message\tTypeError:.*"), 'Message\tTypeError:'), + (re.compile(r"Message\tReferenceError:.*"), 'Message\tReferenceError:'), + + # Ignore uncaught exceptions because V8s error messages are different. + (re.compile(r"CONSOLE MESSAGE: line \d+: .*"), 'CONSOLE MESSAGE: line'), + + # Remove position and size information from text. + (re.compile(r"at \(-?[0-9]+(\.[0-9]+)?,-?[0-9]+(\.[0-9]+)?\) *"), ''), + (re.compile(r"size -?[0-9]+(\.[0-9]+)?x-?[0-9]+(\.[0-9]+)? *"), ''), + (re.compile(r' RTL: "\."'), ': ""'), + (re.compile(r" (RTL|LTR)( override)?: "), ': '), + (re.compile(r"text run width -?[0-9]+: "), ''), + (re.compile(r"\([0-9]+px"), ''), + (re.compile(r"scrollHeight -?[0-9]+"), ''), + (re.compile(r"scrollWidth -?[0-9]+"), ''), + (re.compile(r"scrollX -?[0-9]+"), ''), + (re.compile(r"scrollY -?[0-9]+"), ''), + (re.compile(r"scrolled to [0-9]+,[0-9]+"), 'scrolled to'), + (re.compile(r"caret: position -?[0-9]+"), ''), + + # The file select widget has different text on Mac and Win. + (re.compile(r"Choose File"), "Browse..."), + + # Remove trailing spaces at the end of a line of text. + (re.compile(r' "\n'), '"\n'), + # Remove leading spaces at the beginning of a line of text. + (re.compile(r'(\s+") '), '\\1'), + # Remove empty lines (this only seems to happen with <textareas>) + (re.compile(r'^\s*""\n', re.M), ''), + # Merge text lines together. Lines ending in anything other than a + # hyphen get a space inserted when joined. + (re.compile(r'-"\s+"', re.M), '-'), + (re.compile(r'"\s+"', re.M), ' '), + + # Handle RTL "...Browse" text. The space gets inserted when text lines + # are merged together in the step above. + (re.compile(r"... Browse"), "Browse..."), + + # Some SVG tests inexplicably emit -0.00 rather than 0.00 in the expected results + (re.compile(r"-0\.00"), '0.00'), + + # Remove size information from SVG text + (re.compile(r"(chunk.*width )([0-9]+\.[0-9]{2})"), '\\1'), + + # Remove decimals from SVG paths + (re.compile(r"(RenderPath.*data)(.*)"), simplify_svg_path), + ) + + for regex, subst in simplifications: + text = re.sub(regex, subst, text) + + return text + + def CompareOutput(self, filename, proc, output, unused_test_args): + """Implementation of CompareOutput that removes most numbers before + computing the diff. + + This test does not save new baseline results. + + """ + failures = [] + + # Normalize text to diff + output = self.GetNormalizedOutputText(output) + expected = self.GetNormalizedExpectedText(filename) + + # Don't bother with the simplified text diff if we match before simplifying + # the text. + if output == expected: + return failures + + # Make the simplified text. + output = self._SimplifyText(output) + expected = self._SimplifyText(expected) + + if output != expected: + # Text doesn't match, write output files. + self.WriteOutputFiles(filename, "-simp", ".txt", output, expected) + + # Add failure to return list, unless it's a new test. + if expected != '': + failures.append(test_failures.FailureSimplifiedTextMismatch(self)) + + return failures diff --git a/webkit/tools/layout_tests/test_types/test_type_base.py b/webkit/tools/layout_tests/test_types/test_type_base.py new file mode 100644 index 0000000..8afc316 --- /dev/null +++ b/webkit/tools/layout_tests/test_types/test_type_base.py @@ -0,0 +1,189 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Defines the interface TestTypeBase which other test types inherit from. + +Also defines the TestArguments "struct" to pass them additional arguments. +""" + +import difflib +import os.path + + +import google.path_utils + +from layout_package import path_utils +from layout_package import platform_utils + +class TestArguments(object): + """Struct-like wrapper for additional arguments needed by specific tests.""" + # Outer directory in which to place new baseline results. + new_baseline = None + + # Whether to save new text baseline files (otherwise only save image + # results as a new baseline). + text_baseline = False + + # Path to the actual PNG file generated by pixel tests + png_path = None + + # Value of checksum generated by pixel tests. + hash = None + + +class TestTypeBase(object): + # Filename pieces when writing failures to the test results directory. + FILENAME_SUFFIX_ACTUAL = "-actual-win" + FILENAME_SUFFIX_EXPECTED = "-expected" + FILENAME_SUFFIX_DIFF = "-diff-win" + + def __init__(self, custom_result_id, root_output_dir): + """Initialize a TestTypeBase object. + + Args: + custom_result_id: the string (generally 'kjs' or 'v8') identifying the + custom expected results to be used + root_output_dir: The unix style path to the output dir. + """ + self._root_output_dir = root_output_dir + self._custom_result_id = custom_result_id + + def _MakeOutputDirectory(self, filename): + """Creates the output directory (if needed) for a given test filename.""" + output_filename = os.path.join(self._root_output_dir, + path_utils.RelativeTestFilename(filename)) + google.path_utils.MaybeMakeDirectory(os.path.split(output_filename)[0]) + + def _SaveBaselineData(self, filename, dest_dir, data, modifier): + """Saves a new baseline file. + + The file will be named simply "<test>-expected<modifier>", suitable for + use as the expected results in a later run. + + Args: + filename: the test filename + dest_dir: the outer directory into which the results should be saved. + The subdirectory corresponding to this test will be created if + necessary. + data: result to be saved as the new baseline + modifier: type of the result file, e.g. ".txt" or ".png" + """ + output_filename = os.path.join(dest_dir, + path_utils.RelativeTestFilename(filename)) + output_filename = (os.path.splitext(output_filename)[0] + + self.FILENAME_SUFFIX_EXPECTED + modifier) + google.path_utils.MaybeMakeDirectory(os.path.split(output_filename)[0]) + open(output_filename, "wb").write(data) + + def OutputFilename(self, filename, modifier): + """Returns a filename inside the output dir that contains modifier. + + For example, if filename is c:/.../fast/dom/foo.html and modifier is + "-expected.txt", the return value is + c:/cygwin/tmp/layout-test-results/fast/dom/foo-expected.txt + + Args: + filename: absolute filename to test file + modifier: a string to replace the extension of filename with + + Return: + The absolute windows path to the output filename + """ + output_filename = os.path.join(self._root_output_dir, + path_utils.RelativeTestFilename(filename)) + return os.path.splitext(output_filename)[0] + modifier + + def RelativeOutputFilename(self, filename, modifier): + """Returns a relative filename inside the output dir that contains + modifier. + + For example, if filename is fast\dom\foo.html and modifier is + "-expected.txt", the return value is fast\dom\foo-expected.txt + + Args: + filename: relative filename to test file + modifier: a string to replace the extension of filename with + + Return: + The relative windows path to the output filename + """ + return os.path.splitext(filename)[0] + modifier + + def CompareOutput(self, filename, proc, output, test_args): + """Method that compares the output from the test with the expected value. + + This is an abstract method to be implemented by all sub classes. + + Args: + filename: absolute filename to test file + proc: a reference to the test_shell process + output: a string containing the output of the test + test_args: a TestArguments object holding optional additional arguments + + Return: + a list of TestFailure objects, empty if the test passes + """ + raise NotImplemented + + def WriteOutputFiles(self, filename, test_type, file_type, output, expected, + diff=True): + """Writes the test output, the expected output and optionally the diff + between the two to files in the results directory. + + The full output filename of the actual, for example, will be + <filename><test_type>-actual-win<file_type> + For instance, + my_test-simp-actual-win.txt + + Args: + filename: The test filename + prefix: A string appended to the test filename, e.g. "-simp". May be "". + suffix: A string describing the test output file type, e.g. ".txt" + output: A string containing the test output + expected: A string containing the expected test output + diff: if True, write a file containing the diffs too. This should be + False for results that are not text. + """ + self._MakeOutputDirectory(filename) + actual_filename = self.OutputFilename(filename, + test_type + self.FILENAME_SUFFIX_ACTUAL + file_type) + expected_win_filename = self.OutputFilename(filename, + test_type + self.FILENAME_SUFFIX_EXPECTED + file_type) + open(actual_filename, "wb").write(output) + open(expected_win_filename, "wb").write(expected) + + if diff: + diff = difflib.unified_diff(expected.splitlines(True), + output.splitlines(True), + expected_win_filename, + actual_filename) + + diff_filename = self.OutputFilename(filename, + test_type + self.FILENAME_SUFFIX_DIFF + file_type) + open(diff_filename, "wb").write(''.join(diff)) diff --git a/webkit/tools/layout_tests/test_types/text_diff.py b/webkit/tools/layout_tests/test_types/text_diff.py new file mode 100644 index 0000000..b58fb6a --- /dev/null +++ b/webkit/tools/layout_tests/test_types/text_diff.py @@ -0,0 +1,95 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Compares the text output of a test to the expected text output. + +If the output doesn't match, returns FailureTextMismatch and outputs the diff +files into the layout test results directory. +""" + +import errno +import os.path + +from layout_package import path_utils +from layout_package import platform_utils +from layout_package import test_failures +from test_types import test_type_base + +class TestTextDiff(test_type_base.TestTypeBase): + def GetNormalizedOutputText(self, output): + # Some tests produce "\r\n" explicitly. Our system (Python/Cygwin) + # helpfully changes the "\n" to "\r\n", resulting in "\r\r\n". + norm = output.replace("\r\r\n", "\r\n").strip("\r\n").replace("\r\n", "\n") + return norm + "\n" + + def GetNormalizedExpectedText(self, filename): + """Given the filename of the test, read the expected output from a file + and normalize the text. Returns a string with the expected text, or '' + if the expected output file was not found.""" + # Read the platform-specific expected text, or the Mac default if no + # platform result exists. + expected_filename = path_utils.ExpectedFilename(filename, '.txt', + self._custom_result_id) + try: + expected = open(expected_filename).read() + except IOError, e: + if errno.ENOENT != e.errno: + raise + expected = '' + return expected + + # Normalize line endings + return expected.strip("\r\n") + "\n" + + def CompareOutput(self, filename, proc, output, test_args): + """Implementation of CompareOutput that checks the output text against the + expected text from the LayoutTest directory.""" + failures = [] + + # If we're generating a new baseline, we pass. + if test_args.text_baseline: + self._SaveBaselineData(filename, test_args.new_baseline, + output, ".txt") + return failures + + # Normalize text to diff + output = self.GetNormalizedOutputText(output) + expected = self.GetNormalizedExpectedText(filename) + + # Write output files for new tests, too. + if output != expected: + # Text doesn't match, write output files. + self.WriteOutputFiles(filename, "", ".txt", output, expected) + + if expected == '': + failures.append(test_failures.FailureMissingResult(self)) + else: + failures.append(test_failures.FailureTextMismatch(self)) + + return failures diff --git a/webkit/tools/layout_tests/testdata/README b/webkit/tools/layout_tests/testdata/README new file mode 100644 index 0000000..563e442 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/README @@ -0,0 +1,2 @@ +This directory contains fake data used by the unittests for +run_webkit_tests.py. diff --git a/webkit/tools/layout_tests/testdata/difftests/form-element-geometry-actual-win.txt b/webkit/tools/layout_tests/testdata/difftests/form-element-geometry-actual-win.txt new file mode 100644 index 0000000..2606bcd --- /dev/null +++ b/webkit/tools/layout_tests/testdata/difftests/form-element-geometry-actual-win.txt @@ -0,0 +1,369 @@ +layer at (0,0) size 783x732 + RenderView at (0,0) size 783x600 +layer at (0,0) size 783x732 + RenderBlock {HTML} at (0,0) size 783x732 + RenderBody {BODY} at (8,8) size 767x716 + RenderBlock {H1} at (0,0) size 767x37 + RenderText {#text} at (0,0) size 420x36 + text run at (0,0) width 420: "Form Element Geometry Tests" + RenderBlock {P} at (0,58) size 767x20 + RenderText {#text} at (0,0) size 514x19 + text run at (0,0) width 514: "These tests help us tune the widget classes in KWQ to have all the right fudge factors." + RenderBlock {H2} at (0,97) size 767x27 + RenderText {#text} at (0,0) size 165x26 + text run at (0,0) width 165: "Bounding Boxes" + RenderTable {TABLE} at (0,143) size 214x47 + RenderTableSection {TBODY} at (0,0) size 214x47 + RenderTableRow {TR} at (0,2) size 214x43 + RenderTableCell {TD} at (2,2) size 84x43 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 82x41 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 78x27 + RenderButton {INPUT} at (2,2) size 78x37 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 62x28 + RenderText at (0,0) size 62x27 + text run at (0,0) width 62: "button" + RenderTableCell {TD} at (88,4) size 82x38 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 80x36 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 76x27 + RenderMenuList {SELECT} at (2,2) size 76x32 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 53x28 + RenderText at (0,0) size 53x27 + text run at (0,0) width 53: "menu" + RenderBlock (anonymous) at (1,37) size 80x0 + RenderInline {FONT} at (0,0) size 0x0 + RenderTableCell {TD} at (172,14) size 19x19 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 17x17 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 13x13 + RenderBlock {INPUT} at (2,2) size 13x13 + RenderBlock (anonymous) at (1,18) size 17x0 + RenderInline {FONT} at (0,0) size 0x0 + RenderTableCell {TD} at (193,14) size 19x19 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 17x17 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 13x13 + RenderBlock {INPUT} at (2,2) size 13x13 + RenderBlock (anonymous) at (1,18) size 17x0 + RenderInline {FONT} at (0,0) size 0x0 + RenderTable {TABLE} at (0,190) size 169x39 + RenderTableSection {TBODY} at (0,0) size 169x39 + RenderTableRow {TR} at (0,2) size 169x35 + RenderTableCell {TD} at (2,2) size 60x35 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 58x33 [border: (2px solid #0000FF)] + RenderButton {INPUT} at (2,2) size 54x29 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 38x20 + RenderText at (0,0) size 38x19 + text run at (0,0) width 38: "button" + RenderTableCell {TD} at (64,4) size 61x30 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 59x28 [border: (2px solid #0000FF)] + RenderMenuList {SELECT} at (2,2) size 55x24 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 32x20 + RenderText at (0,0) size 32x19 + text run at (0,0) width 32: "menu" + RenderTableCell {TD} at (127,10) size 19x19 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 17x17 [border: (2px solid #0000FF)] + RenderBlock {INPUT} at (2,2) size 13x13 + RenderTableCell {TD} at (148,10) size 19x19 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 17x17 [border: (2px solid #0000FF)] + RenderBlock {INPUT} at (2,2) size 13x13 + RenderTable {TABLE} at (0,229) size 147x31 + RenderTableSection {TBODY} at (0,0) size 147x31 + RenderTableRow {TR} at (0,2) size 147x27 + RenderTableCell {TD} at (2,2) size 48x27 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 46x25 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 42x12 + RenderButton {INPUT} at (2,2) size 42x21 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 26x12 + RenderText at (0,0) size 26x12 + text run at (0,0) width 26: "button" + RenderTableCell {TD} at (52,4) size 51x22 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 49x20 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 45x12 + RenderMenuList {SELECT} at (2,2) size 45x16 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 22x12 + RenderText at (0,0) size 22x12 + text run at (0,0) width 22: "menu" + RenderTableCell {TD} at (105,6) size 19x19 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 17x17 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 13x10 + RenderBlock {INPUT} at (2,2) size 13x13 + RenderTableCell {TD} at (126,6) size 19x19 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 17x17 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 13x10 + RenderBlock {INPUT} at (2,2) size 13x13 + RenderTable {TABLE} at (0,260) size 622x91 + RenderTableSection {TBODY} at (0,0) size 622x91 + RenderTableRow {TR} at (0,2) size 622x87 + RenderTableCell {TD} at (2,2) size 94x32 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 92x30 [border: (2px solid #0000FF)] + RenderTextControl {INPUT} at (2,2) size 88x26 [bgcolor=#FFFFFF] [border: (2px inset #000000)] + RenderTableCell {TD} at (98,2) size 45x87 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 43x85 [border: (2px solid #0000FF)] + RenderListBox {SELECT} at (2,2) size 39x81 [bgcolor=#FFFFFF] [border: (1px inset #808080)] + RenderTableCell {TD} at (145,2) size 278x35 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 276x33 [border: (2px solid #0000FF)] + RenderFileUploadControl {INPUT} at (2,2) size 272x29 + RenderButton {INPUT} at (0,0) size 75x29 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (425,2) size 195x52 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 193x50 [border: (2px solid #0000FF)] + RenderTextControl {TEXTAREA} at (4,4) size 185x42 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderBlock {H2} at (0,370) size 767x27 + RenderText {#text} at (0,0) size 199x26 + text run at (0,0) width 199: "Baseline Alignment" + RenderBlock {DIV} at (0,416) size 767x41 + RenderInline {FONT} at (0,0) size 269x27 + RenderText {#text} at (0,6) size 43x27 + text run at (0,6) width 43: "text " + RenderButton {INPUT} at (45,2) size 78x37 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 62x28 + RenderText at (0,0) size 62x27 + text run at (0,0) width 62: "button" + RenderText {#text} at (125,6) size 6x27 + text run at (125,6) width 6: " " + RenderMenuList {SELECT} at (133,4) size 76x32 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 53x28 + RenderText at (0,0) size 53x27 + text run at (0,0) width 53: "menu" + RenderText {#text} at (211,6) size 6x27 + text run at (211,6) width 6: " " + RenderBlock {INPUT} at (222,14) size 13x13 + RenderText {#text} at (240,6) size 6x27 + text run at (240,6) width 6: " " + RenderBlock {INPUT} at (251,14) size 13x13 + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,457) size 767x33 + RenderText {#text} at (0,6) size 26x19 + text run at (0,6) width 26: "text " + RenderButton {INPUT} at (28,2) size 54x29 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 38x20 + RenderText at (0,0) size 38x19 + text run at (0,0) width 38: "button" + RenderText {#text} at (84,6) size 4x19 + text run at (84,6) width 4: " " + RenderMenuList {SELECT} at (90,4) size 55x24 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 32x20 + RenderText at (0,0) size 32x19 + text run at (0,0) width 32: "menu" + RenderText {#text} at (147,6) size 4x19 + text run at (147,6) width 4: " " + RenderBlock {INPUT} at (155,8) size 13x13 + RenderText {#text} at (172,6) size 4x19 + text run at (172,6) width 4: " " + RenderBlock {INPUT} at (180,8) size 13x13 + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,490) size 767x23 + RenderInline {FONT} at (0,0) size 148x12 + RenderText {#text} at (0,6) size 18x12 + text run at (0,6) width 18: "text " + RenderButton {INPUT} at (18,2) size 42x21 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 26x12 + RenderText at (0,0) size 26x12 + text run at (0,0) width 26: "button" + RenderText {#text} at (60,6) size 3x12 + text run at (60,6) width 3: " " + RenderMenuList {SELECT} at (63,4) size 45x16 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 22x12 + RenderText at (0,0) size 22x12 + text run at (0,0) width 22: "menu" + RenderText {#text} at (108,6) size 3x12 + text run at (108,6) width 3: " " + RenderBlock {INPUT} at (113,3) size 13x13 + RenderText {#text} at (128,6) size 3x12 + text run at (128,6) width 3: " " + RenderBlock {INPUT} at (133,3) size 13x13 + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,513) size 767x58 + RenderText {#text} at (0,31) size 26x19 + text run at (0,31) width 26: "text " + RenderTextControl {INPUT} at (28,28) size 88x26 [bgcolor=#FFFFFF] [border: (2px inset #000000)] + RenderText {#text} at (118,31) size 4x19 + text run at (118,31) width 4: " " + RenderFileUploadControl {INPUT} at (124,27) size 272x29 + RenderButton {INPUT} at (0,0) size 75x29 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,4) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderText {#text} at (398,31) size 4x19 + text run at (398,31) width 4: " " + RenderTextControl {TEXTAREA} at (404,2) size 185x42 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderText {#text} at (0,0) size 0x0 + RenderBlock {H2} at (0,590) size 767x27 + RenderText {#text} at (0,0) size 195x26 + text run at (0,0) width 195: "Pop-up Menu Sizes" + RenderBlock {DIV} at (0,636) size 767x36 + RenderInline {FONT} at (0,0) size 194x27 + RenderText {#text} at (0,0) size 0x0 + RenderMenuList {SELECT} at (2,2) size 23x32 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 0x28 + RenderBR at (0,0) size 0x27 [bgcolor=#FFFFFF] + RenderText {#text} at (27,4) size 6x27 + text run at (27,4) width 6: " " + RenderMenuList {SELECT} at (35,2) size 28x32 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 5x28 + RenderText at (0,0) size 5x27 + text run at (0,0) width 5: "|" + RenderText {#text} at (65,4) size 6x27 + text run at (65,4) width 6: " " + RenderMenuList {SELECT} at (73,2) size 119x32 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 96x28 + RenderText at (0,0) size 96x27 + text run at (0,0) width 96: "xxxxxxxx" + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,672) size 767x28 + RenderMenuList {SELECT} at (2,2) size 23x24 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 0x20 + RenderBR at (0,0) size 0x19 [bgcolor=#FFFFFF] + RenderText {#text} at (27,4) size 4x19 + text run at (27,4) width 4: " " + RenderMenuList {SELECT} at (33,2) size 26x24 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 3x20 + RenderText at (0,0) size 3x19 + text run at (0,0) width 3: "|" + RenderText {#text} at (61,4) size 4x19 + text run at (61,4) width 4: " " + RenderMenuList {SELECT} at (67,2) size 79x24 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 56x20 + RenderText at (0,0) size 56x19 + text run at (0,0) width 56: "xxxxxxxx" + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,700) size 767x16 + RenderInline {FONT} at (0,0) size 118x12 + RenderText {#text} at (0,0) size 0x0 + RenderMenuList {SELECT} at (0,0) size 23x16 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 0x12 + RenderBR at (0,0) size 0x12 [bgcolor=#FFFFFF] + RenderText {#text} at (23,2) size 3x12 + text run at (23,2) width 3: " " + RenderMenuList {SELECT} at (26,0) size 26x16 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 3x12 + RenderText at (0,0) size 3x12 + text run at (0,0) width 3: "|" + RenderText {#text} at (52,2) size 3x12 + text run at (52,2) width 3: " " + RenderMenuList {SELECT} at (55,0) size 63x16 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (3,2) size 40x12 + RenderText at (0,0) size 40x12 + text run at (0,0) width 40: "xxxxxxxx" + RenderText {#text} at (0,0) size 0x0 +layer at (16,276) size 82x20 + RenderBlock {DIV} at (3,3) size 82x20 + RenderText {#text} at (1,0) size 51x19 + text run at (1,0) width 51: "text field" +layer at (439,276) size 183x40 + RenderBlock {DIV} at (1,1) size 183x40 + RenderText {#text} at (3,0) size 48x19 + text run at (3,0) width 48: "textarea" +layer at (39,552) size 82x20 + RenderBlock {DIV} at (3,3) size 82x20 + RenderText {#text} at (1,0) size 51x19 + text run at (1,0) width 51: "text field" +layer at (413,524) size 183x40 + RenderBlock {DIV} at (1,1) size 183x40 + RenderText {#text} at (3,0) size 48x19 + text run at (3,0) width 48: "textarea" +layer at (0,0) size 1220x583 + RenderView at (0,0) size 800x583 +layer at (0,0) size 1220x583 + RenderBlock {HTML} at (0,0) size 800x583 + RenderBody {BODY} at (8,8) size 784x567 + RenderTable {TABLE} at (0,0) size 1212x131 + RenderTableSection {TBODY} at (0,0) size 1212x131 + RenderTableRow {TR} at (0,2) size 1212x22 + RenderTableCell {TH} at (2,12) size 80x2 [r=0 c=0 rs=1 cs=1] + RenderTableCell {TH} at (84,12) size 280x2 [r=0 c=1 rs=1 cs=1] + RenderTableCell {TH} at (366,2) size 280x22 [r=0 c=2 rs=1 cs=1] + RenderText {#text} at (95,1) size 90x19 + text run at (95,1) width 90: "text-align:left" + RenderTableCell {TH} at (648,2) size 280x22 [r=0 c=3 rs=1 cs=1] + RenderText {#text} at (85,1) size 110x19 + text run at (85,1) width 110: "text-align:center" + RenderTableCell {TH} at (930,2) size 280x22 [r=0 c=4 rs=1 cs=1] + RenderText {#text} at (90,1) size 99x19 + text run at (90,1) width 99: "text-align:right" + RenderTableRow {TR} at (0,26) size 1212x33 + RenderTableCell {TH} at (2,41) size 80x2 [r=1 c=0 rs=1 cs=1] + RenderTableCell {TD} at (84,26) size 280x33 [border: (1px solid #000000)] [r=1 c=1 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (366,26) size 280x33 [border: (1px solid #000000)] [r=1 c=2 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (648,26) size 280x33 [border: (1px solid #000000)] [r=1 c=3 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (930,26) size 280x33 [border: (1px solid #000000)] [r=1 c=4 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableRow {TR} at (0,61) size 1212x33 + RenderTableCell {TH} at (2,66) size 80x22 [r=2 c=0 rs=1 cs=1] + RenderText {#text} at (1,1) size 78x19 + text run at (1,1) width 78: "direction:ltr" + RenderTableCell {TD} at (84,61) size 280x33 [border: (1px solid #000000)] [r=2 c=1 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (366,61) size 280x33 [border: (1px solid #000000)] [r=2 c=2 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (648,61) size 280x33 [border: (1px solid #000000)] [r=2 c=3 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableCell {TD} at (930,61) size 280x33 [border: (1px solid #000000)] [r=2 c=4 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (0,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 59: "Browse..." + RenderTableRow {TR} at (0,96) size 1212x33 + RenderTableCell {TH} at (2,101) size 80x22 [r=3 c=0 rs=1 cs=1] + RenderText {#text} at (1,1) size 78x19 + text run at (1,1) width 78: "direction:rtl" + RenderTableCell {TD} at (84,96) size 280x33 [border: (1px solid #000000)] [r=3 c=1 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (201,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 12 RTL: "..." + text run at (12,0) width 47: "Browse" + RenderTableCell {TD} at (366,96) size 280x33 [border: (1px solid #000000)] [r=3 c=2 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (201,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 12 RTL: "..." + text run at (12,0) width 47: "Browse" + RenderTableCell {TD} at (648,96) size 280x33 [border: (1px solid #000000)] [r=3 c=3 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (201,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 12 RTL: "..." + text run at (12,0) width 47: "Browse" + RenderTableCell {TD} at (930,96) size 280x33 [border: (1px solid #000000)] [r=3 c=4 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 272x25 + RenderButton {INPUT} at (201,0) size 71x25 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (6,2) size 59x20 + RenderText at (0,0) size 59x19 + text run at (0,0) width 12 RTL: "..." + text run at (12,0) width 47: "Browse" diff --git a/webkit/tools/layout_tests/testdata/difftests/form-element-geometry-expected.txt b/webkit/tools/layout_tests/testdata/difftests/form-element-geometry-expected.txt new file mode 100644 index 0000000..e421005 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/difftests/form-element-geometry-expected.txt @@ -0,0 +1,365 @@ +layer at (0,0) size 785x626 + RenderView at (0,0) size 785x600 +layer at (0,0) size 785x626 + RenderBlock {HTML} at (0,0) size 785x626 + RenderBody {BODY} at (8,8) size 769x610 + RenderBlock {H1} at (0,0) size 769x37 + RenderText {#text} at (0,0) size 422x37 + text run at (0,0) width 422: "Form Element Geometry Tests" + RenderBlock {P} at (0,58) size 769x18 + RenderText {#text} at (0,0) size 540x18 + text run at (0,0) width 540: "These tests help us tune the widget classes in KWQ to have all the right fudge factors." + RenderBlock {H2} at (0,95) size 769x28 + RenderText {#text} at (0,0) size 165x28 + text run at (0,0) width 165: "Bounding Boxes" + RenderTable {TABLE} at (0,142) size 172x28 + RenderTableSection {TBODY} at (0,0) size 172x28 + RenderTableRow {TR} at (0,2) size 172x24 + RenderTableCell {TD} at (2,2) size 58x24 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 56x22 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 52x18 + RenderButton {INPUT} at (2,2) size 52x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 36x13 + RenderText at (0,0) size 36x13 + text run at (0,0) width 36: "button" + RenderTableCell {TD} at (62,2) size 68x24 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 66x22 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 62x18 + RenderMenuList {SELECT} at (2,2) size 62x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 31x13 + RenderText at (0,0) size 31x13 + text run at (0,0) width 31: "menu" + RenderBlock (anonymous) at (1,23) size 66x0 + RenderInline {FONT} at (0,0) size 0x0 + RenderTableCell {TD} at (132,4) size 18x19 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 16x17 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 12x13 + RenderBlock {INPUT} at (2,2) size 12x13 + RenderBlock (anonymous) at (1,18) size 16x0 + RenderInline {FONT} at (0,0) size 0x0 + RenderTableCell {TD} at (152,5) size 18x18 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 16x16 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 12x12 + RenderBlock {INPUT} at (2,2) size 12x12 + RenderBlock (anonymous) at (1,17) size 16x0 + RenderInline {FONT} at (0,0) size 0x0 + RenderTable {TABLE} at (0,170) size 172x28 + RenderTableSection {TBODY} at (0,0) size 172x28 + RenderTableRow {TR} at (0,2) size 172x24 + RenderTableCell {TD} at (2,2) size 58x24 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 56x22 [border: (2px solid #0000FF)] + RenderButton {INPUT} at (2,2) size 52x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 36x13 + RenderText at (0,0) size 36x13 + text run at (0,0) width 36: "button" + RenderTableCell {TD} at (62,2) size 68x24 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 66x22 [border: (2px solid #0000FF)] + RenderMenuList {SELECT} at (2,2) size 62x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 31x13 + RenderText at (0,0) size 31x13 + text run at (0,0) width 31: "menu" + RenderTableCell {TD} at (132,4) size 18x19 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 16x17 [border: (2px solid #0000FF)] + RenderBlock {INPUT} at (2,2) size 12x13 + RenderTableCell {TD} at (152,5) size 18x18 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 16x16 [border: (2px solid #0000FF)] + RenderBlock {INPUT} at (2,2) size 12x12 + RenderTable {TABLE} at (0,198) size 172x28 + RenderTableSection {TBODY} at (0,0) size 172x28 + RenderTableRow {TR} at (0,2) size 172x24 + RenderTableCell {TD} at (2,2) size 58x24 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 56x22 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 52x13 + RenderButton {INPUT} at (2,2) size 52x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 36x13 + RenderText at (0,0) size 36x13 + text run at (0,0) width 36: "button" + RenderTableCell {TD} at (62,2) size 68x24 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 66x22 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 62x13 + RenderMenuList {SELECT} at (2,2) size 62x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 31x13 + RenderText at (0,0) size 31x13 + text run at (0,0) width 31: "menu" + RenderTableCell {TD} at (132,4) size 18x19 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 16x17 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 12x12 + RenderBlock {INPUT} at (2,2) size 12x13 + RenderTableCell {TD} at (152,5) size 18x18 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 16x16 [border: (2px solid #0000FF)] + RenderInline {FONT} at (0,0) size 12x12 + RenderBlock {INPUT} at (2,2) size 12x12 + RenderTable {TABLE} at (0,226) size 550x67 + RenderTableSection {TBODY} at (0,0) size 550x67 + RenderTableRow {TR} at (0,2) size 550x63 + RenderTableCell {TD} at (2,2) size 84x25 [r=0 c=0 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 82x23 [border: (2px solid #0000FF)] + RenderTextControl {INPUT} at (2,2) size 78x19 [bgcolor=#FFFFFF] [border: (2px inset #000000)] + RenderTableCell {TD} at (88,2) size 40x63 [r=0 c=1 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 38x61 [border: (2px solid #0000FF)] + RenderListBox {SELECT} at (2,2) size 34x57 [bgcolor=#FFFFFF] [border: (1px inset #808080)] + RenderTableCell {TD} at (130,2) size 243x24 [r=0 c=2 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 241x22 [border: (2px solid #0000FF)] + RenderFileUploadControl {INPUT} at (2,2) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (375,2) size 173x38 [r=0 c=3 rs=1 cs=1] + RenderBlock {DIV} at (1,1) size 171x36 [border: (2px solid #0000FF)] + RenderTextControl {TEXTAREA} at (4,4) size 163x28 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderBlock {H2} at (0,312) size 769x28 + RenderText {#text} at (0,0) size 200x28 + text run at (0,0) width 200: "Baseline Alignment" + RenderBlock {DIV} at (0,359) size 769x29 + RenderInline {FONT} at (0,0) size 219x28 + RenderText {#text} at (0,0) size 43x28 + text run at (0,0) width 43: "text " + RenderButton {INPUT} at (45,9) size 52x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 36x13 + RenderText at (0,0) size 36x13 + text run at (0,0) width 36: "button" + RenderText {#text} at (99,0) size 6x28 + text run at (99,0) width 6: " " + RenderMenuList {SELECT} at (107,9) size 62x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 31x13 + RenderText at (0,0) size 31x13 + text run at (0,0) width 31: "menu" + RenderText {#text} at (171,0) size 6x28 + text run at (171,0) width 6: " " + RenderBlock {INPUT} at (180,11) size 12x13 + RenderText {#text} at (195,0) size 6x28 + text run at (195,0) width 6: " " + RenderBlock {INPUT} at (204,12) size 12x12 + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,388) size 769x22 + RenderText {#text} at (0,1) size 27x18 + text run at (0,1) width 27: "text " + RenderButton {INPUT} at (29,2) size 52x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 36x13 + RenderText at (0,0) size 36x13 + text run at (0,0) width 36: "button" + RenderText {#text} at (83,1) size 4x18 + text run at (83,1) width 4: " " + RenderMenuList {SELECT} at (89,2) size 62x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 31x13 + RenderText at (0,0) size 31x13 + text run at (0,0) width 31: "menu" + RenderText {#text} at (153,1) size 4x18 + text run at (153,1) width 4: " " + RenderBlock {INPUT} at (160,4) size 12x13 + RenderText {#text} at (175,1) size 4x18 + text run at (175,1) width 4: " " + RenderBlock {INPUT} at (182,5) size 12x12 + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,410) size 769x22 + RenderInline {FONT} at (0,0) size 185x13 + RenderText {#text} at (0,5) size 18x13 + text run at (0,5) width 18: "text " + RenderButton {INPUT} at (20,2) size 52x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 36x13 + RenderText at (0,0) size 36x13 + text run at (0,0) width 36: "button" + RenderText {#text} at (74,5) size 3x13 + text run at (74,5) width 3: " " + RenderMenuList {SELECT} at (79,2) size 62x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 31x13 + RenderText at (0,0) size 31x13 + text run at (0,0) width 31: "menu" + RenderText {#text} at (143,5) size 3x13 + text run at (143,5) width 3: " " + RenderBlock {INPUT} at (149,4) size 12x13 + RenderText {#text} at (164,5) size 3x13 + text run at (164,5) width 3: " " + RenderBlock {INPUT} at (170,5) size 12x12 + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,432) size 769x39 + RenderText {#text} at (0,18) size 27x18 + text run at (0,18) width 27: "text " + RenderTextControl {INPUT} at (29,18) size 78x19 [bgcolor=#FFFFFF] [border: (2px inset #000000)] + RenderText {#text} at (109,18) size 4x18 + text run at (109,18) width 4: " " + RenderFileUploadControl {INPUT} at (115,19) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderText {#text} at (354,18) size 4x18 + text run at (354,18) width 4: " " + RenderTextControl {TEXTAREA} at (360,2) size 163x28 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderText {#text} at (0,0) size 0x0 + RenderBlock {H2} at (0,490) size 769x28 + RenderText {#text} at (0,0) size 197x28 + text run at (0,0) width 197: "Pop-up Menu Sizes" + RenderBlock {DIV} at (0,537) size 769x29 + RenderInline {FONT} at (0,0) size 181x28 + RenderText {#text} at (0,0) size 0x0 + RenderMenuList {SELECT} at (2,9) size 36x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 5x13 + RenderBR at (0,0) size 0x13 [bgcolor=#FFFFFF] + RenderText {#text} at (40,0) size 6x28 + text run at (40,0) width 6: " " + RenderMenuList {SELECT} at (48,9) size 36x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 5x13 + RenderText at (0,0) size 5x13 + text run at (0,0) width 5: "|" + RenderText {#text} at (86,0) size 6x28 + text run at (86,0) width 6: " " + RenderMenuList {SELECT} at (94,9) size 85x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 54x13 + RenderText at (0,0) size 54x13 + text run at (0,0) width 54: "xxxxxxxx" + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,566) size 769x22 + RenderMenuList {SELECT} at (2,2) size 36x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 5x13 + RenderBR at (0,0) size 0x13 [bgcolor=#FFFFFF] + RenderText {#text} at (40,1) size 4x18 + text run at (40,1) width 4: " " + RenderMenuList {SELECT} at (46,2) size 36x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 5x13 + RenderText at (0,0) size 5x13 + text run at (0,0) width 5: "|" + RenderText {#text} at (84,1) size 4x18 + text run at (84,1) width 4: " " + RenderMenuList {SELECT} at (90,2) size 85x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 54x13 + RenderText at (0,0) size 54x13 + text run at (0,0) width 54: "xxxxxxxx" + RenderText {#text} at (0,0) size 0x0 + RenderBlock {DIV} at (0,588) size 769x22 + RenderInline {FONT} at (0,0) size 175x13 + RenderText {#text} at (0,0) size 0x0 + RenderMenuList {SELECT} at (2,2) size 36x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 5x13 + RenderBR at (0,0) size 0x13 [bgcolor=#FFFFFF] + RenderText {#text} at (40,5) size 3x13 + text run at (40,5) width 3: " " + RenderMenuList {SELECT} at (45,2) size 36x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 5x13 + RenderText at (0,0) size 5x13 + text run at (0,0) width 5: "|" + RenderText {#text} at (83,5) size 3x13 + text run at (83,5) width 3: " " + RenderMenuList {SELECT} at (88,2) size 85x18 [bgcolor=#FFFFFF] + RenderBlock (anonymous) at (8,2) size 54x13 + RenderText at (0,0) size 54x13 + text run at (0,0) width 54: "xxxxxxxx" + RenderText {#text} at (0,0) size 0x0 +layer at (16,242) size 72x13 + RenderBlock {DIV} at (3,3) size 72x13 + RenderText {#text} at (1,0) size 49x13 + text run at (1,0) width 49: "text field" +layer at (389,242) size 161x26 + RenderBlock {DIV} at (1,1) size 161x26 + RenderText {#text} at (3,0) size 44x13 + text run at (3,0) width 44: "textarea" +layer at (40,461) size 72x13 + RenderBlock {DIV} at (3,3) size 72x13 + RenderText {#text} at (1,0) size 49x13 + text run at (1,0) width 49: "text field" +layer at (369,443) size 161x26 + RenderBlock {DIV} at (1,1) size 161x26 + RenderText {#text} at (3,0) size 44x13 + text run at (3,0) width 44: "textarea" +layer at (0,0) size 1083x585 + RenderView at (0,0) size 800x585 +layer at (0,0) size 1083x585 + RenderBlock {HTML} at (0,0) size 800x585 + RenderBody {BODY} at (8,8) size 784x569 + RenderTable {TABLE} at (0,0) size 1075x108 + RenderTableSection {TBODY} at (0,0) size 1075x108 + RenderTableRow {TR} at (0,2) size 1075x20 + RenderTableCell {TH} at (2,11) size 83x2 [r=0 c=0 rs=1 cs=1] + RenderTableCell {TH} at (87,11) size 245x2 [r=0 c=1 rs=1 cs=1] + RenderTableCell {TH} at (334,2) size 245x20 [r=0 c=2 rs=1 cs=1] + RenderText {#text} at (78,1) size 89x18 + text run at (78,1) width 89: "text-align:left" + RenderTableCell {TH} at (581,2) size 245x20 [r=0 c=3 rs=1 cs=1] + RenderText {#text} at (67,1) size 110x18 + text run at (67,1) width 110: "text-align:center" + RenderTableCell {TH} at (828,2) size 245x20 [r=0 c=4 rs=1 cs=1] + RenderText {#text} at (72,1) size 101x18 + text run at (72,1) width 101: "text-align:right" + RenderTableRow {TR} at (0,24) size 1075x26 + RenderTableCell {TH} at (2,36) size 83x2 [r=1 c=0 rs=1 cs=1] + RenderTableCell {TD} at (87,24) size 245x26 [border: (1px solid #000000)] [r=1 c=1 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (334,24) size 245x26 [border: (1px solid #000000)] [r=1 c=2 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (581,24) size 245x26 [border: (1px solid #000000)] [r=1 c=3 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (828,24) size 245x26 [border: (1px solid #000000)] [r=1 c=4 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableRow {TR} at (0,52) size 1075x26 + RenderTableCell {TH} at (2,55) size 83x20 [r=2 c=0 rs=1 cs=1] + RenderText {#text} at (1,1) size 81x18 + text run at (1,1) width 81: "direction:ltr" + RenderTableCell {TD} at (87,52) size 245x26 [border: (1px solid #000000)] [r=2 c=1 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (334,52) size 245x26 [border: (1px solid #000000)] [r=2 c=2 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (581,52) size 245x26 [border: (1px solid #000000)] [r=2 c=3 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (828,52) size 245x26 [border: (1px solid #000000)] [r=2 c=4 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (0,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableRow {TR} at (0,80) size 1075x26 + RenderTableCell {TH} at (2,83) size 83x20 [r=3 c=0 rs=1 cs=1] + RenderText {#text} at (1,1) size 81x18 + text run at (1,1) width 81: "direction:rtl" + RenderTableCell {TD} at (87,80) size 245x26 [border: (1px solid #000000)] [r=3 c=1 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (159,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (334,80) size 245x26 [border: (1px solid #000000)] [r=3 c=2 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (159,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (581,80) size 245x26 [border: (1px solid #000000)] [r=3 c=3 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (159,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" + RenderTableCell {TD} at (828,80) size 245x26 [border: (1px solid #000000)] [r=3 c=4 rs=1 cs=1] + RenderFileUploadControl {INPUT} at (4,4) size 237x18 + RenderButton {INPUT} at (159,0) size 78x18 [bgcolor=#C0C0C0] + RenderBlock (anonymous) at (8,2) size 62x13 + RenderText at (0,0) size 62x13 + text run at (0,0) width 62: "Choose File" diff --git a/webkit/tools/layout_tests/testdata/difftests/null-offset-parent-actual-win.txt b/webkit/tools/layout_tests/testdata/difftests/null-offset-parent-actual-win.txt new file mode 100644 index 0000000..d905358 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/difftests/null-offset-parent-actual-win.txt @@ -0,0 +1,82 @@ +layer at (0,0) size 800x600 + RenderView at (0,0) size 800x600 +layer at (0,0) size 800x600 + RenderBlock {HTML} at (0,0) size 800x600 + RenderBody {BODY} at (8,8) size 784x584 + RenderText {#text} at (0,0) size 763x39 + text run at (0,0) width 763: "This test verifies that JS access to offsetParent on an element that lacks one, such as the body, doesn't crash. If it didn't crash, it" + text run at (0,20) width 46: "passed." + RenderText {#text} at (0,0) size 0x0 +layer at (0,0) size 800x600 + RenderView at (0,0) size 800x600 +layer at (0,0) size 800x600 + RenderBlock {HTML} at (0,0) size 800x600 + RenderBody {BODY} at (8,8) size 784x584 + RenderBlock {P} at (0,0) size 784x20 + RenderText {#text} at (0,0) size 107x19 + text run at (0,0) width 107: "Tests: the bdo tag" + RenderBlock {P} at (0,36) size 784x20 + RenderText {#text} at (0,0) size 313x19 + text run at (0,0) width 313: "The bdo element overrides the default text direction." + RenderBlock {P} at (0,72) size 784x40 + RenderText {#text} at (0,0) size 757x39 + text run at (0,0) width 696: "If successful, the first sentence would be backward, and the second sentence regular. There should then be an extra " + text run at (696,0) width 61: "blank line," + text run at (0,20) width 622: "followed by a line reading only \"A,\" and finally, a sentence where only the word \"umbrella\" is backward." + RenderBlock {HR} at (0,128) size 784x2 [border: (1px inset #000000)] + RenderBlock (anonymous) at (0,138) size 784x120 + RenderBR {BR} at (0,0) size 0x19 + RenderInline {BDO} at (0,0) size 212x19 + RenderText {#text} at (0,20) size 212x19 + text run at (0,20) width 212 RTL override: "This sentence should be backward." + RenderText {#text} at (212,20) size 4x19 + text run at (212,20) width 4: " " + RenderBR {BR} at (216,35) size 0x0 + RenderInline {BDO} at (0,0) size 199x19 + RenderText {#text} at (0,40) size 199x19 + text run at (0,40) width 199 LTR override: "This sentence should be forward." + RenderText {#text} at (199,40) size 4x19 + text run at (199,40) width 4: " " + RenderBR {BR} at (203,55) size 0x0 + RenderInline {BDO} at (0,0) size 0x0 + RenderText {#text} at (0,0) size 0x0 + RenderBR {BR} at (0,60) size 0x19 + RenderInline {BDO} at (0,0) size 11x19 + RenderText {#text} at (0,80) size 11x19 + text run at (0,80) width 11 RTL override: "A" + RenderText {#text} at (11,80) size 4x19 + text run at (11,80) width 4: " " + RenderBR {BR} at (15,95) size 0x0 + RenderInline {BDO} at (0,0) size 271x19 + RenderText {#text} at (0,100) size 25x19 + text run at (0,100) width 25 LTR override: "My " + RenderInline {BDO} at (0,0) size 51x19 + RenderText {#text} at (25,100) size 51x19 + text run at (25,100) width 51 RTL override: "umbrella" + RenderText {#text} at (76,100) size 195x19 + text run at (76,100) width 195 LTR override: " sure would be useful in this rain." + RenderText {#text} at (0,0) size 0x0 + RenderText {#text} at (0,0) size 0x0 +layer at (0,0) size 800x600 + RenderView at (0,0) size 800x600 +layer at (0,0) size 800x600 + RenderBlock {HTML} at (0,0) size 800x600 + RenderBody {BODY} at (8,8) size 784x576 + RenderBlock {P} at (0,0) size 784x40 + RenderText {#text} at (0,0) size 242x19 + text run at (0,0) width 242: "This test checks for a regression against " + RenderInline {I} at (0,0) size 752x39 + RenderInline {A} at (0,0) size 350x19 [color=#0000EE] + RenderText {#text} at (242,0) size 350x19 + text run at (242,0) width 350: "http://bugzilla.opendarwin.org/show_bug.cgi?id=6214" + RenderText {#text} at (592,0) size 752x39 + text run at (592,0) width 160: " text-indent in RTL block" + text run at (0,20) width 111: "does the opposite" + RenderText {#text} at (111,20) size 4x19 + text run at (111,20) width 4: "." + RenderBlock {HR} at (0,56) size 784x2 [border: (1px inset #000000)] + RenderBlock {P} at (0,74) size 784x40 [bgcolor=#00FFFF] + RenderText {#text} at (5,0) size 779x39 + text run at (5,0) width 729: "The first line of this sententce should be indented 50 pixels to the left, but the rest of it should be flush with the normal right" + text run at (669,20) width 4 RTL: "." + text run at (673,20) width 111: "margin of the page" diff --git a/webkit/tools/layout_tests/testdata/difftests/null-offset-parent-expected.txt b/webkit/tools/layout_tests/testdata/difftests/null-offset-parent-expected.txt new file mode 100644 index 0000000..0afa5c7 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/difftests/null-offset-parent-expected.txt @@ -0,0 +1,82 @@ +layer at (0,0) size 800x600 + RenderView at (0,0) size 800x600 +layer at (0,0) size 800x600 + RenderBlock {HTML} at (0,0) size 800x600 + RenderBody {BODY} at (8,8) size 784x584 + RenderText {#text} at (0,0) size 782x36 + text run at (0,0) width 782: "This test verifies that JS access to offsetParent on an element that lacks one, such as the body, doesn't crash. If it didn't crash," + text run at (0,18) width 58: "it passed." + RenderText {#text} at (0,0) size 0x0 +layer at (0,0) size 800x600 + RenderView at (0,0) size 800x600 +layer at (0,0) size 800x600 + RenderBlock {HTML} at (0,0) size 800x600 + RenderBody {BODY} at (8,8) size 784x584 + RenderBlock {P} at (0,0) size 784x18 + RenderText {#text} at (0,0) size 111x18 + text run at (0,0) width 111: "Tests: the bdo tag" + RenderBlock {P} at (0,34) size 784x18 + RenderText {#text} at (0,0) size 328x18 + text run at (0,0) width 328: "The bdo element overrides the default text direction." + RenderBlock {P} at (0,68) size 784x36 + RenderText {#text} at (0,0) size 762x36 + text run at (0,0) width 727: "If successful, the first sentence would be backward, and the second sentence regular. There should then be an extra " + text run at (727,0) width 35: "blank" + text run at (0,18) width 698: "line, followed by a line reading only \"A,\" and finally, a sentence where only the word \"umbrella\" is backward." + RenderBlock {HR} at (0,120) size 784x2 [border: (1px inset #000000)] + RenderBlock (anonymous) at (0,130) size 784x108 + RenderBR {BR} at (0,0) size 0x18 + RenderInline {BDO} at (0,0) size 221x18 + RenderText {#text} at (0,18) size 221x18 + text run at (0,18) width 221 RTL override: "This sentence should be backward." + RenderText {#text} at (221,18) size 4x18 + text run at (221,18) width 4: " " + RenderBR {BR} at (225,32) size 0x0 + RenderInline {BDO} at (0,0) size 209x18 + RenderText {#text} at (0,36) size 209x18 + text run at (0,36) width 209 LTR override: "This sentence should be forward." + RenderText {#text} at (209,36) size 4x18 + text run at (209,36) width 4: " " + RenderBR {BR} at (213,50) size 0x0 + RenderInline {BDO} at (0,0) size 0x0 + RenderText {#text} at (0,0) size 0x0 + RenderBR {BR} at (0,54) size 0x18 + RenderInline {BDO} at (0,0) size 12x18 + RenderText {#text} at (0,72) size 12x18 + text run at (0,72) width 12 RTL override: "A" + RenderText {#text} at (12,72) size 4x18 + text run at (12,72) width 4: " " + RenderBR {BR} at (16,86) size 0x0 + RenderInline {BDO} at (0,0) size 290x18 + RenderText {#text} at (0,90) size 26x18 + text run at (0,90) width 26 LTR override: "My " + RenderInline {BDO} at (0,0) size 55x18 + RenderText {#text} at (26,90) size 55x18 + text run at (26,90) width 55 RTL override: "umbrella" + RenderText {#text} at (81,90) size 209x18 + text run at (81,90) width 209 LTR override: " sure would be useful in this rain." + RenderText {#text} at (0,0) size 0x0 + RenderText {#text} at (0,0) size 0x0 +layer at (0,0) size 800x600 + RenderView at (0,0) size 800x600 +layer at (0,0) size 800x600 + RenderBlock {HTML} at (0,0) size 800x600 + RenderBody {BODY} at (8,8) size 784x576 + RenderBlock {P} at (0,0) size 784x36 + RenderText {#text} at (0,0) size 253x18 + text run at (0,0) width 253: "This test checks for a regression against " + RenderInline {I} at (0,0) size 757x36 + RenderInline {A} at (0,0) size 348x18 [color=#0000EE] + RenderText {#text} at (253,0) size 348x18 + text run at (253,0) width 348: "http://bugzilla.opendarwin.org/show_bug.cgi?id=6214" + RenderText {#text} at (601,0) size 757x36 + text run at (601,0) width 156: " text-indent in RTL block" + text run at (0,18) width 109: "does the opposite" + RenderText {#text} at (109,18) size 4x18 + text run at (109,18) width 4: "." + RenderBlock {HR} at (0,52) size 784x2 [border: (1px inset #000000)] + RenderBlock {P} at (0,70) size 784x36 [bgcolor=#00FFFF] + RenderText {#text} at (41,0) size 743x36 + text run at (41,0) width 693: "The first line of this sententce should be indented 50 pixels to the left, but the rest of it should be flush with the" + text run at (581,18) width 4 RTL: "." + text run at (585,18) width 199: "normal right margin of the page" diff --git a/webkit/tools/layout_tests/testdata/difftests/textAreaLineHeight-actual-win.txt b/webkit/tools/layout_tests/testdata/difftests/textAreaLineHeight-actual-win.txt new file mode 100644 index 0000000..fbdb37f --- /dev/null +++ b/webkit/tools/layout_tests/testdata/difftests/textAreaLineHeight-actual-win.txt @@ -0,0 +1,75 @@ +layer at (0,0) size 783x1249 + RenderView at (0,0) size 783x600 +layer at (0,0) size 783x1249 + RenderBlock {HTML} at (0,0) size 783x1249 + RenderBody {BODY} at (8,8) size 767x1225 + RenderBlock (anonymous) at (0,0) size 767x20 + RenderText {#text} at (0,0) size 254x19 + text run at (0,0) width 254: "line-height settings not reflected in textarea" + RenderBR {BR} at (254,0) size 0x19 + RenderBlock {P} at (0,36) size 767x267 + RenderText {#text} at (0,0) size 79x19 + text run at (0,0) width 79: "TEXTAREA" + RenderBR {BR} at (79,0) size 0x19 + RenderTextControl {TEXTAREA} at (0,20) size 402x202 [bgcolor=#FFFFFF] [border: (1px dotted #C0C0C0)] + RenderText {#text} at (402,207) size 4x19 + text run at (402,207) width 4: " " + RenderBR {BR} at (0,0) size 0x0 + RenderBR {BR} at (0,227) size 0x19 + RenderText {#text} at (0,247) size 145x19 + text run at (0,247) width 145: "PARAGRAPH - works" + RenderBlock {P} at (0,319) size 402x202 [border: (1px dotted #C0C0C0)] + RenderText {#text} at (1,19) size 385x68 + text run at (1,19) width 385: "Demo text here that wraps a bit and should demonstrate" + text run at (1,71) width 181: "the goodness of line-height" + RenderBlock (anonymous) at (0,534) size 767x40 + RenderBR {BR} at (0,0) size 0x19 + RenderText {#text} at (0,20) size 79x19 + text run at (0,20) width 79: "DIV - works" + RenderBR {BR} at (79,20) size 0x19 + RenderBlock {DIV} at (0,574) size 402x202 [border: (1px dotted #C0C0C0)] + RenderText {#text} at (1,19) size 385x68 + text run at (1,19) width 385: "Demo text here that wraps a bit and should demonstrate" + text run at (1,71) width 181: "the goodness of line-height" + RenderBlock (anonymous) at (0,776) size 767x449 + RenderBR {BR} at (0,0) size 0x19 + RenderBR {BR} at (0,20) size 0x19 + RenderText {#text} at (0,40) size 119x19 + text run at (0,40) width 119: "Un-Styled Textarea" + RenderBR {BR} at (119,40) size 0x19 + RenderTextControl {TEXTAREA} at (2,62) size 183x42 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderText {#text} at (187,91) size 4x19 + text run at (187,91) width 4: " " + RenderBR {BR} at (0,0) size 0x0 + RenderBR {BR} at (0,111) size 0x19 + RenderText {#text} at (0,131) size 203x19 + text run at (0,131) width 203: "Totally Blank Un-Styled Textarea" + RenderBR {BR} at (203,131) size 0x19 + RenderTextControl {TEXTAREA} at (2,153) size 183x42 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderText {#text} at (187,182) size 4x19 + text run at (187,182) width 4: " " + RenderBR {BR} at (0,0) size 0x0 + RenderBR {BR} at (0,202) size 0x19 + RenderText {#text} at (0,222) size 199x19 + text run at (0,222) width 199: "Totally Blank STYLED Textarea" + RenderBR {BR} at (199,222) size 0x19 + RenderTextControl {TEXTAREA} at (0,242) size 402x202 [bgcolor=#FFFFFF] [border: (1px dotted #C0C0C0)] + RenderText {#text} at (0,0) size 0x0 + RenderBlock {P} at (0,1241) size 767x0 +layer at (9,65) size 400x200 + RenderBlock {DIV} at (1,1) size 400x200 + RenderText {#text} at (3,18) size 390x68 + text run at (3,18) width 390: "Demo text here that wraps a bit and should demonstrate " + text run at (3,70) width 181: "the goodness of line-height" +layer at (11,847) size 181x40 clip at (11,847) size 164x40 scrollHeight 79 + RenderBlock {DIV} at (1,1) size 181x40 + RenderText {#text} at (3,0) size 158x79 + text run at (3,0) width 123: "Demo text here that " + text run at (3,20) width 140: "wraps a bit and should " + text run at (3,40) width 157: "demonstrate the goodness" + text run at (160,40) width 1: " " + text run at (3,60) width 77: "of line-height" +layer at (11,938) size 181x40 + RenderBlock {DIV} at (1,1) size 181x40 +layer at (9,1027) size 400x200 + RenderBlock {DIV} at (1,1) size 400x200 diff --git a/webkit/tools/layout_tests/testdata/difftests/textAreaLineHeight-expected.txt b/webkit/tools/layout_tests/testdata/difftests/textAreaLineHeight-expected.txt new file mode 100644 index 0000000..1de6aae --- /dev/null +++ b/webkit/tools/layout_tests/testdata/difftests/textAreaLineHeight-expected.txt @@ -0,0 +1,74 @@ +layer at (0,0) size 785x1191 + RenderView at (0,0) size 785x600 +layer at (0,0) size 785x1191 + RenderBlock {HTML} at (0,0) size 785x1191 + RenderBody {BODY} at (8,8) size 769x1167 + RenderBlock (anonymous) at (0,0) size 769x18 + RenderText {#text} at (0,0) size 269x18 + text run at (0,0) width 269: "line-height settings not reflected in textarea" + RenderBR {BR} at (269,0) size 0x18 + RenderBlock {P} at (0,34) size 769x260 + RenderText {#text} at (0,0) size 87x18 + text run at (0,0) width 87: "TEXTAREA" + RenderBR {BR} at (87,0) size 0x18 + RenderTextControl {TEXTAREA} at (0,18) size 402x202 [bgcolor=#FFFFFF] [border: (1px dotted #C0C0C0)] + RenderText {#text} at (402,206) size 4x18 + text run at (402,206) width 4: " " + RenderBR {BR} at (0,0) size 0x0 + RenderBR {BR} at (0,224) size 0x18 + RenderText {#text} at (0,242) size 152x18 + text run at (0,242) width 152: "PARAGRAPH - works" + RenderBlock {P} at (0,310) size 402x202 [border: (1px dotted #C0C0C0)] + RenderText {#text} at (1,19) size 382x68 + text run at (1,19) width 382: "Demo text here that wraps a bit and should demonstrate" + text run at (1,71) width 182: "the goodness of line-height" + RenderBlock (anonymous) at (0,525) size 769x36 + RenderBR {BR} at (0,0) size 0x18 + RenderText {#text} at (0,18) size 81x18 + text run at (0,18) width 81: "DIV - works" + RenderBR {BR} at (81,18) size 0x18 + RenderBlock {DIV} at (0,561) size 402x202 [border: (1px dotted #C0C0C0)] + RenderText {#text} at (1,19) size 382x68 + text run at (1,19) width 382: "Demo text here that wraps a bit and should demonstrate" + text run at (1,71) width 182: "the goodness of line-height" + RenderBlock (anonymous) at (0,763) size 769x404 + RenderBR {BR} at (0,0) size 0x18 + RenderBR {BR} at (0,18) size 0x18 + RenderText {#text} at (0,36) size 124x18 + text run at (0,36) width 124: "Un-Styled Textarea" + RenderBR {BR} at (124,36) size 0x18 + RenderTextControl {TEXTAREA} at (2,56) size 163x28 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderText {#text} at (167,72) size 4x18 + text run at (167,72) width 4: " " + RenderBR {BR} at (0,0) size 0x0 + RenderBR {BR} at (0,90) size 0x18 + RenderText {#text} at (0,108) size 215x18 + text run at (0,108) width 215: "Totally Blank Un-Styled Textarea" + RenderBR {BR} at (215,108) size 0x18 + RenderTextControl {TEXTAREA} at (2,128) size 163x28 [bgcolor=#FFFFFF] [border: (1px solid #000000)] + RenderText {#text} at (167,144) size 4x18 + text run at (167,144) width 4: " " + RenderBR {BR} at (0,0) size 0x0 + RenderBR {BR} at (0,162) size 0x18 + RenderText {#text} at (0,180) size 213x18 + text run at (0,180) width 213: "Totally Blank STYLED Textarea" + RenderBR {BR} at (213,180) size 0x18 + RenderTextControl {TEXTAREA} at (0,198) size 402x202 [bgcolor=#FFFFFF] [border: (1px dotted #C0C0C0)] + RenderText {#text} at (0,0) size 0x0 + RenderBlock {P} at (0,1183) size 769x0 +layer at (9,61) size 400x200 + RenderBlock {DIV} at (1,1) size 400x200 + RenderText {#text} at (3,18) size 387x68 + text run at (3,18) width 387: "Demo text here that wraps a bit and should demonstrate " + text run at (3,70) width 182: "the goodness of line-height" +layer at (11,828) size 161x26 clip at (11,828) size 146x26 scrollHeight 52 + RenderBlock {DIV} at (1,1) size 161x26 + RenderText {#text} at (3,0) size 130x52 + text run at (3,0) width 112: "Demo text here that " + text run at (3,13) width 126: "wraps a bit and should " + text run at (3,26) width 92: "demonstrate the " + text run at (3,39) width 130: "goodness of line-height" +layer at (11,900) size 161x26 + RenderBlock {DIV} at (1,1) size 161x26 +layer at (9,970) size 400x200 + RenderBlock {DIV} at (1,1) size 400x200 diff --git a/webkit/tools/layout_tests/testdata/expected-crashes-new-passing.txt b/webkit/tools/layout_tests/testdata/expected-crashes-new-passing.txt new file mode 100644 index 0000000..97ec414 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-crashes-new-passing.txt @@ -0,0 +1,2 @@ +fast/bar/crash2.html +fast/foo/crash1.html diff --git a/webkit/tools/layout_tests/testdata/expected-crashes-new-test.txt b/webkit/tools/layout_tests/testdata/expected-crashes-new-test.txt new file mode 100644 index 0000000..d6d3da2 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-crashes-new-test.txt @@ -0,0 +1,4 @@ +fast/bar/betz/crash3.html +fast/bar/crash2.html +fast/foo/crash1.html +new-test.html diff --git a/webkit/tools/layout_tests/testdata/expected-crashes.txt b/webkit/tools/layout_tests/testdata/expected-crashes.txt new file mode 100644 index 0000000..ca65a65 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-crashes.txt @@ -0,0 +1,3 @@ +fast/bar/betz/crash3.html +fast/bar/crash2.html +fast/foo/crash1.html diff --git a/webkit/tools/layout_tests/testdata/expected-failures-added.txt b/webkit/tools/layout_tests/testdata/expected-failures-added.txt new file mode 100644 index 0000000..a327425 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-failures-added.txt @@ -0,0 +1,4 @@ +fast/bar/betz/fail3.html +fast/bar/fail2.html +fast/foo/fail1.html +fast/pass1.html diff --git a/webkit/tools/layout_tests/testdata/expected-failures-new-crash.txt b/webkit/tools/layout_tests/testdata/expected-failures-new-crash.txt new file mode 100644 index 0000000..7c25263 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-failures-new-crash.txt @@ -0,0 +1,4 @@ +fast/bar/betz/crash3.html +fast/bar/betz/fail3.html +fast/bar/fail2.html +fast/foo/fail1.html diff --git a/webkit/tools/layout_tests/testdata/expected-failures-new-passing.txt b/webkit/tools/layout_tests/testdata/expected-failures-new-passing.txt new file mode 100644 index 0000000..d2d2e79 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-failures-new-passing.txt @@ -0,0 +1,2 @@ +fast/bar/betz/fail3.html +fast/foo/fail1.html diff --git a/webkit/tools/layout_tests/testdata/expected-failures-new-test.txt b/webkit/tools/layout_tests/testdata/expected-failures-new-test.txt new file mode 100644 index 0000000..9492214 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-failures-new-test.txt @@ -0,0 +1,4 @@ +fast/bar/betz/fail3.html +fast/bar/fail2.html +fast/foo/fail1.html +new-test.html diff --git a/webkit/tools/layout_tests/testdata/expected-failures.txt b/webkit/tools/layout_tests/testdata/expected-failures.txt new file mode 100644 index 0000000..0e637e1 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-failures.txt @@ -0,0 +1,3 @@ +fast/bar/betz/fail3.html +fast/bar/fail2.html +fast/foo/fail1.html diff --git a/webkit/tools/layout_tests/testdata/expected-passing-new-baseline.txt b/webkit/tools/layout_tests/testdata/expected-passing-new-baseline.txt new file mode 100644 index 0000000..de8a4b6 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-passing-new-baseline.txt @@ -0,0 +1 @@ +fast/foo/pass2.html diff --git a/webkit/tools/layout_tests/testdata/expected-passing-new-passing.txt b/webkit/tools/layout_tests/testdata/expected-passing-new-passing.txt new file mode 100644 index 0000000..3bac884 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-passing-new-passing.txt @@ -0,0 +1,3 @@ +fast/bar/betz/crash3.html +fast/foo/pass2.html +fast/pass1.html diff --git a/webkit/tools/layout_tests/testdata/expected-passing-new-passing2.txt b/webkit/tools/layout_tests/testdata/expected-passing-new-passing2.txt new file mode 100644 index 0000000..72acc8a --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-passing-new-passing2.txt @@ -0,0 +1,3 @@ +fast/bar/fail2.html +fast/foo/pass2.html +fast/pass1.html diff --git a/webkit/tools/layout_tests/testdata/expected-passing-new-test.txt b/webkit/tools/layout_tests/testdata/expected-passing-new-test.txt new file mode 100644 index 0000000..5b5b0ad --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-passing-new-test.txt @@ -0,0 +1,3 @@ +fast/foo/pass2.html +fast/pass1.html +new-test.html diff --git a/webkit/tools/layout_tests/testdata/expected-passing.txt b/webkit/tools/layout_tests/testdata/expected-passing.txt new file mode 100644 index 0000000..2cdebb4 --- /dev/null +++ b/webkit/tools/layout_tests/testdata/expected-passing.txt @@ -0,0 +1,2 @@ +fast/foo/pass2.html +fast/pass1.html diff --git a/webkit/tools/leak_tests/run_node_leak_test.py b/webkit/tools/leak_tests/run_node_leak_test.py new file mode 100644 index 0000000..3d5a815 --- /dev/null +++ b/webkit/tools/leak_tests/run_node_leak_test.py @@ -0,0 +1,303 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +"""Run node leak tests using the test_shell. + +TODO(pjohnson): Add a way for layout tests (and other local files in the +working copy) to be easily run by specifying paths relative to webkit (or +something similar). +""" + +import logging +import optparse +import os +import random +import re +import sys + +import google.logging_utils +import google.path_utils +import google.platform_utils +import google.process_utils + +# Magic exit code to indicate a new fix. +REBASELINE_EXIT_CODE = -88 + +# Status codes. +PASS, FAIL, REBASELINE = range(3) + +# The test list files are found in this subdirectory, which must be a sibling +# to this script itself. +TEST_FILE_DIR = 'test_lists' + +# TODO(pjohnson): Find a way to avoid this duplicate code. This function has +# been shamelessly taken from layout_tests/layout_package. +_webkit_root = None + +def WebKitRoot(): + """Returns the full path to the directory containing webkit.sln. Raises + PathNotFound if we're unable to find webkit.sln. + """ + + global _webkit_root + if _webkit_root: + return _webkit_root + webkit_sln_path = google.path_utils.FindUpward(google.path_utils.ScriptDir(), + 'webkit.sln') + _webkit_root = os.path.dirname(webkit_sln_path) + return _webkit_root + +def GetAbsolutePath(path): + platform_util = google.platform_utils.PlatformUtility(WebKitRoot()) + return platform_util.GetAbsolutePath(path) + +# TODO(pjohnson): Find a way to avoid this duplicated code. This function has +# been mostly copied from another function, TestShellBinary, in +# layout_tests/layout_package. +def TestShellTestBinary(target): + """Gets the full path to the test_shell_tests binary for the target build + configuration. Raises PathNotFound if the file doesn't exist. + """ + + full_path = os.path.join(WebKitRoot(), target, 'test_shell_tests.exe') + if not os.path.exists(full_path): + # Try chrome's output directory in case test_shell was built by chrome.sln. + full_path = google.path_utils.FindUpward(WebKitRoot(), 'chrome', target, + 'test_shell_tests.exe') + if not os.path.exists(full_path): + raise PathNotFound('unable to find test_shell_tests at %s' % full_path) + return full_path + +class NodeLeakTestRunner: + """A class for managing running a series of node leak tests. + """ + + def __init__(self, options, urls): + """Collect a list of URLs to test. + + Args: + options: a dictionary of command line options + urls: a list of URLs in the format: + (url, expected_node_leaks, expected_js_leaks) tuples + """ + + self._options = options + self._urls = urls + + self._test_shell_test_binary = TestShellTestBinary(options.target) + + self._node_leak_matcher = re.compile('LEAK: (\d+) Node') + self._js_leak_matcher = re.compile('Leak (\d+) JS wrappers') + + def RunCommand(self, command): + def FindMatch(line, matcher, group_number): + match = matcher.match(line) + if match: + return int(match.group(group_number)) + return 0 + + (code, output) = google.process_utils.RunCommandFull(command, verbose=True, + collect_output=True, + print_output=False) + node_leaks = 0 + js_leaks = 0 + + # Print a row of dashes. + if code != 0: + print '-' * 75 + print 'OUTPUT' + print + + for line in output: + # Sometimes multiple leak lines are printed out, which is why we + # accumulate them here. + node_leaks += FindMatch(line, self._node_leak_matcher, 1) + js_leaks += FindMatch(line, self._js_leak_matcher, 1) + + # If the code indicates there was an error, print the output to help + # figure out what happened. + if code != 0: + print line + + # Print a row of dashes. + if code != 0: + print '-' * 75 + print + + return (code, node_leaks, js_leaks) + + def RunUrl(self, test_url, expected_node_leaks, expected_js_leaks): + shell_args = ['--gtest_filter=NodeLeakTest.*TestURL', + '--time-out-ms=' + str(self._options.time_out_ms), + '--test-url=' + test_url, + '--playback-mode'] + + if self._options.cache_dir != '': + shell_args.append('--cache-dir=' + self._options.cache_dir) + + command = [self._test_shell_test_binary] + shell_args + (exit_code, actual_node_leaks, actual_js_leaks) = self.RunCommand(command) + + logging.info('%s\n' % test_url) + + if exit_code != 0: + # There was a crash, or something else went wrong, so duck out early. + logging.error('Test returned: %d\n' % exit_code) + return FAIL + + result = ('TEST RESULT\n' + ' Node Leaks: %d (actual), %d (expected)\n' + ' JS Leaks: %d (actual), %d (expected)\n' % + (actual_node_leaks, expected_node_leaks, + actual_js_leaks, expected_js_leaks)) + + success = (actual_node_leaks <= expected_node_leaks and + actual_js_leaks <= expected_js_leaks) + + if success: + logging.info(result) + else: + logging.error(result) + logging.error('Unexpected leaks found!\n') + return FAIL + + if (expected_node_leaks > actual_node_leaks or + expected_js_leaks > actual_js_leaks): + logging.warn('Expectations may need to be re-baselined.\n') + # TODO(pjohnson): Return REBASELINE here once bug 1177263 is fixed and + # the expectations have been lowered again. + + return PASS + + def Run(self): + status = PASS + results = [0, 0, 0] + failed_urls = [] + rebaseline_urls = [] + + for (test_url, expected_node_leaks, expected_js_leaks) in self._urls: + result = self.RunUrl(test_url, expected_node_leaks, expected_js_leaks) + if result == PASS: + results[PASS] += 1 + elif result == FAIL: + results[FAIL] += 1 + failed_urls.append(test_url) + status = FAIL + elif result == REBASELINE: + results[REBASELINE] += 1 + rebaseline_urls.append(test_url) + if status != FAIL: + status = REBASELINE + return (status, results, failed_urls, rebaseline_urls) + +def main(options, args): + if options.seed != None: + random.seed(options.seed) + + # Set up logging so any messages below logging.WARNING are sent to stdout, + # otherwise they are sent to stderr. + google.logging_utils.config_root(level=logging.INFO, + threshold=logging.WARNING) + + if options.url_list == '': + logging.error('URL test list required') + sys.exit(1) + + url_list = os.path.join(os.path.dirname(sys.argv[0]), TEST_FILE_DIR, + options.url_list) + url_list = GetAbsolutePath(url_list); + + lines = [] + file = None + try: + file = open(url_list, 'r') + lines = file.readlines() + finally: + if file != None: + file.close() + + expected_matcher = re.compile('(\d+)\s*,\s*(\d+)') + + urls = [] + for line in lines: + line = line.strip() + if len(line) == 0 or line.startswith('#'): + continue + list = line.rsplit('=', 1) + if len(list) < 2: + logging.error('Line "%s" is not formatted correctly' % line) + continue + match = expected_matcher.match(list[1].strip()) + if not match: + logging.error('Line "%s" is not formatted correctly' % line) + continue + urls.append((list[0].strip(), int(match.group(1)), int(match.group(2)))) + + random.shuffle(urls) + runner = NodeLeakTestRunner(options, urls) + (status, results, failed_urls, rebaseline_urls) = runner.Run() + + logging.info('SUMMARY\n' + ' %d passed\n' + ' %d failed\n' + ' %d re-baseline\n' % + (results[0], results[1], results[2])) + + if len(failed_urls) > 0: + failed_string = '\n'.join(' ' + url for url in failed_urls) + logging.error('FAILED URLs\n%s\n' % failed_string) + + if len(rebaseline_urls) > 0: + rebaseline_string = '\n'.join(' ' + url for url in rebaseline_urls) + logging.warn('RE-BASELINE URLs\n%s\n' % rebaseline_string) + + if status == FAIL: + return 1 + elif status == REBASELINE: + return REBASELINE_EXIT_CODE + return 0 + +if '__main__' == __name__: + option_parser = optparse.OptionParser() + option_parser.add_option('', '--target', default='Debug', + help='build target (Debug or Release)') + option_parser.add_option('', '--cache-dir', default='', + help='use a specified cache directory') + option_parser.add_option('', '--url-list', default='', + help='URL input file, with leak expectations, ' + 'relative to webkit/tools/leak_tests') + option_parser.add_option('', '--time-out-ms', default=30000, + help='time out for each test') + option_parser.add_option('', '--seed', default=None, + help='seed for random number generator, use to ' + 'reproduce the exact same order for a ' + 'specific run') + options, args = option_parser.parse_args() + sys.exit(main(options, args)) diff --git a/webkit/tools/leak_tests/test_lists/alexa_100.txt b/webkit/tools/leak_tests/test_lists/alexa_100.txt new file mode 100644 index 0000000..8c04a02 --- /dev/null +++ b/webkit/tools/leak_tests/test_lists/alexa_100.txt @@ -0,0 +1,92 @@ +# Format: URL = EXPECTED_NODE_LEAKS, EXPECTED_JS_LEAKS +# +# This is a list of the top 100 Alexa URLs (as of 3/28/2008) that loaded +# successfully into our saved cache. +# +# Flaky URLs are covered by bug 1177263. +# Since this test is so flaky, for now set the expected numbers very high to +# catch only the most egregious regressions. + +http://www.yahoo.com = 200,200 +http://www.youtube.com = 200,200 +http://www.live.com = 200,200 +http://www.google.com = 200,200 +http://www.myspace.com = 200,200 +http://www.facebook.com = 200,200 +http://www.msn.com = 200,200 +http://www.hi5.com = 200,200 +http://www.wikipedia.org = 200,200 +http://www.orkut.com = 200,200 +http://www.blogger.com = 200,200 +http://www.fotolog.net = 200,200 +http://www.google.fr = 200,200 +http://www.friendster.com = 200,200 +http://www.microsoft.com = 200,200 +http://www.baidu.com = 200,200 +http://www.megarotic.com = 200,200 +http://www.google.cl = 200,200 +http://www.yahoo.co.jp = 200,200 +http://www.ebay.com = 200,200 +http://www.google.com.br = 200,200 +http://www.google.es = 200,200 +http://www.seznam.cz = 200,200 +http://www.google.com.mx = 200,200 +http://www.dailymotion.com = 200,200 +http://www.photobucket.com = 200,200 +http://www.youporn.com = 200,200 +http://www.imdb.com = 200,200 +http://www.google.pl = 200,200 +http://www.qq.com = 200,200 +http://www.google.co.uk = 200,200 +http://www.flickr.com = 200,200 +http://www.vkontakte.ru = 200,200 +http://www.nasza-klasa.pl = 200,200 +http://www.odnoklassniki.ru = 200,200 +http://www.google.de = 200,200 +http://www.metroflog.com = 200,200 +http://www.google.co.ve = 200,200 +http://www.google.com.ar = 200,200 +http://www.free.fr = 200,200 +http://www.wordpress.com = 200,200 +http://www.wretch.cc = 200,200 +http://www.mininova.org = 200,200 +http://www.onet.pl = 200,200 +http://www.google.com.pe = 200,200 +http://www.aol.com = 200,200 +http://www.google.com.co = 200,200 +http://www.allegro.pl = 200,200 +http://www.yandex.ru = 200,200 +http://www.deviantart.com = 200,200 +http://www.sina.com.cn = 200,200 +http://www.google.co.in = 200,200 +http://www.bbc.co.uk = 200,200 +http://www.google.ca = 200,200 +http://www.craigslist.org = 200,200 +http://www.google.sk = 200,200 +http://www.livejournal.com = 200,200 +http://www.iwiw.hu = 200,200 +http://www.google.com.vn = 200,200 +http://www.globo.com = 200,200 +http://www.wp.pl = 200,200 +http://www.netlog.com = 200,200 +http://www.perfspot.com = 200,200 +http://www.googlesyndication.com = 200,200 +http://www.google.it = 200,200 +http://www.google.co.hu = 200,200 +http://www.fc2.com = 200,200 +http://www.google.cn = 200,200 +http://www.ebay.fr = 200,200 +http://www.veoh.com = 200,200 +http://www.google.co.th = 200,200 +http://www.fotka.pl = 200,200 +http://www.orange.fr = 200,200 +http://www.google.com.tr = 200,200 +http://www.geocities.com = 200,200 +http://www.apple.com = 200,200 +http://www.onemanga.com = 200,200 +http://www.ebay.de = 200,200 +http://www.google.co.jp = 200,200 +http://www.taobao.com = 200,200 +http://www.megaflirt.com = 200,200 +http://www.ebay.co.uk = 200,200 +http://www.sexyono.com = 200,200 diff --git a/webkit/tools/merge/gen-merge-diff.sh b/webkit/tools/merge/gen-merge-diff.sh new file mode 100755 index 0000000..10aa0fb --- /dev/null +++ b/webkit/tools/merge/gen-merge-diff.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# +# Copyright 2007 Google Inc. +# All rights reserved. +# +# This script expects to live in chrome/tools/branch/. If it's ever moved, +# the value of |root| will need to be changed below. +# +# Generate a helper script that does a 3-way diff on each forked WebKit file +# you give it. The diff will be among: +# base: original third_party file from a Chrome trunk checkout +# diff1: updated third_party file from the merge branch +# diff2: forked file in webkit/pending or wherever. +# +# Run this script from the merge branch. Give it the path to the root of your +# trunk checkout (the directory containing src and data directories) as the +# first argument, and a list of forked files (e.g. from either copy of +# webkit/pending/) as the second argument. +# +# Example usage: +# ./gen-merge-diff.sh /cygdrive/c/src/chrome/trunk webkit/pending/*.* +# +# Once the helper script is generated, you can run it to do the 3-way diff. + +wkfilelist=/tmp/webkit-branch-merge/third_party_list.txt + +execdir=`dirname $0` +root=$execdir/../.. + +origtrunk=$1 +origroot=$origtrunk +shift + +if [[ $# -lt 1 || ! -d "$origroot/third_party" ]]; then + echo "Usage: $0 <path-to-the-base-dir-of-chrome-trunk> [list-of-forked-files]" + echo "Example: $0 /cygdrive/c/src/chrome/trunk webkit/pending/*.*" + exit 1 +fi + +if which p4merge.exe >& /dev/null; then + merge_exe=p4merge.exe +else + merge_exe="/cygdrive/c/Program Files/Perforce/p4merge.exe" + if [ ! -e "$merge_exe" ]; then + echo "WARNING: It looks like you don't have p4merge installed." + echo "You must edit merge-diff.sh and change the diff3way function to" + echo "point at a valid merge tool." + fi +fi + +if [ -e $wkfilelist ]; then + echo "Using cached file listing of third_party." + echo "WARNING: If the cached file is not up to date, you should execute:" + echo " rm $wkfilelist" + echo "and run this script again." +else + echo "Creating file listing of third_party..." + (cd $root && find third_party -type f | grep -v '/\.svn' | grep -v 'ForwardingHeaders' | grep -v 'DerivedSources' > $wkfilelist) +fi + +# Prepare the helper script. + +cat > merge-diff.sh <<EOF +#!/bin/sh + +function diff3way { + a=\`cygpath -wa \$1\` + b=\`cygpath -wa \$2\` + c=\`cygpath -wa \$3\` + "$merge_exe" "\$a" "\$b" "\$c" "\$c" +} + +EOF + +chmod +x merge-diff.sh + +# Now add all the diff commands to the helper script. + +for each in "$@"; do + filename=`echo $each | sed -e "s,.*/,,g"` + tpfile=`grep "/\<$filename\>" $wkfilelist` + if [ "$tpfile" != "" ]; then + # Only run the 3-way diff if the upstream file has changed + ignored=`diff "$origroot/$tpfile" "$root/$tpfile"` + if [ $? != 0 ]; then + echo "diff3way \"$origroot/$tpfile\" \"$root/$tpfile\" \"$each\"" >> merge-diff.sh + fi + fi +done + +cat << EOF + +====================== +Created: merge-diff.sh + +Please edit this file to make sure it is correct. Then run it with +'./merge-diff.sh'. +EOF diff --git a/webkit/tools/merge/update-branch-webkit.py b/webkit/tools/merge/update-branch-webkit.py new file mode 100644 index 0000000..8f61003 --- /dev/null +++ b/webkit/tools/merge/update-branch-webkit.py @@ -0,0 +1,774 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +""" +Update a copy of WebKit to a given version, issuing the necessary gvn commands. + + +This script is intended for internal use only, by knowledgeable users, and +therefore does not taint-check user-supplied values. + +Cygwin is required to run this script, for 'cygpath'. + +Only tested on Windows, running in Cygwin. Most of this script should +be compatible with Linux as well. The paths may not be correct for +running in a Windows cmd shell. + +TODO(pamg): Test in Windows cmd shell and fix problems. + +Usage notes: + In normal usage, it's expected that the user will first run svn manually + to update a local copy of the WebKit trunk to a known (or head) revision. + The svn checkout is prone to odd failures (network timeouts, etc.), so + it's safer to run it by hand so you can more easily notice and recover + from any problems. Note that if you're running svn more than once, to + update more than one directory, you'll want to specify a --revision (-r) + on any calls after the first, just in case someone's landed something + while you were checking out the first directory. + For example: + + cd /tmp/webkit-branch-merge/WebKit + svn co http://svn.webkit.org/repository/webkit/trunk/WebCore WebCore + [...Checked out revision 19776.] + svn co -r 19776 \ + http://svn.webkit.org/repository/webkit/trunk/JavaScriptCore \ + JavaScriptCore + cd /path/to/webkit/tools/merge + python ./update-branch-webkit.py --diff --real \ + --old /path/to/mergebranch/ 19776 + + Here the --old parameter points to the root of your merge branch checkout, + containing the src/ and data/ directories. You can also append + --new [directory name] to point to the directory where you've checked out + the latest webkit files (if you checked them out to somewhere other than + /tmp/webkit-branch-merge/WebKit as depicted above). The --new parameter + should be a "WebKit" directory that corresponds to the webkit svn trunk. + + If you want to see what the script will do before doing it, remove the + --real option. When you're satisfied that it's doing the right thing, you + can restore --real and run it one more time. In that case, you can also + leave off --diff on each run after the first, allowing the script to use a + cached file of diffs rather than re-generating it each time. Other options + are available as well; see 'update-branch-webkit --help' for a full list. + + Directories may be specified with Windows-style paths, but the slashes + must be forward-slashes, not backslashes, or the Cygwin shell will remove + them. +""" + +import errno +import logging +import optparse +import os +import re +import shutil +import subprocess +import sys +import traceback + +# Whether to produce additional debugging information. +DEBUGGING = False + +# The filename holding the new WebKit Subversion revision number. +BASE_REV_FILE = 'BASE_REVISION' + +# The URL from which to checkout the new revision if needed. +SVN_URL = 'http://svn.webkit.org/repository/webkit/trunk' + +# The files in which to save lists of added, deleted, and edited files. +SAVE_ADDED_FILES = 'merge-add-files.txt' +SAVE_DELETED_FILES = 'merge-delete-files.txt' +SAVE_EDITED_FILES = 'merge-edit-files.txt' +SAVE_OBSOLETE_FILES = 'merge-obsolete-files.txt' + +# Executable names, found in the system PATH. +SVN = 'svn.exe' +DIFF = 'diff.exe' +CYGPATH = 'cygpath.exe' + +# Directories to be entirely ignored when updating the merge branch. +IGNORE_DIRS = ['.svn', 'DerivedSources'] + +# Global cygpath process. +_cygpath_proc = None + +######################################## +# Error classes + +class LocalError(Exception): + """Base class for local errors.""" + stack_trace = True + code = 1 + +class ArgumentError(LocalError): + """Exception raised for errors in parsed script arguments.""" + stack_trace = False + code = 2 + +class CannotFindError(LocalError): + """Exception raised for an expected file or directory that does not exist.""" + def __init__(self, description, path): + LocalError.__init__(self, "Can't find %s '%s'" % (description, path)) + stack_trace = False + code = 3 + +class CannotWriteError(LocalError): + """Exception raised when writing to a necessary file fails.""" + def __init__(self, path): + LocalError.__init__(self, "Can't write to '%s'" % path) + stack_trace = False + code = 4 + +class CommandError(LocalError): + """Exception raised when an external command fails.""" + def __init__(self, prefix, value): + """ + Build a descriptive error message using available information. + + Args: + prefix: Descriptive text prepended to the error message. + value: Error value as provided in the exception, if any. May be None. + """ + if value is not None: + (errnum, message) = value + LocalError.__init__(self, "%s: %s (%s)" % (prefix, message, errnum)) + else: + LocalError.__init__(self, prefix) + stack_trace = False + code = 5 + + +######################################## +# Utility functions + +def GetAbsolutePath(path): + """Convert an unknown-style path to an absolute, mixed-style Windows path. + + We use an external cygpath binary to do the conversion. For performance + reasons, we use a single cygpath process. + """ + global _cygpath_proc + if not _cygpath_proc: + _cygpath_proc = subprocess.Popen([CYGPATH, '-a', '-m', '-f', '-'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + _cygpath_proc.stdin.write(path + '\n') + return _cygpath_proc.stdout.readline().rstrip() + +def PathFromChromeRoot(subdir): + """Return the path of the given WebKit path relative to the Chrome merge dir. + + We could get fancy and search for these, but it's so much simpler to just + set them, it's arguably even easier to maintain that way. + + Args: + subdir: The relative path to be converted. It may be a directory or a + filename, and it should be specified relative to the WebKit checkout + (e.g., 'WebCore'). + + Returns: + The path to the given subdirectory dir in the Chrome webkit tree, starting + from the src directory. + """ + return os.path.join('third_party', subdir) + +def NewDir(subdir=''): + """Return an absolute path to the subdirectory within the WebKit checkout. + + The subdir is specified relative to the WebKit checkout (e.g., 'WebCore'). + """ + return GetAbsolutePath(os.path.join(options.new, subdir)) + +def OldDir(subdir=''): + """Return an absolute path to the given subdirectory within the merge branch. + + The subdir is specified relative to the WebKit checkout (e.g., 'WebCore'). + """ + reldir = PathFromChromeRoot(subdir) + return GetAbsolutePath(os.path.join(options.old, reldir)) + +def TempDir(relative_path=''): + """Return an absolute path to the given location in the temp directory.""" + return GetAbsolutePath(os.path.join(options.temp_dir, relative_path)) + +def CollectFileList(search_dir, old): + """Recursively collect a set of files within the given directory. + + FIXME(pamg): This function assumes that there are no instances of the string + given by the search_dir argument in either of the source paths (old and + new). That is, if dir is 'WebCore', the --old and --new options must not + contain the string 'WebCore'. + + Args: + search_dir: The directory within which to search. It should be specified + relative to the WebKit checkout (e.g., 'WebCore'). + old: Boolean value indicating whether to look for files in the old + directory (i.e., the Chrome merge branch) or the new one (the WebKit + checkout). + + Returns: + A set of file paths relative to (and including) the given dir, with no + leading slash. + """ + # FIXME(pamg): This replacement is why --old and --new can't contain + # search_dir. + if old: + path = OldDir(search_dir) + basedir = path.replace(search_dir, '') + else: + path = NewDir(search_dir) + basedir = path.replace(search_dir, '') + + result = set() + for root, dirs, files in os.walk(path): + # We want the directory from search_dir onward, and we need '/' separators + # to match what diff returns. + relative_dir = root.replace(basedir, '', 1).replace('\\', '/') + for filename in files: + if not IgnoreFile(root): + result.add('/'.join([relative_dir, filename])) + return result + +def MakeDirectory(path): + """Create a path and its parents, returning a set of the paths created.""" + + logging.debug('Making directory %s' % path) + add = set() + (basepath, subdir) = os.path.split(path) + if not os.path.exists(basepath): + add = add.union(MakeDirectory(basepath)) + if options.real: + os.mkdir(path) + add.add(path) + return add + +def IsAvailable(executable): + """Return True if the given executable can be found. + + An executable is deemed available if it exists (for an absolute or relative + path) or is in the system path (for a simple filename). This is a naive + implementation that assumes that if a file with the given name exists, it is + accessible and executable. + """ + + if os.path.dirname(executable) != '': + # This is a pathname, either relative or absolute. + return os.path.exists(executable) + + paths = os.environ['PATH'].split(os.pathsep) + for path_dir in paths: + if os.path.exists(os.path.join(path_dir, executable)): + return True + return False + +def IgnoreFile(path): + """Return True if the path is in the list of directories to be ignored.""" + for ignore_dir in IGNORE_DIRS: + path = path.replace('\\', '/') + path = '/' + path + '/' + if '/' + ignore_dir + '/' in path: + return True + return False + +def WriteFile(path, data): + """Write the given data to the path. + + Args: + path: The absolute filename to be written. + data: The string data to be written to the file. + + Raises: + CannotWriteError if the write fails. + """ + logging.debug('Writing file to %s' % path) + try: + f = open(path, 'w') + f.write(data) + f.close() + except IOError, e: + raise CannotWriteError(path) + +def WriteAbsoluteFileList(filename, file_list): + """Write a file containing a sorted list of absolute paths in the branch. + + Args: + filename: The file to write. + file_list: An unsorted list of file paths, given relative to the WebKit + checkout (e.g., 'WebCore/ChangeLog'). + """ + logging.info('Saving %s' % filename) + file_paths = [OldDir(x) for x in file_list] + # Sort after converting to absolute paths, in case some of the incoming paths + # were already absolute. + file_paths.sort() + WriteFile(filename, '\n'.join(file_paths)) + +def RunShellCommand(command, description, run=True): + """Run a command in the shell, waiting for completion. + + Args: + command: The shell command to be run, as a string. + description: A brief description of the command, to be used in an error + message if needed. + run: If false, the command will be logged, but it won't actually be + executed. + + Raises: + CommandError if the command runs but exits with a nonzero status. + """ + logging.debug(command) + if run: + result = subprocess.call(command, shell=True) + if result: + raise CommandError(description, None) + + +######################################## +# Task functions. + +def ValidateArguments(): + """Validate incoming options. + + Canonicalize paths and make sure that they exist, make sure a diff file + exists if one is being used, and check for all required executables. + """ + if options.revision is None: + raise ArgumentError('Please specify a --revision.') + try: + # We need the revision as a string, but we want to make sure it's numeric. + options.revision = str(int(options.revision)) + except ValueError: + raise ArgumentError('Revision must be a number.') + if options.revision < 1: + raise ArgumentError('Revision must be at least 1.') + + options.dir_list = options.directories.split(',') + if not len(options.dir_list): + raise ArgumentError('Please list at least one directory.') + + options.old = GetAbsolutePath(options.old) + options.new = GetAbsolutePath(options.new) + options.temp_dir = GetAbsolutePath(options.temp_dir) + + if not os.path.exists(options.old): + raise CannotFindError('old dir', options.old) + if not os.path.exists(options.new): + raise CannotFindError('new dir', options.new) + for dir_name in options.dir_list: + if not os.path.exists(os.path.join(options.old, 'third_party', dir_name)): + raise CannotFindError('old %s' % dir_name, '') + if not os.path.exists(os.path.join(options.new, dir_name)): + raise CannotFindError('new %s' % dir_name, '') + if not os.path.exists(options.temp_dir): + logging.info('Creating temp directory %s' % options.temp_dir) + os.makedirs(options.temp_dir) + + saved_diff_path = TempDir(SAVE_EDITED_FILES) + if not options.diff and not os.path.exists(saved_diff_path): + logging.warning("No saved diff file at '%s'. Running diff again." % + saved_diff_path) + options.diff = True + + if options.svn_checkout: + if not IsAvailable(SVN): + raise CannotFindError('executable', SVN) + else: + checkout_dir = NewDir() + if not IsAvailable(checkout_dir): + raise CannotFindError('new WebKit checkout', checkout_dir) + if options.diff: + if not IsAvailable(DIFF): + raise CannotFindError('executable', DIFF) + if not IsAvailable('gvn.bat'): + raise CannotFindError('batch file', 'gvn.bat') + +def RunSvnCheckout(revision): + """Use svn to checkout a new WebKit from SVN_URL into the temp dir.""" + logging.info('Checking out revision %s', revision) + for checkout_dir in options.dir_list: + new_dir = NewDir(checkout_dir) + url = SVN_URL + '/' + checkout_dir + command = '%s checkout -r %s %s %s' % (SVN, revision, url, new_dir) + RunShellCommand(command, 'Running svn') + +def UpdateBaseRevision(revision): + """Update the merge branch BASE_REVISION file.""" + new_baserev_file = NewDir(BASE_REV_FILE) + try: + existing_revision = open(new_baserev_file, 'r').read().rstrip() + except IOError, e: + if e.errno == errno.ENOENT: + existing_revision = -1 + else: + raise + if revision != existing_revision: + logging.info('Updating %s to %s' % (new_baserev_file, revision)) + WriteFile(new_baserev_file, revision) + +def RunDiff(saved_file): + """Collect a set of files that differ between the WebKit and Chrome trees. + + Run a recursive diff for each of the relevant directories, collecting a set + of files that differ between them. Save the resulting list in a file, and + also return the set. + + Args: + saved_file: The file in which to save the list of differing files. + + Returns: + A set of paths to files that differ between the two trees. Paths are + given relative to the WebKit checkout (e.g., 'WebCore/ChangeLog'). + + Raises: + CommandError if the diff command returns an error or produces unrecognized + output. + """ + # Regular expressions for parsing the output of the diff. + add_delete_re = re.compile("^Only in [^ ]+") + edit_re = re.compile("^Files ([^ ]+) and [^ ]+ differ") + + # Generate a set of the possible leading directory strings, to be removed + # from the file paths reported by diff. + dir_replacements = set() + for checkout_dir in options.dir_list: + # FIXME(pamg): This assumes that the name of the dir being processed is + # not a substring of options.new. See the FIXME for CollectFileList. + base_dir = NewDir(checkout_dir).replace(checkout_dir, '') + dir_replacements.add(base_dir) + + # Collect the set of edited files. + edited = set() + for diff_dir in options.dir_list: + logging.info('Generating diffs for %s' % diff_dir) + command = [DIFF, '--recursive', '--ignore-space-change', '--brief', + NewDir(diff_dir), OldDir(diff_dir)] + logging.debug(command) + try: + p = subprocess.Popen(command, stdout=subprocess.PIPE) + diff_text = p.stdout.read().replace('\r', '') + rc = p.wait() + except OSError, e: + raise CommandError('Running diff ' + command, e) + # Diff exit status is 0 if the directories compared are the same, 1 if + # they differ, or 2 if an error was encountered. + if rc < 0 or rc > 1: + raise CommandError('Error running diff command', None) + + # Examine this directory's diff result and build a list of changed files. + # Although we're not interested in added or deleted files, we parse the + # "Only in..." lines to catch any errors. + # Files C:/new/directory/file.h and C:/old/directory/file.h differ + # (ignored) Only in C:/new/directory: new_filename.h + # (ignored) Only in C:/old/directory: old_filename.h + logging.info('Analyzing %s diff results' % diff_dir) + for diff_line in diff_text.split('\n'): + if IgnoreFile(diff_line): + continue + if edit_re.search(diff_line) is not None: + filename = re.sub(edit_re, r'\1', diff_line) + for replace in dir_replacements: + filename = filename.replace(replace, '', 1) + edited.add(filename) + elif add_delete_re.search(diff_line) is not None: + # Do nothing, but don't complain about an error. + pass + elif diff_line != '': + raise CommandError("Unknown diff result '%s'" % diff_line, None) + + WriteFile(saved_file, '\n'.join(sorted(edited))) + return edited + +def CopyFiles(files): + """Copy files from WebKit to the merge branch, collecting added directories. + + If options.real is False, collect the directories that would be added, but + don't actually make the directories or perform the copies. + + Args: + files: The set of files to be copied, with paths given relative to the + WebKit checkout (e.g., 'WebCore/ChangeLog'). + + Returns: + A set of absolute directory paths that were created in the merge branch + in order to copy the files. + + Raises: + CommandError if the file copy fails. + """ + logging.info('Copying files from %s to %s' % (options.new, options.old)) + added = set() + for file in files: + # Create the directory if it doesn't exist. + # TODO(pamg): This has a lot of redundancy and could probably be made + # faster. + dirname = os.path.dirname(file) + old_dir = OldDir(dirname) + if not os.path.exists(old_dir): + # We need to handle each new directory level individually, for gvn add. + added = added.union(MakeDirectory(old_dir)) + new_path = NewDir(file) + old_path = OldDir(file) + if options.real: + try: + shutil.copyfile(new_path, old_path) + except IOError, e: + raise CommandError('Copying file', e) + return added + +def CopyObsoleteFiles(files): + """Copy new versions of obsolete header files into .obsolete names. + + If options.real is False, log the copy but do not actually perform it. + + Args: + files: The set of header files to be copied, with paths given relative to + the WebKit checkout and not including '.obsolete' (e.g., + 'WebCore/html/HTMLElement.h'). Their directories should already exist. + + Raises: + CommandError if the file copy fails. + """ + for file in files: + # The directory should already exist. + new_path = NewDir(file) + old_path = OldDir(file + '.obsolete') + logging.debug('Copying %s to %s' % (new_path, old_path)) + if options.real: + try: + shutil.copyfile(new_path, old_path) + except IOError, e: + raise CommandError('Copying obsolete file', e) + +def CopyBaseRevisionFile(): + """Copy the BASE_REV_FILE from the WebKit checkout to the merge branch. + + If options.real is False, log the copy but do not actually perform it. + + Raises: + CommandError if the file copy fails. + """ + logging.info('Copying %s to %s' % (BASE_REV_FILE, OldDir())) + if options.real: + try: + shutil.copyfile(NewDir(BASE_REV_FILE), OldDir(BASE_REV_FILE)) + except IOError, e: + raise CommandError('Copying %s' % BASE_REV_FILE, e) + + +def main(options, args): + """Perform the merge.""" + + ValidateArguments() + + # Report configuration. + logging.info('Updating to revision %s' % options.revision) + logging.info('Updating into old directory %s' % options.old) + logging.info('Using WebKit from new directory %s' % options.new) + logging.info('Using temp directory %s' % options.temp_dir) + + if not options.real: + logging.warning('DEBUGGING: Not issuing mkdir, cp, or gvn commands') + + if options.svn_checkout: + RunSvnCheckout(options.revision) + + UpdateBaseRevision(options.revision) + + # Make sure desired directories are in the checkout. + for checkout_dir in options.dir_list: + if not os.path.exists(NewDir(checkout_dir)): + raise CannotFindError('webkit checkout dir', NewDir(checkout_dir)) + + # Generate or load diffs between relevant directories in the two trees. + saved_diff_path = TempDir(SAVE_EDITED_FILES) + if options.diff: + edit_files = RunDiff(saved_diff_path) + else: + logging.info('Reading diffs from %s' % saved_diff_path) + diff_text = open(saved_diff_path, 'r').read() + edit_files = set(diff_text.split('\n')) + + # Collect filenames in each directory. + old_files = set() + new_files = set() + logging.info('Collecting lists of old and new files') + for collect_dir in options.dir_list: + logging.debug('Collecting old files in %s' % collect_dir) + old_files = old_files.union(CollectFileList(collect_dir, old=True)) + logging.debug('Collecting new files in %s' % collect_dir) + new_files = new_files.union(CollectFileList(collect_dir, old=False)) + + add_files = new_files - old_files + delete_files = old_files - new_files + + if DEBUGGING: + WriteFile(TempDir('old.txt'), '\n'.join(sorted(old_files))) + WriteFile(TempDir('new.txt'), '\n'.join(sorted(new_files))) + + # Find any obsolete files ostensibly to be deleted, and rename (rather than + # re-adding) their corresponding new .h files. We trust that no filename + # that ends with '.obsolete' will also contain another '.obsolete' in its + # name. + obsolete_files = set() + # Iterate through a copy of the deleted set since we'll be changing it. + delete_files_copy = delete_files.copy() + for deleted in delete_files_copy: + if deleted.endswith('.obsolete'): + try: + # Only the add_files.remove() should be able to raise a KeyError. + # If we have no original file to rename, don't delete the .obsolete + # one either. + delete_files.remove(deleted) + deleted = deleted.replace('.obsolete', '') + add_files.remove(deleted) + obsolete_files.add(deleted) + except KeyError, e: + logging.warning('No new file found for old %s.obsolete.' % deleted) + + # Save the list of obsolete files for future reference. + WriteFile(TempDir(SAVE_OBSOLETE_FILES), '\n'.join(sorted(obsolete_files))) + + # Save the list of deleted files, converted to absolute paths, for use by + # gvn. + WriteAbsoluteFileList(TempDir(SAVE_DELETED_FILES), delete_files) + + # Issue gvn deletes. We do this before copying over the added files so that + # an apparently new file that only differs from an old one in the case of + # its filename will be copied with the right name even though Windows + # filenames are case-insensitive. (Otherwise, if the file cppparser.h + # already existed in the Chrome branch but was renamed to CPPParser.h in the + # WebKit checkout, the "added" file CPPParser.h would be copied into + # cppparser.h rather than using its new name.) + if len(delete_files): + logging.info('Issuing gvn delete') + command = 'gvn --targets %s delete' % TempDir(SAVE_DELETED_FILES) + RunShellCommand(command, 'Running gvn delete', options.real) + + # Copy added and edited files from the svn checkout, collecting added + # directories in the process. + if not options.deletes_only: + add_dirs = CopyFiles(add_files | edit_files) + for dir_name in add_dirs: + # We want relative paths, so truncate the path. + add_files.add(dir_name[len(OldDir()) + 1:]) + CopyObsoleteFiles(obsolete_files) + CopyBaseRevisionFile() + + # Save the sorted list of added files, converted to absolute paths, for use + # by gvn. Sorting ensures that new directories are added before their + # contents are. + WriteAbsoluteFileList(TempDir(SAVE_ADDED_FILES), add_files) + + # Issue gvn adds. + os.chdir(OldDir()) + if len(add_files) and not options.deletes_only: + logging.info('Issuing gvn add') + command = 'gvn --targets %s add' % TempDir(SAVE_ADDED_FILES) + RunShellCommand(command, 'Running gvn add', options.real) + + # Print statistics. + logging.info('Edited: %s' % len(edit_files)) + logging.info('Added: %s' % len(add_files)) + logging.info('Deleted: %s' % len(delete_files)) + logging.info('Renamed (obsoletes): %s' % len(obsolete_files)) + + return 0 + +if '__main__' == __name__: + # Set up logging. + if DEBUGGING: + loglevel = logging.DEBUG + else: + loglevel = logging.INFO + logging.basicConfig(level=loglevel, + format='%(asctime)s %(levelname)-7s: %(message)s', + datefmt='%H:%M:%S') + + # We need cygpath to convert our default paths. + try: + if not IsAvailable(CYGPATH): + raise CannotFindError('cygpath', CYGPATH) + except LocalError, e: + if e.stack_trace or DEBUGGING: + traceback.print_exc() + logging.error(e) + sys.exit(e.code) + + # Set up option parsing. + opt = optparse.OptionParser() + + opt.add_option('-r', '--revision', default=None, + help='(REQUIRED): New WebKit revision (already checked out, or to be)') + + opt.add_option('', '--directories', + default='JavaScriptCore,WebCore', + help='Comma-separated list of directories to update') + + opt.add_option('', '--old', + default=GetAbsolutePath('/cygdrive/c/src/merge/'), + help='Path to top of branch checkout, holding src/ and data/') + + opt.add_option('', '--new', + default=GetAbsolutePath('/tmp/webkit-branch-merge/WebKit'), + help='Directory containing new WebKit checkout') + + opt.add_option('', '--temp-dir', + default=GetAbsolutePath('/tmp/webkit-branch-merge'), + help='Temp directory available for use (will be created)') + + opt.add_option('', '--svn-checkout', action='store_true', + default=False, + help='Run a new svn checkout into the new dir') + + opt.add_option('', '--diff', action='store_true', + default=False, + help='Run a fresh diff rather than using a saved diff file') + + opt.add_option('', '--deletes-only', action='store_true', + default=False, + help='Only issue gvn commands for deletes (this is useful' + 'when the case of a filename has changed)') + + opt.add_option('', '--real', action='store_true', + default=False, + help="I'm ready: do the mkdir, cp, and gvn commands for real") + + (options, args) = opt.parse_args() + + # Run. + try: + exit_value = main(options, args) + except LocalError, e: + if e.stack_trace or DEBUGGING: + traceback.print_exc() + logging.error(e) + exit_value = e.code + + sys.exit(exit_value) diff --git a/webkit/tools/npapi_layout_test_plugin/PluginObject.cpp b/webkit/tools/npapi_layout_test_plugin/PluginObject.cpp new file mode 100644 index 0000000..b012f58 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/PluginObject.cpp @@ -0,0 +1,622 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "PluginObject.h" + +#include "TestObject.h" +#include <assert.h> +#include <stdio.h> +#ifdef WIN32 +#include <stdlib.h> +#define snprintf sprintf_s +#endif + +static void pluginInvalidate(NPObject *obj); +static bool pluginHasProperty(NPObject *obj, NPIdentifier name); +static bool pluginHasMethod(NPObject *obj, NPIdentifier name); +static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant); +static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant); +static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result); +static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result); +static NPObject *pluginAllocate(NPP npp, NPClass *theClass); +static void pluginDeallocate(NPObject *obj); + +NPNetscapeFuncs *browser; + +static NPClass pluginClass = { + NP_CLASS_STRUCT_VERSION, + pluginAllocate, + pluginDeallocate, + pluginInvalidate, + pluginHasMethod, + pluginInvoke, + pluginInvokeDefault, + pluginHasProperty, + pluginGetProperty, + pluginSetProperty, +}; + +NPClass *getPluginClass(void) +{ + return &pluginClass; +} + +static bool identifiersInitialized = false; + +#define ID_PROPERTY_PROPERTY 0 +#define ID_PROPERTY_EVENT_LOGGING 1 +#define ID_PROPERTY_HAS_STREAM 2 +#define ID_PROPERTY_TEST_OBJECT 3 +#define ID_PROPERTY_LOG_DESTROY 4 +#define ID_PROPERTY_TEST_OBJECT_COUNT 5 +#define NUM_PROPERTY_IDENTIFIERS 6 + +static NPIdentifier pluginPropertyIdentifiers[NUM_PROPERTY_IDENTIFIERS]; +static const NPUTF8 *pluginPropertyIdentifierNames[NUM_PROPERTY_IDENTIFIERS] = { + "property", + "eventLoggingEnabled", + "hasStream", + "testObject", + "logDestroy", + "testObjectCount", +}; + +#define ID_TEST_CALLBACK_METHOD 0 +#define ID_TEST_GETURL 1 +#define ID_REMOVE_DEFAULT_METHOD 2 +#define ID_TEST_DOM_ACCESS 3 +#define ID_TEST_GET_URL_NOTIFY 4 +#define ID_TEST_INVOKE_DEFAULT 5 +#define ID_DESTROY_STREAM 6 +#define ID_TEST_ENUMERATE 7 +#define ID_TEST_GETINTIDENTIFIER 8 +#define ID_TEST_GET_PROPERTY 9 +#define ID_TEST_EVALUATE 10 +#define ID_TEST_GET_PROPERTY_RETURN_VALUE 11 +#define ID_TEST_CALLBACK_METHOD_RET 12 +#define ID_TEST_CREATE_TEST_OBJECT 13 +#define ID_TEST_PASS_TEST_OBJECT 14 +#define ID_TEST_CLONE_OBJECT 15 +#define ID_TEST_SCRIPT_OBJECT_INVOKE 16 +#define NUM_METHOD_IDENTIFIERS 17 + +static NPIdentifier pluginMethodIdentifiers[NUM_METHOD_IDENTIFIERS]; +static const NPUTF8 *pluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = { + "testCallback", + "getURL", + "removeDefaultMethod", + "testDOMAccess", + "getURLNotify", + "testInvokeDefault", + "destroyStream", + "testEnumerate", + "testGetIntIdentifier", + "testGetProperty", + "testEvaluate", + "testGetPropertyReturnValue", + "testCallbackRet", // Chrome bug 897451 + "testCreateTestObject", // Chrome bug 1093606 + "testPassTestObject", // Chrome bug 1093606 + "testCloneObject", + "testScriptObjectInvoke", // Chrome bug 1175346 +}; + +static NPUTF8* createCStringFromNPVariant(const NPVariant *variant) +{ + size_t length = NPVARIANT_TO_STRING(*variant).UTF8Length; + NPUTF8* result = (NPUTF8*)malloc(length + 1); + memcpy(result, NPVARIANT_TO_STRING(*variant).UTF8Characters, length); + result[length] = '\0'; + return result; +} + +static void initializeIdentifiers(void) +{ + browser->getstringidentifiers(pluginPropertyIdentifierNames, NUM_PROPERTY_IDENTIFIERS, pluginPropertyIdentifiers); + browser->getstringidentifiers(pluginMethodIdentifierNames, NUM_METHOD_IDENTIFIERS, pluginMethodIdentifiers); +} + +static bool pluginHasProperty(NPObject *obj, NPIdentifier name) +{ + for (int i = 0; i < NUM_PROPERTY_IDENTIFIERS; i++) + if (name == pluginPropertyIdentifiers[i]) + return true; + return false; +} + +static bool pluginHasMethod(NPObject *obj, NPIdentifier name) +{ + for (int i = 0; i < NUM_METHOD_IDENTIFIERS; i++) + if (name == pluginMethodIdentifiers[i]) + return true; + return false; +} + +static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant) +{ + if (name == pluginPropertyIdentifiers[ID_PROPERTY_PROPERTY]) { + char* mem = static_cast<char*>(browser->memalloc(9)); + strcpy(mem, "property"); + STRINGZ_TO_NPVARIANT(mem, *variant); + return true; + } else if (name == pluginPropertyIdentifiers[ID_PROPERTY_EVENT_LOGGING]) { + BOOLEAN_TO_NPVARIANT(((PluginObject *)obj)->eventLogging, *variant); + return true; + } else if (name == pluginPropertyIdentifiers[ID_PROPERTY_LOG_DESTROY]) { + BOOLEAN_TO_NPVARIANT(((PluginObject *)obj)->logDestroy, *variant); + return true; + } else if (name == pluginPropertyIdentifiers[ID_PROPERTY_HAS_STREAM]) { + BOOLEAN_TO_NPVARIANT(((PluginObject *)obj)->stream != 0, *variant); + return true; + } else if (name == pluginPropertyIdentifiers[ID_PROPERTY_TEST_OBJECT]) { + NPObject *testObject = ((PluginObject *)obj)->testObject; + browser->retainobject(testObject); + OBJECT_TO_NPVARIANT(testObject, *variant); + return true; + } else if (name == pluginPropertyIdentifiers[ID_PROPERTY_TEST_OBJECT_COUNT]) { + INT32_TO_NPVARIANT(getTestObjectCount(), *variant); + return true; + } + return false; +} + +static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant) +{ + if (name == pluginPropertyIdentifiers[ID_PROPERTY_EVENT_LOGGING]) { + ((PluginObject *)obj)->eventLogging = NPVARIANT_TO_BOOLEAN(*variant); + return true; + } else if (name == pluginPropertyIdentifiers[ID_PROPERTY_LOG_DESTROY]) { + ((PluginObject *)obj)->logDestroy = NPVARIANT_TO_BOOLEAN(*variant); + return true; + } + + return false; +} + +static void testDOMAccess(PluginObject *obj) +{ + // Get plug-in's DOM element + NPObject *elementObject; + if (browser->getvalue(obj->npp, NPNVPluginElementNPObject, &elementObject) == NPERR_NO_ERROR) { + // Get style + NPVariant styleVariant; + NPIdentifier styleIdentifier = browser->getstringidentifier("style"); + if (browser->getproperty(obj->npp, elementObject, styleIdentifier, &styleVariant) && NPVARIANT_IS_OBJECT(styleVariant)) { + // Set style.border + NPIdentifier borderIdentifier = browser->getstringidentifier("border"); + NPVariant borderVariant; + STRINGZ_TO_NPVARIANT("3px solid red", borderVariant); + browser->setproperty(obj->npp, NPVARIANT_TO_OBJECT(styleVariant), borderIdentifier, &borderVariant); + browser->releasevariantvalue(&styleVariant); + } + + browser->releaseobject(elementObject); + } +} + +static bool pluginInvoke(NPObject *header, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + PluginObject *obj = (PluginObject *)header; + if (name == pluginMethodIdentifiers[ID_TEST_CALLBACK_METHOD]) { + // call whatever method name we're given + if (argCount > 0 && NPVARIANT_IS_STRING(args[0])) { + NPObject *windowScriptObject; + browser->getvalue(obj->npp, NPNVWindowNPObject, &windowScriptObject); + + NPUTF8* callbackString = createCStringFromNPVariant(&args[0]); + NPIdentifier callbackIdentifier = browser->getstringidentifier(callbackString); + free(callbackString); + + NPVariant browserResult; + browser->invoke(obj->npp, windowScriptObject, callbackIdentifier, 0, 0, &browserResult); + browser->releasevariantvalue(&browserResult); + + VOID_TO_NPVARIANT(*result); + return true; + } + } else if (name == pluginMethodIdentifiers[ID_TEST_GETURL]) { + if (argCount == 2 && NPVARIANT_IS_STRING(args[0]) && NPVARIANT_IS_STRING(args[1])) { + NPUTF8* urlString = createCStringFromNPVariant(&args[0]); + NPUTF8* targetString = createCStringFromNPVariant(&args[1]); + browser->geturl(obj->npp, urlString, targetString); + free(urlString); + free(targetString); + + VOID_TO_NPVARIANT(*result); + return true; + } else if (argCount == 1 && NPVARIANT_IS_STRING(args[0])) { + NPUTF8* urlString = createCStringFromNPVariant(&args[0]); + browser->geturl(obj->npp, urlString, 0); + free(urlString); + + VOID_TO_NPVARIANT(*result); + return true; + } + } else if (name == pluginMethodIdentifiers[ID_REMOVE_DEFAULT_METHOD]) { + pluginClass.invokeDefault = 0; + VOID_TO_NPVARIANT(*result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_DOM_ACCESS]) { + testDOMAccess(obj); + VOID_TO_NPVARIANT(*result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_GET_URL_NOTIFY]) { + if (argCount == 3 + && NPVARIANT_IS_STRING(args[0]) + && (NPVARIANT_IS_STRING(args[1]) || NPVARIANT_IS_NULL(args[1])) + && NPVARIANT_IS_STRING(args[2])) { + NPUTF8* urlString = createCStringFromNPVariant(&args[0]); + NPUTF8* targetString = (NPVARIANT_IS_STRING(args[1]) ? createCStringFromNPVariant(&args[1]) : NULL); + NPUTF8* callbackString = createCStringFromNPVariant(&args[2]); + + NPIdentifier callbackIdentifier = browser->getstringidentifier(callbackString); + browser->geturlnotify(obj->npp, urlString, targetString, callbackIdentifier); + + free(urlString); + free(targetString); + free(callbackString); + + VOID_TO_NPVARIANT(*result); + return true; + } + } else if (name == pluginMethodIdentifiers[ID_TEST_INVOKE_DEFAULT] && NPVARIANT_IS_OBJECT(args[0])) { + NPObject *callback = NPVARIANT_TO_OBJECT(args[0]); + + NPVariant args[1]; + NPVariant browserResult; + + STRINGZ_TO_NPVARIANT("test", args[0]); + bool retval = browser->invokeDefault(obj->npp, callback, args, 1, &browserResult); + + if (retval) + browser->releasevariantvalue(&browserResult); + + BOOLEAN_TO_NPVARIANT(retval, *result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_ENUMERATE]) { + if (argCount == 2 && NPVARIANT_IS_OBJECT(args[0]) && NPVARIANT_IS_OBJECT(args[1])) { + uint32_t count; + NPIdentifier* identifiers; + + if (browser->enumerate(obj->npp, NPVARIANT_TO_OBJECT(args[0]), &identifiers, &count)) { + NPObject* outArray = NPVARIANT_TO_OBJECT(args[1]); + NPIdentifier pushIdentifier = browser->getstringidentifier("push"); + + for (uint32_t i = 0; i < count; i++) { + NPUTF8* string = browser->utf8fromidentifier(identifiers[i]); + + if (!string) + continue; + + NPVariant args[1]; + STRINGZ_TO_NPVARIANT(string, args[0]); + NPVariant browserResult; + browser->invoke(obj->npp, outArray, pushIdentifier, args, 1, &browserResult); + browser->releasevariantvalue(&browserResult); + browser->memfree(string); + } + + browser->memfree(identifiers); + } + + VOID_TO_NPVARIANT(*result); + return true; + } + return false; + } else if (name == pluginMethodIdentifiers[ID_DESTROY_STREAM]) { + NPError npError = browser->destroystream(obj->npp, obj->stream, NPRES_USER_BREAK); + INT32_TO_NPVARIANT(npError, *result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_GETINTIDENTIFIER]) { + if (argCount == 1 && NPVARIANT_IS_DOUBLE(args[0])) { + NPIdentifier identifier = browser->getintidentifier((int)NPVARIANT_TO_DOUBLE(args[0])); + INT32_TO_NPVARIANT((int32)identifier, *result); + return true; + } + } else if (name == pluginMethodIdentifiers[ID_TEST_EVALUATE] && + argCount == 1 && NPVARIANT_IS_STRING(args[0])) { + NPObject *windowScriptObject; + browser->getvalue(obj->npp, NPNVWindowNPObject, &windowScriptObject); + + NPString s = NPVARIANT_TO_STRING(args[0]); + + bool retval = browser->evaluate(obj->npp, windowScriptObject, &s, result); + browser->releaseobject(windowScriptObject); + return retval; + } else if (name == pluginMethodIdentifiers[ID_TEST_GET_PROPERTY] && + argCount > 0) { + NPObject *object; + browser->getvalue(obj->npp, NPNVWindowNPObject, &object); + + for (uint32_t i = 0; i < argCount; i++) { + assert(NPVARIANT_IS_STRING(args[i])); + NPUTF8* propertyString = createCStringFromNPVariant(&args[i]); + NPIdentifier propertyIdentifier = browser->getstringidentifier(propertyString); + free(propertyString); + + NPVariant variant; + bool retval = browser->getproperty(obj->npp, object, propertyIdentifier, &variant); + browser->releaseobject(object); + + if (!retval) + break; + + if (i + 1 < argCount) { + assert(NPVARIANT_IS_OBJECT(variant)); + object = NPVARIANT_TO_OBJECT(variant); + } else { + *result = variant; + return true; + } + } + + VOID_TO_NPVARIANT(*result); + return false; + } else if (name == pluginMethodIdentifiers[ID_TEST_GET_PROPERTY_RETURN_VALUE] && + argCount == 2 && NPVARIANT_IS_OBJECT(args[0]) && NPVARIANT_IS_STRING(args[1])) { + NPUTF8* propertyString = createCStringFromNPVariant(&args[1]); + NPIdentifier propertyIdentifier = browser->getstringidentifier(propertyString); + free(propertyString); + + NPVariant variant; + bool retval = browser->getproperty(obj->npp, NPVARIANT_TO_OBJECT(args[0]), propertyIdentifier, &variant); + if (retval) + browser->releasevariantvalue(&variant); + + BOOLEAN_TO_NPVARIANT(retval, *result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_CALLBACK_METHOD_RET]) { + // call whatever method name we're given, and pass it the 'window' obj. + // we expect the function to return its argument. + if (argCount > 0 && NPVARIANT_IS_STRING(args[0])) { + NPObject *windowScriptObject; + browser->getvalue(obj->npp, NPNVWindowNPObject, &windowScriptObject); + + NPUTF8* callbackString = createCStringFromNPVariant(&args[0]); + NPIdentifier callbackIdentifier = browser->getstringidentifier(callbackString); + free(callbackString); + + NPVariant callbackArgs[1]; + OBJECT_TO_NPVARIANT(windowScriptObject, callbackArgs[0]); + + NPVariant browserResult; + browser->invoke(obj->npp, windowScriptObject, callbackIdentifier, + callbackArgs, 1, &browserResult); + + if (NPVARIANT_IS_OBJECT(browserResult)) { + // Now return the callbacks return value back to our caller. + // BUG 897451: This should be the same as the + // windowScriptObject, but its not (in Chrome) - or at least, it + // has a different refcount. This means Chrome will delete the + // object before returning it and the calling JS gets a garbage + // value. Firefox handles it fine. + OBJECT_TO_NPVARIANT(NPVARIANT_TO_OBJECT(browserResult), *result); + } else { + browser->releasevariantvalue(&browserResult); + VOID_TO_NPVARIANT(*result); + } + + return true; + } + } else if (name == pluginMethodIdentifiers[ID_TEST_CREATE_TEST_OBJECT]) { + NPObject *testObject = browser->createobject(obj->npp, getTestClass()); + assert(testObject->referenceCount == 1); + OBJECT_TO_NPVARIANT(testObject, *result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_PASS_TEST_OBJECT]) { + // call whatever method name we're given, and pass it our second + // argument. + if (argCount > 1 && NPVARIANT_IS_STRING(args[0])) { + NPObject *windowScriptObject; + browser->getvalue(obj->npp, NPNVWindowNPObject, &windowScriptObject); + + NPUTF8* callbackString = createCStringFromNPVariant(&args[0]); + NPIdentifier callbackIdentifier = browser->getstringidentifier(callbackString); + free(callbackString); + + NPVariant browserResult; + browser->invoke(obj->npp, windowScriptObject, callbackIdentifier, &args[1], 1, &browserResult); + browser->releasevariantvalue(&browserResult); + + VOID_TO_NPVARIANT(*result); + return true; + } + } else if (name == pluginMethodIdentifiers[ID_TEST_CLONE_OBJECT]) { + // Create another instance of the same class + NPObject *new_object = browser->createobject(obj->npp, &pluginClass); + assert(new_object->referenceCount == 1); + OBJECT_TO_NPVARIANT(new_object, *result); + return true; + } else if (name == pluginMethodIdentifiers[ID_TEST_SCRIPT_OBJECT_INVOKE]) { + if (argCount > 1 && NPVARIANT_IS_STRING(args[0])) { + // Invoke a script callback to get a script NPObject. Then call + // a method on the script NPObject passing it a freshly created + // NPObject. + // Arguments: + // arg1: Callback that returns a script object. + // arg2: Name of the method to call on the script object returned + // from the callback + NPObject *windowScriptObject; + browser->getvalue(obj->npp, NPNVWindowNPObject, + &windowScriptObject); + + // Arg1 is the name of the callback + NPUTF8* callbackString = createCStringFromNPVariant(&args[0]); + NPIdentifier callbackIdentifier = + browser->getstringidentifier(callbackString); + free(callbackString); + + // Invoke a callback that returns a script object + NPVariant object_result; + browser->invoke(obj->npp, windowScriptObject, callbackIdentifier, + &args[1], 1, &object_result); + + // Script object returned + NPObject *script_object = object_result.value.objectValue; + + // Arg2 is the name of the method to be called on the script object + NPUTF8* object_mehod_string = createCStringFromNPVariant(&args[1]); + NPIdentifier object_method = + browser->getstringidentifier(object_mehod_string); + free(object_mehod_string); + + // Create a fresh NPObject to be passed as an argument + NPObject *object_arg = browser->createobject(obj->npp, &pluginClass); + NPVariant invoke_args[1]; + OBJECT_TO_NPVARIANT(object_arg, invoke_args[0]); + + // Invoke the script method + NPVariant object_method_result; + browser->invoke(obj->npp, script_object, object_method, + invoke_args, 1, &object_method_result); + + browser->releasevariantvalue(&object_result); + VOID_TO_NPVARIANT(*result); + if (NPVARIANT_IS_OBJECT(object_method_result)) { + // Now return the callbacks return value back to our caller. + // BUG 897451: This should be the same as the + // windowScriptObject, but its not (in Chrome) - or at least, it + // has a different refcount. This means Chrome will delete the + // object before returning it and the calling JS gets a garbage + // value. Firefox handles it fine. + OBJECT_TO_NPVARIANT(NPVARIANT_TO_OBJECT(object_method_result), + *result); + } else { + browser->releasevariantvalue(&object_method_result); + VOID_TO_NPVARIANT(*result); + } + return true; + } + } + return false; +} + +static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + INT32_TO_NPVARIANT(1, *result); + return true; +} + +static void pluginInvalidate(NPObject *obj) +{ +} + +static NPObject *pluginAllocate(NPP npp, NPClass *theClass) +{ + PluginObject *newInstance = (PluginObject*)malloc(sizeof(PluginObject)); + + if (!identifiersInitialized) { + identifiersInitialized = true; + initializeIdentifiers(); + } + + newInstance->npp = npp; + newInstance->testObject = browser->createobject(npp, getTestClass()); + newInstance->eventLogging = FALSE; + newInstance->logDestroy = FALSE; + newInstance->logSetWindow = FALSE; + newInstance->returnErrorFromNewStream = FALSE; + newInstance->stream = 0; + + newInstance->firstUrl = NULL; + newInstance->firstHeaders = NULL; + newInstance->lastUrl = NULL; + newInstance->lastHeaders = NULL; + + return (NPObject *)newInstance; +} + +static void pluginDeallocate(NPObject *header) +{ + PluginObject* obj = (PluginObject*)header; + + browser->releaseobject(obj->testObject); + + free(obj->firstUrl); + free(obj->firstHeaders); + free(obj->lastUrl); + free(obj->lastHeaders); + + free(obj); +} + +void handleCallback(PluginObject* object, const char *url, NPReason reason, void *notifyData) +{ + assert(object); + + NPVariant args[2]; + + NPObject *windowScriptObject; + browser->getvalue(object->npp, NPNVWindowNPObject, &windowScriptObject); + + NPIdentifier callbackIdentifier = notifyData; + + INT32_TO_NPVARIANT(reason, args[0]); + + char *strHdr = NULL; + if (object->firstUrl && object->firstHeaders && object->lastUrl && object->lastHeaders) { + // Format expected by JavaScript validator: four fields separated by \n\n: + // First URL; first header block; last URL; last header block. + // Note that header blocks already end with \n due to how NPStream::headers works. + int len = strlen(object->firstUrl) + 2 + + strlen(object->firstHeaders) + 1 + + strlen(object->lastUrl) + 2 + + strlen(object->lastHeaders) + 1; + strHdr = (char*)malloc(len + 1); + snprintf(strHdr, len + 1, "%s\n\n%s\n%s\n\n%s\n", + object->firstUrl, object->firstHeaders, object->lastUrl, object->lastHeaders); + STRINGN_TO_NPVARIANT(strHdr, len, args[1]); + } else + NULL_TO_NPVARIANT(args[1]); + + NPVariant browserResult; + browser->invoke(object->npp, windowScriptObject, callbackIdentifier, args, 2, &browserResult); + browser->releasevariantvalue(&browserResult); + + free(strHdr); +} + +void notifyStream(PluginObject* object, const char *url, const char *headers) +{ + if (object->firstUrl == NULL) { + if (url) + object->firstUrl = strdup(url); + if (headers) + object->firstHeaders = strdup(headers); + } else { + free(object->lastUrl); + free(object->lastHeaders); + object->lastUrl = (url ? strdup(url) : NULL); + object->lastHeaders = (headers ? strdup(headers) : NULL); + } +} diff --git a/webkit/tools/npapi_layout_test_plugin/PluginObject.h b/webkit/tools/npapi_layout_test_plugin/PluginObject.h new file mode 100644 index 0000000..cacfe41 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/PluginObject.h @@ -0,0 +1,56 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "webkit/glue/plugins/nphostapi.h" + +extern NPNetscapeFuncs *browser; + +typedef struct { + NPObject header; + NPP npp; + NPBool eventLogging; + NPBool logSetWindow; + NPBool logDestroy; + NPBool returnErrorFromNewStream; + NPObject* testObject; + NPStream* stream; + char* onStreamLoad; + char* firstUrl; + char* firstHeaders; + char* lastUrl; + char* lastHeaders; +} PluginObject; + +extern NPClass *getPluginClass(void); +extern void handleCallback(PluginObject* object, const char *url, NPReason reason, void *notifyData); +extern void notifyStream(PluginObject* object, const char *url, const char *headers); diff --git a/webkit/tools/npapi_layout_test_plugin/SConscript b/webkit/tools/npapi_layout_test_plugin/SConscript new file mode 100644 index 0000000..3536bd3 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/SConscript @@ -0,0 +1,100 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +Import('env', 'env_res') + +env = env.Clone() +env_res = env_res.Clone() + +input_files = [ + 'main.cpp', + env_res.RES('npapi_layout_test_plugin.rc'), + 'PluginObject.cpp', + 'TestObject.cpp', + 'npapi_layout_test_plugin.def', +] + +env.Append( + CCFLAGS = [ + '/TP', + '/WX', + '/wd4503', + '/wd4819', + ], + + LIBS = [ + 'advapi32.lib', + 'comctl32.lib', + 'comdlg32.lib', + 'delayimp.lib', + 'gdi32.lib', + 'kernel32.lib', + 'msimg32.lib', + 'odbc32.lib', + 'odbccp32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'psapi.lib', + 'rpcrt4.lib', + 'shell32.lib', + 'shlwapi.lib', + 'user32.lib', + 'usp10.lib', + 'uuid.lib', + 'version.lib', + 'wininet.lib', + 'winmm.lib', + 'winspool.lib', + 'ws2_32.lib', + ], + + LINKFLAGS = [ + '/DELAYLOAD:"dwmapi.dll"', + '/DELAYLOAD:"uxtheme.dll"', + '/FIXED:No', + '/SUBSYSTEM:CONSOLE', + '/MACHINE:X86', + '/safeseh', + '/dynamicbase', + '/ignore:4199', + '/nxcompat', + ], +) + +dll = env.SharedLibrary(['npapi_layout_test_plugin', + 'npapi_layout_test_plugin.lib', + 'npapi_layout_test_plugin.ilk', + 'npapi_layout_test_plugin.pdb'], + input_files) + +i = env.Install('$TARGET_ROOT/plugins', dll) +env.Alias('webkit', i) + +i = env.Install('$TARGET_ROOT', dll) +env.Alias('webkit', i) diff --git a/webkit/tools/npapi_layout_test_plugin/TestObject.cpp b/webkit/tools/npapi_layout_test_plugin/TestObject.cpp new file mode 100644 index 0000000..141061d2 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/TestObject.cpp @@ -0,0 +1,169 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TestObject.h" +#include "PluginObject.h" + +#include <stdlib.h> + +static bool testEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count); +static bool testInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result); +static bool testHasProperty(NPObject *obj, NPIdentifier name); +static bool testGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant); +static NPObject *testAllocate(NPP npp, NPClass *theClass); +static void testDeallocate(NPObject *obj); + +static NPClass testClass = { + NP_CLASS_STRUCT_VERSION, + testAllocate, + testDeallocate, + 0, + 0, + 0, + testInvokeDefault, + testHasProperty, + testGetProperty, + 0, + 0, + testEnumerate +}; + +NPClass *getTestClass(void) +{ + return &testClass; +} + +int testObjectCount = 0; + +int getTestObjectCount(void) { + return testObjectCount; +} + +static bool identifiersInitialized = false; + +#define NUM_TEST_IDENTIFIERS 4 +#define ID_PROPERTY_FOO 0 +#define ID_PROPERTY_BAR 1 +#define ID_PROPERTY_TEST_OBJECT 2 +#define ID_PROPERTY_REF_COUNT 3 + +static NPIdentifier testIdentifiers[NUM_TEST_IDENTIFIERS]; +static const NPUTF8 *testIdentifierNames[NUM_TEST_IDENTIFIERS] = { + "foo", + "bar", + "testObject", + "refCount", +}; + +static void initializeIdentifiers(void) +{ + browser->getstringidentifiers(testIdentifierNames, NUM_TEST_IDENTIFIERS, testIdentifiers); +} + +static NPObject *testAllocate(NPP npp, NPClass *theClass) +{ + TestObject *newInstance = + static_cast<TestObject*>(malloc(sizeof(TestObject))); + newInstance->testObject = NULL; + ++testObjectCount; + + if (!identifiersInitialized) { + identifiersInitialized = true; + initializeIdentifiers(); + } + + return reinterpret_cast<NPObject*>(newInstance); +} + +static void testDeallocate(NPObject *obj) +{ + TestObject *testObject = reinterpret_cast<TestObject*>(obj); + if (testObject->testObject) + browser->releaseobject(testObject->testObject); + --testObjectCount; + free(obj); +} + +static bool testInvokeDefault(NPObject *obj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + INT32_TO_NPVARIANT(2, *result); + return true; +} + +static bool testHasProperty(NPObject *obj, NPIdentifier name) +{ + for (unsigned i = 0; i < NUM_TEST_IDENTIFIERS; i++) { + if (testIdentifiers[i] == name) + return true; + } + + return false; +} + +static bool testGetProperty(NPObject *obj, NPIdentifier name, + NPVariant *variant) +{ + if (name == testIdentifiers[ID_PROPERTY_FOO]) { + char* mem = static_cast<char*>(browser->memalloc(4)); + strcpy(mem, "foo"); + STRINGZ_TO_NPVARIANT(mem, *variant); + return true; + } else if (name == testIdentifiers[ID_PROPERTY_BAR]) { + BOOLEAN_TO_NPVARIANT(true, *variant); + return true; + } else if (name == testIdentifiers[ID_PROPERTY_TEST_OBJECT]) { + TestObject* testObject = reinterpret_cast<TestObject*>(obj); + if (testObject->testObject == NULL) + testObject->testObject = browser->createobject(NULL, &testClass); + browser->retainobject(testObject->testObject); + OBJECT_TO_NPVARIANT(testObject->testObject, *variant); + return true; + } else if (name == testIdentifiers[ID_PROPERTY_REF_COUNT]) { + INT32_TO_NPVARIANT(obj->referenceCount, *variant); + return true; + } + return false; +} + +static bool testEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count) +{ + *count = NUM_TEST_IDENTIFIERS; + + *value = (NPIdentifier*)browser->memalloc(NUM_TEST_IDENTIFIERS * sizeof(NPIdentifier)); + memcpy(*value, testIdentifiers, sizeof(NPIdentifier) * NUM_TEST_IDENTIFIERS); + + return true; +} + + diff --git a/webkit/tools/npapi_layout_test_plugin/TestObject.h b/webkit/tools/npapi_layout_test_plugin/TestObject.h new file mode 100644 index 0000000..fe9839f --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/TestObject.h @@ -0,0 +1,44 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bindings/npapi.h" +#include "bindings/npruntime.h" + + +typedef struct { + NPObject header; + NPObject* testObject; +} TestObject; + +NPClass *getTestClass(void); +int getTestObjectCount(void);
\ No newline at end of file diff --git a/webkit/tools/npapi_layout_test_plugin/main.cpp b/webkit/tools/npapi_layout_test_plugin/main.cpp new file mode 100644 index 0000000..0ad2d6b --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/main.cpp @@ -0,0 +1,346 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "PluginObject.h" + +#ifdef WIN32 +#include <stdio.h> +#include <stdlib.h> +#define strcasecmp _stricmp +#define NPAPI WINAPI +#else +#define NPAPI +#endif + +// Mach-o entry points +extern "C" { + NPError NPAPI NP_Initialize(NPNetscapeFuncs *browserFuncs); + NPError NPAPI NP_GetEntryPoints(NPPluginFuncs *pluginFuncs); + void NPAPI NP_Shutdown(void); +} + +// Mach-o entry points +NPError NPAPI NP_Initialize(NPNetscapeFuncs *browserFuncs) +{ + browser = browserFuncs; + return NPERR_NO_ERROR; +} + +NPError NPAPI NP_GetEntryPoints(NPPluginFuncs *pluginFuncs) +{ + pluginFuncs->version = 11; + pluginFuncs->size = sizeof(pluginFuncs); + pluginFuncs->newp = NPP_New; + pluginFuncs->destroy = NPP_Destroy; + pluginFuncs->setwindow = NPP_SetWindow; + pluginFuncs->newstream = NPP_NewStream; + pluginFuncs->destroystream = NPP_DestroyStream; + pluginFuncs->asfile = NPP_StreamAsFile; + pluginFuncs->writeready = NPP_WriteReady; + pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write; + pluginFuncs->print = NPP_Print; + pluginFuncs->event = NPP_HandleEvent; + pluginFuncs->urlnotify = NPP_URLNotify; + pluginFuncs->getvalue = NPP_GetValue; + pluginFuncs->setvalue = NPP_SetValue; + + return NPERR_NO_ERROR; +} + +void NPAPI NP_Shutdown(void) +{ +} + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char *argn[], char *argv[], NPSavedData *saved) +{ + if (browser->version >= 14) { + PluginObject* obj = (PluginObject*)browser->createobject(instance, getPluginClass()); + + obj->onStreamLoad = NULL; + + for (int i = 0; i < argc; i++) { + if (strcasecmp(argn[i], "onstreamload") == 0 && !obj->onStreamLoad) + obj->onStreamLoad = strdup(argv[i]); + else if (strcasecmp(argn[i], "src") == 0 && + strcasecmp(argv[i], "data:application/x-webkit-test-netscape,returnerrorfromnewstream") == 0) + obj->returnErrorFromNewStream = TRUE; + else if (strcasecmp(argn[i], "logfirstsetwindow") == 0) + obj->logSetWindow = TRUE; + } + + instance->pdata = obj; + } + + // On Windows and Unix, plugins only get events if they are windowless. + return browser->setvalue(instance, NPPVpluginWindowBool, NULL); +} + +NPError NPP_Destroy(NPP instance, NPSavedData **save) +{ + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + if (obj) { + if (obj->onStreamLoad) + free(obj->onStreamLoad); + + if (obj->logDestroy) + printf("PLUGIN: NPP_Destroy\n"); + + browser->releaseobject(&obj->header); + } + + fflush(stdout); + + return NPERR_NO_ERROR; +} + +NPError NPP_SetWindow(NPP instance, NPWindow *window) +{ + if (window->window == NULL) { + return NPERR_NO_ERROR; + } + + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + + if (obj) { + if (obj->logSetWindow) { + printf("PLUGIN: NPP_SetWindow: %d %d\n", (int)window->width, (int)window->height); + obj->logSetWindow = false; + } + } + + return NPERR_NO_ERROR; +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream, NPBool seekable, uint16 *stype) +{ + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + obj->stream = stream; + *stype = NP_ASFILEONLY; + + if (obj->returnErrorFromNewStream) + return NPERR_GENERIC_ERROR; + + if (browser->version >= NPVERS_HAS_RESPONSE_HEADERS) + notifyStream(obj, stream->url, stream->headers); + + if (obj->onStreamLoad) { + NPObject *windowScriptObject; + browser->getvalue(obj->npp, NPNVWindowNPObject, &windowScriptObject); + + NPString script; + script.UTF8Characters = obj->onStreamLoad; + script.UTF8Length = strlen(obj->onStreamLoad); + + NPVariant browserResult; + browser->evaluate(obj->npp, windowScriptObject, &script, &browserResult); + browser->releasevariantvalue(&browserResult); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream *stream, NPReason reason) +{ + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + obj->stream = 0; + + return NPERR_NO_ERROR; +} + +int32 NPP_WriteReady(NPP instance, NPStream *stream) +{ + return 0; +} + +int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, int32 len, void *buffer) +{ + return 0; +} + +void NPP_StreamAsFile(NPP instance, NPStream *stream, const char *fname) +{ +} + +void NPP_Print(NPP instance, NPPrint *platformPrint) +{ +} + +int16 NPP_HandleEvent(NPP instance, void *event) +{ + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + if (!obj->eventLogging) + return 0; + +#ifdef WIN32 + // Below is the event handling code. Per the NPAPI spec, the events don't + // map directly between operating systems: + // http://devedge-temp.mozilla.org/library/manuals/2002/plugin/1.0/structures5.html#1000000 + NPEvent* evt = static_cast<NPEvent*>(event); + short x = static_cast<short>(evt->lParam & 0xffff); + short y = static_cast<short>(evt->lParam >> 16); + switch (evt->event) { + case WM_PAINT: + printf("PLUGIN: updateEvt\n"); + break; + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + printf("PLUGIN: mouseDown at (%d, %d)\n", x, y); + break; + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + printf("PLUGIN: mouseUp at (%d, %d)\n", x, y); + break; + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + break; + case WM_MOUSEMOVE: + printf("PLUGIN: adjustCursorEvent\n"); + break; + case WM_KEYUP: + // TODO(tc): We need to convert evt->wParam from virtual-key code + // to key code. + printf("NOTIMPLEMENTED PLUGIN: keyUp '%c'\n", ' '); + break; + case WM_KEYDOWN: + // TODO(tc): We need to convert evt->wParam from virtual-key code + // to key code. + printf("NOTIMPLEMENTED PLUGIN: keyDown '%c'\n", ' '); + break; + case WM_SETCURSOR: + break; + case WM_SETFOCUS: + printf("PLUGIN: getFocusEvent\n"); + break; + case WM_KILLFOCUS: + printf("PLUGIN: loseFocusEvent\n"); + break; + default: + printf("PLUGIN: event %d\n", evt->event); + } + + fflush(stdout); + +#else + EventRecord* evt = static_cast<EventRecord*>(event); + Point pt = { evt->where.v, evt->where.h }; + switch (evt->what) { + case nullEvent: + // these are delivered non-deterministically, don't log. + break; + case mouseDown: + GlobalToLocal(&pt); + printf("PLUGIN: mouseDown at (%d, %d)\n", pt.h, pt.v); + break; + case mouseUp: + GlobalToLocal(&pt); + printf("PLUGIN: mouseUp at (%d, %d)\n", pt.h, pt.v); + break; + case keyDown: + printf("PLUGIN: keyDown '%c'\n", (char)(evt->message & 0xFF)); + break; + case keyUp: + printf("PLUGIN: keyUp '%c'\n", (char)(evt->message & 0xFF)); + break; + case autoKey: + printf("PLUGIN: autoKey '%c'\n", (char)(evt->message & 0xFF)); + break; + case updateEvt: + printf("PLUGIN: updateEvt\n"); + break; + case diskEvt: + printf("PLUGIN: diskEvt\n"); + break; + case activateEvt: + printf("PLUGIN: activateEvt\n"); + break; + case osEvt: + printf("PLUGIN: osEvt - "); + switch ((evt->message & 0xFF000000) >> 24) { + case suspendResumeMessage: + printf("%s\n", (evt->message & 0x1) ? "resume" : "suspend"); + break; + case mouseMovedMessage: + printf("mouseMoved\n"); + break; + default: + printf("%08lX\n", evt->message); + } + break; + case kHighLevelEvent: + printf("PLUGIN: kHighLevelEvent\n"); + break; + // NPAPI events + case getFocusEvent: + printf("PLUGIN: getFocusEvent\n"); + break; + case loseFocusEvent: + printf("PLUGIN: loseFocusEvent\n"); + break; + case adjustCursorEvent: + printf("PLUGIN: adjustCursorEvent\n"); + break; + default: + printf("PLUGIN: event %d\n", evt->what); + } +#endif + + return 0; +} + +void NPP_URLNotify(NPP instance, const char *url, NPReason reason, void *notifyData) +{ + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + + handleCallback(obj, url, reason, notifyData); +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value) +{ + if (variable == NPPVpluginScriptableNPObject) { + void **v = (void **)value; + PluginObject* obj = static_cast<PluginObject*>(instance->pdata); + // Return value is expected to be retained + browser->retainobject((NPObject *)obj); + *v = obj; + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value) +{ + return NPERR_GENERIC_ERROR; +} diff --git a/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.def b/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.def new file mode 100644 index 0000000..dff1e6a --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.def @@ -0,0 +1,6 @@ +LIBRARY npapi_layout_test_plugin + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 diff --git a/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.rc b/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.rc new file mode 100644 index 0000000..080b7a8 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.rc @@ -0,0 +1,104 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "FileDescription", "Simple Netscape plug-in that handles test content for WebKit" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "npapi_te" + VALUE "LegalCopyright", "Copyright (C) 2007" + VALUE "MIMEType", "application/x-webkit-test-netscape" + VALUE "FileExtents", "testnetscape" + VALUE "FileOpenName", "test netscape content" + VALUE "OriginalFilename", "npapi_te.dll" + VALUE "ProductName", "WebKit Test PlugIn" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.vcproj b/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.vcproj new file mode 100644 index 0000000..ffcd474 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/npapi_layout_test_plugin.vcproj @@ -0,0 +1,178 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="npapi_layout_test_plugin" + ProjectGUID="{BE6D5659-A8D5-4890-A42C-090DD10EF62C}" + RootNamespace="npapi_layout_test_plugin" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="2" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;..\..\build\webkit_common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="winmm.lib" + ModuleDefinitionFile="npapi_layout_test_plugin.def" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="xcopy.exe /Y /F $(TargetPath) $(TargetDir)\plugins\" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="2" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;..\..\build\webkit_common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="winmm.lib" + ModuleDefinitionFile="npapi_layout_test_plugin.def" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="xcopy.exe /Y /F $(TargetPath) $(TargetDir)\plugins\" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\main.cpp" + > + </File> + <File + RelativePath=".\npapi_layout_test_plugin.rc" + > + </File> + <File + RelativePath=".\PluginObject.cpp" + > + </File> + <File + RelativePath=".\PluginObject.h" + > + </File> + <File + RelativePath=".\resource.h" + > + </File> + <File + RelativePath=".\TestObject.cpp" + > + </File> + <File + RelativePath=".\TestObject.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/webkit/tools/npapi_layout_test_plugin/resource.h b/webkit/tools/npapi_layout_test_plugin/resource.h new file mode 100644 index 0000000..c2bcf92 --- /dev/null +++ b/webkit/tools/npapi_layout_test_plugin/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by npapi_layout_test_plugin.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/webkit/tools/test_shell/SConscript b/webkit/tools/test_shell/SConscript new file mode 100644 index 0000000..3509a0c --- /dev/null +++ b/webkit/tools/test_shell/SConscript @@ -0,0 +1,227 @@ +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +# OWNER OR 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. + +Import('env', 'env_res') + +env = env.Clone() +env_res = env_res.Clone() + +env_res.Append( + CPPPATH = [ + '.', + '#/..', + '$NET_DIR', + ], + RCFLAGS = [ + ['/l', '0x409'], + ], +) + +input_files = [ + 'drag_delegate.cc', + 'drop_delegate.cc', + 'event_sending_controller.cc', + 'layout_test_controller.cc', + 'simple_resource_loader_bridge.cc', + 'test_navigation_controller.cc', + 'test_shell.cc', + 'test_shell_switches.cc', + 'test_shell_request_context.cc', + 'test_webview_delegate.cc', + 'text_input_controller.cc', + 'webview_host.cc', + 'webwidget_host.cc', + 'temp/navigation_controller_base.cc', +] + +env.Append( + CPPPATH = [ + '$BREAKPAD_DIR/src', + '$WEBKIT_DIR/glue', + '$GTEST_DIR/include', + ], + CCFLAGS = [ + '/TP', + '/WX', + '/Wp64', + '/wd4503', + '/wd4819', + ], + + LIBS = [ + 'comctl32.lib', + 'shlwapi.lib', + 'rpcrt4.lib', + 'winmm.lib', + 'wininet.lib', + 'version.lib', + 'msimg32.lib', + 'ws2_32.lib', + 'usp10.lib', + 'psapi.lib', + 'kernel32.lib', + 'user32.lib', + 'gdi32.lib', + 'winspool.lib', + 'comdlg32.lib', + 'advapi32.lib', + 'shell32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'odbc32.lib', + 'odbccp32.lib', + + 'delayimp.lib', + ], + + LINKFLAGS = [ + '/DELAYLOAD:"ws2_32.dll"', + '/DELAYLOAD:"dwmapi.dll"', + '/DELAYLOAD:"uxtheme.dll"', + '/FIXED:No', + '/SUBSYSTEM:CONSOLE', + '/MACHINE:X86', + '/safeseh', + '/dynamicbase', + '/ignore:4199', + '/nxcompat', + ], +) + +lib = env.Library('test_shell', input_files) + + + + +resources = [ + env_res.RES('resources/test_shell.rc'), + '$NET_DIR/net_resources.res', + '$WEBKIT_DIR/build/localized_strings/webkit_strings_en-US.res', +] + +components = [ + lib, + + '$BASE_DIR/base.lib', + '$BASE_DIR/gfx/base_gfx.lib', + '$BREAKPAD_DIR/breakpad_handler.lib', + '$BREAKPAD_DIR/breakpad_sender.lib', + '$GOOGLEURL_DIR/googleurl.lib', + '$NET_DIR/net.lib', + '$SKIA_DIR/skia.lib', + '$TESTING_DIR/gtest.lib', + '$BZIP2_DIR/bzip2.lib', + '$ICU38_DIR/icuuc.lib', + '$LIBJPEG_DIR/libjpeg.lib', + '$LIBPNG_DIR/libpng.lib', + '$LIBXML_DIR/libxml.lib', + '$LIBXSLT_DIR/libxslt.lib', + '$MODP_B64_DIR/modp_b64.lib', + '$ZLIB_DIR/zlib.lib', + '$V8_DIR/v8.lib', + '$V8_DIR/snapshot-empty.obj', + '$WEBKIT_DIR/JavaScriptCore_pcre.lib', + '$WEBKIT_DIR/Port.lib', + '$WEBKIT_DIR/activex_shim/activex_shim.lib', + '$WEBKIT_DIR/build/JavaScriptCore/WTF.lib', + '$WEBKIT_DIR/build/V8Bindings/V8Bindings.lib', + '$WEBKIT_DIR/build/WebCore/WebCore.lib', + '$WEBKIT_DIR/default_plugin/default_plugin.lib', + '$WEBKIT_DIR/glue/Glue.lib', +] + +test_shell = env.Program(['test_shell.exe', + 'test_shell.ilk', + 'test_shell.pdb'], + components + resources + + ['test_shell_main.cc']) +i = env.Install('$TARGET_ROOT', test_shell) +env.Alias('webkit', i) + +# This call can NOT use $WEBKIT_DIR because we need to copy +# directly from the source location. +i = env.Install('$TARGET_ROOT', '#/../webkit/tools/test_shell/resources/fonts') +env.Alias('webkit', i) + +env.Depends(test_shell, '$V8_DIR/vc80.pdb') + + +test_files = [ + 'drag_delegate.cc', + 'drop_delegate.cc', + 'event_sending_controller.cc', + 'image_decoder_unittest.cc', + 'keyboard_unittest.cc', + 'layout_test_controller.cc', + 'layout_test_controller_unittest.cc', + 'node_leak_test.cc', + 'plugin_tests.cc', + 'run_all_tests.cc', + 'simple_resource_loader_bridge.cc', + 'temp/navigation_controller_base.cc', + 'test_navigation_controller.cc', + 'test_shell.cc', + 'test_shell_request_context.cc', + 'test_shell_switches.cc', + 'test_shell_test.cc', + 'test_webview_delegate.cc', + 'text_input_controller.cc', + 'text_input_controller_unittest.cc', + 'webview_host.cc', + 'webwidget_host.cc', + '$WEBKIT_DIR/glue/autocomplete_input_listener_unittest.cc', + '$WEBKIT_DIR/glue/bookmarklet_unittest.cc', + '$WEBKIT_DIR/glue/context_menu_unittest.cc', + '$WEBKIT_DIR/glue/cpp_bound_class_unittest.cc', + '$WEBKIT_DIR/glue/cpp_variant_unittest.cc', + '$WEBKIT_DIR/glue/dom_operations_unittest.cc', + '$WEBKIT_DIR/glue/dom_serializer_unittest.cc', + '$WEBKIT_DIR/glue/glue_serialize_unittest.cc', + '$WEBKIT_DIR/glue/iframe_redirect_unittest.cc', + '$WEBKIT_DIR/glue/mimetype_unittest.cc', + '$WEBKIT_DIR/glue/multipart_response_delegate_unittest.cc', + '$WEBKIT_DIR/glue/password_autocomplete_listener_unittest.cc', + '$WEBKIT_DIR/glue/regular_expression_unittest.cc', + '$WEBKIT_DIR/glue/resource_fetcher_unittest.cc', + # Commented out until a regression is fixed and this file is restored. + #'$WEBKIT_DIR/glue/stringimpl_unittest.cc', + '$WEBKIT_DIR/glue/webplugin_impl_unittest.cc', + '$WEBKIT_DIR/port/platform/GKURL_unittest.cpp', + '$WEBKIT_DIR/port/platform/image-decoders/bmp/BMPImageDecoder_unittest.cpp', + '$WEBKIT_DIR/port/platform/image-decoders/ico/ICOImageDecoder_unittest.cpp', + '$WEBKIT_DIR/port/platform/image-decoders/xbm/XBMImageDecoder_unittest.cpp', +] + +test_shell_tests = env.Program(['test_shell_tests', + 'test_shell_tests.ilk', + 'test_shell_tests.pdb'], + components + resources + test_files) +i = env.Install('$TARGET_ROOT', test_shell_tests) +env.Alias('webkit', i) diff --git a/webkit/tools/test_shell/drag_delegate.cc b/webkit/tools/test_shell/drag_delegate.cc new file mode 100644 index 0000000..f0d01a8 --- /dev/null +++ b/webkit/tools/test_shell/drag_delegate.cc @@ -0,0 +1,65 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/drag_delegate.h" + +#include <atltypes.h> + +#include "webkit/glue/webview.h" + +namespace { + +void GetCursorPositions(HWND hwnd, CPoint* client, CPoint* screen) { + // GetCursorPos will fail if the input desktop isn't the current desktop. + // See http://b/1173534. (0,0) is wrong, but better than uninitialized. + if (!GetCursorPos(screen)) + screen->SetPoint(0, 0); + + *client = *screen; + ScreenToClient(hwnd, client); +} + +} // anonymous namespace + +void TestDragDelegate::OnDragSourceCancel() { + OnDragSourceDrop(); +} + +void TestDragDelegate::OnDragSourceDrop() { + CPoint client; + CPoint screen; + GetCursorPositions(source_hwnd_, &client, &screen); + webview_->DragSourceEndedAt(client.x, client.y, screen.x, screen.y); +} +void TestDragDelegate::OnDragSourceMove() { + CPoint client; + CPoint screen; + GetCursorPositions(source_hwnd_, &client, &screen); + webview_->DragSourceMovedTo(client.x, client.y, screen.x, screen.y); +} diff --git a/webkit/tools/test_shell/drag_delegate.h b/webkit/tools/test_shell/drag_delegate.h new file mode 100644 index 0000000..2872cdf --- /dev/null +++ b/webkit/tools/test_shell/drag_delegate.h @@ -0,0 +1,60 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. +// +// A class that implements BaseDragSource for the test shell webview delegate. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_DRAG_DELEGATE_H__ +#define WEBKIT_TOOLS_TEST_SHELL_DRAG_DELEGATE_H__ + +#include "base/base_drag_source.h" + +class WebView; + +class TestDragDelegate : public BaseDragSource { + public: + TestDragDelegate(HWND source_hwnd, WebView* webview) + : BaseDragSource(), + source_hwnd_(source_hwnd), + webview_(webview) { } + + protected: + // BaseDragSource + virtual void OnDragSourceCancel(); + virtual void OnDragSourceDrop(); + virtual void OnDragSourceMove(); + + private: + WebView* webview_; + + // A HWND for the source we are associated with, used for translating + // mouse coordinates from screen to client coordinates. + HWND source_hwnd_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_DRAG_DELEGATE_H__ diff --git a/webkit/tools/test_shell/drop_delegate.cc b/webkit/tools/test_shell/drop_delegate.cc new file mode 100644 index 0000000..0463190 --- /dev/null +++ b/webkit/tools/test_shell/drop_delegate.cc @@ -0,0 +1,76 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/drop_delegate.h" + +#include "webkit/glue/webdropdata.h" +#include "webkit/glue/webview.h" + +// BaseDropTarget methods ---------------------------------------------------- +DWORD TestDropDelegate::OnDragEnter(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + WebDropData drop_data; + WebDropData::PopulateWebDropData(data_object, &drop_data); + + POINT client_pt = cursor_position; + ScreenToClient(GetHWND(), &client_pt); + bool valid = webview_->DragTargetDragEnter(drop_data, client_pt.x, + client_pt.y, cursor_position.x, cursor_position.y); + return valid ? DROPEFFECT_COPY : DROPEFFECT_NONE; +} + +DWORD TestDropDelegate::OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + POINT client_pt = cursor_position; + ScreenToClient(GetHWND(), &client_pt); + bool valid = webview_->DragTargetDragOver(client_pt.x, + client_pt.y, cursor_position.x, cursor_position.y); + return valid ? DROPEFFECT_COPY : DROPEFFECT_NONE; +} + +void TestDropDelegate::OnDragLeave(IDataObject* data_object) { + webview_->DragTargetDragLeave(); +} + +DWORD TestDropDelegate::OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + POINT client_pt = cursor_position; + ScreenToClient(GetHWND(), &client_pt); + webview_->DragTargetDrop(client_pt.x, client_pt.y, + cursor_position.x, cursor_position.y); + + // webkit win port always returns DROPEFFECT_NONE + return DROPEFFECT_NONE; +} diff --git a/webkit/tools/test_shell/drop_delegate.h b/webkit/tools/test_shell/drop_delegate.h new file mode 100644 index 0000000..e2fa493 --- /dev/null +++ b/webkit/tools/test_shell/drop_delegate.h @@ -0,0 +1,66 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. +// +// A class that implements BaseDropTarget for the test shell webview delegate. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_DROP_DELEGATE_H__ +#define WEBKIT_TOOLS_TEST_SHELL_DROP_DELEGATE_H__ + +#include "base/base_drop_target.h" + +class WebView; + +class TestDropDelegate : public BaseDropTarget { + public: + TestDropDelegate(HWND source_hwnd, WebView* webview) + : BaseDropTarget(source_hwnd), + webview_(webview) { } + + protected: + // BaseDropTarget methods + virtual DWORD OnDragEnter(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + virtual DWORD OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + virtual void OnDragLeave(IDataObject* data_object); + virtual DWORD OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + + private: + WebView* webview_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_DROP_DELEGATE_H__ diff --git a/webkit/tools/test_shell/event_sending_controller.cc b/webkit/tools/test_shell/event_sending_controller.cc new file mode 100644 index 0000000..dee5eaf --- /dev/null +++ b/webkit/tools/test_shell/event_sending_controller.cc @@ -0,0 +1,511 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// This file contains the definition for EventSendingController. +// +// Some notes about drag and drop handling: +// Windows drag and drop goes through a system call to DoDragDrop. At that +// point, program control is given to Windows which then periodically makes +// callbacks into the webview. This won't work for layout tests, so instead, +// we queue up all the mouse move and mouse up events. When the test tries to +// start a drag (by calling EvenSendingController::DoDragDrop), we take the +// events in the queue and replay them. +// The behavior of queueing events and replaying them can be disabled by a +// layout test by setting eventSender.dragMode to false. + +#include "webkit/tools/test_shell/event_sending_controller.h" + +#include <objidl.h> +#include <queue> + +#include "base/ref_counted.h" +#include "base/string_util.h" +#include "webkit/glue/webview.h" +#include "webkit/tools/test_shell/test_shell.h" + +// TODO(mpcomplete): layout before each event? +// TODO(mpcomplete): do we need modifiers for mouse events? + +TestShell* EventSendingController::shell_ = NULL; +gfx::Point EventSendingController::last_mouse_pos_; +WebMouseEvent::Button EventSendingController::pressed_button_ = + WebMouseEvent::BUTTON_NONE; + +namespace { + +static scoped_refptr<IDataObject> drag_data_object; +static bool replaying_saved_events = false; +static std::queue<WebMouseEvent> mouse_event_queue; + +// Time and place of the last mouse up event. +static double last_click_time_sec = 0; +static gfx::Point last_click_pos; +static int click_count = 0; + +// maximum distance (in space and time) for a mouse click +// to register as a double or triple click +static const double kMultiClickTimeSec = 1; +static const int kMultiClickRadiusPixels = 5; + +inline bool outside_multiclick_radius(const gfx::Point &a, const gfx::Point &b) { + return ((a.x() - b.x()) * (a.x() - b.x()) + (a.y() - b.y()) * (a.y() - b.y())) > + kMultiClickRadiusPixels * kMultiClickRadiusPixels; +} + +// Used to offset the time the event hander things an event happened. This is +// done so tests can run without a delay, but bypass checks that are time +// dependent (e.g., dragging has a timeout vs selection). +static uint32 time_offset_ms = 0; + +double GetCurrentEventTimeSec() { + return (GetTickCount() + time_offset_ms) / 1000.0; +} + +void AdvanceEventTime(int32 delta_ms) { + time_offset_ms += delta_ms; +} + +void InitMouseEvent(WebInputEvent::Type t, WebMouseEvent::Button b, + const gfx::Point& pos, WebMouseEvent* e) { + e->type = t; + e->button = b; + e->modifiers = 0; + e->x = pos.x(); + e->y = pos.y(); + e->global_x = pos.x(); + e->global_y = pos.y(); + e->timestamp_sec = GetCurrentEventTimeSec(); + e->layout_test_click_count = click_count; +} + +void ApplyKeyModifiers(const CppVariant* arg, WebKeyboardEvent* event) { + std::vector<std::wstring> args = arg->ToStringVector(); + for (std::vector<std::wstring>::iterator i = args.begin(); + i != args.end(); ++i) { + const wchar_t* arg_string = (*i).c_str(); + if (!wcscmp(arg_string, L"ctrlKey")) { + event->modifiers |= WebInputEvent::CTRL_KEY; + } else if (!wcscmp(arg_string, L"shiftKey")) { + event->modifiers |= WebInputEvent::SHIFT_KEY; + } else if (!wcscmp(arg_string, L"altKey")) { + event->modifiers |= WebInputEvent::ALT_KEY; + event->system_key = true; + } else if (!wcscmp(arg_string, L"metaKey")) { + event->modifiers |= WebInputEvent::META_KEY; + } + } +} + +} // anonymous namespace + +EventSendingController::EventSendingController(TestShell* shell) { + // Set static shell_ variable since we can't do it in an initializer list. + // We also need to be careful not to assign shell_ to new windows which are + // temporary. + if (NULL == shell_) + shell_ = shell; + + // Initialize the map that associates methods of this class with the names + // they will use when called by JavaScript. The actual binding of those + // names to their methods will be done by calling BindToJavaScript() (defined + // by CppBoundClass, the parent to EventSendingController). + BindMethod("mouseDown", &EventSendingController::mouseDown); + BindMethod("mouseUp", &EventSendingController::mouseUp); + BindMethod("contextClick", &EventSendingController::contextClick); + BindMethod("mouseMoveTo", &EventSendingController::mouseMoveTo); + BindMethod("leapForward", &EventSendingController::leapForward); + BindMethod("keyDown", &EventSendingController::keyDown); + BindMethod("enableDOMUIEventLogging", &EventSendingController::enableDOMUIEventLogging); + BindMethod("fireKeyboardEventsToElement", &EventSendingController::fireKeyboardEventsToElement); + BindMethod("clearKillRing", &EventSendingController::clearKillRing); + BindMethod("textZoomIn", &EventSendingController::textZoomIn); + BindMethod("textZoomOut", &EventSendingController::textZoomOut); + + // When set to true (the default value), we batch mouse move and mouse up + // events so we can simulate drag & drop. + BindProperty("dragMode", &dragMode); +} + +void EventSendingController::Reset() { + // The test should have finished a drag and the mouse button state. + DCHECK(!drag_data_object); + drag_data_object = NULL; + pressed_button_ = WebMouseEvent::BUTTON_NONE; + dragMode.Set(true); + last_click_time_sec = 0; + click_count = 0; +} + +/* static */ WebView* EventSendingController::webview() { + return shell_->webView(); +} + +/* static */ void EventSendingController::DoDragDrop(IDataObject* data_obj) { + drag_data_object = data_obj; + + DWORD effect = 0; + POINTL screen_ptl = {0, 0}; + TestWebViewDelegate* delegate = shell_->delegate(); + delegate->drop_delegate()->DragEnter(drag_data_object, MK_LBUTTON, + screen_ptl, &effect); + + // Finish processing events. + ReplaySavedEvents(); +} + +// +// Implemented javascript methods. +// + + void EventSendingController::mouseDown( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + webview()->Layout(); + + if ((GetCurrentEventTimeSec() - last_click_time_sec >= kMultiClickTimeSec) || + outside_multiclick_radius(last_mouse_pos_, last_click_pos)) { + click_count = 1; + } else { + ++click_count; + } + + WebMouseEvent event; + pressed_button_ = WebMouseEvent::BUTTON_LEFT; + InitMouseEvent(WebInputEvent::MOUSE_DOWN, WebMouseEvent::BUTTON_LEFT, + last_mouse_pos_, &event); + webview()->HandleInputEvent(&event); + +} + + void EventSendingController::mouseUp( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + webview()->Layout(); + + WebMouseEvent event; + InitMouseEvent(WebInputEvent::MOUSE_UP, WebMouseEvent::BUTTON_LEFT, + last_mouse_pos_, &event); + if (drag_mode() && !replaying_saved_events) { + mouse_event_queue.push(event); + ReplaySavedEvents(); + } else { + DoMouseUp(event); + } + + last_click_time_sec = event.timestamp_sec; + last_click_pos = gfx::Point(event.x, event.y); +} + +/* static */ void EventSendingController::DoMouseUp(const WebMouseEvent& e) { + webview()->HandleInputEvent(&e); + pressed_button_ = WebMouseEvent::BUTTON_NONE; + + // If we're in a drag operation, complete it. + if (drag_data_object) { + TestWebViewDelegate* delegate = shell_->delegate(); + // Get screen mouse position. + POINT screen_pt = { static_cast<LONG>(e.x), + static_cast<LONG>(e.y) }; + ClientToScreen(shell_->webViewWnd(), &screen_pt); + POINTL screen_ptl = { screen_pt.x, screen_pt.y }; + + DWORD effect = 0; + delegate->drop_delegate()->DragOver(0, screen_ptl, &effect); + HRESULT hr = delegate->drag_delegate()->QueryContinueDrag(0, 0); + if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) { + DWORD effect = 0; + delegate->drop_delegate()->Drop(drag_data_object.get(), 0, screen_ptl, + &effect); + } else { + delegate->drop_delegate()->DragLeave(); + } + drag_data_object = NULL; + } +} + + void EventSendingController::mouseMoveTo( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + if (args.size() >= 2 && args[0].isNumber() && args[1].isNumber()) { + webview()->Layout(); + + WebMouseEvent event; + last_mouse_pos_.SetPoint(args[0].ToInt32(), args[1].ToInt32()); + InitMouseEvent(WebInputEvent::MOUSE_MOVE, pressed_button_, + last_mouse_pos_, &event); + + if (drag_mode() && pressed_button_ != WebMouseEvent::BUTTON_NONE && + !replaying_saved_events) { + mouse_event_queue.push(event); + } else { + DoMouseMove(event); + } + } +} + +/* static */ void EventSendingController::DoMouseMove(const WebMouseEvent& e) { + webview()->HandleInputEvent(&e); + + if (pressed_button_ != WebMouseEvent::BUTTON_NONE && drag_data_object) { + TestWebViewDelegate* delegate = shell_->delegate(); + // Get screen mouse position. + POINT screen_pt = { static_cast<LONG>(e.x), + static_cast<LONG>(e.y) }; + ClientToScreen(shell_->webViewWnd(), &screen_pt); + POINTL screen_ptl = { screen_pt.x, screen_pt.y }; + + HRESULT hr = delegate->drag_delegate()->QueryContinueDrag(0, MK_LBUTTON); + DWORD effect = 0; + delegate->drop_delegate()->DragOver(MK_LBUTTON, screen_ptl, &effect); + + delegate->drag_delegate()->GiveFeedback(effect); + } +} + + void EventSendingController::keyDown( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + static const int kPercentVirtualKeyCode = 0x25; + static const int kAmpersandVirtualKeyCode = 0x26; + + static const int kLeftParenthesesVirtualKeyCode = 0x28; + static const int kRightParenthesesVirtualKeyCode = 0x29; + + static const int kLeftCurlyBracketVirtualKeyCode = 0x7B; + static const int kRightCurlyBracketVirtualKeyCode = 0x7D; + + bool generate_char = false; + + if (args.size() >= 1 && args[0].isString()) { + // TODO(mpcomplete): I'm not exactly sure how we should convert the string + // to a key event. This seems to work in the cases I tested. + // TODO(mpcomplete): Should we also generate a KEY_UP? + std::wstring code_str = UTF8ToWide(args[0].ToString()); + + // Convert \n -> VK_RETURN. Some layout tests use \n to mean "Enter", when + // Windows uses \r for "Enter". + wchar_t code; + bool needs_shift_key_modifier = false; + if (L"\n" == code_str) { + generate_char = true; + code = VK_RETURN; + } else if (L"rightArrow" == code_str) { + code = VK_RIGHT; + } else if (L"downArrow" == code_str) { + code = VK_DOWN; + } else if (L"leftArrow" == code_str) { + code = VK_LEFT; + } else if (L"upArrow" == code_str) { + code = VK_UP; + } else if (L"delete" == code_str) { + code = VK_BACK; + } else { + DCHECK(code_str.length() == 1); + code = code_str[0]; + needs_shift_key_modifier = NeedsShiftModifer(code); + generate_char = true; + } + + // NOTE(jnd):For one keydown event, we need to generate + // keyDown/keyUp pair, refer EventSender.cpp in + // WebKit/WebKitTools/DumpRenderTree/win. We may also need + // to generate a keyChar event in certain cases. + WebKeyboardEvent event_down, event_up; + event_down.type = WebInputEvent::KEY_DOWN; + event_down.modifiers = 0; + event_down.key_code = code; + event_down.key_data = code; + + if (args.size() >= 2 && args[1].isObject()) + ApplyKeyModifiers(&(args[1]), &event_down); + + if (needs_shift_key_modifier) + event_down.modifiers |= WebInputEvent::SHIFT_KEY; + + event_up = event_down; + event_up.type = WebInputEvent::KEY_UP; + // EventSendingController.m forces a layout here, with at least one + // test (fast\forms\focus-control-to-page.html) relying on this. + webview()->Layout(); + + webview()->HandleInputEvent(&event_down); + + if (generate_char) { + WebKeyboardEvent event_char = event_down; + if (event_down.modifiers & WebInputEvent::SHIFT_KEY) { + // Special case for the following characters when the shift key is + // pressed in conjunction with these characters. + // Windows generates a WM_KEYDOWN message with the ASCII code of + // the character followed by a WM_CHAR for the corresponding + // virtual key code. + // We check for these keys to catch regressions in keyEvent handling + // in webkit. + switch(code) { + case '5': + event_char.key_code = kPercentVirtualKeyCode; + event_char.key_data = kPercentVirtualKeyCode; + break; + case '7': + event_char.key_code = kAmpersandVirtualKeyCode; + event_char.key_data = kAmpersandVirtualKeyCode; + break; + case '9': + event_char.key_code = kLeftParenthesesVirtualKeyCode; + event_char.key_data = kLeftParenthesesVirtualKeyCode; + break; + case '0': + event_char.key_code = kRightParenthesesVirtualKeyCode; + event_char.key_data = kRightParenthesesVirtualKeyCode; + break; + // '[{' for US + case VK_OEM_4: + event_char.key_code = kLeftCurlyBracketVirtualKeyCode; + event_char.key_data = kLeftCurlyBracketVirtualKeyCode; + break; + // ']}' for US + case VK_OEM_6: + event_char.key_code = kRightCurlyBracketVirtualKeyCode; + event_char.key_data = kRightCurlyBracketVirtualKeyCode; + break; + default: + break; + } + } + event_char.type = WebInputEvent::CHAR; + webview()->HandleInputEvent(&event_char); + } + + webview()->HandleInputEvent(&event_up); + } +} + + bool EventSendingController::NeedsShiftModifer(wchar_t key_code) { + // If code is an uppercase letter, assign a SHIFT key to + // event_down.modifier, this logic comes from + // WebKit/WebKitTools/DumpRenderTree/Win/EventSender.cpp + if ((LOBYTE(key_code)) >= 'A' && (LOBYTE(key_code)) <= 'Z') + return true; + return false; + } + + void EventSendingController::leapForward( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + // TODO(mpcomplete): DumpRenderTree defers this under certain conditions. + + if (args.size() >=1 && args[0].isNumber()) { + AdvanceEventTime(args[0].ToInt32()); + } +} + +// Apple's port of webkit zooms by a factor of 1.2 (see +// WebKit/WebView/WebView.mm) + void EventSendingController::textZoomIn( + const CppArgumentList& args, CppVariant* result) { + webview()->MakeTextLarger(); + result->SetNull(); +} + + void EventSendingController::textZoomOut( + const CppArgumentList& args, CppVariant* result) { + webview()->MakeTextSmaller(); + result->SetNull(); +} + + void EventSendingController::ReplaySavedEvents() { + replaying_saved_events = true; + while (!mouse_event_queue.empty()) { + WebMouseEvent event = mouse_event_queue.front(); + mouse_event_queue.pop(); + + switch (event.type) { + case WebInputEvent::MOUSE_UP: + DoMouseUp(event); + break; + case WebInputEvent::MOUSE_MOVE: + DoMouseMove(event); + break; + default: + NOTREACHED(); + } + } + + replaying_saved_events = false; +} + + void EventSendingController::contextClick( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + webview()->Layout(); + + if (GetCurrentEventTimeSec() - last_click_time_sec >= 1) { + click_count = 1; + } else { + ++click_count; + } + + // Generate right mouse down and up. + + WebMouseEvent event; + pressed_button_ = WebMouseEvent::BUTTON_RIGHT; + InitMouseEvent(WebInputEvent::MOUSE_DOWN, WebMouseEvent::BUTTON_RIGHT, + last_mouse_pos_, &event); + webview()->HandleInputEvent(&event); + + InitMouseEvent(WebInputEvent::MOUSE_UP, WebMouseEvent::BUTTON_RIGHT, + last_mouse_pos_, &event); + webview()->HandleInputEvent(&event); + + pressed_button_ = WebMouseEvent::BUTTON_NONE; +} + +// +// Unimplemented stubs +// + + void EventSendingController::enableDOMUIEventLogging( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + + void EventSendingController::fireKeyboardEventsToElement( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + + void EventSendingController::clearKillRing( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} diff --git a/webkit/tools/test_shell/event_sending_controller.h b/webkit/tools/test_shell/event_sending_controller.h new file mode 100644 index 0000000..ddcff48 --- /dev/null +++ b/webkit/tools/test_shell/event_sending_controller.h @@ -0,0 +1,108 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + + +/* + EventSendingController class: + Bound to a JavaScript window.eventSender object using + CppBoundClass::BindToJavascript(), this allows layout tests that are run in + the test_shell to fire DOM events. + + The OSX reference file is in + WebKit/WebKitTools/DumpRenderTree/EventSendingController.m +*/ + +#ifndef WEBKIT_TOOLS_TEST_SHELL_EVENT_SENDING_CONTROLLER_H__ +#define WEBKIT_TOOLS_TEST_SHELL_EVENT_SENDING_CONTROLLER_H__ + +#include "base/gfx/point.h" +#include "webkit/glue/cpp_bound_class.h" +#include "webkit/glue/webinputevent.h" + +struct IDataObject; +struct IDropSource; +class TestShell; +class WebView; + +class EventSendingController : public CppBoundClass { + public: + // Builds the property and method lists needed to bind this class to a JS + // object. + EventSendingController(TestShell* shell); + + // Resets some static variable state. + void Reset(); + + // Simulate Windows' drag&drop system call. + static void DoDragDrop(IDataObject* drag_data); + + // JS callback methods. + void mouseDown(const CppArgumentList& args, CppVariant* result); + void mouseUp(const CppArgumentList& args, CppVariant* result); + void mouseMoveTo(const CppArgumentList& args, CppVariant* result); + void leapForward(const CppArgumentList& args, CppVariant* result); + void keyDown(const CppArgumentList& args, CppVariant* result); + void textZoomIn(const CppArgumentList& args, CppVariant* result); + void textZoomOut(const CppArgumentList& args, CppVariant* result); + + // Unimplemented stubs + void contextClick(const CppArgumentList& args, CppVariant* result); + void enableDOMUIEventLogging(const CppArgumentList& args, CppVariant* result); + void fireKeyboardEventsToElement(const CppArgumentList& args, CppVariant* result); + void clearKillRing(const CppArgumentList& args, CppVariant* result); + CppVariant dragMode; + + private: + // Returns the test shell's webview. + static WebView* webview(); + + // Returns true if dragMode is true. + bool drag_mode() { return dragMode.isBool() && dragMode.ToBoolean(); } + + // Sometimes we queue up mouse move and mouse up events for drag drop + // handling purposes. These methods dispatch the event. + static void DoMouseMove(const WebMouseEvent& e); + static void DoMouseUp(const WebMouseEvent& e); + static void ReplaySavedEvents(); + + // Returns true if the key_code passed in needs a shift key modifier to + // be passed into the generated event. + bool NeedsShiftModifer(wchar_t key_code); + + // Non-owning pointer. The LayoutTestController is owned by the host. + static TestShell* shell_; + + // Location of last mouseMoveTo event. + static gfx::Point last_mouse_pos_; + + // Currently pressed mouse button (Left/Right/Middle or None) + static WebMouseEvent::Button pressed_button_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_EVENT_SENDING_CONTROLLER_H__ diff --git a/webkit/tools/test_shell/foreground_helper.h b/webkit/tools/test_shell/foreground_helper.h new file mode 100644 index 0000000..359428e --- /dev/null +++ b/webkit/tools/test_shell/foreground_helper.h @@ -0,0 +1,111 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 <atlbase.h> +#include <atlwin.h> + +#include "base/logging.h" + +// Helper class for moving a window to the foreground. +// Windows XP and later will not allow a window which is in the background to +// move to the foreground, unless requested by the current window in the +// foreground. For automated testing, we really want some of our windows +// to be capable of moving to the foreground. +// +// This is probably leveraging a windows bug. +class ForegroundHelper : public CWindowImpl<ForegroundHelper> { + public: +BEGIN_MSG_MAP(ForegroundHelper) + MESSAGE_HANDLER(WM_HOTKEY, OnHotKey) +END_MSG_MAP() + + // Brings a window into the foreground. + // Can be called from any window, even if the caller is not the + // foreground window. + static HRESULT SetForeground(HWND window) { + DCHECK(::IsWindow(window)); + ForegroundHelper foreground_helper; + CHECK(foreground_helper.ForegroundHotKey(window) == S_OK); + return S_OK; + } + + private: + HRESULT ForegroundHotKey(HWND window) { + // This implementation registers a hot key (F22) and then + // triggers the hot key. When receiving the hot key, we'll + // be in the foreground and allowed to move the target window + // into the foreground too. + + if(NULL == Create(NULL, NULL, NULL, WS_POPUP)) + return AtlHresultFromLastError(); + + static const int hotkey_id = 0x0000baba; + + // Store the target window into our USERDATA for use in our + // HotKey handler. + SetWindowLongPtr(GWLP_USERDATA, reinterpret_cast<ULONG_PTR>(window)); + RegisterHotKey(m_hWnd, hotkey_id, 0, VK_F22); + + // If the calling thread is not yet a UI thread, call PeekMessage + // to ensure creation of its message queue. + MSG msg = {0}; + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); + + // Send the Hotkey. + INPUT hotkey = {0}; + hotkey.type = INPUT_KEYBOARD; + hotkey.ki.wVk = VK_F22; + if (1 != SendInput(1, &hotkey, sizeof(hotkey))) + return E_FAIL; + + // Loop until we get the key. + // TODO: It may be possible to get stuck here if the + // message gets lost? + while(GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + + if(WM_HOTKEY == msg.message) + break; + } + + UnregisterHotKey(m_hWnd, hotkey_id); + DestroyWindow(); + + return S_OK; + } + + // Handle the registered Hotkey being pressed. + LRESULT OnHotKey(UINT /*uMsg*/, WPARAM /*wParam*/, + LPARAM /*lParam*/, BOOL& bHandled) { + HWND window = reinterpret_cast<HWND>(GetWindowLongPtr(GWLP_USERDATA)); + SetForegroundWindow(window); + return 1; + } +}; diff --git a/webkit/tools/test_shell/image_decoder_unittest.cc b/webkit/tools/test_shell/image_decoder_unittest.cc new file mode 100644 index 0000000..9e640fd --- /dev/null +++ b/webkit/tools/test_shell/image_decoder_unittest.cc @@ -0,0 +1,211 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 <windows.h> + +#include "base/file_util.h" +#include "base/md5.h" +#include "base/path_service.h" +#include "base/scoped_handle.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "webkit/tools/test_shell/image_decoder_unittest.h" + +void ReadFileToVector(const std::wstring& path, Vector<char>* contents) { + std::string contents_str; + file_util::ReadFileToString(path, &contents_str); + contents->resize(contents_str.size()); + memcpy(&contents->first(), contents_str.data(), contents_str.size()); +} + +std::wstring GetMD5SumPath(const std::wstring& path) { + static const std::wstring kDecodedDataExtension(L".md5sum"); + return path + kDecodedDataExtension; +} + +#ifdef CALCULATE_MD5_SUMS +void SaveMD5Sum(const std::wstring& path, WebCore::RGBA32Buffer* buffer) { + // Create the file to write. + ScopedHandle handle(CreateFile(path.c_str(), GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL)); + ASSERT_TRUE(handle.IsValid()); + + // Calculate MD5 sum. + MD5Digest digest; + SkAutoLockPixels bmp_lock(buffer->bitmap()); + MD5Sum(buffer->bitmap().getPixels(), + buffer->rect().width() * buffer->rect().height() * sizeof(unsigned), + &digest); + + // Write sum to disk. + DWORD bytes_written; + ASSERT_TRUE(WriteFile(handle, &digest, sizeof digest, &bytes_written, + NULL)); + ASSERT_EQ(sizeof digest, bytes_written); +} +#else +void VerifyImage(WebCore::ImageDecoder* decoder, + const std::wstring& path, + const std::wstring& md5_sum_path) { + // Make sure decoding can complete successfully. + EXPECT_TRUE(decoder->isSizeAvailable()) << path; + WebCore::RGBA32Buffer* image_buffer = decoder->frameBufferAtIndex(0); + ASSERT_NE(static_cast<WebCore::RGBA32Buffer*>(NULL), image_buffer) << path; + EXPECT_EQ(WebCore::RGBA32Buffer::FrameComplete, image_buffer->status()) << + path; + EXPECT_FALSE(decoder->failed()) << path; + + // Calculate MD5 sum. + MD5Digest actual_digest; + SkAutoLockPixels bmp_lock(image_buffer->bitmap()); + MD5Sum(image_buffer->bitmap().getPixels(), image_buffer->rect().width() * + image_buffer->rect().height() * sizeof(unsigned), &actual_digest); + + // Read the MD5 sum off disk. + std::string file_bytes; + file_util::ReadFileToString(md5_sum_path, &file_bytes); + MD5Digest expected_digest; + ASSERT_EQ(sizeof expected_digest, file_bytes.size()) << path; + memcpy(&expected_digest, file_bytes.data(), sizeof expected_digest); + + // Verify that the sums are the same. + EXPECT_EQ(0, memcmp(&expected_digest, &actual_digest, sizeof(MD5Digest))) << + path; +} +#endif + +void ImageDecoderTest::SetUp() { + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir_)); + file_util::AppendToPath(&data_dir_, L"webkit"); + file_util::AppendToPath(&data_dir_, L"data"); + file_util::AppendToPath(&data_dir_, format_ + L"_decoder"); + ASSERT_TRUE(file_util::PathExists(data_dir_)); +} + +std::vector<std::wstring> ImageDecoderTest::GetImageFiles() const { + std::wstring find_string(data_dir_); + file_util::AppendToPath(&find_string, L"*." + format_); + WIN32_FIND_DATA find_data; + ScopedFindFileHandle handle(FindFirstFile(find_string.c_str(), + &find_data)); + EXPECT_TRUE(handle.IsValid()); + + std::vector<std::wstring> image_files; + do { + std::wstring image_path = data_dir_; + file_util::AppendToPath(&image_path, find_data.cFileName); + image_files.push_back(image_path); + } while (FindNextFile(handle, &find_data)); + + return image_files; +} + +bool ImageDecoderTest::ShouldImageFail(const std::wstring& path) const { + static const std::wstring kBadSuffix(L".bad."); + return (path.length() > (kBadSuffix.length() + format_.length()) && + !path.compare(path.length() - format_.length() - kBadSuffix.length(), + kBadSuffix.length(), kBadSuffix)); +} + +void ImageDecoderTest::TestDecoding() const { + const std::vector<std::wstring> image_files(GetImageFiles()); + for (std::vector<std::wstring>::const_iterator i(image_files.begin()); + i != image_files.end(); ++i) { + Vector<char> image_contents; + ReadFileToVector(*i, &image_contents); + + scoped_ptr<WebCore::ImageDecoder> decoder(CreateDecoder()); + RefPtr<WebCore::SharedBuffer> shared_contents(new WebCore::SharedBuffer); + shared_contents->append(image_contents.data(), + static_cast<int>(image_contents.size())); + decoder->setData(shared_contents.get(), true); + + if (ShouldImageFail(*i)) { + // We should always get a non-NULL frame buffer, but when the decoder + // tries to produce it, it should fail, and the frame buffer shouldn't + // complete. + WebCore::RGBA32Buffer* const image_buffer = + decoder->frameBufferAtIndex(0); + ASSERT_NE(static_cast<WebCore::RGBA32Buffer*>(NULL), image_buffer) << + (*i); + EXPECT_NE(image_buffer->status(), WebCore::RGBA32Buffer::FrameComplete) << + (*i); + EXPECT_TRUE(decoder->failed()) << (*i); + continue; + } + +#ifdef CALCULATE_MD5_SUMS + SaveMD5Sum(GetMD5SumPath(*i), decoder->frameBufferAtIndex(0)); +#else + VerifyImage(decoder.get(), *i, GetMD5SumPath(*i)); +#endif + } +} + +#ifndef CALCULATE_MD5_SUMS +void ImageDecoderTest::TestChunkedDecoding() const { + // Init random number generator with current day, so a failing case will fail + // consistently over the course of a whole day. + const Time today = Time::Now().LocalMidnight(); + srand(static_cast<unsigned int>(today.ToInternalValue())); + + const std::vector<std::wstring> image_files(GetImageFiles()); + for (std::vector<std::wstring>::const_iterator i(image_files.begin()); + i != image_files.end(); ++i) { + if (ShouldImageFail(*i)) + continue; + + // Read the file and split it at an arbitrary point. + Vector<char> image_contents; + ReadFileToVector(*i, &image_contents); + const int partial_size = static_cast<int>( + (static_cast<double>(rand()) / RAND_MAX) * image_contents.size()); + RefPtr<WebCore::SharedBuffer> partial_contents(new WebCore::SharedBuffer); + partial_contents->append(image_contents.data(), partial_size); + + // Make sure the image decoder doesn't fail when we ask for the frame buffer + // for this partial image. + scoped_ptr<WebCore::ImageDecoder> decoder(CreateDecoder()); + decoder->setData(partial_contents.get(), false); + EXPECT_NE(static_cast<WebCore::RGBA32Buffer*>(NULL), + decoder->frameBufferAtIndex(0)) << (*i); + EXPECT_FALSE(decoder->failed()) << (*i); + + // Make sure passing the complete image results in successful decoding. + partial_contents->append( + &image_contents.data()[partial_size], + static_cast<int>(image_contents.size() - partial_size)); + decoder->setData(partial_contents.get(), true); + VerifyImage(decoder.get(), *i, GetMD5SumPath(*i)); + } +} +#endif diff --git a/webkit/tools/test_shell/image_decoder_unittest.h b/webkit/tools/test_shell/image_decoder_unittest.h new file mode 100644 index 0000000..21a0f71 --- /dev/null +++ b/webkit/tools/test_shell/image_decoder_unittest.h @@ -0,0 +1,103 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 <vector> + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "Vector.h" +#include "ImageDecoder.h" + +// If CALCULATE_MD5_SUMS is not defined, then this test decodes a handful of +// image files and compares their MD5 sums to the stored sums on disk. +// +// To recalculate the MD5 sums, uncommment CALCULATE_MD5_SUMS. +// +// The image files and corresponding MD5 sums live in the directory +// chrome/test/data/*_decoder (where "*" is the format being tested). +// +// Note: The MD5 sums calculated in this test by little- and big-endian systems +// will differ, since no endianness correction is done. If we start compiling +// for big endian machines this should be fixed. + +//#define CALCULATE_MD5_SUMS + +// Reads the contents of the specified file into the specified vector. +void ReadFileToVector(const std::wstring& path, Vector<char>* contents); + +// Returns the path the decoded data is saved at. +std::wstring GetMD5SumPath(const std::wstring& path); + +#ifdef CALCULATE_MD5_SUMS +// Saves the MD5 sum to the specified file. +void SaveMD5Sum(const std::wstring& path, WebCore::RGBA32Buffer* buffer); +#else +// Verifies the image. |path| identifies the path the image was loaded from. +void VerifyImage(WebCore::ImageDecoder* decoder, + const std::wstring& path, + const std::wstring& md5_sum_path); +#endif + +class ImageDecoderTest : public testing::Test { + public: + explicit ImageDecoderTest(const std::wstring& format) : format_(format) { } + + protected: + virtual void SetUp(); + + // Returns the vector of image files for testing. + std::vector<std::wstring> GetImageFiles() const; + + // Returns true if the image is bogus and should not be successfully decoded. + bool ShouldImageFail(const std::wstring& path) const; + + // Verifies each of the test image files is decoded correctly and matches the + // expected state. + void TestDecoding() const; + +#ifndef CALCULATE_MD5_SUMS + // Verifies that decoding still works correctly when the files are split into + // pieces at a random point. + void TestChunkedDecoding() const; +#endif + + // Returns the correct type of image decoder for this test. + virtual WebCore::ImageDecoder* CreateDecoder() const = 0; + + // The format to be decoded, like "bmp" or "ico". + std::wstring format_; + + protected: + // Path to the test files. + std::wstring data_dir_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(ImageDecoderTest); +}; diff --git a/webkit/tools/test_shell/keyboard_unittest.cc b/webkit/tools/test_shell/keyboard_unittest.cc new file mode 100644 index 0000000..5f19e27 --- /dev/null +++ b/webkit/tools/test_shell/keyboard_unittest.cc @@ -0,0 +1,293 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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" + +#pragma warning(push, 0) +#include "EventNames.h" +#include "EventTarget.h" +#include "KeyboardEvent.h" +#pragma warning(pop) + +#include "webkit/glue/editor_client_impl.h" +#include "webkit/glue/event_conversion.h" +#include "webkit/glue/webinputevent.h" + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(KeyboardUnitTestKeyDown, TestCtrlReturn) { + WebCore::EventNames::init(); + + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 0xD; + keyboard_event.key_data = 0xD; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertNewline"); +} + +TEST(KeyboardUnitTestKeyDown, TestCtrlZ) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 'Z'; + keyboard_event.key_data = 'Z'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "Undo"); +} + +TEST(KeyboardUnitTestKeyDown, TestCtrlA) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 'A'; + keyboard_event.key_data = 'A'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "SelectAll"); +} + +TEST(KeyboardUnitTestKeyDown, TestCtrlX) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 'X'; + keyboard_event.key_data = 'X'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + + EXPECT_STREQ(result, "Cut"); +} + +TEST(KeyboardUnitTestKeyDown, TestCtrlC) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 'C'; + keyboard_event.key_data = 'C'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "Copy"); +} + +TEST(KeyboardUnitTestKeyDown, TestCtrlV) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 'V'; + keyboard_event.key_data = 'V'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "Paste"); +} + +TEST(KeyboardUnitTestKeyDown, TestEscape) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = VK_ESCAPE; + keyboard_event.key_data = VK_ESCAPE; + keyboard_event.modifiers = 0; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "Cancel"); +} + +TEST(KeyboardUnitTestKeyDown, TestRedo) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = 'Y'; + keyboard_event.key_data = 'Y'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::RawKeyDown); + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "Redo"); +} + + +TEST(KeyboardUnitTestKeyPress, TestInsertTab) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\t'; + keyboard_event.key_data = '\t'; + keyboard_event.modifiers = 0; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertTab"); +} + +TEST(KeyboardUnitTestKeyPress, TestInsertBackTab) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\t'; + keyboard_event.key_data = '\t'; + keyboard_event.modifiers = WebInputEvent::SHIFT_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertBacktab"); +} + +TEST(KeyboardUnitTestKeyPress, TestInsertNewline) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\r'; + keyboard_event.key_data = '\r'; + keyboard_event.modifiers = 0; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertNewline"); +} + +TEST(KeyboardUnitTestKeyPress, TestInsertNewline2) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\r'; + keyboard_event.key_data = '\r'; + keyboard_event.modifiers = WebInputEvent::CTRL_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertNewline"); +} + +TEST(KeyboardUnitTestKeyPress, TestInsertlinebreak) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\r'; + keyboard_event.key_data = '\r'; + keyboard_event.modifiers = WebInputEvent::SHIFT_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertLineBreak"); +} + +TEST(KeyboardUnitTestKeyPress, TestInsertNewline3) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\r'; + keyboard_event.key_data = '\r'; + keyboard_event.modifiers = WebInputEvent::ALT_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertNewline"); +} + +TEST(KeyboardUnitTestKeyPress, TestInsertNewline4) { + EditorClientImpl editor_impl(NULL); + + WebKeyboardEvent keyboard_event; + keyboard_event.key_code = '\r'; + keyboard_event.key_data = '\r'; + keyboard_event.modifiers = WebInputEvent::ALT_KEY | WebInputEvent::SHIFT_KEY; + keyboard_event.type = WebInputEvent::KEY_DOWN; + + MakePlatformKeyboardEvent evt(keyboard_event); + evt.SetKeyType(WebCore::PlatformKeyboardEvent::Char); + + WebCore::KeyboardEvent keyboardEvent(evt, NULL); + const char* result = editor_impl.interpretKeyEvent(&keyboardEvent); + EXPECT_STREQ(result, "InsertNewline"); +}
\ No newline at end of file diff --git a/webkit/tools/test_shell/layout_test_controller.cc b/webkit/tools/test_shell/layout_test_controller.cc new file mode 100644 index 0000000..ff48763 --- /dev/null +++ b/webkit/tools/test_shell/layout_test_controller.cc @@ -0,0 +1,595 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// This file contains the definition for LayoutTestController. + +#include <vector> + +#include "webkit/tools/test_shell/layout_test_controller.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/path_service.h" +#include "webkit/glue/webframe.h" +#include "webkit/glue/webpreferences.h" +#include "webkit/glue/webview.h" +#include "webkit/tools/test_shell/test_shell.h" + +using std::string; +using std::wstring; + +namespace { + +// Stops the test from running and prints a brief warning to stdout. Called +// when the timer for loading a layout test expires. +VOID CALLBACK TestTimeout(HWND hwnd, UINT msg, UINT_PTR timer_id, DWORD ms) { + reinterpret_cast<TestShell*>(timer_id)->TestFinished(); + // Print a warning to be caught by the layout-test script. + puts("#TEST_TIMED_OUT\n"); +} + +} + + +TestShell* LayoutTestController::shell_ = NULL; +bool LayoutTestController::dump_as_text_ = false; +bool LayoutTestController::dump_editing_callbacks_ = false; +bool LayoutTestController::dump_frame_load_callbacks_ = false; +bool LayoutTestController::dump_resource_load_callbacks_ = false; +bool LayoutTestController::dump_back_forward_list_ = false; +bool LayoutTestController::dump_child_frame_scroll_positions_ = false; +bool LayoutTestController::dump_child_frames_as_text_ = false; +bool LayoutTestController::dump_title_changes_ = false; +bool LayoutTestController::accepts_editing_ = true; +bool LayoutTestController::wait_until_done_ = false; +bool LayoutTestController::can_open_windows_ = false; +bool LayoutTestController::close_remaining_windows_ = true; +bool LayoutTestController::should_add_file_to_pasteboard_ = false; +LayoutTestController::WorkQueue LayoutTestController::work_queue_; +CppVariant LayoutTestController::globalFlag_; + +LayoutTestController::LayoutTestController(TestShell* shell) { + // Set static shell_ variable since we can't do it in an initializer list. + // We also need to be careful not to assign shell_ to new windows which are + // temporary. + if (NULL == shell_) + shell_ = shell; + + // Initialize the map that associates methods of this class with the names + // they will use when called by JavaScript. The actual binding of those + // names to their methods will be done by calling BindToJavaScript() (defined + // by CppBoundClass, the parent to LayoutTestController). + BindMethod("dumpAsText", &LayoutTestController::dumpAsText); + BindMethod("dumpChildFrameScrollPositions", &LayoutTestController::dumpChildFrameScrollPositions); + BindMethod("dumpChildFramesAsText", &LayoutTestController::dumpChildFramesAsText); + BindMethod("dumpEditingCallbacks", &LayoutTestController::dumpEditingCallbacks); + BindMethod("dumpBackForwardList", &LayoutTestController::dumpBackForwardList); + BindMethod("dumpFrameLoadCallbacks", &LayoutTestController::dumpFrameLoadCallbacks); + BindMethod("dumpResourceLoadCallbacks", &LayoutTestController::dumpResourceLoadCallbacks); + BindMethod("dumpTitleChanges", &LayoutTestController::dumpTitleChanges); + BindMethod("setAcceptsEditing", &LayoutTestController::setAcceptsEditing); + BindMethod("waitUntilDone", &LayoutTestController::waitUntilDone); + BindMethod("notifyDone", &LayoutTestController::notifyDone); + BindMethod("queueReload", &LayoutTestController::queueReload); + BindMethod("queueScript", &LayoutTestController::queueScript); + BindMethod("queueLoad", &LayoutTestController::queueLoad); + BindMethod("queueBackNavigation", &LayoutTestController::queueBackNavigation); + BindMethod("queueForwardNavigation", &LayoutTestController::queueForwardNavigation); + BindMethod("windowCount", &LayoutTestController::windowCount); + BindMethod("setCanOpenWindows", &LayoutTestController::setCanOpenWindows); + BindMethod("setCloseRemainingWindowsWhenComplete", &LayoutTestController::setCloseRemainingWindowsWhenComplete); + BindMethod("objCIdentityIsEqual", &LayoutTestController::objCIdentityIsEqual); + BindMethod("setWindowIsKey", &LayoutTestController::setWindowIsKey); + BindMethod("setTabKeyCyclesThroughElements", &LayoutTestController::setTabKeyCyclesThroughElements); + BindMethod("setUserStyleSheetLocation", &LayoutTestController::setUserStyleSheetLocation); + BindMethod("setUserStyleSheetEnabled", &LayoutTestController::setUserStyleSheetEnabled); + BindMethod("pathToLocalResource", &LayoutTestController::pathToLocalResource); + BindMethod("addFileToPasteboardOnDrag", &LayoutTestController::addFileToPasteboardOnDrag); + BindMethod("execCommand", &LayoutTestController::execCommand); + + // The following are stubs. + BindMethod("dumpAsWebArchive", &LayoutTestController::dumpAsWebArchive); + BindMethod("setMainFrameIsFirstResponder", &LayoutTestController::setMainFrameIsFirstResponder); + BindMethod("dumpSelectionRect", &LayoutTestController::dumpSelectionRect); + BindMethod("display", &LayoutTestController::display); + BindMethod("testRepaint", &LayoutTestController::testRepaint); + BindMethod("repaintSweepHorizontally", &LayoutTestController::repaintSweepHorizontally); + BindMethod("clearBackForwardList", &LayoutTestController::clearBackForwardList); + BindMethod("keepWebHistory", &LayoutTestController::keepWebHistory); + BindMethod("storeWebScriptObject", &LayoutTestController::storeWebScriptObject); + BindMethod("accessStoredWebScriptObject", &LayoutTestController::accessStoredWebScriptObject); + BindMethod("objCClassNameOf", &LayoutTestController::objCClassNameOf); + BindMethod("addDisallowedURL", &LayoutTestController::addDisallowedURL); + BindMethod("setCallCloseOnWebViews", &LayoutTestController::setCallCloseOnWebViews); + BindMethod("setPrivateBrowsingEnabled", &LayoutTestController::setPrivateBrowsingEnabled); + BindMethod("setUseDashboardCompatibilityMode", &LayoutTestController::setUseDashboardCompatibilityMode); + BindMethod("setCustomPolicyDelegate", &LayoutTestController::setCustomPolicyDelegate); + + // This typo (missing 'i') is intentional as it matches the typo in the layout test + // see: LayoutTests/fast/canvas/fill-stroke-clip-reset-path.html. + // If Apple ever fixes this, we'll need to update it. + BindMethod("setUseDashboardCompatiblityMode", &LayoutTestController::setUseDashboardCompatibilityMode); + + // The fallback method is called when an unknown method is invoked. + BindFallbackMethod(&LayoutTestController::fallbackMethod); + + // Shared property used by a number of layout tests in + // LayoutTests\http\tests\security\dataURL. + BindProperty("globalFlag", &globalFlag_); +} + +LayoutTestController::WorkQueue::~WorkQueue() { + Reset(); +} + +void LayoutTestController::WorkQueue::ProcessWork() { + // Quit doing work once a load is in progress. + while (!queue_.empty() && !shell_->delegate()->top_loading_frame()) { + queue_.front()->Run(shell_); + delete queue_.front(); + queue_.pop(); + } + + if (!shell_->delegate()->top_loading_frame() && !wait_until_done_) { + shell_->TestFinished(); + } +} + +void LayoutTestController::WorkQueue::Reset() { + frozen_ = false; + while (!queue_.empty()) { + delete queue_.front(); + queue_.pop(); + } +} + +void LayoutTestController::WorkQueue::AddWork(WorkItem* work) { + if (frozen_) { + delete work; + return; + } + queue_.push(work); +} + +void LayoutTestController::dumpAsText(const CppArgumentList& args, + CppVariant* result) { + dump_as_text_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpEditingCallbacks( + const CppArgumentList& args, CppVariant* result) { + dump_editing_callbacks_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpBackForwardList( + const CppArgumentList& args, CppVariant* result) { + dump_back_forward_list_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpFrameLoadCallbacks( + const CppArgumentList& args, CppVariant* result) { + dump_frame_load_callbacks_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpResourceLoadCallbacks( + const CppArgumentList& args, CppVariant* result) { + dump_resource_load_callbacks_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpChildFrameScrollPositions( + const CppArgumentList& args, CppVariant* result) { + dump_child_frame_scroll_positions_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpChildFramesAsText( + const CppArgumentList& args, CppVariant* result) { + dump_child_frames_as_text_ = true; + result->SetNull(); +} + +void LayoutTestController::dumpTitleChanges( + const CppArgumentList& args, CppVariant* result) { + dump_title_changes_ = true; + result->SetNull(); +} + +void LayoutTestController::setAcceptsEditing( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) + accepts_editing_ = args[0].value.boolValue; + result->SetNull(); +} + +void LayoutTestController::waitUntilDone( + const CppArgumentList& args, CppVariant* result) { + // Set a timer in case something hangs. We use a custom timer rather than + // the one managed by the message loop so we can kill it when the load + // finishes successfully. + if (!::IsDebuggerPresent()) { + UINT_PTR timer_id = reinterpret_cast<UINT_PTR>(shell_); + SetTimer(shell_->mainWnd(), timer_id, shell_->GetFileTestTimeout(), + &TestTimeout); + } + wait_until_done_ = true; + result->SetNull(); +} + +void LayoutTestController::notifyDone( + const CppArgumentList& args, CppVariant* result) { + if (!shell_->interactive() && wait_until_done_ && + !shell_->delegate()->top_loading_frame() && work_queue_.empty()) { + shell_->TestFinished(); + } + wait_until_done_ = false; + result->SetNull(); +} + +class WorkItemBackForward : public LayoutTestController::WorkItem { + public: + WorkItemBackForward(int distance) : distance_(distance) {} + void Run(TestShell* shell) { + shell->GoBackOrForward(distance_); + } + private: + int distance_; +}; + +void LayoutTestController::queueBackNavigation( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isNumber()) + work_queue_.AddWork(new WorkItemBackForward(-args[0].ToInt32())); + result->SetNull(); +} + +void LayoutTestController::queueForwardNavigation( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isNumber()) + work_queue_.AddWork(new WorkItemBackForward(args[0].ToInt32())); + result->SetNull(); +} + +class WorkItemReload : public LayoutTestController::WorkItem { + public: + void Run(TestShell* shell) { + shell->Reload(); + } +}; + +void LayoutTestController::queueReload( + const CppArgumentList& args, CppVariant* result) { + work_queue_.AddWork(new WorkItemReload); + result->SetNull(); +} + +class WorkItemScript : public LayoutTestController::WorkItem { + public: + WorkItemScript(const string& script) : script_(script) {} + void Run(TestShell* shell) { + wstring url = L"javascript:" + UTF8ToWide(script_); + shell->LoadURL(url.c_str()); + } + private: + string script_; +}; + +void LayoutTestController::queueScript( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isString()) + work_queue_.AddWork(new WorkItemScript(args[0].ToString())); + result->SetNull(); +} + +class WorkItemLoad : public LayoutTestController::WorkItem { + public: + WorkItemLoad(const GURL& url, const string& target) + : url_(url), target_(target) {} + void Run(TestShell* shell) { + shell->LoadURLForFrame(UTF8ToWide(url_.spec()).c_str(), + UTF8ToWide(target_).c_str()); + } + private: + GURL url_; + string target_; +}; + +void LayoutTestController::queueLoad( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isString()) { + GURL current_url = shell_->webView()->GetMainFrame()->GetURL(); + GURL full_url = current_url.Resolve(args[0].ToString()); + + string target = ""; + if (args.size() > 1 && args[1].isString()) + target = args[1].ToString(); + + work_queue_.AddWork(new WorkItemLoad(full_url, target)); + } + result->SetNull(); +} + +void LayoutTestController::objCIdentityIsEqual( + const CppArgumentList& args, CppVariant* result) { + if (args.size() < 2) { + // This is the best we can do to return an error. + result->SetNull(); + return; + } + result->Set(args[0].isEqual(args[1])); +} + +void LayoutTestController::Reset() { + if (shell_) { + shell_->webView()->MakeTextStandardSize(); + shell_->webView()->SetTabKeyCyclesThroughElements(true); + } + dump_as_text_ = false; + dump_editing_callbacks_ = false; + dump_frame_load_callbacks_ = false; + dump_resource_load_callbacks_ = false; + dump_back_forward_list_ = false; + dump_child_frame_scroll_positions_ = false; + dump_child_frames_as_text_ = false; + dump_title_changes_ = false; + accepts_editing_ = true; + wait_until_done_ = false; + can_open_windows_ = false; + should_add_file_to_pasteboard_ = false; + globalFlag_.Set(false); + + if (close_remaining_windows_) { + // Iterate through the window list and close everything except the original + // shell. We don't want to delete elements as we're iterating, so we copy + // to a temp vector first. + WindowList* windows = TestShell::windowList(); + std::vector<HWND> windows_to_delete; + for (WindowList::iterator i = windows->begin(); i != windows->end(); ++i) { + if (*i != shell_->mainWnd()) + windows_to_delete.push_back(*i); + } + DCHECK(windows_to_delete.size() + 1 == windows->size()); + for (size_t i = 0; i < windows_to_delete.size(); ++i) { + DestroyWindow(windows_to_delete[i]); + } + DCHECK(windows->size() == 1); + } else { + // Reset the value + close_remaining_windows_ = true; + } + work_queue_.Reset(); +} + +void LayoutTestController::LocationChangeDone() { + // no more new work after the first complete load. + work_queue_.set_frozen(true); + + if (!wait_until_done_) + work_queue_.ProcessWork(); +} + +void LayoutTestController::setCanOpenWindows( + const CppArgumentList& args, CppVariant* result) { + can_open_windows_ = true; + result->SetNull(); +} + +void LayoutTestController::setTabKeyCyclesThroughElements( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) { + shell_->webView()->SetTabKeyCyclesThroughElements(args[0].ToBoolean()); + } + result->SetNull(); +} + +void LayoutTestController::windowCount( + const CppArgumentList& args, CppVariant* result) { + int num_windows = static_cast<int>(TestShell::windowList()->size()); + result->Set(num_windows); +} + +void LayoutTestController::setCloseRemainingWindowsWhenComplete( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) { + close_remaining_windows_ = args[0].value.boolValue; + } + result->SetNull(); +} + +void LayoutTestController::setWindowIsKey( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) { + shell_->SetFocus(shell_->webViewHost(), args[0].value.boolValue); + } + result->SetNull(); +} + +void LayoutTestController::setUserStyleSheetEnabled( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) { + shell_->delegate()->SetUserStyleSheetEnabled(args[0].value.boolValue); + } + + result->SetNull(); +} + +void LayoutTestController::setUserStyleSheetLocation( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isString()) { + GURL location(TestShell::RewriteLocalUrl(args[0].ToString())); + shell_->delegate()->SetUserStyleSheetLocation(location); + } + + result->SetNull(); +} + +void LayoutTestController::execCommand( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isString()) { + std::string command = args[0].ToString(); + std::string value(""); + + // Ignore the second parameter (which is userInterface) + // since this command emulates a manual action. + if (args.size() >= 3 && args[2].isString()) + value = args[2].ToString(); + + // Note: webkit's version does not return the boolean, so neither do we. + shell_->webView()->GetFocusedFrame()->ExecuteCoreCommandByName(command, + value); + } + result->SetNull(); +} + +void LayoutTestController::setUseDashboardCompatibilityMode( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) { + shell_->delegate()->SetDashboardCompatibilityMode(args[0].value.boolValue); + } + + result->SetNull(); +} + +void LayoutTestController::setCustomPolicyDelegate( + const CppArgumentList& args, CppVariant* result) { + if (args.size() > 0 && args[0].isBool()) { + shell_->delegate()->SetCustomPolicyDelegate(args[0].value.boolValue); + } + + result->SetNull(); +} + +void LayoutTestController::pathToLocalResource( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + if (args.size() <= 0 || !args[0].isString()) + return; + + std::string url = args[0].ToString(); + // Some layout tests use file://// which we resolve as a UNC path. Normalize + // them to just file:///. + while (StartsWithASCII(url, "file:////", false)) { + url = url.substr(0, 8) + url.substr(9); + } + GURL location(TestShell::RewriteLocalUrl(url)); + result->Set(location.spec()); +} + +void LayoutTestController::addFileToPasteboardOnDrag( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + should_add_file_to_pasteboard_ = true; +} + +// +// Unimplemented stubs +// + +void LayoutTestController::dumpAsWebArchive( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::setMainFrameIsFirstResponder( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::dumpSelectionRect( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::display( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::testRepaint( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::repaintSweepHorizontally( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::clearBackForwardList( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::keepWebHistory( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::storeWebScriptObject( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::accessStoredWebScriptObject( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::objCClassNameOf( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} +void LayoutTestController::addDisallowedURL( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} +void LayoutTestController::setCallCloseOnWebViews( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} +void LayoutTestController::setPrivateBrowsingEnabled( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); +} + +void LayoutTestController::fallbackMethod( + const CppArgumentList& args, CppVariant* result) { + std::wstring message(L"JavaScript ERROR: unknown method called on LayoutTestController"); + if (shell_->interactive()) { + logging::LogMessage("CONSOLE:", 0).stream() << message; + } else { + printf("CONSOLE MESSAGE: %S\n", message.c_str()); + } + result->SetNull(); +} diff --git a/webkit/tools/test_shell/layout_test_controller.h b/webkit/tools/test_shell/layout_test_controller.h new file mode 100644 index 0000000..5924a6e --- /dev/null +++ b/webkit/tools/test_shell/layout_test_controller.h @@ -0,0 +1,306 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +/* + LayoutTestController class: + Bound to a JavaScript window.layoutTestController object using the + CppBoundClass::BindToJavascript(), this allows layout tests that are run in + the test_shell (or, in principle, any web page loaded into a client app built + with this class) to control various aspects of how the tests are run and what + sort of output they produce. +*/ + +#ifndef WEBKIT_TOOLS_TEST_SHELL_LAYOUT_TEST_CONTROLLER_H__ +#define WEBKIT_TOOLS_TEST_SHELL_LAYOUT_TEST_CONTROLLER_H__ + +#include <queue> + +#include "webkit/glue/cpp_bound_class.h" + +class TestShell; + +class LayoutTestController : public CppBoundClass { + public: + // Builds the property and method lists needed to bind this class to a JS + // object. + LayoutTestController(TestShell* shell); + + // This function sets a flag that tells the test_shell to dump pages as + // plain text, rather than as a text representation of the renderer's state. + // It takes no arguments, and ignores any that may be present. + void dumpAsText(const CppArgumentList& args, CppVariant* result); + + // This function sets a flag that tells the test_shell to print a line of + // descriptive test for each editing command. It takes no arguments, and + // ignores any that may be present. + void dumpEditingCallbacks(const CppArgumentList& args, CppVariant* result); + + // This function sets a flag that tells the test_shell to print a line of + // descriptive test for each frame load callback. It takes no arguments, and + // ignores any that may be present. + void dumpFrameLoadCallbacks(const CppArgumentList& args, CppVariant* result); + + // This function sets a flag that tells the test_shell to print out a text + // representation of the back/forward list. It ignores all args. + void dumpBackForwardList(const CppArgumentList& args, CppVariant* result); + + // This function sets a flag that tells the test_shell to print out the + // scroll offsets of the child frames. It ignores all args. + void dumpChildFrameScrollPositions(const CppArgumentList& args, CppVariant* result); + + // This function sets a flag that tells the test_shell to recursively + // dump all frames as plain text if the dumpAsText flag is set. + // It takes no arguments, and ignores any that may be present. + void dumpChildFramesAsText(const CppArgumentList& args, CppVariant* result); + + // When called with a boolean argument, this sets a flag that controls + // whether content-editable elements accept editing focus when an editing + // attempt is made. It ignores any additional arguments. + void setAcceptsEditing(const CppArgumentList& args, CppVariant* result); + + // Functions for dealing with windows. By default we block all new windows. + void windowCount(const CppArgumentList& args, CppVariant* result); + void setCanOpenWindows(const CppArgumentList& args, CppVariant* result); + void setCloseRemainingWindowsWhenComplete(const CppArgumentList& args, CppVariant* result); + + // By default, tests end when page load is complete. These methods are used + // to delay the completion of the test until notifyDone is called. + void waitUntilDone(const CppArgumentList& args, CppVariant* result); + void notifyDone(const CppArgumentList& args, CppVariant* result); + + // Methods for adding actions to the work queue. Used in conjunction with + // waitUntilDone/notifyDone above. + void queueBackNavigation(const CppArgumentList& args, CppVariant* result); + void queueForwardNavigation(const CppArgumentList& args, CppVariant* result); + void queueReload(const CppArgumentList& args, CppVariant* result); + void queueScript(const CppArgumentList& args, CppVariant* result); + void queueLoad(const CppArgumentList& args, CppVariant* result); + + // Although this is named "objC" to match the Mac version, it actually tests + // the identity of its two arguments in C++. + void objCIdentityIsEqual(const CppArgumentList& args, CppVariant* result); + + // Gives focus to the window. + void setWindowIsKey(const CppArgumentList& args, CppVariant* result); + + // Method that controls whether pressing Tab key cycles through page elements + // or inserts a '\t' char in text area + void setTabKeyCyclesThroughElements(const CppArgumentList& args, CppVariant* result); + + // Passes through to WebPreferences which allows the user to have a custom + // style sheet. + void setUserStyleSheetEnabled(const CppArgumentList& args, CppVariant* result); + void setUserStyleSheetLocation(const CppArgumentList& args, CppVariant* result); + + // Puts Webkit in "dashboard compatibility mode", which is used in obscure + // Mac-only circumstances. It's not really necessary, and will most likely + // never be used by Chrome, but some layout tests depend on its presence. + void setUseDashboardCompatibilityMode(const CppArgumentList& args, CppVariant* result); + + // Causes navigation actions just printout the intended navigation instead + // of taking you to the page. This is used for cases like mailto, where you + // don't actually want to open the mail program. + void setCustomPolicyDelegate(const CppArgumentList& args, CppVariant* result); + + // Converts a URL starting with file:///tmp/ to the local mapping. + void pathToLocalResource(const CppArgumentList& args, CppVariant* result); + + // Sets a bool such that when a drag is started, we fill the drag clipboard + // with a fake file object. + void addFileToPasteboardOnDrag(const CppArgumentList& args, CppVariant* result); + + // Executes an internal command (superset of document.execCommand() commands) + void execCommand(const CppArgumentList& args, CppVariant* result);; + + // The following are only stubs. TODO(pamg): Implement any of these that + // are needed to pass the layout tests. + void dumpAsWebArchive(const CppArgumentList& args, CppVariant* result); + void dumpTitleChanges(const CppArgumentList& args, CppVariant* result); + void dumpResourceLoadCallbacks(const CppArgumentList& args, CppVariant* result); + void setMainFrameIsFirstResponder(const CppArgumentList& args, CppVariant* result); + void dumpSelectionRect(const CppArgumentList& args, CppVariant* result); + void display(const CppArgumentList& args, CppVariant* result); + void testRepaint(const CppArgumentList& args, CppVariant* result); + void repaintSweepHorizontally(const CppArgumentList& args, CppVariant* result); + void clearBackForwardList(const CppArgumentList& args, CppVariant* result); + void keepWebHistory(const CppArgumentList& args, CppVariant* result); + void storeWebScriptObject(const CppArgumentList& args, CppVariant* result); + void accessStoredWebScriptObject(const CppArgumentList& args, CppVariant* result); + void objCClassNameOf(const CppArgumentList& args, CppVariant* result); + void addDisallowedURL(const CppArgumentList& args, CppVariant* result); + void setCallCloseOnWebViews(const CppArgumentList& args, CppVariant* result); + void setPrivateBrowsingEnabled(const CppArgumentList& args, CppVariant* result); + + // The fallback method is called when a nonexistent method is called on + // the layout test controller object. + // It is usefull to catch typos in the JavaScript code (a few layout tests + // do have typos in them) and it allows the script to continue running in + // that case (as the Mac does). + void fallbackMethod(const CppArgumentList& args, CppVariant* result); + + public: + // The following methods are not exposed to JavaScript. + void SetWorkQueueFrozen(bool frozen) { work_queue_.set_frozen(frozen); } + + bool ShouldDumpAsText() { return dump_as_text_; } + bool ShouldDumpEditingCallbacks() { return dump_editing_callbacks_; } + bool ShouldDumpFrameLoadCallbacks() { return dump_frame_load_callbacks_; } + void SetShouldDumpFrameLoadCallbacks(bool value) { + dump_frame_load_callbacks_ = value; + } + bool ShouldDumpResourceLoadCallbacks() { + return dump_resource_load_callbacks_; + } + bool ShouldDumpBackForwardList() { return dump_back_forward_list_; } + bool ShouldDumpTitleChanges() { return dump_title_changes_; } + bool ShouldDumpChildFrameScrollPositions() { + return dump_child_frame_scroll_positions_; + } + bool ShouldDumpChildFramesAsText() { + return dump_child_frames_as_text_; + } + bool AcceptsEditing() { return accepts_editing_; } + bool CanOpenWindows() { return can_open_windows_; } + bool ShouldAddFileToPasteboard() { return should_add_file_to_pasteboard_; } + + // If we have queued events, fire them and then dump the test output. + // Otherwise, just dump the test output. + // Used by the layout tests for tests that span more than a single load. + // This is called by the test webview delegate when a page finishes + // loading (successful or not). Once all the work has been processed, we + // dump the test output. + void ProcessWork() { work_queue_.ProcessWork(); } + + // Called by the webview delegate when the toplevel frame load is done. + void LocationChangeDone(); + + // Reinitializes all static values. The Reset() method should be called + // before the start of each test (currently from + // TestShell::RunFileTest). + void Reset(); + + // A single item in the work queue. + class WorkItem { + public: + virtual ~WorkItem() {}; + virtual void Run(TestShell* shell) = 0; + }; + + // Used to clear the value of shell_ from test_shell_tests. + static void ClearShell() { shell_ = NULL; } + + private: + friend class WorkItem; + + // Helper class for managing events queued by methods like queueLoad or + // queueScript. + class WorkQueue { + public: + virtual ~WorkQueue(); + void ProcessWork(); + + // Reset the state of the class between tests. + void Reset(); + + void AddWork(WorkItem* work); + + void set_frozen(bool frozen) { frozen_ = frozen; } + bool empty() { return queue_.empty(); } + + private: + std::queue<WorkItem*> queue_; + bool frozen_; + }; + + // Non-owning pointer. The LayoutTestController is owned by the host. + static TestShell* shell_; + + // If true, the test_shell will produce a plain text dump rather than a + // text representation of the renderer. + static bool dump_as_text_; + + // If true, the test_shell will write a descriptive line for each editing + // command. + static bool dump_editing_callbacks_; + + // If true, the test_shell will output a descriptive line for each frame + // load callback. + static bool dump_frame_load_callbacks_; + + // If true, the test_shell will output a descriptive line for each resource + // load callback. + static bool dump_resource_load_callbacks_; + + // If true, the test_shell will produce a dump of the back forward list as + // well. + static bool dump_back_forward_list_; + + // If true, the test_shell will print out the child frame scroll offsets as + // well. + static bool dump_child_frame_scroll_positions_; + + // If true and if dump_as_text_ is true, the test_shell will recursively + // dump all frames as plain text. + static bool dump_child_frames_as_text_; + + // If true, output a message when the page title is changed. + static bool dump_title_changes_; + + // If true, the element will be treated as editable. This value is returned + // from various editing callbacks that are called just before edit operations + // are allowed. + static bool accepts_editing_; + + // If true, new windows can be opened via javascript or by plugins. By + // default, set to false and can be toggled to true using + // setCanOpenWindows(). + static bool can_open_windows_; + + // When reset is called, go through and close all but the main test shell + // window. By default, set to true but toggled to false using + // setCloseRemainingWindowsWhenComplete(). + static bool close_remaining_windows_; + + // If true and a drag starts, adds a file to the drag&drop clipboard. + static bool should_add_file_to_pasteboard_; + + // If true, don't dump output until notifyDone is called. + static bool wait_until_done_; + + // To prevent infinite loops, only the first page of a test can add to a + // work queue (since we may well come back to that same page). + static bool work_queue_frozen_; + + + static WorkQueue work_queue_; + + static CppVariant globalFlag_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_LAYOUT_TEST_CONTROLLER_H__ diff --git a/webkit/tools/test_shell/layout_test_controller_unittest.cc b/webkit/tools/test_shell/layout_test_controller_unittest.cc new file mode 100644 index 0000000..4158b32 --- /dev/null +++ b/webkit/tools/test_shell/layout_test_controller_unittest.cc @@ -0,0 +1,114 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 <map> + +#include "webkit/tools/test_shell/layout_test_controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +// A subclass of LayoutTestController, with additional accessors. +class TestLayoutTestController : public LayoutTestController { + public: + TestLayoutTestController() : LayoutTestController(NULL) { + } + + size_t MethodCount() { + return methods_.size(); + } + + void Reset() { + LayoutTestController::Reset(); + } +}; + +class LayoutTestControllerTest : public testing::Test { +}; +} // namespace + +TEST(LayoutTestControllerTest, MethodMapIsInitialized) { + const char* test_methods[] = { + "dumpAsText", + "waitUntilDone", + "notifyDone", + "dumpEditingCallbacks", + "queueLoad", + "windowCount", + NULL + }; + TestLayoutTestController controller; + for (const char** method = test_methods; *method; ++method) { + EXPECT_TRUE(controller.IsMethodRegistered(*method)); + } + + // One more case, to test our test. + EXPECT_FALSE(controller.IsMethodRegistered("nonexistent_method")); +} + +TEST(LayoutTestControllerTest, DumpAsTextSetAndCleared) { + TestLayoutTestController controller; + CppArgumentList empty_args; + CppVariant ignored_result; + EXPECT_FALSE(controller.ShouldDumpAsText()); + controller.dumpAsText(empty_args, &ignored_result); + EXPECT_TRUE(ignored_result.isNull()); + EXPECT_TRUE(controller.ShouldDumpAsText()); + + // Don't worry about closing remaining windows when we call reset. + CppArgumentList args; + CppVariant bool_false; + bool_false.Set(false); + args.push_back(bool_false); + CppVariant result; + controller.setCloseRemainingWindowsWhenComplete(args, &result); + + controller.Reset(); + EXPECT_FALSE(controller.ShouldDumpAsText()); +} + +TEST(LayoutTestControllerTest, DumpChildFramesAsTextSetAndCleared) { + TestLayoutTestController controller; + CppArgumentList empty_args; + CppVariant ignored_result; + EXPECT_FALSE(controller.ShouldDumpChildFramesAsText()); + controller.dumpChildFramesAsText(empty_args, &ignored_result); + EXPECT_TRUE(ignored_result.isNull()); + EXPECT_TRUE(controller.ShouldDumpChildFramesAsText()); + + // Don't worry about closing remaining windows when we call reset. + CppArgumentList args; + CppVariant bool_false; + bool_false.Set(false); + args.push_back(bool_false); + CppVariant result; + controller.setCloseRemainingWindowsWhenComplete(args, &result); + + controller.Reset(); + EXPECT_FALSE(controller.ShouldDumpChildFramesAsText()); +} diff --git a/webkit/tools/test_shell/node_leak_test.cc b/webkit/tools/test_shell/node_leak_test.cc new file mode 100644 index 0000000..92ba447 --- /dev/null +++ b/webkit/tools/test_shell/node_leak_test.cc @@ -0,0 +1,113 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "net/http/http_cache.h" +#include "net/url_request/url_request_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell.h" +#include "webkit/tools/test_shell/test_shell_request_context.h" +#include "webkit/tools/test_shell/test_shell_switches.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +namespace { + +const wchar_t kTestUrlSwitch[] = L"test-url"; + +// A test to help determine if any nodes have been leaked as a result of +// visiting a given URL. If enabled in WebCore, the number of leaked nodes +// can be printed upon termination. This is only enabled in debug builds, so +// it only makes sense to run this using a debug build. +// +// It will load a URL, visit about:blank, and then perform garbage collection. +// The number of remaining (potentially leaked) nodes will be printed on exit. +class NodeLeakTest : public TestShellTest { + public: + virtual void SetUp() { + CommandLine parsed_command_line; + + std::wstring js_flags = + parsed_command_line.GetSwitchValue(test_shell::kJavaScriptFlags); + CommandLine::AppendSwitch(&js_flags, L"expose-gc"); + webkit_glue::SetJavaScriptFlags(js_flags); + + std::wstring cache_path = + parsed_command_line.GetSwitchValue(test_shell::kCacheDir); + if (cache_path.empty()) { + PathService::Get(base::DIR_EXE, &cache_path); + file_util::AppendToPath(&cache_path, L"cache"); + } + + if (parsed_command_line.HasSwitch(test_shell::kTestShellTimeOut)) { + const std::wstring timeout_str = parsed_command_line.GetSwitchValue( + test_shell::kTestShellTimeOut); + int timeout_ms = static_cast<int>(StringToInt64(timeout_str.c_str())); + if (timeout_ms > 0) + TestShell::SetFileTestTimeout(timeout_ms); + } + + // Optionally use playback mode (for instance if running automated tests). + net::HttpCache::Mode mode = + parsed_command_line.HasSwitch(test_shell::kPlaybackMode) ? + net::HttpCache::PLAYBACK : net::HttpCache::NORMAL; + SimpleResourceLoaderBridge::Init( + new TestShellRequestContext(cache_path, mode)); + + TestShellTest::SetUp(); + } + + virtual void TearDown() { + TestShellTest::TearDown(); + + SimpleResourceLoaderBridge::Shutdown(); + } + + void NavigateToURL(const std::wstring& test_url) { + test_shell_->LoadURL(test_url.c_str()); + test_shell_->WaitTestFinished(); + + // Depends on TestShellTests::TearDown to load blank page and + // the TestShell destructor to call garbage collection. + } +}; + +} // namespace + +TEST_F(NodeLeakTest, TestURL) { + CommandLine parsed_command_line; + + if (parsed_command_line.HasSwitch(kTestUrlSwitch)) { + NavigateToURL(parsed_command_line.GetSwitchValue(kTestUrlSwitch).c_str()); + } +} diff --git a/webkit/tools/test_shell/plugin_tests.cc b/webkit/tools/test_shell/plugin_tests.cc new file mode 100644 index 0000000..bfde855 --- /dev/null +++ b/webkit/tools/test_shell/plugin_tests.cc @@ -0,0 +1,147 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 <string> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "net/base/cookie_monster.h" +#include "net/base/net_util.h" +#include "net/http/http_cache.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +static const char kTestCompleteCookie[] = "status"; +static const char kTestCompleteSuccess[] = "OK"; + +// Provides functionality for creating plugin tests. +class PluginTest : public TestShellTest { + // A basic URLRequestContext that only provides an in-memory cookie store. + class RequestContext : public TestURLRequestContext { + public: + RequestContext() { + cookie_store_ = new CookieMonster(); + } + + virtual ~RequestContext() { + delete cookie_store_; + } + }; + + public: + PluginTest() {} + ~PluginTest() {} + + void NavigateToURL(const std::wstring& test_url) { + ASSERT_TRUE(file_util::PathExists(test_url)); + test_url_ = net_util::FilePathToFileURL(test_url); + test_shell_->LoadURL(test_url.c_str()); + } + + // Waits for the test case to finish. + // ASSERTS if there are test failures. + void WaitForFinish(const std::string &name, const std::string &id) { + test_shell_->WaitTestFinished(); + + std::string cookies = + request_context_->cookie_store()->GetCookies(test_url_); + EXPECT_FALSE(cookies.empty()); + + std::string cookieName = name; + cookieName.append("."); + cookieName.append(id); + cookieName.append("."); + cookieName.append(kTestCompleteCookie); + cookieName.append("="); + std::string::size_type idx = cookies.find(cookieName); + std::string cookie; + if (idx != std::string::npos) { + cookies.erase(0, idx + cookieName.length()); + cookie = cookies.substr(0, cookies.find(";")); + } + + EXPECT_EQ(kTestCompleteSuccess, cookie); + } + + protected: + virtual void SetUp() { + // We need to copy our test-plugin into the plugins directory so that + // the test can load it. + std::wstring current_directory; + PathService::Get(base::DIR_EXE, ¤t_directory); + std::wstring plugin_src = current_directory + L"\\npapi_test_plugin.dll"; + ASSERT_TRUE(file_util::PathExists(plugin_src)); + + plugin_dll_path_ = current_directory + L"\\plugins"; + ::CreateDirectory(plugin_dll_path_.c_str(), NULL); + + plugin_dll_path_ += L"\\npapi_test_plugin.dll"; + ASSERT_TRUE(CopyFile(plugin_src.c_str(), plugin_dll_path_.c_str(), FALSE)); + + // The plugin list has to be refreshed to ensure that the npapi_test_plugin + // is loaded by webkit. + std::vector<WebPluginInfo> plugin_list; + bool refresh = true; + NPAPI::PluginList::Singleton()->GetPlugins(refresh, &plugin_list); + + TestShellTest::SetUp(); + + plugin_data_dir_ = data_dir_; + file_util::AppendToPath(&plugin_data_dir_, L"plugin_tests"); + ASSERT_TRUE(file_util::PathExists(plugin_data_dir_)); + } + + virtual void TearDown() { + TestShellTest::TearDown(); + + // TODO(iyengar) The DeleteFile call fails in some cases as the plugin is + // still in use. Needs more investigation. + ::DeleteFile(plugin_dll_path_.c_str()); + } + + std::wstring plugin_data_dir_; + std::wstring plugin_dll_path_; + RequestContext* request_context_; + GURL test_url_; +}; + +TEST_F(PluginTest, DISABLED_VerifyPluginWindowRect) { + std::wstring test_url = GetTestURL(plugin_data_dir_, + L"verify_plugin_window_rect.html"); + NavigateToURL(test_url); + WaitForFinish("checkwindowrect", "1"); +} diff --git a/webkit/tools/test_shell/resource.h b/webkit/tools/test_shell/resource.h new file mode 100644 index 0000000..d56339e --- /dev/null +++ b/webkit/tools/test_shell/resource.h @@ -0,0 +1,38 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by test_shell.rc +// + +#define IDS_APP_TITLE 103 + +#define IDR_MAINFRAME 128 +#define IDD_TESTSHELL_DIALOG 102 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDM_DUMP_BODY_TEXT 110 +#define IDM_DUMP_RENDER_TREE 111 +#define IDM_SHOW_WEB_INSPECTOR 112 +#define IDI_TESTSHELL 107 +#define IDI_SMALL 108 +#define IDC_TESTSHELL 109 +#define IDC_MYICON 2 +#define IDC_NAV_BACK 1001 +#define IDC_NAV_FORWARD 1002 +#define IDC_NAV_RELOAD 1003 +#define IDC_NAV_STOP 1004 +#ifndef IDC_STATIC +#define IDC_STATIC -1 +#endif +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS + +#define _APS_NO_MFC 130 +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 113 +#endif +#endif diff --git a/webkit/tools/test_shell/resources/AHEM____.TTF b/webkit/tools/test_shell/resources/AHEM____.TTF Binary files differnew file mode 100644 index 0000000..ac81cb0 --- /dev/null +++ b/webkit/tools/test_shell/resources/AHEM____.TTF diff --git a/webkit/tools/test_shell/resources/README.txt b/webkit/tools/test_shell/resources/README.txt new file mode 100644 index 0000000..c227a5d --- /dev/null +++ b/webkit/tools/test_shell/resources/README.txt @@ -0,0 +1,24 @@ +missingImage.gif was created from Webkit data: WebCore/Resources/missingImage.tiff + +Licence text for missingImage.tiff from which missingImage.gif was generated: + +Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc. All rights reserved. +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 COMPUTER, INC. ``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 COMPUTER, INC. OR +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. diff --git a/webkit/tools/test_shell/resources/missingImage.gif b/webkit/tools/test_shell/resources/missingImage.gif Binary files differnew file mode 100644 index 0000000..0f7215d --- /dev/null +++ b/webkit/tools/test_shell/resources/missingImage.gif diff --git a/webkit/tools/test_shell/resources/small.ico b/webkit/tools/test_shell/resources/small.ico Binary files differnew file mode 100644 index 0000000..d551aa3 --- /dev/null +++ b/webkit/tools/test_shell/resources/small.ico diff --git a/webkit/tools/test_shell/resources/test_shell.ico b/webkit/tools/test_shell/resources/test_shell.ico Binary files differnew file mode 100644 index 0000000..d551aa3 --- /dev/null +++ b/webkit/tools/test_shell/resources/test_shell.ico diff --git a/webkit/tools/test_shell/resources/test_shell.rc b/webkit/tools/test_shell/resources/test_shell.rc new file mode 100644 index 0000000..a8fe86e --- /dev/null +++ b/webkit/tools/test_shell/resources/test_shell.rc @@ -0,0 +1,133 @@ +//Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. + +IDI_TESTSHELL ICON "test_shell.ico" +IDI_SMALL ICON "small.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDC_TESTSHELL MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "E&xit", IDM_EXIT + END + POPUP "&Debug" + BEGIN + MENUITEM "Dump body text...", IDM_DUMP_BODY_TEXT + MENUITEM "Dump render tree...", IDM_DUMP_RENDER_TREE + MENUITEM "Show web inspector...", IDM_SHOW_WEB_INSPECTOR + END + POPUP "&Help" + BEGIN + MENUITEM "&About ...", IDM_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDC_TESTSHELL ACCELERATORS +BEGIN + "?", IDM_ABOUT, ASCII, ALT + "/", IDM_ABOUT, ASCII, ALT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG 22, 17, 230, 75 +STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU +CAPTION "About" +FONT 8, "System" +BEGIN + ICON IDI_TESTSHELL,IDC_MYICON,14,9,16,16 + LTEXT "TestShell Version 1.0",IDC_STATIC,49,10,119,8,SS_NOPREFIX + LTEXT "Copyright (C) 2006",IDC_STATIC,49,20,119,8 + DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDC_TESTSHELL "TESTSHELL" + IDS_APP_TITLE "TestShell" +END + +#endif +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/webkit/tools/test_shell/run_all_tests.cc b/webkit/tools/test_shell/run_all_tests.cc new file mode 100644 index 0000000..05cdc43 --- /dev/null +++ b/webkit/tools/test_shell/run_all_tests.cc @@ -0,0 +1,85 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// Run all of our test shell tests. This is just an entry point +// to kick off gTest's RUN_ALL_TESTS(). + +#include <windows.h> +#include <commctrl.h> + +#include "base/icu_util.h" +#include "base/message_loop.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell.h" +#include "webkit/tools/test_shell/test_shell_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +const char* TestShellTest::kJavascriptDelayExitScript = + "<script>" + "window.layoutTestController.waitUntilDone();" + "window.addEventListener('load', function() {" + " var x = document.body.clientWidth;" // Force a document layout + " window.layoutTestController.notifyDone();" + "});" + "</script>"; + +int main(int argc, char* argv[]) { + TestShell::InitLogging(true); // suppress error dialogs + + // Initialize test shell in non-interactive mode, which will let us load one + // request than automatically quit. + TestShell::InitializeTestShell(false); + + // Some of the individual tests wind up calling TestShell::WaitTestFinished + // which has a timeout in it. For these tests, we don't care about a timeout + // so just set it to be a really large number. This is necessary because + // when running under Purify, we were hitting those timeouts. + TestShell::SetFileTestTimeout(USER_TIMER_MAXIMUM); + + // Allocate a message loop for this thread. Although it is not used + // directly, its constructor sets up some necessary state. + MessageLoop main_message_loop; + + // Load ICU data tables + icu_util::Initialize(); + + INITCOMMONCONTROLSEX InitCtrlEx; + + InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCtrlEx.dwICC = ICC_STANDARD_CLASSES; + InitCommonControlsEx(&InitCtrlEx); + + // Run the actual tests + testing::InitGoogleTest(&argc, argv); + int result = RUN_ALL_TESTS(); + + TestShell::ShutdownTestShell(); + TestShell::CleanupLogging(); + return result; +} diff --git a/webkit/tools/test_shell/simple_resource_loader_bridge.cc b/webkit/tools/test_shell/simple_resource_loader_bridge.cc new file mode 100644 index 0000000..e8dcbf2 --- /dev/null +++ b/webkit/tools/test_shell/simple_resource_loader_bridge.cc @@ -0,0 +1,584 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. +// +// This file contains an implementation of the ResourceLoaderBridge class. +// The class is implemented using URLRequest, meaning it is a "simple" version +// that directly issues requests. The more complicated one used in the +// browser uses IPC. +// +// Because URLRequest only provides an asynchronous resource loading API, this +// file makes use of URLRequest from a background IO thread. Requests for +// cookies and synchronously loaded resources result in the main thread of the +// application blocking until the IO thread completes the operation. (See +// GetCookies and SyncLoad) +// +// Main thread IO thread +// ----------- --------- +// ResourceLoaderBridge <---o---------> RequestProxy (normal case) +// \ -> URLRequest +// o-------> SyncRequestProxy (synchronous case) +// -> URLRequest +// SetCookie <------------------------> CookieSetter +// -> net_util::SetCookie +// GetCookies <-----------------------> CookieGetter +// -> net_util::GetCookies +// +// NOTE: The implementation in this file may be used to have WebKit fetch +// resources in-process. For example, it is handy for building a single- +// process WebKit embedding (e.g., test_shell) that can use URLRequest to +// perform URL loads. See renderer/resource_dispatcher.h for details on an +// alternate implementation that defers fetching to another process. + +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" + +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "net/base/cookie_monster.h" +#include "net/base/net_util.h" +#include "net/base/upload_data.h" +#include "net/url_request/url_request.h" +#include "webkit/glue/resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell_request_context.h" + +using webkit_glue::ResourceLoaderBridge; +using net::HttpResponseHeaders; + +namespace { + +//----------------------------------------------------------------------------- + +URLRequestContext* request_context = NULL; +Thread* io_thread = NULL; + +class IOThread : public Thread { + public: + IOThread() : Thread("IOThread") { + } + + ~IOThread() { + // We cannot rely on our base class to stop the thread since we want our + // CleanUp function to run. + Stop(); + } + + virtual void CleanUp() { + if (request_context) { + request_context->Release(); + request_context = NULL; + } + } +}; + +bool EnsureIOThread() { + if (io_thread) + return true; + + if (!request_context) + SimpleResourceLoaderBridge::Init(NULL); + + io_thread = new IOThread(); + return io_thread->Start(); +} + +//----------------------------------------------------------------------------- + +struct RequestParams { + std::string method; + GURL url; + GURL policy_url; + GURL referrer; + std::string headers; + int load_flags; + scoped_refptr<net::UploadData> upload; +}; + +// The RequestProxy does most of its work on the IO thread. The Start and +// Cancel methods are proxied over to the IO thread, where an URLRequest object +// is instantiated. +class RequestProxy : public URLRequest::Delegate, + public base::RefCountedThreadSafe<RequestProxy> { + public: + // Takes ownership of the params. + RequestProxy() { + } + + virtual ~RequestProxy() { + // If we have a request, then we'd better be on the io thread! + DCHECK(!request_.get() || + MessageLoop::current() == io_thread->message_loop()); + } + + void DropPeer() { + peer_ = NULL; + } + + void Start(ResourceLoaderBridge::Peer* peer, RequestParams* params) { + peer_ = peer; + owner_loop_ = MessageLoop::current(); + + // proxy over to the io thread + io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::AsyncStart, params)); + } + + void Cancel() { + // proxy over to the io thread + io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::AsyncCancel)); + } + + protected: + // -------------------------------------------------------------------------- + // The following methods are called on the owner's thread in response to + // various URLRequest callbacks. The event hooks, defined below, trigger + // these methods asynchronously. + + void NotifyReceivedRedirect(const GURL& new_url) { + if (peer_) + peer_->OnReceivedRedirect(new_url); + } + + void NotifyReceivedResponse(const ResourceLoaderBridge::ResponseInfo& info) { + if (peer_) + peer_->OnReceivedResponse(info); + } + + void NotifyReceivedData(int bytes_read) { + if (!peer_) + return; + + // Make a local copy of buf_, since AsyncReadData reuses it. + scoped_array<char> buf_copy(new char[bytes_read]); + memcpy(buf_copy.get(), buf_, bytes_read); + + // Continue reading more data into buf_ + // Note: Doing this before notifying our peer ensures our load events get + // dispatched in a manner consistent with DumpRenderTree (and also avoids a + // race condition). If the order of the next 2 functions were reversed, the + // peer could generate new requests in reponse to the received data, which + // when run on the io thread, could race against this function in doing + // another InvokeLater. See bug 769249. + io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::AsyncReadData)); + + peer_->OnReceivedData(buf_copy.get(), bytes_read); + } + + void NotifyCompletedRequest(const URLRequestStatus& status) { + if (peer_) { + peer_->OnCompletedRequest(status); + DropPeer(); // ensure no further notifications + } + } + + // -------------------------------------------------------------------------- + // The following methods are called on the io thread. They correspond to + // actions performed on the owner's thread. + + void AsyncStart(RequestParams* params) { + request_.reset(new URLRequest(params->url, this)); + request_->set_method(params->method); + request_->set_policy_url(params->policy_url); + request_->set_referrer(params->referrer.spec()); + request_->SetExtraRequestHeaders(params->headers); + request_->set_load_flags(params->load_flags); + request_->set_upload(params->upload.get()); + request_->set_context(request_context); + request_->Start(); + + delete params; + } + + void AsyncCancel() { + // This can be null in cases where the request is already done. + if (!request_.get()) + return; + + request_->Cancel(); + Done(); + } + + void AsyncReadData() { + // This can be null in cases where the request is already done. + if (!request_.get()) + return; + + if (request_->status().is_success()) { + int bytes_read; + if (request_->Read(buf_, sizeof(buf_), &bytes_read) && bytes_read) { + OnReceivedData(bytes_read); + } else if (!request_->status().is_io_pending()) { + Done(); + } // else wait for OnReadCompleted + } else { + Done(); + } + } + + // -------------------------------------------------------------------------- + // The following methods are event hooks (corresponding to URLRequest + // callbacks) that run on the IO thread. They are designed to be overridden + // by the SyncRequestProxy subclass. + + virtual void OnReceivedRedirect(const GURL& new_url) { + owner_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::NotifyReceivedRedirect, new_url)); + } + + virtual void OnReceivedResponse( + const ResourceLoaderBridge::ResponseInfo& info) { + owner_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::NotifyReceivedResponse, info)); + } + + virtual void OnReceivedData(int bytes_read) { + owner_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::NotifyReceivedData, bytes_read)); + } + + virtual void OnCompletedRequest(const URLRequestStatus& status) { + owner_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &RequestProxy::NotifyCompletedRequest, status)); + } + + // -------------------------------------------------------------------------- + // URLRequest::Delegate implementation: + + virtual void OnReceivedRedirect(URLRequest* request, + const GURL& new_url) { + DCHECK(request->status().is_success()); + OnReceivedRedirect(new_url); + } + + virtual void OnResponseStarted(URLRequest* request) { + if (request->status().is_success()) { + ResourceLoaderBridge::ResponseInfo info; + info.request_time = request->request_time(); + info.response_time = request->response_time(); + info.headers = request->response_headers(); + request->GetMimeType(&info.mime_type); + request->GetCharset(&info.charset); + OnReceivedResponse(info); + AsyncReadData(); // start reading + } else { + Done(); + } + } + + virtual void OnReadCompleted(URLRequest* request, int bytes_read) { + if (request->status().is_success() && bytes_read > 0) { + OnReceivedData(bytes_read); + } else { + Done(); + } + } + + // -------------------------------------------------------------------------- + // Helpers and data: + + void Done() { + DCHECK(request_.get()); + OnCompletedRequest(request_->status()); + request_.reset(); // destroy on the io thread + } + + scoped_ptr<URLRequest> request_; + + // Size of our async IO data buffers + static const int kDataSize = 16*1024; + + // read buffer for async IO + char buf_[kDataSize]; + + MessageLoop* owner_loop_; + + // This is our peer in WebKit (implemented as ResourceHandleInternal). We do + // not manage its lifetime, and we may only access it from the owner's + // message loop (owner_loop_). + ResourceLoaderBridge::Peer* peer_; +}; + +//----------------------------------------------------------------------------- + +class SyncRequestProxy : public RequestProxy { + public: + explicit SyncRequestProxy(ResourceLoaderBridge::SyncLoadResponse* result) + : event_(::CreateEvent(NULL, TRUE, FALSE, NULL)), + result_(result) { + } + + virtual ~SyncRequestProxy() { + CloseHandle(event_); + } + + HANDLE event() const { + return event_; + } + + // -------------------------------------------------------------------------- + // Event hooks that run on the IO thread: + + virtual void OnReceivedRedirect(const GURL& new_url) { + result_->url = new_url; + } + + virtual void OnReceivedResponse( + const ResourceLoaderBridge::ResponseInfo& info) { + *static_cast<ResourceLoaderBridge::ResponseInfo*>(result_) = info; + } + + virtual void OnReceivedData(int bytes_read) { + result_->data.append(buf_, bytes_read); + AsyncReadData(); // read more (may recurse) + } + + virtual void OnCompletedRequest(const URLRequestStatus& status) { + result_->status = status; + ::SetEvent(event_); + } + + private: + ResourceLoaderBridge::SyncLoadResponse* result_; + HANDLE event_; +}; + +//----------------------------------------------------------------------------- + +class ResourceLoaderBridgeImpl : public ResourceLoaderBridge { + public: + ResourceLoaderBridgeImpl(const std::string& method, + const GURL& url, + const GURL& policy_url, + const GURL& referrer, + const std::string& headers, + int load_flags) + : params_(new RequestParams), + proxy_(NULL) { + params_->method = method; + params_->url = url; + params_->policy_url = policy_url; + params_->referrer = referrer; + params_->headers = headers; + params_->load_flags = load_flags; + } + + virtual ~ResourceLoaderBridgeImpl() { + if (proxy_) { + proxy_->DropPeer(); + // Let the proxy die on the IO thread + io_thread->message_loop()->ReleaseSoon(FROM_HERE, proxy_); + } + } + + // -------------------------------------------------------------------------- + // ResourceLoaderBridge implementation: + + virtual void AppendDataToUpload(const char* data, int data_len) { + DCHECK(params_.get()); + if (!params_->upload) + params_->upload = new net::UploadData(); + params_->upload->AppendBytes(data, data_len); + } + + virtual void AppendFileRangeToUpload(const std::wstring& file_path, + uint64 offset, uint64 length) { + DCHECK(params_.get()); + if (!params_->upload) + params_->upload = new net::UploadData(); + params_->upload->AppendFileRange(file_path, offset, length); + } + + virtual bool Start(Peer* peer) { + DCHECK(!proxy_); + + if (!EnsureIOThread()) + return false; + + proxy_ = new RequestProxy(); + proxy_->AddRef(); + + proxy_->Start(peer, params_.release()); + + return true; // Any errors will be reported asynchronously. + } + + virtual void Cancel() { + DCHECK(proxy_); + proxy_->Cancel(); + } + + virtual void SetDefersLoading(bool value) { + // TODO(darin): implement me + } + + virtual void SyncLoad(SyncLoadResponse* response) { + DCHECK(!proxy_); + + if (!EnsureIOThread()) + return; + + // this may change as the result of a redirect + response->url = params_->url; + + proxy_ = new SyncRequestProxy(response); + proxy_->AddRef(); + + proxy_->Start(NULL, params_.release()); + + HANDLE event = static_cast<SyncRequestProxy*>(proxy_)->event(); + + if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0) + NOTREACHED(); + } + + private: + // Ownership of params_ is transfered to the proxy when the proxy is created. + scoped_ptr<RequestParams> params_; + + // The request proxy is allocated when we start the request, and then it + // sticks around until this ResourceLoaderBridge is destroyed. + RequestProxy* proxy_; +}; + +//----------------------------------------------------------------------------- + +class CookieSetter : public base::RefCountedThreadSafe<CookieSetter> { + public: + void Set(const GURL& url, const std::string& cookie) { + DCHECK(MessageLoop::current() == io_thread->message_loop()); + request_context->cookie_store()->SetCookie(url, cookie); + } +}; + +class CookieGetter : public base::RefCountedThreadSafe<CookieGetter> { + public: + CookieGetter() + : event_(::CreateEvent(NULL, FALSE, FALSE, NULL)) { + } + + ~CookieGetter() { + CloseHandle(event_); + } + + void Get(const GURL& url) { + result_ = request_context->cookie_store()->GetCookies(url); + SetEvent(event_); + } + + std::string GetResult() { + if (WaitForSingleObject(event_, INFINITE) != WAIT_OBJECT_0) + NOTREACHED(); + return result_; + } + + private: + HANDLE event_; + std::string result_; +}; + +} // anonymous namespace + +//----------------------------------------------------------------------------- + +namespace webkit_glue { + +// factory function +ResourceLoaderBridge* ResourceLoaderBridge::Create( + WebFrame* webframe, + const std::string& method, + const GURL& url, + const GURL& policy_url, + const GURL& referrer, + const std::string& headers, + int load_flags, + int origin_pid, + ResourceType::Type request_type, + bool mixed_contents) { + return new ResourceLoaderBridgeImpl(method, url, policy_url, referrer, + headers, load_flags); +} + +void SetCookie(const GURL& url, const GURL& policy_url, + const std::string& cookie) { + // Proxy to IO thread to synchronize w/ network loading. + + if (!EnsureIOThread()) { + NOTREACHED(); + return; + } + + scoped_refptr<CookieSetter> cookie_setter = new CookieSetter(); + io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + cookie_setter.get(), &CookieSetter::Set, url, cookie)); +} + +std::string GetCookies(const GURL& url, const GURL& policy_url) { + // Proxy to IO thread to synchronize w/ network loading + + if (!EnsureIOThread()) { + NOTREACHED(); + return std::string(); + } + + scoped_refptr<CookieGetter> getter = new CookieGetter(); + + io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + getter.get(), &CookieGetter::Get, url)); + + return getter->GetResult(); +} + +} // namespace webkit_glue + +//----------------------------------------------------------------------------- + +// static +void SimpleResourceLoaderBridge::Init(URLRequestContext* context) { + // Make sure to stop any existing IO thread since it may be using the + // current request context. + Shutdown(); + + if (context) { + request_context = context; + } else { + request_context = new TestShellRequestContext(); + } + request_context->AddRef(); +} + +// static +void SimpleResourceLoaderBridge::Shutdown() { + if (io_thread) { + delete io_thread; + io_thread = NULL; + + DCHECK(!request_context) << "should have been nulled by thread dtor"; + } +} diff --git a/webkit/tools/test_shell/simple_resource_loader_bridge.h b/webkit/tools/test_shell/simple_resource_loader_bridge.h new file mode 100644 index 0000000..3c06594 --- /dev/null +++ b/webkit/tools/test_shell/simple_resource_loader_bridge.h @@ -0,0 +1,55 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_SIMPLE_RESOURCE_LOADER_BRIDGE_H__ +#define WEBKIT_TOOLS_TEST_SHELL_SIMPLE_RESOURCE_LOADER_BRIDGE_H__ + +#include "base/ref_counted.h" + +class URLRequestContext; + +class SimpleResourceLoaderBridge { + public: + // Call this function to initialize the simple resource loader bridge. If + // the given context is null, then a default TestShellRequestContext will be + // instantiated. Otherwise, a reference is taken to the given request + // context, which will be released when Shutdown is called. The caller + // should not hold another reference to the request context! It is safe to + // call this function multiple times. + // + // NOTE: If this function is not called, then a default request context will + // be initialized lazily. + // + static void Init(URLRequestContext* context); + + // Call this function to shutdown the simple resource loader bridge. + static void Shutdown(); +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_SIMPLE_RESOURCE_LOADER_BRIDGE_H__ diff --git a/webkit/tools/test_shell/temp/navigation_controller_base.cc b/webkit/tools/test_shell/temp/navigation_controller_base.cc new file mode 100644 index 0000000..287e870 --- /dev/null +++ b/webkit/tools/test_shell/temp/navigation_controller_base.cc @@ -0,0 +1,301 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/temp/navigation_controller_base.h" + +#include <algorithm> + +#include "webkit/tools/test_shell/temp/navigation_entry.h" +#include "base/logging.h" + +NavigationControllerBase::NavigationControllerBase() + : pending_entry_(NULL), + last_committed_entry_index_(-1), + pending_entry_index_(-1) { +} + +NavigationControllerBase::~NavigationControllerBase() { + // NOTE: This does NOT invoke Reset as Reset is virtual. + ResetInternal(); +} + +void NavigationControllerBase::Reset() { + ResetInternal(); + + last_committed_entry_index_ = -1; +} + +NavigationEntry* NavigationControllerBase::GetActiveEntry() const { + NavigationEntry* entry = pending_entry_; + if (!entry) + entry = GetLastCommittedEntry(); + return entry; +} + +int NavigationControllerBase::GetCurrentEntryIndex() const { + if (pending_entry_index_ != -1) + return pending_entry_index_; + return last_committed_entry_index_; +} + +NavigationEntry* NavigationControllerBase::GetLastCommittedEntry() const { + if (last_committed_entry_index_ == -1) + return NULL; + return entries_[last_committed_entry_index_]; +} + +int NavigationControllerBase::GetEntryIndexWithPageID( + TabContentsType type, int32 page_id) const { + for (int i = static_cast<int>(entries_.size())-1; i >= 0; --i) { + if (entries_[i]->GetType() == type && entries_[i]->GetPageID() == page_id) + return i; + } + return -1; +} + +NavigationEntry* NavigationControllerBase::GetEntryWithPageID( + TabContentsType type, int32 page_id) const { + int index = GetEntryIndexWithPageID(type, page_id); + return (index != -1) ? entries_[index] : NULL; +} + +NavigationEntry* NavigationControllerBase::GetEntryAtOffset(int offset) const { + int index = last_committed_entry_index_ + offset; + if (index < 0 || index >= GetEntryCount()) + return NULL; + + return entries_[index]; +} + +bool NavigationControllerBase::CanStop() const { + // TODO(darin): do we have something pending that we can stop? + return false; +} + +bool NavigationControllerBase::CanGoBack() const { + return entries_.size() > 1 && GetCurrentEntryIndex() > 0; +} + +bool NavigationControllerBase::CanGoForward() const { + int index = GetCurrentEntryIndex(); + return index >= 0 && index < (static_cast<int>(entries_.size()) - 1); +} + +void NavigationControllerBase::GoBack() { + DCHECK(CanGoBack()); + + // Base the navigation on where we are now... + int current_index = GetCurrentEntryIndex(); + + DiscardPendingEntry(); + + pending_entry_index_ = current_index - 1; + NavigateToPendingEntry(false); +} + +void NavigationControllerBase::GoForward() { + DCHECK(CanGoForward()); + + // Base the navigation on where we are now... + int current_index = GetCurrentEntryIndex(); + + DiscardPendingEntry(); + + pending_entry_index_ = current_index + 1; + NavigateToPendingEntry(false); +} + +void NavigationControllerBase::GoToIndex(int index) { + DCHECK(index >= 0); + DCHECK(index < static_cast<int>(entries_.size())); + + DiscardPendingEntry(); + + pending_entry_index_ = index; + NavigateToPendingEntry(false); +} + +void NavigationControllerBase::GoToOffset(int offset) { + int index = last_committed_entry_index_ + offset; + if (index < 0 || index >= GetEntryCount()) + return; + + GoToIndex(index); +} + +void NavigationControllerBase::Stop() { + DCHECK(CanStop()); + + // TODO(darin): we probably want to just call Stop on the active tab + // contents, but should we also call DiscardPendingEntry? + NOTREACHED() << "implement me"; +} + +void NavigationControllerBase::Reload() { + // Base the navigation on where we are now... + int current_index = GetCurrentEntryIndex(); + + // If we are no where, then we can't reload. TODO(darin): We should add a + // CanReload method. + if (current_index == -1) + return; + + DiscardPendingEntryInternal(); + + pending_entry_index_ = current_index; + entries_[pending_entry_index_]->SetTransition(PageTransition::RELOAD); + NavigateToPendingEntry(true); +} + +void NavigationControllerBase::LoadEntry(NavigationEntry* entry) { + // When navigating to a new page, we don't know for sure if we will actually + // end up leaving the current page. The new page load could for example + // result in a download or a 'no content' response (e.g., a mailto: URL). + + DiscardPendingEntryInternal(); + pending_entry_ = entry; + NavigateToPendingEntry(false); +} + +void NavigationControllerBase::DidNavigateToEntry(NavigationEntry* entry) { + // If the entry is that of a page with PageID larger than any this Tab has + // seen before, then consider it a new navigation. + if (entry->GetPageID() > GetMaxPageID()) { + InsertEntry(entry); + return; + } + + // Otherwise, we just need to update an existing entry with matching PageID. + // If the existing entry corresponds to the entry which is pending, then we + // must update the current entry index accordingly. When navigating to the + // same URL, a new PageID is not created. + + int existing_entry_index = GetEntryIndexWithPageID(entry->GetType(), + entry->GetPageID()); + NavigationEntry* existing_entry = + (existing_entry_index != -1) ? entries_[existing_entry_index] : NULL; + if (!existing_entry) { + // No existing entry, then simply ignore this navigation! + DLOG(WARNING) << "ignoring navigation for page: " << entry->GetPageID(); + } else if (existing_entry == pending_entry_) { + // The given entry might provide a new URL... e.g., navigating back to a + // page in session history could have resulted in a new client redirect. + existing_entry->SetURL(entry->GetURL()); + existing_entry->SetContentState(entry->GetContentState()); + last_committed_entry_index_ = pending_entry_index_; + pending_entry_index_ = -1; + pending_entry_ = NULL; + IndexOfActiveEntryChanged(); + } else if (pending_entry_ && pending_entry_->GetPageID() == -1 && + pending_entry_->GetURL() == existing_entry->GetURL()) { + // Not a new navigation + DiscardPendingEntry(); + } else { + // The given entry might provide a new URL... e.g., navigating to a page + // might result in a client redirect, which should override the URL of the + // existing entry. + existing_entry->SetURL(entry->GetURL()); + existing_entry->SetContentState(entry->GetContentState()); + + // The navigation could have been issued by the renderer, so be sure that + // we update our current index. + last_committed_entry_index_ = existing_entry_index; + IndexOfActiveEntryChanged(); + } + + delete entry; + + NotifyNavigationStateChanged(); +} + +void NavigationControllerBase::DiscardPendingEntry() { + DiscardPendingEntryInternal(); + + // Derived classes may do additional things in this case. +} + +int NavigationControllerBase::GetIndexOfEntry( + const NavigationEntry* entry) const { + NavigationEntryList::const_iterator i = find(entries_.begin(), entries_.end(), entry); + if (i == entries_.end()) + return -1; + return static_cast<int>(i - entries_.begin()); +} + +void NavigationControllerBase::DiscardPendingEntryInternal() { + if (pending_entry_index_ == -1) + delete pending_entry_; + pending_entry_ = NULL; + pending_entry_index_ = -1; +} + +void NavigationControllerBase::InsertEntry(NavigationEntry* entry) { + DCHECK(entry->GetTransition() != PageTransition::AUTO_SUBFRAME); + + DiscardPendingEntryInternal(); + + int current_size = static_cast<int>(entries_.size()); + + // Prune any entry which are in front of the current entry + if (current_size > 0) { + while (last_committed_entry_index_ < (current_size - 1)) { + PruneEntryAtIndex(current_size - 1); + delete entries_[current_size - 1]; + entries_.pop_back(); + current_size--; + } + NotifyPrunedEntries(); + } + + entries_.push_back(entry); + last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1; + + NotifyNavigationStateChanged(); +} + +void NavigationControllerBase::ResetInternal() { + // WARNING: this is invoked from the destructor, be sure not to invoke any + // virtual methods from this. + for (int i = 0, c = static_cast<int>(entries_.size()); i < c; ++i) + delete entries_[i]; + entries_.clear(); + + DiscardPendingEntryInternal(); +} + +#ifndef NDEBUG + +void NavigationControllerBase::Dump() { + int i,c; + for (i = 1, c = static_cast<int>(entries_.size()); i < c; ++i) { + DLOG(INFO) << entries_[i]->GetURL().spec(); + } +} + +#endif diff --git a/webkit/tools/test_shell/temp/navigation_controller_base.h b/webkit/tools/test_shell/temp/navigation_controller_base.h new file mode 100644 index 0000000..d4b3785 --- /dev/null +++ b/webkit/tools/test_shell/temp/navigation_controller_base.h @@ -0,0 +1,220 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEMP_NAVIGATION_CONTROLLER_BASE_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEMP_NAVIGATION_CONTROLLER_BASE_H__ + +#include <vector> + +#include "webkit/tools/test_shell/temp/page_transition_types.h" + +class NavigationEntry; + +typedef int TabContentsType; + +//////////////////////////////////////////////////////////////////////////////// +// +// NavigationControllerBase class +// +// A NavigationControllerBase maintains navigation data (like session history). +// +//////////////////////////////////////////////////////////////////////////////// +class NavigationControllerBase { + public: + NavigationControllerBase(); + virtual ~NavigationControllerBase(); + + // Empties the history list. + virtual void Reset(); + + // Returns the active entry, which is the pending entry if a navigation is in + // progress or the last committed entry otherwise. NOTE: This can be NULL!! + // + // If you are trying to get the current state of the NavigationControllerBase, + // this is the method you will typically want to call. + // + NavigationEntry* GetActiveEntry() const; + + // Returns the index from which we would go back/forward or reload. This is + // the last_committed_entry_index_ if pending_entry_index_ is -1. Otherwise, + // it is the pending_entry_index_. + int GetCurrentEntryIndex() const; + + // Returns the pending entry corresponding to the navigation that is + // currently in progress, or null if there is none. + NavigationEntry* GetPendingEntry() const { + return pending_entry_; + } + + // Returns the index of the pending entry or -1 if the pending entry + // corresponds to a new navigation (created via LoadURL). + int GetPendingEntryIndex() const { + return pending_entry_index_; + } + + // Returns the last committed entry, which may be null if there are no + // committed entries. + NavigationEntry* GetLastCommittedEntry() const; + + // Returns the index of the last committed entry. + int GetLastCommittedEntryIndex() const { + return last_committed_entry_index_; + } + + // Returns the number of entries in the NavigationControllerBase, excluding + // the pending entry if there is one. + int GetEntryCount() const { + return static_cast<int>(entries_.size()); + } + + NavigationEntry* GetEntryAtIndex(int index) const { + return entries_.at(index); + } + + // Returns the entry at the specified offset from current. Returns NULL + // if out of bounds. + NavigationEntry* GetEntryAtOffset(int offset) const; + + bool CanStop() const; + + // Return whether this controller can go back. + bool CanGoBack() const; + + // Return whether this controller can go forward. + bool CanGoForward() const; + + // Causes the controller to go back. + void GoBack(); + + // Causes the controller to go forward. + void GoForward(); + + // Causes the controller to go to the specified index. + void GoToIndex(int index); + + // Causes the controller to go to the specified offset from current. Does + // nothing if out of bounds. + void GoToOffset(int offset); + + // Causes the controller to stop a pending navigation if any. + void Stop(); + + // Causes the controller to reload the current (or pending) entry. + void Reload(); + + // Causes the controller to load the specified entry. The controller + // assumes ownership of the entry. + // NOTE: Do not pass an entry that the controller already owns! + void LoadEntry(NavigationEntry* entry); + + // Return the entry with the corresponding type and page_id, or NULL if + // not found. + NavigationEntry* GetEntryWithPageID(TabContentsType type, + int32 page_id) const; + +#ifndef NDEBUG + void Dump(); +#endif + + // -------------------------------------------------------------------------- + // For use by NavigationControllerBase clients: + + // Used to inform the NavigationControllerBase of a navigation being committed + // for a tab. The controller takes ownership of the entry. Any entry located + // forward to the current entry will be deleted. The new entry becomes the + // current entry. + virtual void DidNavigateToEntry(NavigationEntry* entry); + + // Used to inform the NavigationControllerBase to discard its pending entry. + virtual void DiscardPendingEntry(); + + // Returns the index of the specified entry, or -1 if entry is not contained + // in this NavigationControllerBase. + int GetIndexOfEntry(const NavigationEntry* entry) const; + + protected: + // Returns the largest page ID seen. When PageIDs come in larger than + // this (via DidNavigateToEntry), we know that we've navigated to a new page. + virtual int GetMaxPageID() const = 0; + + // Actually issues the navigation held in pending_entry. + virtual void NavigateToPendingEntry(bool reload) = 0; + + // Allows the derived class to issue notifications that a load has been + // committed. + virtual void NotifyNavigationStateChanged() {} + + // Invoked when entries have been pruned, or removed. For example, if the + // current entries are [google, digg, yahoo], with the current entry google, + // and the user types in cnet, then digg and yahoo are pruned. + virtual void NotifyPrunedEntries() {} + + // Invoked when the index of the active entry may have changed. + virtual void IndexOfActiveEntryChanged() {} + + // Inserts an entry after the current position, removing all entries after it. + // The new entry will become the active one. + virtual void InsertEntry(NavigationEntry* entry); + + // Called when navigations cause entries forward of and including the + // specified index are pruned. + virtual void PruneEntryAtIndex(int prune_index) { } + + // Discards the pending entry without updating active_contents_ + void DiscardPendingEntryInternal(); + + // Return the index of the entry with the corresponding type and page_id, + // or -1 if not found. + int GetEntryIndexWithPageID(TabContentsType type, int32 page_id) const; + + // List of NavigationEntry for this tab + typedef std::vector<NavigationEntry*> NavigationEntryList; + typedef NavigationEntryList::iterator NavigationEntryListIterator; + NavigationEntryList entries_; + + // An entry we haven't gotten a response for yet. This will be discarded + // when we navigate again. It's used only so we know what the currently + // displayed tab is. + NavigationEntry* pending_entry_; + + // currently visible entry + int last_committed_entry_index_; + + // index of pending entry if it is in entries_, or -1 if pending_entry_ is a + // new entry (created by LoadURL). + int pending_entry_index_; + + private: + // Implementation of Reset and the destructor. Deletes entries + void ResetInternal(); + + DISALLOW_EVIL_CONSTRUCTORS(NavigationControllerBase); +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEMP_NAVIGATION_CONTROLLER_BASE_H__ diff --git a/webkit/tools/test_shell/temp/navigation_entry.h b/webkit/tools/test_shell/temp/navigation_entry.h new file mode 100644 index 0000000..c40eb5b --- /dev/null +++ b/webkit/tools/test_shell/temp/navigation_entry.h @@ -0,0 +1,154 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEMP_NAVIGATION_ENTRY_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEMP_NAVIGATION_ENTRY_H__ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "webkit/tools/test_shell/temp/page_transition_types.h" +#include "googleurl/src/gurl.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// NavigationEntry class +// +// A NavigationEntry is a data structure that captures all the information +// required to recreate a browsing state. This includes some opaque binary +// state as provided by the TabContents as well as some clear text title and +// uri which is used for our user interface. +// +//////////////////////////////////////////////////////////////////////////////// +class NavigationEntry { + public: + // Create a new NavigationEntry. + explicit NavigationEntry(TabContentsType type) + : type_(type), + page_id_(-1), + transition_(PageTransition::LINK) { + } + + NavigationEntry(TabContentsType type, + int page_id, + const GURL& url, + const std::wstring& title, + PageTransition::Type transition) + : type_(type), + page_id_(page_id), + url_(url), + title_(title), + transition_(transition) { + } + + // virtual to allow test_shell to extend the class. + virtual ~NavigationEntry() { + } + + // Return the TabContents type required to display this entry. Immutable + // because a tab can never change its type. + TabContentsType GetType() const { return type_; } + + // Set / Get the URI + void SetURL(const GURL& url) { url_ = url; } + const GURL& GetURL() const { return url_; } + + void SetDisplayURL(const GURL& url) { + if (url == url_) { + display_url_ = GURL::EmptyGURL(); + } else { + display_url_ = url; + } + } + + bool HasDisplayURL() { return !display_url_.is_empty(); } + + const GURL& GetDisplayURL() const { + if (display_url_.is_empty()) { + return url_; + } else { + return display_url_; + } + } + + // Set / Get the title + void SetTitle(const std::wstring& a_title) { title_ = a_title; } + const std::wstring& GetTitle() const { return title_; } + + // Set / Get opaque state. + // WARNING: This state is saved to the database and used to restore previous + // states. If you use write a custom TabContents and provide your own + // state make sure you have the ability to modify the format in the future + // while being able to deal with older versions. + void SetContentState(const std::string& state) { state_ = state; } + const std::string& GetContentState() const { return state_; } + + // Get the page id corresponding to the tab's state. + void SetPageID(int page_id) { page_id_ = page_id; } + int32 GetPageID() const { return page_id_; } + + // The transition type indicates what the user did to move to this page from + // the previous page. + void SetTransition(PageTransition::Type transition) { + transition_ = transition; + } + PageTransition::Type GetTransition() const { return transition_; } + + // Set / Get favicon URL. + void SetFavIconURL(const GURL& url) { fav_icon_url_ = url; } + const GURL& GetFavIconURL() const { return fav_icon_url_; } + + // This is the URL the user typed in. This may be invalid. + void SetUserTypedURL(const GURL& url) { user_typed_url_ = url; } + const GURL& GetUserTypedURL() const { return user_typed_url_; } + + // If the user typed url is valid it is returned, otherwise url is returned. + const GURL& GetUserTypedURLOrURL() const { + return user_typed_url_.is_valid() ? user_typed_url_ : url_; + } + + private: + TabContentsType type_; + + // Describes the current page that the tab represents. This is not relevant + // for all tab contents types. + int32 page_id_; + + GURL url_; + // The URL the user typed in. + GURL user_typed_url_; + std::wstring title_; + GURL fav_icon_url_; + GURL display_url_; + + std::string state_; + + PageTransition::Type transition_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEMP_NAVIGATION_ENTRY_H__ diff --git a/webkit/tools/test_shell/temp/page_transition_types.h b/webkit/tools/test_shell/temp/page_transition_types.h new file mode 100644 index 0000000..1253963 --- /dev/null +++ b/webkit/tools/test_shell/temp/page_transition_types.h @@ -0,0 +1,173 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEMP_PAGE_TRANSITION_TYPES_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEMP_PAGE_TRANSITION_TYPES_H__ + +#include "base/basictypes.h" +#include "base/logging.h" + +// This class is for scoping only. +class PageTransition { + public: + // Types of transitions between pages. These are stored in the history + // database to separate visits, and are reported by the renderer for page + // navigations. + // + // WARNING: don't change these numbers, they are written directly into the + // history database, so future versions will need the same values to match + // the enums. + // + // A type is made of a core value and a set of qualifiers. A type has one + // core value and 0 or or more qualifiers. + enum Type { + // User got to this page by clicking a link on another page. + LINK = 0, + + // User got this page by typing the URL in the URL bar. This should not be + // used for cases where the user selected a choice that didn't look at all + // like a URL; see GENERATED below. + // + // We also use this for other "explicit" navigation actions such as command + // line arguments. + TYPED = 1, + + // User got to this page through a suggestion in the UI, for example, + // through the destinations page. + AUTO_BOOKMARK = 2, + + // This is a subframe navigation. This is any content that is automatically + // loaded in a non-toplevel frame. For example, if a page consists of + // several frames containing ads, those ad URLs will have this transition + // type. The user may not even realize the content in these pages is a + // separate frame, so may not care about the URL (see MANUAL below). + AUTO_SUBFRAME = 3, + + // For subframe navigations that are explicitly requested by the user and + // generate new navigation entries in the back/forward list. These are + // probably more important than frames that were automatically loaded in + // the background because the user probably cares about the fact that this + // link was loaded. + MANUAL_SUBFRAME = 4, + + // User got to this page by typing in the URL bar and selecting an entry + // that did not look like a URL. For example, a match might have the URL + // of a Google search result page, but appear like "Search Google for ...". + // These are not quite the same as TYPED navigations because the user + // didn't type or see the destination URL. + GENERATED = 5, + + // The page was specified in the command line or is the start page. + START_PAGE = 6, + + // The user filled out values in a form and submitted it. NOTE that in + // some situations submitting a form does not result in this transition + // type. This can happen if the form uses script to submit the contents. + FORM_SUBMIT = 7, + + // The user "reloaded" the page, either by hitting the reload button or by + // hitting enter in the address bar. NOTE: This is distinct from the + // concept of whether a particular load uses "reload semantics" (i.e. + // bypasses cached data). For this reason, lots of code needs to pass + // around the concept of whether a load should be treated as a "reload" + // separately from their tracking of this transition type, which is mainly + // used for proper scoring for consumers who care about how frequently a + // user typed/visited a particular URL. + RELOAD = 8, + + // ADDING NEW CORE VALUE? Be sure to update the LAST_CORE and CORE_MASK + // values below. + LAST_CORE = RELOAD, + CORE_MASK = 0xFF, + + // Qualifiers + // Any of the core values above can be augmented by one or more qualifiers. + // These qualifiers further define the transition + + // The beginning of a navigation chain. + CHAIN_START = 0x10000000, + + // The last transition in a redirect chain. + CHAIN_END = 0x20000000, + + // Redirects caused by JavaScript or a meta refresh tag on the page. + CLIENT_REDIRECT = 0x40000000, + + // Redirects sent from the server by HTTP headers. It might be nice to + // break this out into 2 types in the future: permanent or temporary if we + // can get that information from WebKit. + SERVER_REDIRECT = 0x80000000, + + // Used to test whether a transition involves a redirect + IS_REDIRECT_MASK = 0xC0000000, + + // General mask defining the bits used for the qualifiers + QUALIFIER_MASK = 0xFFFFFF00 + }; + + static bool ValidType(int32 type) { + int32 t = StripQualifier(static_cast<Type>(type)); + return (t >= 0 && t <= LAST_CORE && + (type & ~(QUALIFIER_MASK | CORE_MASK)) == 0); + } + + static Type FromInt(int32 type) { + if (!ValidType(type)) { + NOTREACHED() << "Invalid transition type " << type; + + // Return a safe default so we don't have corrupt data in release mode. + return LINK; + } + return static_cast<Type>(type); + } + + // Returns true if the given transition is a top-level frame transition, or + // false if the transition was for a subframe. + static bool IsMainFrame(Type type) { + int32 t = StripQualifier(type); + return (t != AUTO_SUBFRAME && t != MANUAL_SUBFRAME); + } + + // Returns whether a transition involves a redirection + static bool IsRedirect(Type type) { + return (type & IS_REDIRECT_MASK) != 0; + } + + // Simplifies the provided transition by removing any qualifier + static Type StripQualifier(Type type) { + return static_cast<Type>(type & ~QUALIFIER_MASK); + } + + // Return the qualifier + static int32 GetQualifier(Type type) { + return type & QUALIFIER_MASK; + } +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEMP_PAGE_TRANSITION_TYPES_H__ diff --git a/webkit/tools/test_shell/test_navigation_controller.cc b/webkit/tools/test_shell/test_navigation_controller.cc new file mode 100644 index 0000000..8262967 --- /dev/null +++ b/webkit/tools/test_shell/test_navigation_controller.cc @@ -0,0 +1,107 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/test_navigation_controller.h" + +#include "base/logging.h" +#include "webkit/glue/webhistoryitem.h" +#include "webkit/tools/test_shell/test_shell.h" + +// ---------------------------------------------------------------------------- +// TestNavigationEntry + +TestNavigationEntry::TestNavigationEntry() + : NavigationEntry(GetTabContentsType()) { +} + +TestNavigationEntry::TestNavigationEntry(int page_id, + const GURL& url, + const std::wstring& title, + PageTransition::Type transition, + const std::wstring& target_frame) + : NavigationEntry(GetTabContentsType(), page_id, url, title, transition), + target_frame_(target_frame) { +} + +TestNavigationEntry::~TestNavigationEntry() { +} + +void TestNavigationEntry::SetContentState(const std::string& state) { + cached_history_item_ = NULL; // invalidate our cached item + NavigationEntry::SetContentState(state); +} + +WebHistoryItem* TestNavigationEntry::GetHistoryItem() const { + if (!cached_history_item_) { + TestShellExtraRequestData* extra_data = + new TestShellExtraRequestData(GetPageID(), GetTransition()); + cached_history_item_ = + WebHistoryItem::Create(GetURL(), GetTitle(), GetContentState(), + extra_data); + } + return cached_history_item_; +} + +// ---------------------------------------------------------------------------- +// TestNavigationController + +TestNavigationController::TestNavigationController(TestShell* shell) + : shell_(shell), + max_page_id_(-1) { +} + +TestNavigationController::~TestNavigationController() { +} + +void TestNavigationController::Reset() { + NavigationControllerBase::Reset(); + max_page_id_ = -1; +} + +void TestNavigationController::NavigateToPendingEntry(bool reload) { + // For session history navigations only the pending_entry_index_ is set. + if (!pending_entry_) { + DCHECK(pending_entry_index_ != -1); + pending_entry_ = entries_[pending_entry_index_]; + } + + if (shell_->Navigate(*pending_entry_, reload)) { + // Note: this is redundant if navigation completed synchronously because + // DidNavigateToEntry call this as well. + NotifyNavigationStateChanged(); + } else { + DiscardPendingEntry(); + } +} + +void TestNavigationController::NotifyNavigationStateChanged() { + NavigationEntry* entry = GetActiveEntry(); + if (entry) + max_page_id_ = std::max(max_page_id_, entry->GetPageID()); +} diff --git a/webkit/tools/test_shell/test_navigation_controller.h b/webkit/tools/test_shell/test_navigation_controller.h new file mode 100644 index 0000000..4bdcad8 --- /dev/null +++ b/webkit/tools/test_shell/test_navigation_controller.h @@ -0,0 +1,118 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEST_NAVIGATION_CONTROLLER_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEST_NAVIGATION_CONTROLLER_H__ + +#include <vector> +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "webkit/glue/weburlrequest.h" +#include "webkit/tools/test_shell/temp/navigation_controller_base.h" +#include "webkit/tools/test_shell/temp/navigation_entry.h" +#include "webkit/tools/test_shell/temp/page_transition_types.h" + +class GURL; +class TestShell; +class WebHistoryItem; + +// Associated with browser-initated navigations to hold tracking data. +class TestShellExtraRequestData : public WebRequest::ExtraData { + public: + TestShellExtraRequestData(int32 pending_page_id, + PageTransition::Type transition) + : WebRequest::ExtraData(), + pending_page_id(pending_page_id), + transition_type(transition), + request_committed(false) { + } + + // Contains the page_id for this navigation or -1 if there is none yet. + int32 pending_page_id; + + // Contains the transition type that the browser specified when it + // initiated the load. + PageTransition::Type transition_type; + + // True if we have already processed the "DidCommitLoad" event for this + // request. Used by session history. + bool request_committed; +}; + +// Same as TestNavigationEntry, but caches the HistoryItem. +class TestNavigationEntry : public NavigationEntry { + public: + TestNavigationEntry(); + TestNavigationEntry(int page_id, + const GURL& url, + const std::wstring& title, + PageTransition::Type transition, + const std::wstring& target_frame); + + ~TestNavigationEntry(); + + // We don't care about tab contents types, so just pick one and use it + // everywhere. + static TabContentsType GetTabContentsType() { + return 0; + } + + void SetContentState(const std::string& state); + WebHistoryItem* GetHistoryItem() const; + + const std::wstring& GetTargetFrame() const { return target_frame_; } + +private: + mutable scoped_refptr<WebHistoryItem> cached_history_item_; + std::wstring target_frame_; +}; + +// Test shell's NavigationController. The goal is to be as close to the Chrome +// version as possible. +class TestNavigationController : public NavigationControllerBase { + public: + TestNavigationController(TestShell* shell); + ~TestNavigationController(); + + virtual void Reset(); + + private: + virtual int GetMaxPageID() const { return max_page_id_; } + virtual void NavigateToPendingEntry(bool reload); + virtual void NotifyNavigationStateChanged(); + + TestShell* shell_; + int max_page_id_; + + DISALLOW_EVIL_CONSTRUCTORS(TestNavigationController); +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_NAVIGATION_CONTROLLER_H__ diff --git a/webkit/tools/test_shell/test_shell.cc b/webkit/tools/test_shell/test_shell.cc new file mode 100644 index 0000000..407b95d --- /dev/null +++ b/webkit/tools/test_shell/test_shell.cc @@ -0,0 +1,1128 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 <windows.h> +#include <atlbase.h> +#include <commdlg.h> +#include <objbase.h> +#include <shlwapi.h> +#include <wininet.h> + +#include "webkit/tools/test_shell/test_shell.h" + +#include "base/command_line.h" +#include "base/debug_on_start.h" +#include "base/file_util.h" +#include "base/gfx/bitmap_platform_device.h" +#include "base/gfx/png_encoder.h" +#include "base/gfx/size.h" +#include "base/icu_util.h" +#include "base/md5.h" +#include "base/memory_debug.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/stats_table.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "googleurl/src/url_util.h" +#include "net/base/mime_util.h" +#include "net/url_request/url_request_file_job.h" +#include "net/url_request/url_request_filter.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/webdatasource.h" +#include "webkit/glue/webframe.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/webkit_resources.h" +#include "webkit/glue/webpreferences.h" +#include "webkit/glue/weburlrequest.h" +#include "webkit/glue/webview.h" +#include "webkit/glue/webwidget.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_navigation_controller.h" + +#include "webkit_strings.h" + +#include "SkBitmap.h" + +using std::min; +using std::max; + +#define MAX_LOADSTRING 100 + +#define BUTTON_WIDTH 72 +#define URLBAR_HEIGHT 24 + +// Global Variables: +static TCHAR g_windowTitle[MAX_LOADSTRING]; // The title bar text +static TCHAR g_windowClass[MAX_LOADSTRING]; // The main window class name + +// Forward declarations of functions included in this code module: +static INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); + +// Default timeout for page load when running non-interactive file +// tests, in ms. +const int kDefaultFileTestTimeoutMillisecs = 10 * 1000; + +// Content area size for newly created windows. +const int kTestWindowWidth = 800; +const int kTestWindowHeight = 600; + +// The W3C SVG layout tests use a different size than the other layout tests +const int kSVGTestWindowWidth = 480; +const int kSVGTestWindowHeight = 360; + +// Hide the window offscreen when it is non-interactive. +// This would correspond with a minimized window position if x = y = -32000. +// However we shift the x to 0 to pass test cross-frame-access-put.html +// which expects screenX/screenLeft to be 0 (http://b/issue?id=1227945). +// TODO(ericroman): x should be defined as 0 rather than -4. There is +// probably a frameborder not being accounted for in the setting/getting. +const int kTestWindowXLocation = -4; +const int kTestWindowYLocation = -32000; + +// Initialize static member variable +WindowList* TestShell::window_list_; +HINSTANCE TestShell::instance_handle_; +WebPreferences* TestShell::web_prefs_ = NULL; +bool TestShell::interactive_ = true; +int TestShell::file_test_timeout_ms_ = kDefaultFileTestTimeoutMillisecs; + +// URLRequestTestShellFileJob is used to serve the inspector +class URLRequestTestShellFileJob : public URLRequestFileJob { + public: + virtual ~URLRequestTestShellFileJob() { } + + static URLRequestJob* InspectorFactory(URLRequest* request, + const std::string& scheme) { + std::wstring path; + PathService::Get(base::DIR_EXE, &path); + file_util::AppendToPath(&path, L"Resources"); + file_util::AppendToPath(&path, L"Inspector"); + file_util::AppendToPath(&path, UTF8ToWide(request->url().path())); + return new URLRequestTestShellFileJob(request, path); + } + + private: + URLRequestTestShellFileJob(URLRequest* request, const std::wstring& path) + : URLRequestFileJob(request) { + this->file_path_ = path; // set URLRequestFileJob::file_path_ + } + + DISALLOW_EVIL_CONSTRUCTORS(URLRequestTestShellFileJob); +}; + +TestShell::TestShell() + : m_mainWnd(NULL), + m_editWnd(NULL), + m_webViewHost(NULL), + m_popupHost(NULL), + m_focusedWidgetHost(NULL), + default_edit_wnd_proc_(0), + delegate_(new TestWebViewDelegate(this)), + test_is_preparing_(false), + test_is_pending_(false), + is_modal_(false), + dump_stats_table_on_exit_(false) { + layout_test_controller_.reset(new LayoutTestController(this)); + event_sending_controller_.reset(new EventSendingController(this)); + text_input_controller_.reset(new TextInputController(this)); + navigation_controller_.reset(new TestNavigationController(this)); + + URLRequestFilter* filter = URLRequestFilter::GetInstance(); + filter->AddHostnameHandler("test-shell-resource", "inspector", + &URLRequestTestShellFileJob::InspectorFactory); + url_util::AddStandardScheme("test-shell-resource"); +} + +TestShell::~TestShell() { + + // Call GC twice to clean up garbage. + CallJSGC(); + CallJSGC(); + + // When the window is destroyed, tell the Edit field to forget about us, + // otherwise we will crash. + win_util::SetWindowProc(m_editWnd, default_edit_wnd_proc_); + win_util::SetWindowUserData(m_editWnd, NULL); + + StatsTable *table = StatsTable::current(); + if (dump_stats_table_on_exit_) { + // Dump the stats table. + printf("<stats>\n"); + if (table != NULL) { + int counter_max = table->GetMaxCounters(); + for (int index=0; index < counter_max; index++) { + std::wstring name(table->GetRowName(index)); + if (name.length() > 0) { + int value = table->GetRowValue(index); + printf("%s:\t%d\n", WideToUTF8(name).c_str(), value); + } + } + } + printf("</stats>\n"); + } +} + +// All fatal log messages (e.g. DCHECK failures) imply unit test failures +static void UnitTestAssertHandler(const std::string& str) { + FAIL() << str; +} + +// static +void TestShell::InitLogging(bool suppress_error_dialogs) { + if (!IsDebuggerPresent() && suppress_error_dialogs) { + UINT new_flags = SEM_FAILCRITICALERRORS | + SEM_NOGPFAULTERRORBOX | + SEM_NOOPENFILEERRORBOX; + // Preserve existing error mode, as discussed at http://t/dmea + UINT existing_flags = SetErrorMode(new_flags); + SetErrorMode(existing_flags | new_flags); + + logging::SetLogAssertHandler(UnitTestAssertHandler); + } + + // We might have multiple test_shell processes going at once + std::wstring log_filename; + PathService::Get(base::DIR_EXE, &log_filename); + file_util::AppendToPath(&log_filename, L"test_shell.log"); + logging::InitLogging(log_filename.c_str(), + logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG, + logging::LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE); + + // we want process and thread IDs because we may have multiple processes + logging::SetLogItems(true, true, false, true); +} + +// static +void TestShell::CleanupLogging() { + logging::CloseLogFile(); +} + +// static +void TestShell::InitializeTestShell(bool interactive) { + // Start COM stuff. + HRESULT res = OleInitialize(NULL); + DCHECK(SUCCEEDED(res)); + + window_list_ = new WindowList; + instance_handle_ = ::GetModuleHandle(NULL); + interactive_ = interactive; + + web_prefs_ = new WebPreferences; + + ResetWebPreferences(); +} + +// static +void TestShell::ResetWebPreferences() { + DCHECK(web_prefs_); + + // Match the settings used by Mac DumpRenderTree. + if (web_prefs_) { + *web_prefs_ = WebPreferences(); + web_prefs_->standard_font_family = L"Times"; + web_prefs_->fixed_font_family = L"Courier"; + web_prefs_->serif_font_family = L"Times"; + web_prefs_->sans_serif_font_family = L"Helvetica"; + web_prefs_->cursive_font_family = L"Apple Chancery"; + web_prefs_->fantasy_font_family = L"Papyrus"; + web_prefs_->default_encoding = L"ISO-8859-1"; + web_prefs_->default_font_size = 16; + web_prefs_->default_fixed_font_size = 13; + web_prefs_->minimum_font_size = 1; + web_prefs_->minimum_logical_font_size = 9; + web_prefs_->javascript_can_open_windows_automatically = true; + web_prefs_->dom_paste_enabled = true; + web_prefs_->developer_extras_enabled = interactive_; + web_prefs_->shrinks_standalone_images_to_fit = false; + web_prefs_->uses_universal_detector = false; + web_prefs_->text_areas_are_resizable = false; + web_prefs_->user_agent = webkit_glue::GetDefaultUserAgent(); + web_prefs_->dashboard_compatibility_mode = false; + web_prefs_->java_enabled = true; + } +} + +// static +void TestShell::ShutdownTestShell() { + delete window_list_; + SimpleResourceLoaderBridge::Shutdown(); + delete TestShell::web_prefs_; + OleUninitialize(); +} + +bool TestShell::Initialize(const std::wstring& startingURL) { + // Perform application initialization: + m_mainWnd = CreateWindow(g_windowClass, g_windowTitle, + WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, + NULL, NULL, instance_handle_, NULL); + win_util::SetWindowUserData(m_mainWnd, this); + + HWND hwnd; + int x = 0; + + hwnd = CreateWindow(L"BUTTON", L"Back", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON , + x, 0, BUTTON_WIDTH, URLBAR_HEIGHT, + m_mainWnd, (HMENU) IDC_NAV_BACK, instance_handle_, 0); + x += BUTTON_WIDTH; + + hwnd = CreateWindow(L"BUTTON", L"Forward", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON , + x, 0, BUTTON_WIDTH, URLBAR_HEIGHT, + m_mainWnd, (HMENU) IDC_NAV_FORWARD, instance_handle_, 0); + x += BUTTON_WIDTH; + + hwnd = CreateWindow(L"BUTTON", L"Reload", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON , + x, 0, BUTTON_WIDTH, URLBAR_HEIGHT, + m_mainWnd, (HMENU) IDC_NAV_RELOAD, instance_handle_, 0); + x += BUTTON_WIDTH; + + hwnd = CreateWindow(L"BUTTON", L"Stop", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON , + x, 0, BUTTON_WIDTH, URLBAR_HEIGHT, + m_mainWnd, (HMENU) IDC_NAV_STOP, instance_handle_, 0); + x += BUTTON_WIDTH; + + // this control is positioned by ResizeSubViews + m_editWnd = CreateWindow(L"EDIT", 0, + WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | + ES_AUTOVSCROLL | ES_AUTOHSCROLL, + x, 0, 0, 0, m_mainWnd, 0, instance_handle_, 0); + + default_edit_wnd_proc_ = + win_util::SetWindowProc(m_editWnd, TestShell::EditWndProc); + win_util::SetWindowUserData(m_editWnd, this); + + // create webview + m_webViewHost.reset( + WebViewHost::Create(m_mainWnd, delegate_.get(), *TestShell::web_prefs_)); + webView()->SetUseEditorDelegate(true); + delegate_->RegisterDragDrop(); + + // Load our initial content. + if (!startingURL.empty()) + LoadURL(startingURL.c_str()); + + ShowWindow(webViewWnd(), SW_SHOW); + + bool bIsSVGTest = startingURL.find(L"W3C-SVG-1.1") != std::wstring::npos; + + if (bIsSVGTest) { + SizeTo(kSVGTestWindowWidth, kSVGTestWindowHeight); + } else { + SizeToDefault(); + } + + return true; +} + +void TestShell::TestFinished() { + if (!test_is_pending_) + return; // reached when running under test_shell_tests + + UINT_PTR timer_id = reinterpret_cast<UINT_PTR>(this); + KillTimer(mainWnd(), timer_id); + + test_is_pending_ = false; + MessageLoop::current()->Quit(); +} + +// Thread main to run for the thread which just tests for timeout. +unsigned int __stdcall WatchDogThread(void *arg) +{ + // If we're debugging a layout test, don't timeout. + if (::IsDebuggerPresent()) + return 0; + + TestShell* shell = static_cast<TestShell*>(arg); + DWORD timeout = static_cast<DWORD>(shell->GetFileTestTimeout() * 2.5); + DWORD rv = WaitForSingleObject(shell->finished_event(), timeout); + if (rv == WAIT_TIMEOUT) { + // Print a warning to be caught by the layout-test script. + // Note: the layout test driver may or may not recognize + // this as a timeout. + puts("#TEST_TIMED_OUT\n"); + puts("#EOF\n"); + fflush(stdout); + TerminateProcess(GetCurrentProcess(), 0); + } + // Finished normally. + return 0; +} + +void TestShell::WaitTestFinished() { + DCHECK(!test_is_pending_) << "cannot be used recursively"; + + test_is_pending_ = true; + + // Create a watchdog thread which just sets a timer and + // kills the process if it times out. This catches really + // bad hangs where the shell isn't coming back to the + // message loop. If the watchdog is what catches a + // timeout, it can't do anything except terminate the test + // shell, which is unfortunate. + finished_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); + DCHECK(finished_event_ != NULL); + + HANDLE thread_handle = reinterpret_cast<HANDLE>(_beginthreadex( + NULL, + 0, + &WatchDogThread, + this, + 0, + 0)); + DCHECK(thread_handle != NULL); + + // TestFinished() will post a quit message to break this loop when the page + // finishes loading. + while (test_is_pending_) + MessageLoop::current()->Run(); + + // Tell the watchdog that we are finished. + SetEvent(finished_event_); + + // Wait to join the watchdog thread. (up to 1s, then quit) + WaitForSingleObject(thread_handle, 1000); +} + +void TestShell::Show(WebView* webview, WindowOpenDisposition disposition) { + delegate_->Show(webview, disposition); +} + +void TestShell::SetFocus(WebWidgetHost* host, bool enable) { + if (interactive_) { + if (enable) { + ::SetFocus(host->window_handle()); + } else { + if (GetFocus() == host->window_handle()) + ::SetFocus(NULL); + } + } else { + if (enable) { + if (m_focusedWidgetHost != host) { + if (m_focusedWidgetHost) + m_focusedWidgetHost->webwidget()->SetFocus(false); + host->webwidget()->SetFocus(enable); + m_focusedWidgetHost = host; + } + } else { + if (m_focusedWidgetHost == host) { + host->webwidget()->SetFocus(enable); + m_focusedWidgetHost = NULL; + } + } + } +} + +void TestShell::BindJSObjectsToWindow(WebFrame* frame) { + // Only bind the test classes if we're running tests. + if (!interactive_) { + layout_test_controller_->BindToJavascript(frame, + L"layoutTestController"); + event_sending_controller_->BindToJavascript(frame, + L"eventSender"); + text_input_controller_->BindToJavascript(frame, + L"textInputController"); + } +} + + +void TestShell::CallJSGC() { + WebFrame* frame = webView()->GetMainFrame(); + frame->CallJSGC(); +} + + +/*static*/ +bool TestShell::CreateNewWindow(const std::wstring& startingURL, + TestShell** result) +{ + TestShell* shell = new TestShell(); + bool rv = shell->Initialize(startingURL); + if (rv) { + if (result) + *result = shell; + TestShell::windowList()->push_back(shell->m_mainWnd); + } + return rv; +} + +WebView* TestShell::CreateWebView(WebView* webview) { + // If we're running layout tests, only open a new window if the test has + // called layoutTestController.setCanOpenWindows() + if (!interactive_ && !layout_test_controller_->CanOpenWindows()) + return NULL; + + TestShell* new_win; + if (!CreateNewWindow(std::wstring(), &new_win)) + return NULL; + + return new_win->webView(); +} + +WebWidget* TestShell::CreatePopupWidget(WebView* webview) { + DCHECK(!m_popupHost); + m_popupHost = WebWidgetHost::Create(NULL, delegate_.get()); + ShowWindow(popupWnd(), SW_SHOW); + + return m_popupHost->webwidget(); +} + +void TestShell::ClosePopup() { + PostMessage(popupWnd(), WM_CLOSE, 0, 0); + m_popupHost = NULL; +} + +void TestShell::SizeToDefault() { + SizeTo(kTestWindowWidth, kTestWindowHeight); +} + +void TestShell::SizeTo(int width, int height) { + RECT rc, rw; + GetClientRect(m_mainWnd, &rc); + GetWindowRect(m_mainWnd, &rw); + + int client_width = rc.right - rc.left; + int window_width = rw.right - rw.left; + window_width = (window_width - client_width) + width; + + int client_height = rc.bottom - rc.top; + int window_height = rw.bottom - rw.top; + window_height = (window_height - client_height) + height; + + // add space for the url bar: + window_height += URLBAR_HEIGHT; + + SetWindowPos(m_mainWnd, NULL, 0, 0, window_width, window_height, + SWP_NOMOVE | SWP_NOZORDER); +} + +void TestShell::ResizeSubViews() { + RECT rc; + GetClientRect(m_mainWnd, &rc); + + int x = BUTTON_WIDTH * 4; + MoveWindow(m_editWnd, x, 0, rc.right - x, URLBAR_HEIGHT, TRUE); + + MoveWindow(webViewWnd(), 0, URLBAR_HEIGHT, rc.right, + rc.bottom - URLBAR_HEIGHT, TRUE); +} + +/* static */ std::string TestShell::DumpImage( + WebFrame* web_frame, + const std::wstring& file_name) { + gfx::BitmapPlatformDevice device(web_frame->CaptureImage(true)); + const SkBitmap& src_bmp = device.accessBitmap(false); + + // Encode image. + std::vector<unsigned char> png; + SkAutoLockPixels src_bmp_lock(src_bmp); + PNGEncoder::Encode( + reinterpret_cast<const unsigned char*>(src_bmp.getPixels()), + PNGEncoder::FORMAT_BGRA, src_bmp.width(), src_bmp.height(), + static_cast<int>(src_bmp.rowBytes()), true, &png); + + // Write to disk. + FILE* file = NULL; + if (_wfopen_s(&file, file_name.c_str(), L"wb") == 0) { + fwrite(&png[0], 1, png.size(), file); + fclose(file); + } + + // Compute MD5 sum. + MD5Context ctx; + MD5Init(&ctx); + MD5Update(&ctx, src_bmp.getPixels(), src_bmp.getSize()); + + MD5Digest digest; + MD5Final(&digest, &ctx); + return MD5DigestToBase16(digest); +} + +/* static */ void TestShell::DumpBackForwardList(std::wstring* result) { + result->clear(); + for (WindowList::iterator iter = TestShell::windowList()->begin(); + iter != TestShell::windowList()->end(); iter++) { + HWND hwnd = *iter; + TestShell* shell = + static_cast<TestShell*>(win_util::GetWindowUserData(hwnd)); + webkit_glue::DumpBackForwardList(shell->webView(), NULL, result); + } +} + +/* static */ bool TestShell::RunFileTest(const char *filename, + const TestParams& params) { + // Load the test file into the first available window. + if (TestShell::windowList()->empty()) { + LOG(ERROR) << "No windows open."; + return false; + } + + HWND hwnd = *(TestShell::windowList()->begin()); + TestShell* shell = + static_cast<TestShell*>(win_util::GetWindowUserData(hwnd)); + shell->ResetTestController(); + + // ResetTestController may have closed the window we were holding on to. + // Grab the first window again. + hwnd = *(TestShell::windowList()->begin()); + shell = static_cast<TestShell*>(win_util::GetWindowUserData(hwnd)); + DCHECK(shell); + + // Clear focus between tests. + shell->m_focusedWidgetHost = NULL; + + // Make sure the previous load is stopped. + shell->webView()->StopLoading(); + shell->navigation_controller()->Reset(); + + // Clean up state between test runs. + webkit_glue::ResetBeforeTestRun(shell->webView()); + ResetWebPreferences(); + shell->webView()->SetPreferences(*web_prefs_); + + SetWindowPos(shell->m_mainWnd, NULL, + kTestWindowXLocation, kTestWindowYLocation, 0, 0, + SWP_NOSIZE | SWP_NOZORDER); + shell->ResizeSubViews(); + + if (strstr(filename, "loading/") || strstr(filename, "loading\\")) + shell->layout_test_controller()->SetShouldDumpFrameLoadCallbacks(true); + + shell->test_is_preparing_ = true; + + std::wstring wstr = UTF8ToWide(filename); + shell->LoadURL(wstr.c_str()); + + shell->test_is_preparing_ = false; + shell->WaitTestFinished(); + + // Echo the url in the output so we know we're not getting out of sync. + printf("#URL:%s\n", filename); + + // Dump the requested representation. + WebFrame* webFrame = shell->webView()->GetMainFrame(); + if (webFrame) { + bool should_dump_as_text = + shell->layout_test_controller_->ShouldDumpAsText(); + bool dumped_anything = false; + if (params.dump_tree) { + dumped_anything = true; + // Text output: the test page can request different types of output + // which we handle here. + if (!should_dump_as_text) { + // Plain text pages should be dumped as text + std::wstring mime_type = webFrame->GetDataSource()->GetResponseMimeType(); + should_dump_as_text = (mime_type == L"text/plain"); + } + if (should_dump_as_text) { + bool recursive = shell->layout_test_controller_-> + ShouldDumpChildFramesAsText(); + std::string data_utf8 = WideToUTF8( + webkit_glue::DumpFramesAsText(webFrame, recursive)); + fwrite(data_utf8.c_str(), 1, data_utf8.size(), stdout); + } else { + printf("%s", WideToUTF8( + webkit_glue::DumpRenderer(webFrame)).c_str()); + + bool recursive = shell->layout_test_controller_-> + ShouldDumpChildFrameScrollPositions(); + printf("%s", WideToUTF8( + webkit_glue::DumpFrameScrollPosition(webFrame, recursive)). + c_str()); + } + + if (shell->layout_test_controller_->ShouldDumpBackForwardList()) { + std::wstring bfDump; + DumpBackForwardList(&bfDump); + printf("%s", WideToUTF8(bfDump).c_str()); + } + } + + if (params.dump_pixels && !should_dump_as_text) { + // Image output: we write the image data to the file given on the + // command line (for the dump pixels argument), and the MD5 sum to + // stdout. + dumped_anything = true; + std::string md5sum = DumpImage(webFrame, params.pixel_file_name); + printf("#MD5:%s\n", md5sum.c_str()); + } + if (dumped_anything) + printf("#EOF\n"); + fflush(stdout); + } + + return true; +} + + +/* static */ +ATOM TestShell::RegisterWindowClass() +{ + LoadString(instance_handle_, IDS_APP_TITLE, g_windowTitle, MAX_LOADSTRING); + LoadString(instance_handle_, IDC_TESTSHELL, g_windowClass, MAX_LOADSTRING); + + WNDCLASSEX wcex = { + /* cbSize = */ sizeof(WNDCLASSEX), + /* style = */ CS_HREDRAW | CS_VREDRAW, + /* lpfnWndProc = */ TestShell::WndProc, + /* cbClsExtra = */ 0, + /* cbWndExtra = */ 0, + /* hInstance = */ instance_handle_, + /* hIcon = */ LoadIcon(instance_handle_, MAKEINTRESOURCE(IDI_TESTSHELL)), + /* hCursor = */ LoadCursor(NULL, IDC_ARROW), + /* hbrBackground = */ 0, + /* lpszMenuName = */ MAKEINTRESOURCE(IDC_TESTSHELL), + /* lpszClassName = */ g_windowClass, + /* hIconSm = */ LoadIcon(instance_handle_, MAKEINTRESOURCE(IDI_SMALL)), + }; + return RegisterClassEx(&wcex); +} + +LRESULT CALLBACK TestShell::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + TestShell* shell = static_cast<TestShell*>(win_util::GetWindowUserData(hwnd)); + + switch (message) { + case WM_COMMAND: + { + int wmId = LOWORD(wParam); + int wmEvent = HIWORD(wParam); + + switch (wmId) { + case IDM_ABOUT: + DialogBox(shell->instance_handle_, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, + About); + break; + case IDM_EXIT: + DestroyWindow(hwnd); + break; + case IDC_NAV_BACK: + shell->GoBackOrForward(-1); + break; + case IDC_NAV_FORWARD: + shell->GoBackOrForward(1); + break; + case IDC_NAV_RELOAD: + case IDC_NAV_STOP: + { + if (wmId == IDC_NAV_RELOAD) { + shell->Reload(); + } else { + shell->webView()->StopLoading(); + } + } + break; + case IDM_DUMP_BODY_TEXT: + shell->DumpDocumentText(); + break; + case IDM_DUMP_RENDER_TREE: + shell->DumpRenderTree(); + break; + case IDM_SHOW_WEB_INSPECTOR: + shell->webView()->InspectElement(0, 0); + break; + } + } + break; + + case WM_DESTROY: + { + // Dump all in use memory just before shutdown if in use memory + // debugging has been enabled. + base::MemoryDebug::DumpAllMemoryInUse(); + + WindowList::iterator entry = + std::find(TestShell::windowList()->begin(), + TestShell::windowList()->end(), hwnd); + if (entry != TestShell::windowList()->end()) + TestShell::windowList()->erase(entry); + + if (TestShell::windowList()->empty() || shell->is_modal()) + MessageLoop::current()->Quit(); + delete shell; + } + return 0; + + case WM_SIZE: + if (shell->webView()) + shell->ResizeSubViews(); + return 0; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + + +#define MAX_URL_LENGTH 1024 + +LRESULT CALLBACK TestShell::EditWndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + TestShell* shell = + static_cast<TestShell*>(win_util::GetWindowUserData(hwnd)); + + switch (message) { + case WM_CHAR: + if (wParam == 13) { // Enter Key + wchar_t strPtr[MAX_URL_LENGTH]; + *((LPWORD)strPtr) = MAX_URL_LENGTH; + LRESULT strLen = SendMessage(hwnd, EM_GETLINE, 0, (LPARAM)strPtr); + if (strLen > 0) + shell->LoadURL(strPtr); + + return 0; + } + } + + return (LRESULT) CallWindowProc(shell->default_edit_wnd_proc_, hwnd, + message, wParam, lParam); +} + + +// Message handler for about box. +INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} + +void TestShell::LoadURL(const wchar_t* url) +{ + LoadURLForFrame(url, NULL); +} + +void TestShell::LoadURLForFrame(const wchar_t* url, + const wchar_t* frame_name) { + if (!url) + return; + + bool bIsSVGTest = wcsstr(url, L"W3C-SVG-1.1") > 0; + + if (bIsSVGTest) { + SizeTo(kSVGTestWindowWidth, kSVGTestWindowHeight); + } else { + SizeToDefault(); + } + + std::wstring urlString(url); + if (!urlString.empty() && (PathFileExists(url) || PathIsUNC(url))) { + TCHAR fileURL[INTERNET_MAX_URL_LENGTH]; + DWORD fileURLLength = sizeof(fileURL)/sizeof(fileURL[0]); + if (SUCCEEDED(UrlCreateFromPath(url, fileURL, &fileURLLength, 0))) + urlString.assign(fileURL); + } + + std::wstring frame_string; + if (frame_name) + frame_string = frame_name; + + navigation_controller_->LoadEntry(new TestNavigationEntry( + -1, GURL(urlString), std::wstring(), PageTransition::LINK, + frame_string)); +} + +bool TestShell::Navigate(const NavigationEntry& entry, bool reload) { + const TestNavigationEntry& test_entry = + *static_cast<const TestNavigationEntry*>(&entry); + + WebRequestCachePolicy cache_policy; + if (reload) { + cache_policy = WebRequestReloadIgnoringCacheData; + } else if (entry.GetPageID() != -1) { + cache_policy = WebRequestReturnCacheDataElseLoad; + } else { + cache_policy = WebRequestUseProtocolCachePolicy; + } + + scoped_ptr<WebRequest> request(WebRequest::Create(entry.GetURL())); + request->SetCachePolicy(cache_policy); + // If we are reloading, then WebKit will use the state of the current page. + // Otherwise, we give it the state to navigate to. + if (!reload) + request->SetHistoryState(entry.GetContentState()); + + request->SetExtraData( + new TestShellExtraRequestData(entry.GetPageID(), + entry.GetTransition())); + + // Get the right target frame for the entry. + WebFrame* frame = webView()->GetMainFrame(); + if (!test_entry.GetTargetFrame().empty()) + frame = webView()->GetFrameWithName(test_entry.GetTargetFrame()); + // TODO(mpcomplete): should we clear the target frame, or should + // back/forward navigations maintain the target frame? + + frame->LoadRequest(request.get()); + SetFocus(webViewHost(), true); + + return true; +} + +void TestShell::GoBackOrForward(int offset) { + navigation_controller_->GoToOffset(offset); +} + +bool TestShell::PromptForSaveFile(const wchar_t* prompt_title, + std::wstring* result) +{ + wchar_t path_buf[MAX_PATH] = L"data.txt"; + + OPENFILENAME info = {0}; + info.lStructSize = sizeof(info); + info.hwndOwner = m_mainWnd; + info.hInstance = instance_handle_; + info.lpstrFilter = L"*.txt"; + info.lpstrFile = path_buf; + info.nMaxFile = arraysize(path_buf); + info.lpstrTitle = prompt_title; + if (!GetSaveFileName(&info)) + return false; + + result->assign(info.lpstrFile); + return true; +} + +static void WriteTextToFile(const std::wstring& data, + const std::wstring& file_path) +{ + FILE* fp; + errno_t err = _wfopen_s(&fp, file_path.c_str(), L"wt"); + if (err) + return; + std::string data_utf8 = WideToUTF8(data); + fwrite(data_utf8.c_str(), 1, data_utf8.size(), fp); + fclose(fp); +} + +std::wstring TestShell::GetDocumentText() +{ + return webkit_glue::DumpDocumentText(webView()->GetMainFrame()); +} + +void TestShell::DumpDocumentText() +{ + std::wstring file_path; + if (!PromptForSaveFile(L"Dump document text", &file_path)) + return; + + WriteTextToFile(webkit_glue::DumpDocumentText(webView()->GetMainFrame()), + file_path); +} + +void TestShell::DumpRenderTree() +{ + std::wstring file_path; + if (!PromptForSaveFile(L"Dump render tree", &file_path)) + return; + + WriteTextToFile(webkit_glue::DumpRenderer(webView()->GetMainFrame()), + file_path); +} + +void TestShell::Reload() { + navigation_controller_->Reload(); +} + +/* static */ +std::string TestShell::RewriteLocalUrl(const std::string& url) { + // Convert file:///tmp/LayoutTests urls to the actual location on disk. + const char kPrefix[] = "file:///tmp/LayoutTests/"; + const int kPrefixLen = arraysize(kPrefix) - 1; + + std::string new_url(url); + if (url.compare(0, kPrefixLen, kPrefix, kPrefixLen) == 0) { + std::wstring replace_url; + PathService::Get(base::DIR_EXE, &replace_url); + file_util::UpOneDirectory(&replace_url); + file_util::UpOneDirectory(&replace_url); + file_util::AppendToPath(&replace_url, L"webkit"); + file_util::AppendToPath(&replace_url, L"data"); + file_util::AppendToPath(&replace_url, L"layout_tests"); + file_util::AppendToPath(&replace_url, L"LayoutTests"); + replace_url.push_back(file_util::kPathSeparator); + new_url = std::string("file:///") + + WideToUTF8(replace_url).append(url.substr(kPrefixLen)); + } + return new_url; +} + +//----------------------------------------------------------------------------- + +namespace webkit_glue { + +bool HistoryContains(const char16* url, int url_len, + const char* document_host, int document_host_len, + bool is_dns_prefetch_enabled) { + return false; +} + +void DnsPrefetchUrl(const char16* url, int url_length) {} + +void PrecacheUrl(const char16* url, int url_length) {} + +void AppendToLog(const char* file, int line, const char* msg) { + logging::LogMessage(file, line).stream() << msg; +} + +bool GetMimeTypeFromExtension(std::wstring &ext, std::string *mime_type) { + return mime_util::GetMimeTypeFromExtension(ext, mime_type); +} + +bool GetMimeTypeFromFile(const std::wstring &file_path, + std::string *mime_type) { + return mime_util::GetMimeTypeFromFile(file_path, mime_type); +} + +bool GetPreferredExtensionForMimeType(const std::string& mime_type, + std::wstring* ext) { + return mime_util::GetPreferredExtensionForMimeType(mime_type, ext); +} + +IMLangFontLink2* GetLangFontLink() { + return webkit_glue::GetLangFontLinkHelper(); +} + +std::wstring GetLocalizedString(int message_id) { + const ATLSTRINGRESOURCEIMAGE* image = + AtlGetStringResourceImage(_AtlBaseModule.GetModuleInstance(), + message_id); + if (!image) { + NOTREACHED(); + return L"No string for this identifier!"; + } + return std::wstring(image->achString, image->nLength); +} + +std::string GetDataResource(int resource_id) { + if (resource_id == IDR_BROKENIMAGE) { + // Use webkit's broken image icon (16x16) + static std::string broken_image_data; + if (broken_image_data.empty()) { + std::wstring path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + file_util::AppendToPath(&path, L"webkit"); + file_util::AppendToPath(&path, L"tools"); + file_util::AppendToPath(&path, L"test_shell"); + file_util::AppendToPath(&path, L"resources"); + file_util::AppendToPath(&path, L"missingImage.gif"); + bool success = file_util::ReadFileToString(path, &broken_image_data); + if (!success) { + LOG(FATAL) << "Failed reading: " << path; + } + } + return broken_image_data; + } else if (resource_id == IDR_FEED_PREVIEW) { + // It is necessary to return a feed preview template that contains + // a {{URL}} substring where the feed URL should go; see the code + // that computes feed previews in feed_preview.cc:MakeFeedPreview. + // This fixes issue #932714. + return std::string("Feed preview for {{URL}}"); + } else { + return std::string(); + } +} + +HCURSOR LoadCursor(int cursor_id) { + return NULL; +} + +bool GetApplicationDirectory(std::wstring *path) { + return PathService::Get(base::DIR_EXE, path); +} + +GURL GetInspectorURL() { + return GURL("test-shell-resource://inspector/inspector.html"); +} + +std::string GetUIResourceProtocol() { + return "test-shell-resource"; +} + +bool GetExeDirectory(std::wstring *path) { + return PathService::Get(base::DIR_EXE, path); +} + +bool SpellCheckWord(const wchar_t* word, int word_len, + int* misspelling_start, int* misspelling_len) { + // Report all words being correctly spelled. + *misspelling_start = 0; + *misspelling_len = 0; + return true; +} + +bool GetPlugins(bool refresh, std::vector<WebPluginInfo>* plugins) { + return NPAPI::PluginList::Singleton()->GetPlugins(refresh, plugins); +} + +bool webkit_glue::IsPluginRunningInRendererProcess() { + return true; +} + +bool EnsureFontLoaded(HFONT font) { + return true; +} + +MONITORINFOEX GetMonitorInfoForWindow(HWND window) { + return webkit_glue::GetMonitorInfoForWindowHelper(window); +} + +bool DownloadUrl(const std::string& url, HWND caller_window) { + return false; +} + +bool GetPluginFinderURL(std::string* plugin_finder_url) { + return false; +} + +bool IsDefaultPluginEnabled() { + return false; +} + +std::wstring GetWebKitLocale() { + return L"en-US"; +} + +} // namespace webkit_glue diff --git a/webkit/tools/test_shell/test_shell.h b/webkit/tools/test_shell/test_shell.h new file mode 100644 index 0000000..b9b110a8 --- /dev/null +++ b/webkit/tools/test_shell/test_shell.h @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * 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 COMPUTER, INC. ``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 COMPUTER, INC. OR + * 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. + */ + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_H__ + +#pragma once + +#include <string> +#include <list> + +#include "base/ref_counted.h" +#include "webkit/tools/test_shell/event_sending_controller.h" +#include "webkit/tools/test_shell/layout_test_controller.h" +#include "webkit/tools/test_shell/resource.h" +#include "webkit/tools/test_shell/temp/page_transition_types.h" +#include "webkit/tools/test_shell/text_input_controller.h" +#include "webkit/tools/test_shell/test_webview_delegate.h" +#include "webkit/tools/test_shell/webview_host.h" +#include "webkit/tools/test_shell/webwidget_host.h" + +typedef std::list<HWND> WindowList; + +struct WebPreferences; +class NavigationEntry; +class TestNavigationController; + +class TestShell { +public: + struct TestParams { + // Load the test defaults. + TestParams() : dump_tree(true), dump_pixels(false) { + } + + // The kind of output we want from this test. + bool dump_tree; + bool dump_pixels; + + // Filename we dump pixels to (when pixel testing is enabled). + std::wstring pixel_file_name; + }; + + TestShell(); + virtual ~TestShell(); + + // Initialization and clean up of logging. + static void InitLogging(bool suppress_error_dialogs); + static void CleanupLogging(); + + // Initialization and clean up of a static member variable. + static void InitializeTestShell(bool interactive); + static void ShutdownTestShell(); + + static bool interactive() { return interactive_; } + + WebView* webView() { + return m_webViewHost.get() ? m_webViewHost->webview() : NULL; + } + WebViewHost* webViewHost() { return m_webViewHost.get(); } + WebWidget* popup() { return m_popupHost ? m_popupHost->webwidget() : NULL; } + WebWidgetHost* popupHost() { return m_popupHost; } + + // Called by the LayoutTestController to signal test completion. + void TestFinished(); + + // Called to block the calling thread until TestFinished is called. + void WaitTestFinished(); + + void Show(WebView* webview, WindowOpenDisposition disposition); + + // We use this to avoid relying on Windows focus during non-interactive + // mode. + void SetFocus(WebWidgetHost* host, bool enable); + + LayoutTestController* layout_test_controller() { + return layout_test_controller_.get(); + } + TestWebViewDelegate* delegate() { return delegate_.get(); } + TestNavigationController* navigation_controller() { + return navigation_controller_.get(); + } + + // Resets the LayoutTestController and EventSendingController. Should be + // called before loading a page, since some end-editing event notifications + // may arrive after the previous page has finished dumping its text and + // therefore end up in the next test's results if the messages are still + // enabled. + void ResetTestController() { + layout_test_controller_->Reset(); + event_sending_controller_->Reset(); + } + + // Passes options from LayoutTestController through to the delegate (or + // any other caller). + bool ShouldDumpEditingCallbacks() { + return !interactive_ && + layout_test_controller_->ShouldDumpEditingCallbacks(); + } + bool ShouldDumpFrameLoadCallbacks() { + return !interactive_ && (test_is_preparing_ || test_is_pending_) && + layout_test_controller_->ShouldDumpFrameLoadCallbacks(); + } + bool ShouldDumpResourceLoadCallbacks() { + return !interactive_ && (test_is_preparing_ || test_is_pending_) && + layout_test_controller_->ShouldDumpResourceLoadCallbacks(); + } + bool ShouldDumpTitleChanges() { + return !interactive_ && + layout_test_controller_->ShouldDumpTitleChanges(); + } + bool AcceptsEditing() { + return layout_test_controller_->AcceptsEditing(); + } + + void LoadURL(const wchar_t* url); + void LoadURLForFrame(const wchar_t* url, const wchar_t* frame_name); + void GoBackOrForward(int offset); + void Reload(); + bool Navigate(const NavigationEntry& entry, bool reload); + + bool PromptForSaveFile(const wchar_t* prompt_title, std::wstring* result); + std::wstring GetDocumentText(); + void DumpDocumentText(); + void DumpRenderTree(); + + HWND mainWnd() const { return m_mainWnd; } + HWND webViewWnd() const { return m_webViewHost->window_handle(); } + HWND editWnd() const { return m_editWnd; } + HWND popupWnd() const { return m_popupHost->window_handle(); } + + static WindowList* windowList() { return window_list_; } + + // If shell is non-null, then *shell is assigned upon successful return + static bool CreateNewWindow(const std::wstring& startingURL, + TestShell** shell = NULL); + + // Implements CreateWebView for TestWebViewDelegate, which in turn + // is called as a WebViewDelegate. + WebView* CreateWebView(WebView* webview); + WebWidget* CreatePopupWidget(WebView* webview); + void ClosePopup(); + + static ATOM RegisterWindowClass(); + + // Called by the WebView delegate WindowObjectCleared() method, this + // binds the layout_test_controller_ and other C++ controller classes to + // window JavaScript objects so they can be accessed by layout tests. + virtual void BindJSObjectsToWindow(WebFrame* frame); + + // Runs a layout test. Loads a single file into the first available + // window, then dumps the requested text representation to stdout. + // Returns false if the test cannot be run because no windows are open. + static bool RunFileTest(const char* filename, const TestParams& params); + + // Writes the back-forward list data for every open window into result. + static void DumpBackForwardList(std::wstring* result); + + // Writes the image captured from the given web frame to the given file. + // The returned string is the ASCII-ized MD5 sum of the image. + static std::string DumpImage(WebFrame* web_frame, + const std::wstring& file_name); + + static void ResetWebPreferences(); + + WebPreferences* GetWebPreferences() { return web_prefs_; } + + // Some layout tests hardcode a file:///tmp/LayoutTests URL. We get around + // this by substituting "tmp" with the path to the LayoutTests parent dir. + static std::string RewriteLocalUrl(const std::string& url); + + // Set the timeout for running a test. + static void SetFileTestTimeout(int timeout_ms) { + file_test_timeout_ms_ = timeout_ms; + } + + // Get the timeout for running a test. + static int GetFileTestTimeout() { return file_test_timeout_ms_; } + + // Access to the finished event. Used by the static WatchDog + // thread. + HANDLE finished_event() { return finished_event_; } + + // Have the shell print the StatsTable to stdout on teardown. + void DumpStatsTableOnExit() { dump_stats_table_on_exit_ = true; } + + void CallJSGC(); + + void set_is_modal(bool value) { is_modal_ = value; } + bool is_modal() const { return is_modal_; } + +protected: + bool Initialize(const std::wstring& startingURL); + void SizeToDefault(); + void SizeTo(int width, int height); + void ResizeSubViews(); + + static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK EditWndProc(HWND, UINT, WPARAM, LPARAM); + +protected: + HWND m_mainWnd; + HWND m_editWnd; + scoped_ptr<WebViewHost> m_webViewHost; + WebWidgetHost* m_popupHost; + WNDPROC default_edit_wnd_proc_; + + // Primitive focus controller for layout test mode. + WebWidgetHost* m_focusedWidgetHost; + +private: + // A set of all our windows. + static WindowList* window_list_; + + static HINSTANCE instance_handle_; + + // False when the app is being run using the --layout-tests switch. + static bool interactive_; + + // Timeout for page load when running non-interactive file tests, in ms. + static int file_test_timeout_ms_; + + scoped_ptr<LayoutTestController> layout_test_controller_; + + scoped_ptr<EventSendingController> event_sending_controller_; + + scoped_ptr<TextInputController> text_input_controller_; + + scoped_ptr<TestNavigationController> navigation_controller_; + + scoped_refptr<TestWebViewDelegate> delegate_; + + // True while a test is preparing to run + bool test_is_preparing_; + + // True while a test is running + bool test_is_pending_; + + // True if driven from a nested message loop. + bool is_modal_; + + // The preferences for the test shell. + static WebPreferences* web_prefs_; + + // Used by the watchdog to know when it's finished. + HANDLE finished_event_; + + // Dump the stats table counters on exit. + bool dump_stats_table_on_exit_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_H__ diff --git a/webkit/tools/test_shell/test_shell.vcproj b/webkit/tools/test_shell/test_shell.vcproj new file mode 100644 index 0000000..06fd305 --- /dev/null +++ b/webkit/tools/test_shell/test_shell.vcproj @@ -0,0 +1,546 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="test_shell" + ProjectGUID="{FA39524D-3067-4141-888D-28A86C66F2B9}" + RootNamespace="test_shell" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + <ToolFile + RelativePath="..\..\build\font_file_copy.rules" + /> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;.\test_shell.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="Font file copy" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + SubSystem="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;.\test_shell.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="Font file copy" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + SubSystem="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="resources" + > + <File + RelativePath=".\resources\fonts\ahem-fallback.txt" + > + </File> + <File + RelativePath=".\resources\fonts\Ahem.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Apple_Chancery.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Apple_Symbols.afm" + > + </File> + <File + RelativePath=".\resources\fonts\AppleMyungjo.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Arial.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Arialb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Ariali.afm" + > + </File> + <File + RelativePath=".\resources\fonts\comic_sans_ms-fallback.txt" + > + </File> + <File + RelativePath=".\resources\fonts\Comic_Sans_MS.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Comic_Sans_MSb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\courier-fallback.txt" + > + </File> + <File + RelativePath=".\resources\fonts\Courier.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Courier_New.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Courierb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Courierbi.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Courieri.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Geeza_Pro.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Geneva.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Georgia.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Georgiab.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Georgiai.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Helvetica.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Helvetica_Neueb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Helveticab.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Helveticabi.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Helveticai.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Hiragino_Kaku_Gothic_Pro.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Hiragino_Mincho_Pro.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Hiragino_Mincho_Prob.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Impact.afm" + > + </File> + <File + RelativePath=".\resources\fonts\lucida_grande-fallback.txt" + > + </File> + <File + RelativePath=".\resources\fonts\Lucida_Grande.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Lucida_Grandeb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Monaco.afm" + > + </File> + <File + RelativePath=".\resources\fonts\MS_Gothic.afm" + > + </File> + <File + RelativePath=".\resources\fonts\MS_PGothic.afm" + > + </File> + <File + RelativePath=".\resources\fonts\MS_PMincho.afm" + > + </File> + <File + RelativePath="..\..\..\net\base\net_resources.rc" + > + </File> + <File + RelativePath=".\resources\fonts\Osaka.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Papyrus.afm" + > + </File> + <File + RelativePath=".\resource.h" + > + </File> + <File + RelativePath=".\resources\small.ico" + > + </File> + <File + RelativePath=".\resources\fonts\STKaiti.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Symbol.afm" + > + </File> + <File + RelativePath=".\resources\test_shell.ico" + > + </File> + <File + RelativePath=".\resources\test_shell.rc" + > + </File> + <File + RelativePath=".\resources\fonts\times-fallback.txt" + > + </File> + <File + RelativePath=".\resources\fonts\Times.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Times_New_Roman.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Times_New_Romanb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Timesb.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Timesbi.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Timesi.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Trebuchet_MS.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Verdana.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Verdanab.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Verdanabi.afm" + > + </File> + <File + RelativePath=".\resources\fonts\Verdanai.afm" + > + </File> + <File + RelativePath="$(IntDir)\..\localized_strings\webkit_strings_en-US.rc" + > + </File> + <File + RelativePath=".\resources\fonts\Zapf_Dingbats.afm" + > + </File> + </Filter> + <Filter + Name="temp" + > + <File + RelativePath=".\temp\navigation_controller_base.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)\$(InputName)1.obj" + XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)\$(InputName)1.obj" + XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\temp\navigation_controller_base.h" + > + </File> + <File + RelativePath=".\temp\navigation_entry.h" + > + </File> + <File + RelativePath=".\temp\page_transition_types.h" + > + </File> + </Filter> + <File + RelativePath=".\drag_delegate.cc" + > + </File> + <File + RelativePath=".\drag_delegate.h" + > + </File> + <File + RelativePath=".\drop_delegate.cc" + > + </File> + <File + RelativePath=".\drop_delegate.h" + > + </File> + <File + RelativePath=".\event_sending_controller.cc" + > + </File> + <File + RelativePath=".\event_sending_controller.h" + > + </File> + <File + RelativePath=".\layout_test_controller.cc" + > + </File> + <File + RelativePath=".\layout_test_controller.h" + > + </File> + <File + RelativePath=".\simple_resource_loader_bridge.cc" + > + </File> + <File + RelativePath=".\test_navigation_controller.cc" + > + </File> + <File + RelativePath=".\test_navigation_controller.h" + > + </File> + <File + RelativePath=".\test_shell.cc" + > + </File> + <File + RelativePath=".\test_shell.h" + > + </File> + <File + RelativePath=".\test_shell_main.cc" + > + </File> + <File + RelativePath=".\test_shell_request_context.cc" + > + </File> + <File + RelativePath=".\test_shell_request_context.h" + > + </File> + <File + RelativePath=".\test_shell_switches.cc" + > + </File> + <File + RelativePath=".\test_shell_switches.h" + > + </File> + <File + RelativePath=".\test_webview_delegate.cc" + > + </File> + <File + RelativePath=".\test_webview_delegate.h" + > + </File> + <File + RelativePath=".\text_input_controller.cc" + > + </File> + <File + RelativePath=".\text_input_controller.h" + > + </File> + <File + RelativePath=".\webview_host.cc" + > + </File> + <File + RelativePath=".\webview_host.h" + > + </File> + <File + RelativePath=".\webwidget_host.cc" + > + </File> + <File + RelativePath=".\webwidget_host.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/webkit/tools/test_shell/test_shell.vsprops b/webkit/tools/test_shell/test_shell.vsprops new file mode 100644 index 0000000..f2f0b9a --- /dev/null +++ b/webkit/tools/test_shell/test_shell.vsprops @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="test_shell" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\breakpad\using_breakpad.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\npapi\using_npapi.vsprops;$(SolutionDir)..\skia\using_skia.vsprops" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""$(OutDir)\WebKit";"$(SolutionDir)";"$(IntDir)\..\localized_strings";"$(SolutionDir)webkit\port\bridge";"$(SolutionDir)webkit\port\platform";"$(SolutionDir)webkit\port\platform\network";"$(SolutionDir)webkit\glue";"$(SolutionDir)third_party\webkit\src\";"$(OutDir)\obj\WebCore\JavaScriptHeaders";"$(OutDir)"" + PreprocessorDefinitions="_CRT_SECURE_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="comctl32.lib shlwapi.lib rpcrt4.lib winmm.lib" + AdditionalLibraryDirectories=""$(OutDir)"" + /> + <Tool + Name="VCResourceCompilerTool" + AdditionalIncludeDirectories=""$(SolutionDir)";"$(IntDir)\..\"" + /> +</VisualStudioPropertySheet> diff --git a/webkit/tools/test_shell/test_shell_main.cc b/webkit/tools/test_shell/test_shell_main.cc new file mode 100644 index 0000000..0256fed --- /dev/null +++ b/webkit/tools/test_shell/test_shell_main.cc @@ -0,0 +1,360 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// Creates an instance of the test_shell. + +#include <stdlib.h> // required by _set_abort_behavior + +#include <windows.h> +#include <commctrl.h> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/event_recorder.h" +#include "base/file_util.h" +#include "base/fixed_string.h" +#include "base/gfx/native_theme.h" +#include "base/icu_util.h" +#include "base/memory_debug.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/resource_util.h" +#include "base/stats_table.h" +#include "base/string_util.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" +#include "net/base/cookie_monster.h" +#include "net/base/net_module.h" +#include "net/http/http_cache.h" +#include "net/url_request/url_request_context.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/window_open_disposition.h" +#include "webkit/tools/test_shell/foreground_helper.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell.h" +#include "webkit/tools/test_shell/test_shell_request_context.h" +#include "webkit/tools/test_shell/test_shell_switches.h" + +// This is only set for layout tests. +static wchar_t g_currentTestName[MAX_PATH]; + +namespace { + +// StatsTable initialization parameters. +static wchar_t* kStatsFile = L"testshell"; +static int kStatsFileThreads = 20; +static int kStatsFileCounters = 200; + +std::string GetDataResource(HMODULE module, int resource_id) { + void* data_ptr; + size_t data_size; + return base::GetDataResourceFromModule(module, resource_id, &data_ptr, + &data_size) ? + std::string(static_cast<char*>(data_ptr), data_size) : std::string(); +} + +// This is called indirectly by the network layer to access resources. +std::string NetResourceProvider(int key) { + return GetDataResource(::GetModuleHandle(NULL), key); +} + +void SetCurrentTestName(char* path) +{ + char* lastSlash = strrchr(path, '/'); + if (lastSlash) { + ++lastSlash; + } else { + lastSlash = path; + } + + wcscpy_s(g_currentTestName, arraysize(g_currentTestName), + UTF8ToWide(lastSlash).c_str()); +} + +bool MinidumpCallback(const wchar_t *dumpPath, + const wchar_t *minidumpID, + void *context, + EXCEPTION_POINTERS *exinfo, + MDRawAssertionInfo *assertion, + bool succeeded) +{ + // Warning: Don't use the heap in this function. It may be corrupted. + if (!g_currentTestName[0]) + return false; + + // Try to rename the minidump file to include the crashed test's name. + FixedString<wchar_t, MAX_PATH> origPath; + origPath.Append(dumpPath); + origPath.Append(file_util::kPathSeparator); + origPath.Append(minidumpID); + origPath.Append(L".dmp"); + + FixedString<wchar_t, MAX_PATH> newPath; + newPath.Append(dumpPath); + newPath.Append(file_util::kPathSeparator); + newPath.Append(g_currentTestName); + newPath.Append(L"-"); + newPath.Append(minidumpID); + newPath.Append(L".dmp"); + + // May use the heap, but oh well. If this fails, we'll just have the + // original dump file lying around. + _wrename(origPath.get(), newPath.get()); + + return false; +} +} // namespace + +int main(int argc, char* argv[]) +{ +#ifdef _CRTDBG_MAP_ALLOC + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); +#endif + + CommandLine parsed_command_line; + if (parsed_command_line.HasSwitch(test_shell::kStartupDialog)) + MessageBox(NULL, L"attach to me?", L"test_shell", MB_OK); + //webkit_glue::SetLayoutTestMode(true); + + // Allocate a message loop for this thread. Although it is not used + // directly, its constructor sets up some necessary state. + MessageLoop main_message_loop; + + bool suppress_error_dialogs = + (GetEnvironmentVariable(L"CHROME_HEADLESS", NULL, 0) || + parsed_command_line.HasSwitch(test_shell::kNoErrorDialogs) || + parsed_command_line.HasSwitch(test_shell::kLayoutTests)); + TestShell::InitLogging(suppress_error_dialogs); + + // Suppress abort message in v8 library in debugging mode. + // V8 calls abort() when it hits assertion errors. + if (suppress_error_dialogs) { + _set_abort_behavior(0, _WRITE_ABORT_MSG); + } + + bool layout_test_mode = + parsed_command_line.HasSwitch(test_shell::kLayoutTests); + + net::HttpCache::Mode cache_mode = net::HttpCache::NORMAL; + bool playback_mode = + parsed_command_line.HasSwitch(test_shell::kPlaybackMode); + bool record_mode = + parsed_command_line.HasSwitch(test_shell::kRecordMode); + + if (playback_mode) + cache_mode = net::HttpCache::PLAYBACK; + else if (record_mode) + cache_mode = net::HttpCache::RECORD; + + if (layout_test_mode || + parsed_command_line.HasSwitch(test_shell::kEnableFileCookies)) + CookieMonster::EnableFileScheme(); + + std::wstring cache_path = + parsed_command_line.GetSwitchValue(test_shell::kCacheDir); + if (cache_path.empty()) { + PathService::Get(base::DIR_EXE, &cache_path); + file_util::AppendToPath(&cache_path, L"cache"); + } + + // Initializing with a default context, which means no on-disk cookie DB, + // and no support for directory listings. + SimpleResourceLoaderBridge::Init( + new TestShellRequestContext(cache_path, cache_mode)); + + // Load ICU data tables + icu_util::Initialize(); + + // Config the network module so it has access to a limited set of resources. + NetModule::SetResourceProvider(NetResourceProvider); + + INITCOMMONCONTROLSEX InitCtrlEx; + + InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCtrlEx.dwICC = ICC_STANDARD_CLASSES; + InitCommonControlsEx(&InitCtrlEx); + + bool interactive = !layout_test_mode; + TestShell::InitializeTestShell(interactive); + + // Disable user themes for layout tests so pixel tests are consistent. + if (!interactive) + gfx::NativeTheme::instance()->DisableTheming(); + + if (parsed_command_line.HasSwitch(test_shell::kTestShellTimeOut)) { + const std::wstring timeout_str = parsed_command_line.GetSwitchValue( + test_shell::kTestShellTimeOut); + int timeout_ms = static_cast<int>(StringToInt64(timeout_str.c_str())); + if (timeout_ms > 0) + TestShell::SetFileTestTimeout(timeout_ms); + } + + // Initialize global strings + TestShell::RegisterWindowClass(); + + // Treat the first loose value as the initial URL to open. + std::wstring uri; + + // Default to a homepage if we're interactive. + if (interactive) { + PathService::Get(base::DIR_SOURCE_ROOT, &uri); + file_util::AppendToPath(&uri, L"webkit"); + file_util::AppendToPath(&uri, L"data"); + file_util::AppendToPath(&uri, L"test_shell"); + file_util::AppendToPath(&uri, L"index.html"); + } + + if (parsed_command_line.GetLooseValueCount() > 0) { + CommandLine::LooseValueIterator iter = parsed_command_line.GetLooseValuesBegin(); + uri = *iter; + } + + if (parsed_command_line.HasSwitch(test_shell::kCrashDumps)) { + std::wstring dir = parsed_command_line.GetSwitchValue(test_shell::kCrashDumps); + new google_breakpad::ExceptionHandler(dir, 0, &MinidumpCallback, 0, true); + } + + std::wstring js_flags = + parsed_command_line.GetSwitchValue(test_shell::kJavaScriptFlags); + // Test shell always exposes the GC. + CommandLine::AppendSwitch(&js_flags, L"expose-gc"); + webkit_glue::SetJavaScriptFlags(js_flags); + + // load and initialize the stats table. + StatsTable *table = new StatsTable(kStatsFile, kStatsFileThreads, kStatsFileCounters); + StatsTable::set_current(table); + + TestShell* shell; + if (TestShell::CreateNewWindow(uri, &shell)) { + if (record_mode || playback_mode) { + // Move the window to the upper left corner for consistent + // record/playback mode. For automation, we want this to work + // on build systems where the script invoking us is a background + // process. So for this case, make our window the topmost window + // as well. + ForegroundHelper::SetForeground(shell->mainWnd()); + ::SetWindowPos(shell->mainWnd(), HWND_TOP, 0, 0, 600, 800, 0); + // Tell webkit as well. + webkit_glue::SetRecordPlaybackMode(true); + } + + shell->Show(shell->webView(), NEW_WINDOW); + + if (parsed_command_line.HasSwitch(test_shell::kDumpStatsTable)) + shell->DumpStatsTableOnExit(); + + bool no_events = parsed_command_line.HasSwitch(test_shell::kNoEvents); + if ((record_mode || playback_mode) && !no_events) { + std::wstring script_path = cache_path; + // Create the cache directory in case it doesn't exist. + file_util::CreateDirectory(cache_path); + file_util::AppendToPath(&script_path, L"script.log"); + if (record_mode) + base::EventRecorder::current()->StartRecording(script_path); + if (playback_mode) + base::EventRecorder::current()->StartPlayback(script_path); + } + + if (parsed_command_line.HasSwitch(test_shell::kDebugMemoryInUse)) { + base::MemoryDebug::SetMemoryInUseEnabled(true); + // Dump all in use memory at startup + base::MemoryDebug::DumpAllMemoryInUse(); + } + + // See if we need to run the tests. + if (layout_test_mode) { + webkit_glue::SetLayoutTestMode(true); + + // Set up for the kind of test requested. + TestShell::TestParams params; + if (parsed_command_line.HasSwitch(test_shell::kDumpPixels)) { + // The pixel test flag also gives the image file name to use. + params.dump_pixels = true; + params.pixel_file_name = parsed_command_line.GetSwitchValue( + test_shell::kDumpPixels); + if (params.pixel_file_name.size() == 0) { + fprintf(stderr, "No file specified for pixel tests"); + exit(1); + } + } + if (parsed_command_line.HasSwitch(test_shell::kNoTree)) { + params.dump_tree = false; + } + + if (uri.length() == 0) { + // Watch stdin for URLs. + char filenameBuffer[2048]; + while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) { + char *newLine = strchr(filenameBuffer, '\n'); + if (newLine) + *newLine = '\0'; + if (!*filenameBuffer) + continue; + + SetCurrentTestName(filenameBuffer); + + if (!TestShell::RunFileTest(filenameBuffer, params)) + break; + } + } else { + TestShell::RunFileTest(WideToUTF8(uri).c_str(), params); + } + + shell->CallJSGC(); + shell->CallJSGC(); + if (shell) delete shell; + } else { + MessageLoop::current()->Run(); + } + + // Flush any remaining messages. This ensures that any + // accumulated Task objects get destroyed before we exit, + // which avoids noise in purify leak-test results. + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); + + if (record_mode) + base::EventRecorder::current()->StopRecording(); + if (playback_mode) + base::EventRecorder::current()->StopPlayback(); + } + + TestShell::ShutdownTestShell(); + TestShell::CleanupLogging(); + + // Tear down shared StatsTable; prevents unit_tests from leaking it. + StatsTable::set_current(NULL); + delete table; + +#ifdef _CRTDBG_MAP_ALLOC + _CrtDumpMemoryLeaks(); +#endif + return 0; +} + diff --git a/webkit/tools/test_shell/test_shell_request_context.cc b/webkit/tools/test_shell/test_shell_request_context.cc new file mode 100644 index 0000000..0406e01 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_request_context.cc @@ -0,0 +1,69 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/test_shell_request_context.h" + +#include "net/base/cookie_monster.h" +#include "webkit/glue/webkit_glue.h" + +TestShellRequestContext::TestShellRequestContext() { + Init(std::wstring(), net::HttpCache::NORMAL); +} + +TestShellRequestContext::TestShellRequestContext( + const std::wstring& cache_path, + net::HttpCache::Mode cache_mode) { + Init(cache_path, cache_mode); +} + +void TestShellRequestContext::Init( + const std::wstring& cache_path, + net::HttpCache::Mode cache_mode) { + cookie_store_ = new CookieMonster(); + + user_agent_ = webkit_glue::GetDefaultUserAgent(); + + // hard-code A-L and A-C for test shells + accept_language_ = "en-us,en"; + accept_charset_ = "iso-8859-1,*,utf-8"; + + net::HttpCache *cache; + if (cache_path.empty()) { + cache = new net::HttpCache(NULL, 0); + } else { + cache = new net::HttpCache(NULL, cache_path, 0); + } + cache->set_mode(cache_mode); + http_transaction_factory_ = cache; +} + +TestShellRequestContext::~TestShellRequestContext() { + delete cookie_store_; + delete http_transaction_factory_; +} diff --git a/webkit/tools/test_shell/test_shell_request_context.h b/webkit/tools/test_shell/test_shell_request_context.h new file mode 100644 index 0000000..255fb64 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_request_context.h @@ -0,0 +1,53 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_REQUEST_CONTEXT_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_REQUEST_CONTEXT_H__ + +#include "net/http/http_cache.h" +#include "net/url_request/url_request_context.h" + +// A basic URLRequestContext that only provides an in-memory cookie store. +class TestShellRequestContext : public URLRequestContext { + public: + // Use an in-memory cache + TestShellRequestContext(); + + // Use an on-disk cache at the specified location. Optionally, use the cache + // in playback or record mode. + TestShellRequestContext(const std::wstring& cache_path, + net::HttpCache::Mode cache_mode); + + ~TestShellRequestContext(); + + private: + void Init(const std::wstring& cache_path, net::HttpCache::Mode cache_mode); +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_REQUEST_CONTEXT_H__ diff --git a/webkit/tools/test_shell/test_shell_switches.cc b/webkit/tools/test_shell/test_shell_switches.cc new file mode 100644 index 0000000..3560d60 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_switches.cc @@ -0,0 +1,76 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/test_shell_switches.h" + +namespace test_shell { + +// Suppresses all error dialogs when present. +const wchar_t kNoErrorDialogs[] = L"noerrdialogs"; + +// Causes the test_shell to run using stdin and stdout for URLs and output, +// respectively, and interferes with interactive use of the UI. +const wchar_t kLayoutTests[] = L"layout-tests"; +const wchar_t kCrashDumps[] = L"crash-dumps"; // Enable crash dumps + +// Command line flags that control the tests when layout-tests is specified. +const wchar_t kNoTree[] = L"notree"; // Don't dump the render tree. +const wchar_t kDumpPixels[] = L"pixel-tests"; // Enable pixel tests. +// Optional command line switch that specifies timeout time for page load when +// running non-interactive file tests, in ms. +const wchar_t kTestShellTimeOut[] = L"time-out-ms"; + +const wchar_t kStartupDialog[] = L"testshell-startup-dialog"; + +// JavaScript flags passed to engine. +const wchar_t kJavaScriptFlags[] = L"js-flags"; + +// Run the http cache in record mode. +const wchar_t kRecordMode[] = L"record-mode"; + +// Run the http cache in playback mode. +const wchar_t kPlaybackMode[] = L"playback-mode"; + +// Don't record/playback events when using record & playback. +const wchar_t kNoEvents[] = L"no-events"; + +// Dump stats table on exit. +const wchar_t kDumpStatsTable[] = L"stats"; + +// Use a specified cache directory. +const wchar_t kCacheDir[] = L"cache-dir"; + +// When being run through a memory profiler, trigger memory in use dumps at +// startup and just prior to shutdown. +const wchar_t kDebugMemoryInUse[] = L"debug-memory-in-use"; + +// Enable cookies on the file:// scheme. --layout-tests also enables this. +const wchar_t kEnableFileCookies[] = L"enable-file-cookies"; + +} // namespace test_shell diff --git a/webkit/tools/test_shell/test_shell_switches.h b/webkit/tools/test_shell/test_shell_switches.h new file mode 100644 index 0000000..c92ce59 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_switches.h @@ -0,0 +1,55 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// Defines all the command-line switches used by Chrome. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_SWITCHES_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_SWITCHES_H__ + +namespace test_shell { + +extern const wchar_t kCrashDumps[]; +extern const wchar_t kDumpPixels[]; +extern const wchar_t kLayoutTests[]; +extern const wchar_t kNoErrorDialogs[]; +extern const wchar_t kNoTree[]; +extern const wchar_t kTestShellTimeOut[]; +extern const wchar_t kStartupDialog[]; +extern const wchar_t kJavaScriptFlags[]; +extern const wchar_t kRecordMode[]; +extern const wchar_t kPlaybackMode[]; +extern const wchar_t kNoEvents[]; +extern const wchar_t kDumpStatsTable[]; +extern const wchar_t kCacheDir[]; +extern const wchar_t kDebugMemoryInUse[]; +extern const wchar_t kEnableFileCookies[]; + +} // namespace test_shell + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_SWITCHES_H__ diff --git a/webkit/tools/test_shell/test_shell_test.cc b/webkit/tools/test_shell/test_shell_test.cc new file mode 100644 index 0000000..8b433d2 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_test.cc @@ -0,0 +1,63 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/test_shell_test.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" + +std::wstring TestShellTest::GetTestURL(std::wstring test_case_path, + const std::wstring& test_case) { + file_util::AppendToPath(&test_case_path, test_case); + return test_case_path; +} + +void TestShellTest::SetUp() { + // Make a test shell for use by the test. + TestShell::RegisterWindowClass(); + CreateEmptyWindow(); + test_shell_->Show(test_shell_->webView(), NEW_WINDOW); + + // Point data_dir_ to the root of the test case dir + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir_)); + file_util::AppendToPath(&data_dir_, L"webkit"); + file_util::AppendToPath(&data_dir_, L"data"); + ASSERT_TRUE(file_util::PathExists(data_dir_)); +} + +void TestShellTest::TearDown() { + // Loading a blank url clears the memory in the current page. + test_shell_->LoadURL(L"about:blank"); + DestroyWindow(test_shell_->mainWnd()); + LayoutTestController::ClearShell(); +} + +void TestShellTest::CreateEmptyWindow() { + TestShell::CreateNewWindow(L"about:blank", &test_shell_); +}
\ No newline at end of file diff --git a/webkit/tools/test_shell/test_shell_test.h b/webkit/tools/test_shell/test_shell_test.h new file mode 100644 index 0000000..b48e22d --- /dev/null +++ b/webkit/tools/test_shell/test_shell_test.h @@ -0,0 +1,63 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +/** + * Base test class used by all test shell tests. Provides boiler plate + * code to create and destroy a new test shell for each gTest test. + */ + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_TEST_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_TEST_H__ + +#include "webkit/glue/window_open_disposition.h" +#include "webkit/tools/test_shell/test_shell.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TestShellTest : public testing::Test { + protected: + // Returns the path "test_case_path/test_case". + std::wstring GetTestURL(std::wstring test_case_path, + const std::wstring& test_case); + + virtual void SetUp(); + virtual void TearDown(); + + // Don't refactor away; some unittests override this! + virtual void CreateEmptyWindow(); + + static const char* kJavascriptDelayExitScript; + + protected: + // Location of SOURCE_ROOT/webkit/data/ + std::wstring data_dir_; + + TestShell* test_shell_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_TEST_H__ diff --git a/webkit/tools/test_shell/test_shell_tests.vcproj b/webkit/tools/test_shell/test_shell_tests.vcproj new file mode 100644 index 0000000..569dbd0 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_tests.vcproj @@ -0,0 +1,393 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="test_shell_tests" + ProjectGUID="{E6766F81-1FCD-4CD7-BC16-E36964A14867}" + RootNamespace="test_shell_tests" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;.\test_shell_tests.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;.\test_shell_tests.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="support" + > + <File + RelativePath=".\drag_delegate.cc" + > + </File> + <File + RelativePath=".\drag_delegate.h" + > + </File> + <File + RelativePath=".\drop_delegate.cc" + > + </File> + <File + RelativePath=".\drop_delegate.h" + > + </File> + <File + RelativePath=".\event_sending_controller.cc" + > + </File> + <File + RelativePath=".\event_sending_controller.h" + > + </File> + <File + RelativePath=".\image_decoder_unittest.cc" + > + </File> + <File + RelativePath=".\image_decoder_unittest.h" + > + </File> + <File + RelativePath=".\layout_test_controller.cc" + > + </File> + <File + RelativePath=".\layout_test_controller.h" + > + </File> + <File + RelativePath=".\temp\navigation_controller_base.cc" + > + </File> + <File + RelativePath="..\..\..\net\base\net_resources.rc" + > + </File> + <File + RelativePath=".\resource.h" + > + </File> + <File + RelativePath=".\run_all_tests.cc" + > + </File> + <File + RelativePath=".\simple_resource_loader_bridge.cc" + > + </File> + <File + RelativePath=".\resources\small.ico" + > + </File> + <File + RelativePath=".\test_navigation_controller.cc" + > + </File> + <File + RelativePath=".\test_navigation_controller.h" + > + </File> + <File + RelativePath=".\test_shell.cc" + > + </File> + <File + RelativePath=".\test_shell.h" + > + </File> + <File + RelativePath=".\resources\test_shell.ico" + > + </File> + <File + RelativePath=".\resources\test_shell.rc" + > + </File> + <File + RelativePath=".\test_shell_request_context.cc" + > + </File> + <File + RelativePath=".\test_shell_request_context.h" + > + </File> + <File + RelativePath=".\test_shell_switches.cc" + > + </File> + <File + RelativePath=".\test_shell_switches.h" + > + </File> + <File + RelativePath=".\test_shell_test.cc" + > + </File> + <File + RelativePath=".\test_shell_test.h" + > + </File> + <File + RelativePath=".\test_webview_delegate.cc" + > + </File> + <File + RelativePath=".\test_webview_delegate.h" + > + </File> + <File + RelativePath=".\text_input_controller.cc" + > + </File> + <File + RelativePath=".\text_input_controller.h" + > + </File> + <File + RelativePath=".\webview_host.cc" + > + </File> + <File + RelativePath=".\webview_host.h" + > + </File> + <File + RelativePath=".\webwidget_host.cc" + > + </File> + <File + RelativePath=".\webwidget_host.h" + > + </File> + </Filter> + <Filter + Name="tests" + > + <File + RelativePath="..\..\glue\autocomplete_input_listener_unittest.cc" + > + </File> + <File + RelativePath="..\..\port\platform\image-decoders\bmp\BMPImageDecoder_unittest.cpp" + > + </File> + <File + RelativePath="..\..\glue\bookmarklet_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\context_menu_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\cpp_bound_class_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\cpp_variant_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\dom_operations_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\dom_serializer_unittest.cc" + > + </File> + <File + RelativePath="..\..\port\platform\GKURL_unittest.cpp" + > + </File> + <File + RelativePath="..\..\glue\glue_serialize_unittest.cc" + > + </File> + <File + RelativePath="..\..\port\platform\image-decoders\ico\ICOImageDecoder_unittest.cpp" + > + </File> + <File + RelativePath="..\..\glue\iframe_redirect_unittest.cc" + > + </File> + <File + RelativePath=".\keyboard_unittest.cc" + > + </File> + <File + RelativePath=".\layout_test_controller_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\mimetype_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\multipart_response_delegate_unittest.cc" + > + </File> + <File + RelativePath=".\node_leak_test.cc" + > + </File> + <File + RelativePath="..\..\glue\password_autocomplete_listener_unittest.cc" + > + </File> + <File + RelativePath=".\plugin_tests.cc" + > + </File> + <File + RelativePath="..\..\glue\regular_expression_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\resource_fetcher_unittest.cc" + > + </File> + <File + RelativePath=".\text_input_controller_unittest.cc" + > + </File> + <File + RelativePath="..\..\glue\webplugin_impl_unittest.cc" + > + </File> + <File + RelativePath="..\..\port\platform\image-decoders\xbm\XBMImageDecoder_unittest.cpp" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/webkit/tools/test_shell/test_shell_tests.vsprops b/webkit/tools/test_shell/test_shell_tests.vsprops new file mode 100644 index 0000000..0e11457 --- /dev/null +++ b/webkit/tools/test_shell/test_shell_tests.vsprops @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="test_shell_tests" + InheritedPropertySheets=".\test_shell.vsprops;..\..\build\webkit_common.vsprops" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="$(SolutionDir)..\v8\public" + PreprocessorDefinitions="_CRT_SECURE_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="winmm.lib" + /> +</VisualStudioPropertySheet> diff --git a/webkit/tools/test_shell/test_webview_delegate.cc b/webkit/tools/test_shell/test_webview_delegate.cc new file mode 100644 index 0000000..231415f --- /dev/null +++ b/webkit/tools/test_shell/test_webview_delegate.cc @@ -0,0 +1,943 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// This file contains the implementation of TestWebViewDelegate, which serves +// as the WebViewDelegate for the TestShellWebHost. The host is expected to +// have initialized a MessageLoop before these methods are called. + +#include "webkit/tools/test_shell/test_webview_delegate.h" + +#include <objidl.h> +#include <shlobj.h> + +#include "base/gfx/point.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "webkit/glue/webdatasource.h" +#include "webkit/glue/webdropdata.h" +#include "webkit/glue/weberror.h" +#include "webkit/glue/webframe.h" +#include "webkit/glue/webpreferences.h" +#include "webkit/glue/weburlrequest.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/webview.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/webplugin_delegate_impl.h" +#include "webkit/glue/window_open_disposition.h" +#include "webkit/tools/test_shell/drag_delegate.h" +#include "webkit/tools/test_shell/drop_delegate.h" +#include "webkit/tools/test_shell/test_navigation_controller.h" +#include "webkit/tools/test_shell/test_shell.h" + +namespace { + +static int next_page_id_ = 1; + +// Used to write a platform neutral file:/// URL by only taking the filename +// (e.g., converts "file:///tmp/foo.txt" to just "foo.txt"). +std::wstring UrlSuitableForTestResult(const std::wstring& url) { + if (url.empty() || std::wstring::npos == url.find(L"file://")) + return url; + + return PathFindFileNameW(url.c_str()); +} + +// Adds a file called "DRTFakeFile" to |data_object| (CF_HDROP). Use to fake +// dragging a file. +void AddDRTFakeFileToDataObject(IDataObject* data_object) { + STGMEDIUM medium = {0}; + medium.tymed = TYMED_HGLOBAL; + + const char filename[] = "DRTFakeFile"; + const int filename_len = arraysize(filename); + + // Allocate space for the DROPFILES struct, filename, and 2 null characters. + medium.hGlobal = GlobalAlloc(GPTR, sizeof(DROPFILES) + filename_len + 2); + DCHECK(medium.hGlobal); + DROPFILES* drop_files = static_cast<DROPFILES*>(GlobalLock(medium.hGlobal)); + drop_files->pFiles = sizeof(DROPFILES); + drop_files->fWide = 0; // Filenames are ascii + strcpy_s(reinterpret_cast<char*>(drop_files) + sizeof(DROPFILES), + filename_len, filename); + GlobalUnlock(medium.hGlobal); + + FORMATETC file_desc_fmt = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + data_object->SetData(&file_desc_fmt, &medium, TRUE); +} + +} // namespace + +// WebViewDelegate ----------------------------------------------------------- + +TestWebViewDelegate::~TestWebViewDelegate() { + if (custom_cursor_) + DestroyIcon(custom_cursor_); + RevokeDragDrop(shell_->webViewWnd()); +} + +WebView* TestWebViewDelegate::CreateWebView(WebView* webview, + bool user_gesture) { + return shell_->CreateWebView(webview); +} + +WebWidget* TestWebViewDelegate::CreatePopupWidget(WebView* webview) { + return shell_->CreatePopupWidget(webview); +} + +WebPluginDelegate* TestWebViewDelegate::CreatePluginDelegate( + WebView* webview, + const GURL& url, + const std::string& mime_type, + const std::string& clsid, + std::string* actual_mime_type) { + HWND hwnd = GetContainingWindow(webview); + if (!hwnd) + return NULL; + + bool allow_wildcard = true; + WebPluginInfo info; + if (!NPAPI::PluginList::Singleton()->GetPluginInfo(url, mime_type, clsid, + allow_wildcard, &info, + actual_mime_type)) + return NULL; + + if (actual_mime_type && !actual_mime_type->empty()) + return WebPluginDelegateImpl::Create(info.file, *actual_mime_type, hwnd); + else + return WebPluginDelegateImpl::Create(info.file, mime_type, hwnd); +} + +void TestWebViewDelegate::OpenURL(WebView* webview, const GURL& url, + WindowOpenDisposition disposition) { + DCHECK_NE(disposition, CURRENT_TAB); // No code for this + if (disposition == SUPPRESS_OPEN) + return; + TestShell* shell; + if (TestShell::CreateNewWindow(UTF8ToWide(url.spec()), &shell)) + shell->Show(shell->webView(), disposition); +} + +void TestWebViewDelegate::DidStartLoading(WebView* webview) { + if (page_is_loading_) { + LOG(ERROR) << "DidStartLoading called while loading"; + return; + } + page_is_loading_ = true; +} + +void TestWebViewDelegate::DidStopLoading(WebView* webview) { + if (!page_is_loading_) { + LOG(ERROR) << "DidStopLoading called while not loading"; + return; + } + page_is_loading_ = false; +} + +void TestWebViewDelegate::WindowObjectCleared(WebFrame* webframe) { + shell_->BindJSObjectsToWindow(webframe); +} + +WindowOpenDisposition TestWebViewDelegate::DispositionForNavigationAction( + WebView* webview, + WebFrame* frame, + const WebRequest* request, + WebNavigationType type, + WindowOpenDisposition disposition, + bool is_redirect) { + if (is_custom_policy_delegate_) { + std::wstring frame_name = frame->GetName(); + printf("Policy delegate: attempt to load %s\n", + request->GetURL().spec().c_str()); + return IGNORE_ACTION; + } else { + return WebViewDelegate::DispositionForNavigationAction( + webview, frame, request, type, disposition, is_redirect); + } +} + +void TestWebViewDelegate::SetCustomPolicyDelegate(bool isCustom) { + is_custom_policy_delegate_ = isCustom; +} + +void TestWebViewDelegate::AssignIdentifierToRequest(WebView* webview, + uint32 identifier, + const WebRequest& request) { + if (shell_->ShouldDumpResourceLoadCallbacks()) { + resource_identifier_map_[identifier] = request.GetURL().possibly_invalid_spec(); + } +} + +std::string TestWebViewDelegate::GetResourceDescription(uint32 identifier) { + ResourceMap::iterator it = resource_identifier_map_.find(identifier); + return it != resource_identifier_map_.end() ? it->second : "<unknown>"; +} + +void TestWebViewDelegate::WillSendRequest(WebView* webview, + uint32 identifier, + WebRequest* request) { + std::string request_url = request->GetURL().possibly_invalid_spec(); + + if (shell_->ShouldDumpResourceLoadCallbacks()) { + printf("%s - willSendRequest <WebRequest URL \"%s\">\n", + GetResourceDescription(identifier).c_str(), + request_url.c_str()); + } + + // Set the new substituted URL. + request->SetURL(GURL(TestShell::RewriteLocalUrl(request_url))); +} + +void TestWebViewDelegate::DidFinishLoading(WebView* webview, + uint32 identifier) { + if (shell_->ShouldDumpResourceLoadCallbacks()) { + printf("%s - didFinishLoading\n", + GetResourceDescription(identifier).c_str()); + } + + resource_identifier_map_.erase(identifier); +} + +void TestWebViewDelegate::DidFailLoadingWithError(WebView* webview, + uint32 identifier, + const WebError& error) { + if (shell_->ShouldDumpResourceLoadCallbacks()) { + printf("%s - didFailLoadingWithError <WebError code %d," + " failing URL \"%s\">\n", + GetResourceDescription(identifier).c_str(), + error.GetErrorCode(), + error.GetFailedURL().spec().c_str()); + } + + resource_identifier_map_.erase(identifier); +} + +void TestWebViewDelegate::DidStartProvisionalLoadForFrame( + WebView* webview, + WebFrame* frame, + NavigationGesture gesture) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didStartProvisionalLoadForFrame\n", + GetFrameDescription(frame).c_str()); + } + + if (!top_loading_frame_) { + top_loading_frame_ = frame; + } + UpdateAddressBar(webview); +} + +void TestWebViewDelegate::DidReceiveServerRedirectForProvisionalLoadForFrame( + WebView* webview, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didReceiveServerRedirectForProvisionalLoadForFrame\n", + GetFrameDescription(frame).c_str()); + } + + UpdateAddressBar(webview); +} + +void TestWebViewDelegate::DidFailProvisionalLoadWithError( + WebView* webview, + const WebError& error, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didFailProvisionalLoadWithError\n", + GetFrameDescription(frame).c_str()); + } + + if (page_is_loading_) + DidStopLoading(webview); + LocationChangeDone(frame->GetProvisionalDataSource()); + + // Don't display an error page if we're running layout tests, because + // DumpRenderTree doesn't. + if (!shell_->interactive()) + return; + + // Don't display an error page if this is simply a cancelled load. Aside + // from being dumb, WebCore doesn't expect it and it will cause a crash. + if (error.GetErrorCode() == net::ERR_ABORTED) + return; + + const WebRequest& failed_request = + frame->GetProvisionalDataSource()->GetRequest(); + TestShellExtraRequestData* extra_data = + static_cast<TestShellExtraRequestData*>(failed_request.GetExtraData()); + bool replace = extra_data && extra_data->pending_page_id != -1; + + scoped_ptr<WebRequest> request(failed_request.Clone()); + request->SetURL(GURL("testshell-error:")); + + std::string error_text = + StringPrintf("Error loading url: %d", error.GetErrorCode()); + + frame->LoadAlternateHTMLString(request.get(), error_text, + error.GetFailedURL(), replace); +} + +void TestWebViewDelegate::DidCommitLoadForFrame(WebView* webview, + WebFrame* frame, + bool is_new_navigation) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didCommitLoadForFrame\n", + GetFrameDescription(frame).c_str()); + } + + UpdateForCommittedLoad(frame, is_new_navigation); +} + +void TestWebViewDelegate::DidReceiveTitle(WebView* webview, + const std::wstring& title, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didReceiveTitle\n", + GetFrameDescription(frame).c_str()); + } + + if (shell_->ShouldDumpTitleChanges()) { + printf("TITLE CHANGED: %S\n", title.c_str()); + } +} + +void TestWebViewDelegate::DidFinishLoadForFrame(WebView* webview, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didFinishLoadForFrame\n", + GetFrameDescription(frame).c_str()); + } + + UpdateAddressBar(webview); + LocationChangeDone(frame->GetDataSource()); +} + +void TestWebViewDelegate::DidFailLoadWithError(WebView* webview, + const WebError& error, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didFailLoadWithError\n", + GetFrameDescription(frame).c_str()); + } + + if (page_is_loading_) + DidStopLoading(webview); + LocationChangeDone(frame->GetDataSource()); +} + +void TestWebViewDelegate::DidFinishDocumentLoadForFrame(WebView* webview, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didFinishDocumentLoadForFrame\n", + GetFrameDescription(frame).c_str()); + } +} + +void TestWebViewDelegate::DidHandleOnloadEventsForFrame(WebView* webview, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didHandleOnloadEventsForFrame\n", + GetFrameDescription(frame).c_str()); + } +} + +void TestWebViewDelegate::DidChangeLocationWithinPageForFrame( + WebView* webview, WebFrame* frame, bool is_new_navigation) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didChangeLocationWithinPageForFrame\n", + GetFrameDescription(frame).c_str()); + } + + UpdateForCommittedLoad(frame, is_new_navigation); +} + +void TestWebViewDelegate::DidReceiveIconForFrame(WebView* webview, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didReceiveIconForFrame\n", + GetFrameDescription(frame).c_str()); + } +} + +void TestWebViewDelegate::WillPerformClientRedirect(WebView* webview, + WebFrame* frame, + const std::wstring& dest_url, + unsigned int delay_seconds, + unsigned int fire_date) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + // FIXME: prettyprint the url? + printf("%S - willPerformClientRedirectToURL: %S\n", + GetFrameDescription(frame).c_str(), dest_url.c_str()); + } +} + +void TestWebViewDelegate::DidCancelClientRedirect(WebView* webview, + WebFrame* frame) { + if (shell_->ShouldDumpFrameLoadCallbacks()) { + printf("%S - didCancelClientRedirectForFrame\n", + GetFrameDescription(frame).c_str()); + } +} + +void TestWebViewDelegate::AddMessageToConsole(WebView* webview, + const std::wstring& message, + unsigned int line_no, + const std::wstring& source_id) { + if (shell_->interactive()) { + logging::LogMessage("CONSOLE", 0).stream() << "\"" + << message.c_str() + << ",\" source: " + << source_id.c_str() + << "(" + << line_no + << ")"; + } else { + // This matches win DumpRenderTree's UIDelegate.cpp. + std::wstring new_message = message; + if (!message.empty()) { + new_message = message; + size_t file_protocol = new_message.find(L"file://"); + if (file_protocol != std::wstring::npos) { + new_message = new_message.substr(0, file_protocol) + + UrlSuitableForTestResult(new_message); + } + } + + std::string utf8 = WideToUTF8(new_message); + printf("CONSOLE MESSAGE: line %d: %s\n", line_no, utf8.c_str()); + } +} + +void TestWebViewDelegate::RunJavaScriptAlert(WebView* webview, + const std::wstring& message) { + if (shell_->interactive()) { + MessageBox(shell_->mainWnd(), + message.c_str(), + L"JavaScript Alert", + MB_OK); + } else { + std::string utf8 = WideToUTF8(message); + printf("ALERT: %s\n", utf8.c_str()); + } +} + +bool TestWebViewDelegate::RunJavaScriptConfirm(WebView* webview, + const std::wstring& message) { + if (!shell_->interactive()) { + // When running tests, write to stdout. + std::string utf8 = WideToUTF8(message); + printf("CONFIRM: %s\n", utf8.c_str()); + return true; + } + return false; +} + +bool TestWebViewDelegate::RunJavaScriptPrompt(WebView* webview, + const std::wstring& message, const std::wstring& default_value, + std::wstring* result) { + if (!shell_->interactive()) { + // When running tests, write to stdout. + std::string utf8_message = WideToUTF8(message); + std::string utf8_default_value = WideToUTF8(default_value); + printf("PROMPT: %s, default text: %s\n", utf8_message.c_str(), + utf8_default_value.c_str()); + return true; + } + return false; +} + +void TestWebViewDelegate::StartDragging(WebView* webview, + const WebDropData& drop_data) { + + if (!drag_delegate_) + drag_delegate_ = new TestDragDelegate(shell_->webViewWnd(), + shell_->webView()); + if (webkit_glue::IsLayoutTestMode()) { + if (shell_->layout_test_controller()->ShouldAddFileToPasteboard()) { + // Add a file called DRTFakeFile to the drag&drop clipboard. + AddDRTFakeFileToDataObject(drop_data.data_object); + } + + // When running a test, we need to fake a drag drop operation otherwise + // Windows waits for real mouse events to know when the drag is over. + EventSendingController::DoDragDrop(drop_data.data_object); + } else { + const DWORD ok_effect = DROPEFFECT_COPY | DROPEFFECT_LINK | DROPEFFECT_MOVE; + DWORD effect; + HRESULT res = DoDragDrop(drop_data.data_object, drag_delegate_.get(), + ok_effect, &effect); + DCHECK(DRAGDROP_S_DROP == res || DRAGDROP_S_CANCEL == res); + } + webview->DragSourceSystemDragEnded(); +} + +void TestWebViewDelegate::ShowContextMenu(WebView* webview, + ContextNode::Type type, + int x, + int y, + const GURL& link_url, + const GURL& image_url, + const GURL& page_url, + const GURL& frame_url, + const std::wstring& selection_text, + const std::wstring& misspelled_word, + int edit_flags) { + CapturedContextMenuEvent context(type, x, y); + captured_context_menu_events_.push_back(context); +} + +// The output from these methods in non-interactive mode should match that +// expected by the layout tests. See EditingDelegate.m in DumpRenderTree. +bool TestWebViewDelegate::ShouldBeginEditing(WebView* webview, + std::wstring range) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8 = WideToUTF8(range); + printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", + utf8.c_str()); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::ShouldEndEditing(WebView* webview, + std::wstring range) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8 = WideToUTF8(range); + printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", + utf8.c_str()); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::ShouldInsertNode(WebView* webview, + std::wstring node, + std::wstring range, + std::wstring action) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8_node = WideToUTF8(node); + std::string utf8_range = WideToUTF8(range); + std::string utf8_action = WideToUTF8(action); + printf("EDITING DELEGATE: shouldInsertNode:%s " + "replacingDOMRange:%s givenAction:%s\n", + utf8_node.c_str(), utf8_range.c_str(), utf8_action.c_str()); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::ShouldInsertText(WebView* webview, + std::wstring text, + std::wstring range, + std::wstring action) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8_text = WideToUTF8(text); + std::string utf8_range = WideToUTF8(range); + std::string utf8_action = WideToUTF8(action); + printf("EDITING DELEGATE: shouldInsertText:%s " + "replacingDOMRange:%s givenAction:%s\n", + utf8_text.c_str(), utf8_range.c_str(), utf8_action.c_str()); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::ShouldChangeSelectedRange(WebView* webview, + std::wstring fromRange, + std::wstring toRange, + std::wstring affinity, + bool stillSelecting) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8_from = WideToUTF8(fromRange); + std::string utf8_to = WideToUTF8(toRange); + std::string utf8_affinity = WideToUTF8(affinity); + printf("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s " + "toDOMRange:%s affinity:%s stillSelecting:%s\n", + utf8_from.c_str(), + utf8_to.c_str(), + utf8_affinity.c_str(), + (stillSelecting ? "TRUE" : "FALSE")); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::ShouldDeleteRange(WebView* webview, + std::wstring range) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8 = WideToUTF8(range); + printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", utf8.c_str()); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::ShouldApplyStyle(WebView* webview, + std::wstring style, + std::wstring range) { + if (shell_->ShouldDumpEditingCallbacks()) { + std::string utf8_style = WideToUTF8(style); + std::string utf8_range = WideToUTF8(range); + printf("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n", + utf8_style.c_str(), utf8_range.c_str()); + } + return shell_->AcceptsEditing(); +} + +bool TestWebViewDelegate::SmartInsertDeleteEnabled() { + return true; +} + +void TestWebViewDelegate::DidBeginEditing() { + if (shell_->ShouldDumpEditingCallbacks()) { + printf("EDITING DELEGATE: " + "webViewDidBeginEditing:WebViewDidBeginEditingNotification\n"); + } +} + +void TestWebViewDelegate::DidChangeSelection() { + if (shell_->ShouldDumpEditingCallbacks()) { + printf("EDITING DELEGATE: " + "webViewDidChangeSelection:WebViewDidChangeSelectionNotification\n"); + } +} + +void TestWebViewDelegate::DidChangeContents() { + if (shell_->ShouldDumpEditingCallbacks()) { + printf("EDITING DELEGATE: " + "webViewDidChange:WebViewDidChangeNotification\n"); + } +} + +void TestWebViewDelegate::DidEndEditing() { + if (shell_->ShouldDumpEditingCallbacks()) { + printf("EDITING DELEGATE: " + "webViewDidEndEditing:WebViewDidEndEditingNotification\n"); + } +} + +WebHistoryItem* TestWebViewDelegate::GetHistoryEntryAtOffset(int offset) { + TestNavigationEntry* entry = static_cast<TestNavigationEntry*>( + shell_->navigation_controller()->GetEntryAtOffset(offset)); + if (!entry) + return NULL; + + return entry->GetHistoryItem(); +} + +void TestWebViewDelegate::GoToEntryAtOffsetAsync(int offset) { + shell_->navigation_controller()->GoToOffset(offset); +} + +int TestWebViewDelegate::GetHistoryBackListCount() { + int current_index = + shell_->navigation_controller()->GetLastCommittedEntryIndex(); + return current_index; +} + +int TestWebViewDelegate::GetHistoryForwardListCount() { + int current_index = + shell_->navigation_controller()->GetLastCommittedEntryIndex(); + return shell_->navigation_controller()->GetEntryCount() - current_index - 1; +} + +void TestWebViewDelegate::SetUserStyleSheetEnabled(bool is_enabled) { + WebPreferences* prefs = shell_->GetWebPreferences(); + prefs->user_style_sheet_enabled = is_enabled; + shell_->webView()->SetPreferences(*prefs); +} + +void TestWebViewDelegate::SetUserStyleSheetLocation(const GURL& location) { + WebPreferences* prefs = shell_->GetWebPreferences(); + prefs->user_style_sheet_enabled = true; + prefs->user_style_sheet_location = location; + shell_->webView()->SetPreferences(*prefs); +} + +void TestWebViewDelegate::SetDashboardCompatibilityMode(bool use_mode) { + WebPreferences* prefs = shell_->GetWebPreferences(); + prefs->dashboard_compatibility_mode = use_mode; + shell_->webView()->SetPreferences(*prefs); +} + +// WebWidgetDelegate --------------------------------------------------------- + +HWND TestWebViewDelegate::GetContainingWindow(WebWidget* webwidget) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) + return host->window_handle(); + + return NULL; +} + +void TestWebViewDelegate::DidInvalidateRect(WebWidget* webwidget, + const gfx::Rect& rect) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) + host->DidInvalidateRect(rect); +} + +void TestWebViewDelegate::DidScrollRect(WebWidget* webwidget, int dx, int dy, + const gfx::Rect& clip_rect) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) + host->DidScrollRect(dx, dy, clip_rect); +} + +void TestWebViewDelegate::Show(WebWidget* webwidget, WindowOpenDisposition) { + if (webwidget == shell_->webView()) { + ShowWindow(shell_->mainWnd(), SW_SHOW); + UpdateWindow(shell_->mainWnd()); + } else if (webwidget == shell_->popup()) { + ShowWindow(shell_->popupWnd(), SW_SHOW); + UpdateWindow(shell_->popupWnd()); + } +} + +void TestWebViewDelegate::CloseWidgetSoon(WebWidget* webwidget) { + if (webwidget == shell_->webView()) { + PostMessage(shell_->mainWnd(), WM_CLOSE, 0, 0); + } else if (webwidget == shell_->popup()) { + shell_->ClosePopup(); + } +} + +void TestWebViewDelegate::Focus(WebWidget* webwidget) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) + shell_->SetFocus(host, true); +} + +void TestWebViewDelegate::Blur(WebWidget* webwidget) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) + shell_->SetFocus(host, false); +} + +void TestWebViewDelegate::SetCursor(WebWidget* webwidget, + const WebCursor& cursor) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) { + if (custom_cursor_) { + DestroyIcon(custom_cursor_); + custom_cursor_ = NULL; + } + if (cursor.type() == WebCursor::CUSTOM) { + custom_cursor_ = cursor.GetCustomCursor(); + host->SetCursor(custom_cursor_); + } else { + HINSTANCE mod_handle = GetModuleHandle(NULL); + host->SetCursor(cursor.GetCursor(mod_handle)); + } + } +} + +void TestWebViewDelegate::GetWindowLocation(WebWidget* webwidget, + gfx::Point* origin) { + if (WebWidgetHost* host = GetHostForWidget(webwidget)) { + RECT rect; + GetWindowRect(host->window_handle(), &rect); + origin->SetPoint(rect.left, rect.top); + } +} + +void TestWebViewDelegate::SetWindowRect(WebWidget* webwidget, + const gfx::Rect& rect) { + if (webwidget == shell_->webView()) { + // ignored + } else if (webwidget == shell_->popup()) { + MoveWindow(shell_->popupWnd(), + rect.x(), rect.y(), rect.width(), rect.height(), FALSE); + } +} + +void TestWebViewDelegate::DidMove(WebWidget* webwidget, + const WebPluginGeometry& move) { + WebPluginDelegateImpl::MoveWindow( + move.window, move.window_rect, move.clip_rect, move.visible); +} + +void TestWebViewDelegate::RunModal(WebWidget* webwidget) { + Show(webwidget, NEW_WINDOW); + + WindowList* wl = TestShell::windowList(); + for (WindowList::const_iterator i = wl->begin(); i != wl->end(); ++i) { + if (*i != shell_->mainWnd()) + EnableWindow(*i, FALSE); + } + + shell_->set_is_modal(true); + MessageLoop::current()->Run(); + + for (WindowList::const_iterator i = wl->begin(); i != wl->end(); ++i) + EnableWindow(*i, TRUE); +} + +void TestWebViewDelegate::RegisterDragDrop() { + DCHECK(!drop_delegate_); + drop_delegate_ = new TestDropDelegate(shell_->webViewWnd(), + shell_->webView()); +} + +// Private methods ----------------------------------------------------------- + +void TestWebViewDelegate::UpdateAddressBar(WebView* webView) { + WebFrame* mainFrame = webView->GetMainFrame(); + + WebDataSource* dataSource = mainFrame->GetDataSource(); + if (!dataSource) + dataSource = mainFrame->GetProvisionalDataSource(); + if (!dataSource) + return; + + std::wstring frameURL = + UTF8ToWide(dataSource->GetRequest().GetMainDocumentURL().spec()); + SendMessage(shell_->editWnd(), WM_SETTEXT, 0, + reinterpret_cast<LPARAM>(frameURL.c_str())); +} + +void TestWebViewDelegate::LocationChangeDone(WebDataSource* data_source) { + if (data_source->GetWebFrame() == top_loading_frame_) { + top_loading_frame_ = NULL; + + if (!shell_->interactive()) + shell_->layout_test_controller()->LocationChangeDone(); + } +} + +WebWidgetHost* TestWebViewDelegate::GetHostForWidget(WebWidget* webwidget) { + if (webwidget == shell_->webView()) + return shell_->webViewHost(); + if (webwidget == shell_->popup()) + return shell_->popupHost(); + return NULL; +} + +void TestWebViewDelegate::UpdateForCommittedLoad(WebFrame* frame, + bool is_new_navigation) { + WebView* webview = shell_->webView(); + + // Code duplicated from RenderView::DidCommitLoadForFrame. + const WebRequest& request = + webview->GetMainFrame()->GetDataSource()->GetRequest(); + TestShellExtraRequestData* extra_data = + static_cast<TestShellExtraRequestData*>(request.GetExtraData()); + + if (is_new_navigation) { + // New navigation. + UpdateSessionHistory(frame); + page_id_ = next_page_id_++; + } else if (extra_data && extra_data->pending_page_id != -1 && + !extra_data->request_committed) { + // This is a successful session history navigation! + UpdateSessionHistory(frame); + page_id_ = extra_data->pending_page_id; + } + + // Don't update session history multiple times. + if (extra_data) + extra_data->request_committed = true; + + UpdateURL(frame); +} + +void TestWebViewDelegate::UpdateURL(WebFrame* frame) { + WebDataSource* ds = frame->GetDataSource(); + DCHECK(ds); + + const WebRequest& request = ds->GetRequest(); + + // We don't hold a reference to the extra data. The request's reference will + // be sufficient because we won't modify it during our call. MAY BE NULL. + TestShellExtraRequestData* extra_data = + static_cast<TestShellExtraRequestData*>(request.GetExtraData()); + + // Type is unused. + scoped_ptr<TestNavigationEntry> entry(new TestNavigationEntry); + + // Bug 654101: the referrer will be empty on https->http transitions. It + // would be nice if we could get the real referrer from somewhere. + entry->SetPageID(page_id_); + if (ds->HasUnreachableURL()) { + entry->SetURL(GURL(ds->GetUnreachableURL())); + } else { + entry->SetURL(GURL(request.GetURL())); + } + + if (shell_->webView()->GetMainFrame() == frame) { + // Top-level navigation. + + PageTransition::Type transition = extra_data ? + extra_data->transition_type : PageTransition::LINK; + if (!PageTransition::IsMainFrame(transition)) { + transition = PageTransition::LINK; + } + entry->SetTransition(transition); + } else { + PageTransition::Type transition; + if (page_id_ > last_page_id_updated_) + transition = PageTransition::MANUAL_SUBFRAME; + else + transition = PageTransition::AUTO_SUBFRAME; + entry->SetTransition(transition); + } + + shell_->navigation_controller()->DidNavigateToEntry(entry.release()); + + last_page_id_updated_ = std::max(last_page_id_updated_, page_id_); +} + +void TestWebViewDelegate::UpdateSessionHistory(WebFrame* frame) { + // If we have a valid page ID at this point, then it corresponds to the page + // we are navigating away from. Otherwise, this is the first navigation, so + // there is no past session history to record. + if (page_id_ == -1) + return; + + TestNavigationEntry* entry = static_cast<TestNavigationEntry*>( + shell_->navigation_controller()->GetEntryWithPageID( + TestNavigationEntry::GetTabContentsType(), page_id_)); + if (!entry) + return; + + GURL url; + std::wstring title; + std::string state; + if (!shell_->webView()->GetMainFrame()-> + GetPreviousState(&url, &title, &state)) + return; + + entry->SetURL(url); + entry->SetTitle(title); + entry->SetContentState(state); +} + +std::wstring TestWebViewDelegate::GetFrameDescription(WebFrame* webframe) { + std::wstring name = webframe->GetName(); + + if (webframe == shell_->webView()->GetMainFrame()) { + if (name.length()) + return L"main frame \"" + name + L"\""; + else + return L"main frame"; + } else { + if (name.length()) + return L"frame \"" + name + L"\""; + else + return L"frame (anonymous)"; + } +} diff --git a/webkit/tools/test_shell/test_webview_delegate.h b/webkit/tools/test_shell/test_webview_delegate.h new file mode 100644 index 0000000..9a6f5e7 --- /dev/null +++ b/webkit/tools/test_shell/test_webview_delegate.h @@ -0,0 +1,303 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// TestWebViewDelegate class: +// This class implements the WebViewDelegate methods for the test shell. One +// instance is owned by each TestShell. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEST_WEBVIEW_DELEGATE_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEST_WEBVIEW_DELEGATE_H__ + +#include <windows.h> +#include <map> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "webkit/glue/webview_delegate.h" +#include "webkit/glue/webwidget_delegate.h" +#include "webkit/tools/test_shell/drag_delegate.h" +#include "webkit/tools/test_shell/drop_delegate.h" + +struct WebPreferences; +class GURL; +class TestShell; +class WebDataSource; +class WebWidgetHost; + +class TestWebViewDelegate : public base::RefCounted<TestWebViewDelegate>, public WebViewDelegate { + public: + struct CapturedContextMenuEvent { + CapturedContextMenuEvent(ContextNode::Type in_type, + int in_x, + int in_y) + : type(in_type), + x(in_x), + y(in_y) { + } + + ContextNode::Type type; + int x; + int y; + }; + + typedef std::vector<CapturedContextMenuEvent> CapturedContextMenuEvents; + + TestWebViewDelegate(TestShell* shell) + : shell_(shell), + top_loading_frame_(NULL), + page_id_(-1), + last_page_id_updated_(-1), + page_is_loading_(false), + is_custom_policy_delegate_(false), + custom_cursor_(NULL) { + } + virtual ~TestWebViewDelegate(); + + // WebViewDelegate + virtual WebView* CreateWebView(WebView* webview, bool user_gesture); + virtual WebWidget* CreatePopupWidget(WebView* webview); + virtual WebPluginDelegate* CreatePluginDelegate( + WebView* webview, + const GURL& url, + const std::string& mime_type, + const std::string& clsid, + std::string* actual_mime_type); + virtual void OpenURL(WebView* webview, + const GURL& url, + WindowOpenDisposition disposition); + virtual void RunJavaScriptAlert(WebView* webview, + const std::wstring& message); + virtual bool RunJavaScriptConfirm(WebView* webview, + const std::wstring& message); + virtual bool RunJavaScriptPrompt(WebView* webview, + const std::wstring& message, + const std::wstring& default_value, + std::wstring* result); + virtual void AddMessageToConsole(WebView* webview, + const std::wstring& message, + unsigned int line_no, + const std::wstring& source_id); + virtual void StartDragging(WebView* webview, + const WebDropData& drop_data); + virtual void ShowContextMenu(WebView* webview, + ContextNode::Type type, + int x, + int y, + const GURL& link_url, + const GURL& image_url, + const GURL& page_url, + const GURL& frame_url, + const std::wstring& selection_text, + const std::wstring& misspelled_word, + int edit_flags); + virtual void DidStartProvisionalLoadForFrame( + WebView* webview, + WebFrame* frame, + NavigationGesture gesture); + virtual void DidReceiveServerRedirectForProvisionalLoadForFrame( + WebView* webview, WebFrame* frame); + virtual void DidFailProvisionalLoadWithError(WebView* webview, + const WebError& error, + WebFrame* frame); + virtual void DidCommitLoadForFrame(WebView* webview, WebFrame* frame, + bool is_new_navigation); + virtual void DidReceiveTitle(WebView* webview, + const std::wstring& title, + WebFrame* frame); + virtual void DidFinishDocumentLoadForFrame(WebView* webview, + WebFrame* frame); + virtual void DidHandleOnloadEventsForFrame(WebView* webview, + WebFrame* frame); + virtual void DidChangeLocationWithinPageForFrame(WebView* webview, + WebFrame* frame, + bool is_new_navigation); + virtual void DidReceiveIconForFrame(WebView* webview, WebFrame* frame); + + virtual void WillPerformClientRedirect(WebView* webview, + WebFrame* frame, + const std::wstring& dest_url, + unsigned int delay_seconds, + unsigned int fire_date); + virtual void DidCancelClientRedirect(WebView* webview, + WebFrame* frame); + + virtual void DidFinishLoadForFrame(WebView* webview, WebFrame* frame); + virtual void DidFailLoadWithError(WebView* webview, + const WebError& error, + WebFrame* forFrame); + + virtual void AssignIdentifierToRequest(WebView* webview, + uint32 identifier, + const WebRequest& request); + virtual void WillSendRequest(WebView* webview, + uint32 identifier, + WebRequest* request); + virtual void DidFinishLoading(WebView* webview, uint32 identifier); + virtual void DidFailLoadingWithError(WebView* webview, + uint32 identifier, + const WebError& error); + + virtual bool ShouldBeginEditing(WebView* webview, std::wstring range); + virtual bool ShouldEndEditing(WebView* webview, std::wstring range); + virtual bool ShouldInsertNode(WebView* webview, + std::wstring node, + std::wstring range, + std::wstring action); + virtual bool ShouldInsertText(WebView* webview, + std::wstring text, + std::wstring range, + std::wstring action); + virtual bool ShouldChangeSelectedRange(WebView* webview, + std::wstring fromRange, + std::wstring toRange, + std::wstring affinity, + bool stillSelecting); + virtual bool ShouldDeleteRange(WebView* webview, std::wstring range); + virtual bool ShouldApplyStyle(WebView* webview, + std::wstring style, + std::wstring range); + virtual bool SmartInsertDeleteEnabled(); + virtual void DidBeginEditing(); + virtual void DidChangeSelection(); + virtual void DidChangeContents(); + virtual void DidEndEditing(); + + virtual void DidStartLoading(WebView* webview); + virtual void DidStopLoading(WebView* webview); + + virtual void WindowObjectCleared(WebFrame* webframe); + virtual WindowOpenDisposition DispositionForNavigationAction( + WebView* webview, + WebFrame* frame, + const WebRequest* request, + WebNavigationType type, + WindowOpenDisposition disposition, + bool is_redirect); + void TestWebViewDelegate::SetCustomPolicyDelegate(bool isCustom); + virtual WebHistoryItem* GetHistoryEntryAtOffset(int offset); + virtual void GoToEntryAtOffsetAsync(int offset); + virtual int GetHistoryBackListCount(); + virtual int GetHistoryForwardListCount(); + + // WebWidgetDelegate + virtual HWND GetContainingWindow(WebWidget* webwidget); + virtual void DidInvalidateRect(WebWidget* webwidget, const gfx::Rect& rect); + virtual void DidScrollRect(WebWidget* webwidget, int dx, int dy, + const gfx::Rect& clip_rect); + virtual void Show(WebWidget* webview, WindowOpenDisposition disposition); + virtual void CloseWidgetSoon(WebWidget* webwidget); + virtual void Focus(WebWidget* webwidget); + virtual void Blur(WebWidget* webwidget); + virtual void SetCursor(WebWidget* webwidget, + const WebCursor& cursor); + virtual void GetWindowLocation(WebWidget* webwidget, gfx::Point* origin); + virtual void SetWindowRect(WebWidget* webwidget, const gfx::Rect& rect); + virtual void DidMove(WebWidget* webwidget, const WebPluginGeometry& move); + virtual void RunModal(WebWidget* webwidget); + virtual void AddRef() { + RefCounted<TestWebViewDelegate>::AddRef(); + } + virtual void Release() { + RefCounted<TestWebViewDelegate>::Release(); + } + + // Additional accessors + WebFrame* top_loading_frame() { return top_loading_frame_; } + IDropTarget* drop_delegate() { return drop_delegate_.get(); } + IDropSource* drag_delegate() { return drag_delegate_.get(); } + const CapturedContextMenuEvents& captured_context_menu_events() const { + return captured_context_menu_events_; + } + void clear_captured_context_menu_events() { + captured_context_menu_events_.clear(); + } + + // Methods for modifying WebPreferences + void SetUserStyleSheetEnabled(bool is_enabled); + void SetUserStyleSheetLocation(const GURL& location); + void SetDashboardCompatibilityMode(bool use_mode); + + // Sets the webview as a drop target. + void RegisterDragDrop(); + + protected: + void UpdateAddressBar(WebView* webView); + + // In the Mac code, this is called to trigger the end of a test after the + // page has finished loading. From here, we can generate the dump for the + // test. + void LocationChangeDone(WebDataSource* data_source); + + WebWidgetHost* GetHostForWidget(WebWidget* webwidget); + + void UpdateForCommittedLoad(WebFrame* webframe, bool is_new_navigation); + void UpdateURL(WebFrame* frame); + void UpdateSessionHistory(WebFrame* frame); + + // Get a string suitable for dumping a frame to the console. + std::wstring GetFrameDescription(WebFrame* webframe); + + private: + // True while a page is in the process of being loaded. This flag should + // not be necessary, but it helps guard against mismatched messages for + // starting and ending loading frames. + bool page_is_loading_; + + // Causes navigation actions just printout the intended navigation instead + // of taking you to the page. This is used for cases like mailto, where you + // don't actually want to open the mail program. + bool is_custom_policy_delegate_; + + // Non-owning pointer. The delegate is owned by the host. + TestShell* shell_; + + // This is non-NULL IFF a load is in progress. + WebFrame* top_loading_frame_; + + // For tracking session history. See RenderView. + int page_id_; + int last_page_id_updated_; + + // Maps resource identifiers to a descriptive string. + typedef std::map<uint32, std::string> ResourceMap; + ResourceMap resource_identifier_map_; + std::string GetResourceDescription(uint32 identifier); + + HCURSOR custom_cursor_; + + // Classes needed by drag and drop. + scoped_refptr<TestDragDelegate> drag_delegate_; + scoped_refptr<TestDropDelegate> drop_delegate_; + + CapturedContextMenuEvents captured_context_menu_events_; + + DISALLOW_EVIL_CONSTRUCTORS(TestWebViewDelegate); +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_WEBVIEW_DELEGATE_H__ diff --git a/webkit/tools/test_shell/text_input_controller.cc b/webkit/tools/test_shell/text_input_controller.cc new file mode 100644 index 0000000..d74346b --- /dev/null +++ b/webkit/tools/test_shell/text_input_controller.cc @@ -0,0 +1,249 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/text_input_controller.h" + +#include "webkit/glue/webview.h" +#include "webkit/glue/webframe.h" +#include "webkit/glue/webtextinput.h" +#include "webkit/tools/test_shell/test_shell.h" + +TestShell* TextInputController::shell_ = NULL; + +TextInputController::TextInputController(TestShell* shell) { + // Set static shell_ variable. Be careful not to assign shell_ to new + // windows which are temporary. + if (NULL == shell_) + shell_ = shell; + + BindMethod("insertText", &TextInputController::insertText); + BindMethod("doCommand", &TextInputController::doCommand); + BindMethod("setMarkedText", &TextInputController::setMarkedText); + BindMethod("unmarkText", &TextInputController::unmarkText); + BindMethod("hasMarkedText", &TextInputController::hasMarkedText); + BindMethod("conversationIdentifier", &TextInputController::conversationIdentifier); + BindMethod("substringFromRange", &TextInputController::substringFromRange); + BindMethod("attributedSubstringFromRange", &TextInputController::attributedSubstringFromRange); + BindMethod("markedRange", &TextInputController::markedRange); + BindMethod("selectedRange", &TextInputController::selectedRange); + BindMethod("firstRectForCharacterRange", &TextInputController::firstRectForCharacterRange); + BindMethod("characterIndexForPoint", &TextInputController::characterIndexForPoint); + BindMethod("validAttributesForMarkedText", &TextInputController::validAttributesForMarkedText); + BindMethod("makeAttributedString", &TextInputController::makeAttributedString); +} + +/* static */ WebView* TextInputController::webview() { + return shell_->webView(); +} + +/* static */ WebTextInput* TextInputController::GetTextInput() { + return webview()->GetMainFrame()->GetTextInput(); +} + +void TextInputController::insertText( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 1 && args[0].isString()) { + text_input->InsertText(args[0].ToString()); + } +} + +void TextInputController::doCommand( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 1 && args[0].isString()) { + text_input->DoCommand(args[0].ToString()); + } +} + +void TextInputController::setMarkedText( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 3 && args[0].isString() + && args[1].isNumber() && args[2].isNumber()) { + text_input->SetMarkedText(args[0].ToString(), + args[1].ToInt32(), + args[2].ToInt32()); + } +} + +void TextInputController::unmarkText( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + text_input->UnMarkText(); +} + +void TextInputController::hasMarkedText( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + result->Set(text_input->HasMarkedText()); +} + +void TextInputController::conversationIdentifier( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + text_input->ConversationIdentifier(); +} + +void TextInputController::substringFromRange( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 2 && args[0].isNumber() && args[1].isNumber()) { + text_input->SubstringFromRange(args[0].ToInt32(), args[1].ToInt32()); + } +} + +void TextInputController::attributedSubstringFromRange( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 2 && args[0].isNumber() && args[1].isNumber()) { + text_input->AttributedSubstringFromRange(args[0].ToInt32(), + args[1].ToInt32()); + } +} + +void TextInputController::markedRange( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + std::string range_str; + text_input->MarkedRange(&range_str); + result->Set(range_str); +} + +void TextInputController::selectedRange( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + std::string range_str; + text_input->SelectedRange(&range_str); + result->Set(range_str); +} + +void TextInputController::firstRectForCharacterRange( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 2 && args[0].isNumber() && args[1].isNumber()) { + text_input->FirstRectForCharacterRange(args[0].ToInt32(), + args[1].ToInt32()); + } +} + +void TextInputController::characterIndexForPoint( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 2 && args[0].isDouble() && args[1].isDouble()) { + text_input->CharacterIndexForPoint(args[0].ToDouble(), + args[1].ToDouble()); + } +} + +void TextInputController::validAttributesForMarkedText( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + std::string attributes_str; + text_input->ValidAttributesForMarkedText(&attributes_str); + result->Set(attributes_str); +} + +void TextInputController::makeAttributedString( + const CppArgumentList& args, CppVariant* result) { + result->SetNull(); + + WebTextInput* text_input = GetTextInput(); + if (!text_input) + return; + + if (args.size() >= 1 && args[0].isString()) { + text_input->MakeAttributedString(args[0].ToString()); + } +} diff --git a/webkit/tools/test_shell/text_input_controller.h b/webkit/tools/test_shell/text_input_controller.h new file mode 100644 index 0000000..f54a307 --- /dev/null +++ b/webkit/tools/test_shell/text_input_controller.h @@ -0,0 +1,74 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +// TextInputController is bound to window.textInputController in Javascript +// when test_shell is running noninteractively. Layout tests use it to +// exercise various corners of text input. +// +// Mac equivalent: WebKit/WebKitTools/DumpRenderTree/TextInputController.{h,m} + +#ifndef WEBKIT_TOOLS_TEST_SHELL_TEXT_INPUT_CONTROLLER_H__ +#define WEBKIT_TOOLS_TEST_SHELL_TEXT_INPUT_CONTROLLER_H__ + +#include "webkit/glue/cpp_bound_class.h" + +class TestShell; +class WebView; +class WebTextInput; + +class TextInputController : public CppBoundClass { + public: + TextInputController(TestShell* shell); + + void insertText(const CppArgumentList& args, CppVariant* result); + void doCommand(const CppArgumentList& args, CppVariant* result); + void setMarkedText(const CppArgumentList& args, CppVariant* result); + void unmarkText(const CppArgumentList& args, CppVariant* result); + void hasMarkedText(const CppArgumentList& args, CppVariant* result); + void conversationIdentifier(const CppArgumentList& args, CppVariant* result); + void substringFromRange(const CppArgumentList& args, CppVariant* result); + void attributedSubstringFromRange(const CppArgumentList& args, CppVariant* result); + void markedRange(const CppArgumentList& args, CppVariant* result); + void selectedRange(const CppArgumentList& args, CppVariant* result); + void firstRectForCharacterRange(const CppArgumentList& args, CppVariant* result); + void characterIndexForPoint(const CppArgumentList& args, CppVariant* result); + void validAttributesForMarkedText(const CppArgumentList& args, CppVariant* result); + void makeAttributedString(const CppArgumentList& args, CppVariant* result); + + private: + static WebTextInput* GetTextInput(); + + // Returns the test shell's webview. + static WebView* webview(); + + // Non-owning pointer. The LayoutTestController is owned by the host. + static TestShell* shell_; +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_TEXT_INPUT_CONTROLLER_H__ diff --git a/webkit/tools/test_shell/text_input_controller_unittest.cc b/webkit/tools/test_shell/text_input_controller_unittest.cc new file mode 100644 index 0000000..1e2da90 --- /dev/null +++ b/webkit/tools/test_shell/text_input_controller_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/text_input_controller.h" + +#include "testing/gtest/include/gtest/gtest.h" + +// Test class to let us check TextInputController's method table. +class TestTextInputController : public TextInputController { + public: + TestTextInputController() : TextInputController(NULL) {} + + size_t MethodCount() { + return methods_.size(); + } +}; + + +TEST(TextInputControllerTest, MethodMapIsInitialized) { + TestTextInputController text_input_controller; + + EXPECT_EQ(14, text_input_controller.MethodCount()); + + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "insertText")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "doCommand")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "setMarkedText")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "unmarkText")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "hasMarkedText")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "conversationIdentifier")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "substringFromRange")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "attributedSubstringFromRange")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "markedRange")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "selectedRange")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "firstRectForCharacterRange")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "characterIndexForPoint")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "validAttributesForMarkedText")); + EXPECT_TRUE(text_input_controller.IsMethodRegistered( + "makeAttributedString")); + + // Negative test. + EXPECT_FALSE(text_input_controller.IsMethodRegistered( + "momeRathsOutgrabe")); +} diff --git a/webkit/tools/test_shell/webview_host.cc b/webkit/tools/test_shell/webview_host.cc new file mode 100644 index 0000000..61f66f0 --- /dev/null +++ b/webkit/tools/test_shell/webview_host.cc @@ -0,0 +1,72 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/webview_host.h" + +#include "base/gfx/platform_canvas.h" +#include "base/gfx/rect.h" +#include "base/gfx/size.h" +#include "base/win_util.h" +#include "webkit/glue/webinputevent.h" +#include "webkit/glue/webview.h" + +static const wchar_t kWindowClassName[] = L"WebViewHost"; + +/*static*/ +WebViewHost* WebViewHost::Create(HWND parent_window, WebViewDelegate* delegate, + const WebPreferences& prefs) { + WebViewHost* host = new WebViewHost(); + + static bool registered_class = false; + if (!registered_class) { + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(wcex); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = WebWidgetHost::WndProc; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = kWindowClassName; + RegisterClassEx(&wcex); + registered_class = true; + } + + host->hwnd_ = CreateWindow(kWindowClassName, NULL, + WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS, 0, 0, + 0, 0, parent_window, NULL, + GetModuleHandle(NULL), NULL); + win_util::SetWindowUserData(host->hwnd_, host); + + host->webwidget_ = WebView::Create(delegate, prefs); + + return host; +} + +WebView* WebViewHost::webview() const { + return static_cast<WebView*>(webwidget_); +} diff --git a/webkit/tools/test_shell/webview_host.h b/webkit/tools/test_shell/webview_host.h new file mode 100644 index 0000000..e2f84d6 --- /dev/null +++ b/webkit/tools/test_shell/webview_host.h @@ -0,0 +1,61 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_WEBVIEW_HOST_H__ +#define WEBKIT_TOOLS_TEST_SHELL_WEBVIEW_HOST_H__ + +#include <windows.h> + +#include "base/gfx/rect.h" +#include "base/scoped_ptr.h" +#include "webkit/tools/test_shell/webwidget_host.h" + +struct WebPreferences; +class WebView; +class WebViewDelegate; + +// This class is a simple HWND-based host for a WebView +class WebViewHost : public WebWidgetHost { + public: + // The new instance is deleted once the associated HWND is destroyed. The + // newly created window should be resized after it is created, using the + // MoveWindow (or equivalent) function. + static WebViewHost* Create(HWND parent_window, + WebViewDelegate* delegate, + const WebPreferences& prefs); + + WebView* webview() const; + + protected: + virtual bool WndProc(UINT message, WPARAM wparam, LPARAM lparam) { + return false; + } +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_WEBVIEW_HOST_H__ diff --git a/webkit/tools/test_shell/webwidget_host.cc b/webkit/tools/test_shell/webwidget_host.cc new file mode 100644 index 0000000..b037ca9 --- /dev/null +++ b/webkit/tools/test_shell/webwidget_host.cc @@ -0,0 +1,354 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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 "webkit/tools/test_shell/webwidget_host.h" + +#include "base/gfx/platform_canvas.h" +#include "base/gfx/rect.h" +#include "base/gfx/size.h" +#include "base/win_util.h" +#include "webkit/glue/webinputevent.h" +#include "webkit/glue/webwidget.h" + +static const wchar_t kWindowClassName[] = L"WebWidgetHost"; + +/*static*/ +WebWidgetHost* WebWidgetHost::Create(HWND parent_window, WebWidgetDelegate* delegate) { + WebWidgetHost* host = new WebWidgetHost(); + + static bool registered_class = false; + if (!registered_class) { + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(wcex); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = WebWidgetHost::WndProc; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = kWindowClassName; + RegisterClassEx(&wcex); + registered_class = true; + } + + host->hwnd_ = CreateWindowEx(WS_EX_TOOLWINDOW, + kWindowClassName, kWindowClassName, WS_POPUP, + 0, 0, 0, 0, + parent_window, NULL, GetModuleHandle(NULL), NULL); + + win_util::SetWindowUserData(host->hwnd_, host); + + host->webwidget_ = WebWidget::Create(delegate); + + return host; +} + +/*static*/ +WebWidgetHost* WebWidgetHost::FromWindow(HWND hwnd) { + return reinterpret_cast<WebWidgetHost*>(win_util::GetWindowUserData(hwnd)); +} + +/*static*/ +LRESULT CALLBACK WebWidgetHost::WndProc(HWND hwnd, UINT message, WPARAM wparam, + LPARAM lparam) { + WebWidgetHost* host = FromWindow(hwnd); + if (host && !host->WndProc(message, wparam, lparam)) { + switch (message) { + case WM_DESTROY: + delete host; + break; + + case WM_PAINT: + host->Paint(); + return 0; + + case WM_ERASEBKGND: + // Do nothing here to avoid flashing, the background will be erased + // during painting. + return 0; + + case WM_SIZE: + host->Resize(lparam); + return 0; + + case WM_MOUSEMOVE: + case WM_MOUSELEAVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + host->MouseEvent(message, wparam, lparam); + break; + + case WM_MOUSEWHEEL: + host->WheelEvent(wparam, lparam); + break; + + case WM_CAPTURECHANGED: + case WM_CANCELMODE: + host->CaptureLostEvent(); + break; + + // TODO(darin): add WM_SYSKEY{DOWN/UP} to capture ALT key actions + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_CHAR: + case WM_SYSCHAR: + case WM_IME_CHAR: + host->KeyEvent(message, wparam, lparam); + break; + + case WM_SETFOCUS: + host->SetFocus(true); + break; + + case WM_KILLFOCUS: + host->SetFocus(false); + break; + } + } + + return DefWindowProc(hwnd, message, wparam, lparam);; +} + +void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) { + DLOG_IF(WARNING, painting_) << "unexpected invalidation while painting"; + + // If this invalidate overlaps with a pending scroll, then we have to + // downgrade to invalidating the scroll rect. + if (damaged_rect.Intersects(scroll_rect_)) { + paint_rect_ = paint_rect_.Union(scroll_rect_); + ResetScrollRect(); + } + paint_rect_ = paint_rect_.Union(damaged_rect); + + RECT r = damaged_rect.ToRECT(); + InvalidateRect(hwnd_, &r, FALSE); +} + +void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { + DCHECK(dx || dy); + + // If we already have a pending scroll operation or if this scroll operation + // intersects the existing paint region, then just failover to invalidating. + if (!scroll_rect_.IsEmpty() || paint_rect_.Intersects(clip_rect)) { + paint_rect_ = paint_rect_.Union(scroll_rect_); + ResetScrollRect(); + paint_rect_ = paint_rect_.Union(clip_rect); + } + + // We will perform scrolling lazily, when requested to actually paint. + scroll_rect_ = clip_rect; + scroll_dx_ = dx; + scroll_dy_ = dy; + + RECT r = clip_rect.ToRECT(); + InvalidateRect(hwnd_, &r, FALSE); +} + +void WebWidgetHost::SetCursor(HCURSOR cursor) { + SetClassLong(hwnd_, GCL_HCURSOR, + static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor))); + ::SetCursor(cursor); +} + +void WebWidgetHost::DiscardBackingStore() { + canvas_.reset(); +} + +WebWidgetHost::WebWidgetHost() + : hwnd_(NULL), + webwidget_(NULL), + track_mouse_leave_(false), + scroll_dx_(0), + scroll_dy_(0) { + set_painting(false); +} + +WebWidgetHost::~WebWidgetHost() { + win_util::SetWindowUserData(hwnd_, 0); + + TrackMouseLeave(false); + + webwidget_->Close(); + webwidget_->Release(); +} + +bool WebWidgetHost::WndProc(UINT message, WPARAM wparam, LPARAM lparam) { + switch (message) { + case WM_ACTIVATE: + if (wparam == WA_INACTIVE) { + PostMessage(hwnd_, WM_CLOSE, 0, 0); + return true; + } + break; + } + + return false; +} + +void WebWidgetHost::Paint() { + RECT r; + GetClientRect(hwnd_, &r); + gfx::Rect client_rect(r); + + // Allocate a canvas if necessary + if (!canvas_.get()) { + ResetScrollRect(); + paint_rect_ = client_rect; + canvas_.reset(new gfx::PlatformCanvas( + paint_rect_.width(), paint_rect_.height(), true)); + } + + // This may result in more invalidation + webwidget_->Layout(); + + // Scroll the canvas if necessary + scroll_rect_ = client_rect.Intersect(scroll_rect_); + if (!scroll_rect_.IsEmpty()) { + HDC hdc = canvas_->getTopPlatformDevice().getBitmapDC(); + + RECT damaged_rect, r = scroll_rect_.ToRECT(); + ScrollDC(hdc, scroll_dx_, scroll_dy_, NULL, &r, NULL, &damaged_rect); + + PaintRect(gfx::Rect(damaged_rect)); + } + ResetScrollRect(); + + // Paint the canvas if necessary. Allow painting to generate extra rects the + // first time we call it. This is necessary because some WebCore rendering + // objects update their layout only when painted. + for (int i = 0; i < 2; ++i) { + paint_rect_ = client_rect.Intersect(paint_rect_); + if (!paint_rect_.IsEmpty()) { + gfx::Rect rect(paint_rect_); + paint_rect_ = gfx::Rect(); + + DLOG_IF(WARNING, i == 1) << "painting caused additional invalidations"; + PaintRect(rect); + } + } + DCHECK(paint_rect_.IsEmpty()); + + // Paint to the screen + PAINTSTRUCT ps; + BeginPaint(hwnd_, &ps); + canvas_->getTopPlatformDevice().drawToHDC(ps.hdc, + ps.rcPaint.left, + ps.rcPaint.top, + &ps.rcPaint); + EndPaint(hwnd_, &ps); + + // Draw children + UpdateWindow(hwnd_); +} + +void WebWidgetHost::Resize(LPARAM lparam) { + // Force an entire re-paint. TODO(darin): Maybe reuse this memory buffer. + DiscardBackingStore(); + + webwidget_->Resize(gfx::Size(LOWORD(lparam), HIWORD(lparam))); +} + +void WebWidgetHost::MouseEvent(UINT message, WPARAM wparam, LPARAM lparam) { + WebMouseEvent event(hwnd_, message, wparam, lparam); + switch (event.type) { + case WebInputEvent::MOUSE_MOVE: + TrackMouseLeave(true); + break; + case WebInputEvent::MOUSE_LEAVE: + TrackMouseLeave(false); + break; + case WebInputEvent::MOUSE_DOWN: + SetCapture(hwnd_); + break; + case WebInputEvent::MOUSE_UP: + if (GetCapture() == hwnd_) + ReleaseCapture(); + break; + } + webwidget_->HandleInputEvent(&event); +} + +void WebWidgetHost::WheelEvent(WPARAM wparam, LPARAM lparam) { + WebMouseWheelEvent event(hwnd_, WM_MOUSEWHEEL, wparam, lparam); + webwidget_->HandleInputEvent(&event); +} + +void WebWidgetHost::KeyEvent(UINT message, WPARAM wparam, LPARAM lparam) { + WebKeyboardEvent event(hwnd_, message, wparam, lparam); + webwidget_->HandleInputEvent(&event); +} + +void WebWidgetHost::CaptureLostEvent() { + webwidget_->MouseCaptureLost(); +} + +void WebWidgetHost::SetFocus(bool enable) { + webwidget_->SetFocus(enable); +} + +void WebWidgetHost::TrackMouseLeave(bool track) { + if (track == track_mouse_leave_) + return; + track_mouse_leave_ = track; + + DCHECK(hwnd_); + + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + if (!track_mouse_leave_) + tme.dwFlags |= TME_CANCEL; + tme.hwndTrack = hwnd_; + + TrackMouseEvent(&tme); +} + +void WebWidgetHost::ResetScrollRect() { + scroll_rect_ = gfx::Rect(); + scroll_dx_ = 0; + scroll_dy_ = 0; +} + +void WebWidgetHost::PaintRect(const gfx::Rect& rect) { +#ifndef NDEBUG + DCHECK(!painting_); +#endif + DCHECK(canvas_.get()); + + set_painting(true); + webwidget_->Paint(canvas_.get(), rect); + set_painting(false); +} diff --git a/webkit/tools/test_shell/webwidget_host.h b/webkit/tools/test_shell/webwidget_host.h new file mode 100644 index 0000000..e0dde64 --- /dev/null +++ b/webkit/tools/test_shell/webwidget_host.h @@ -0,0 +1,111 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT +// OWNER OR 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. + +#ifndef WEBKIT_TOOLS_TEST_SHELL_WEBWIDGET_HOST_H__ +#define WEBKIT_TOOLS_TEST_SHELL_WEBWIDGET_HOST_H__ + +#include <windows.h> + +#include "base/gfx/rect.h" +#include "base/scoped_ptr.h" + +class WebWidget; +class WebWidgetDelegate; + +namespace gfx { +class PlatformCanvas; +class Size; +} + +// This class is a simple HWND-based host for a WebWidget +class WebWidgetHost { + public: + // The new instance is deleted once the associated HWND is destroyed. The + // newly created window should be resized after it is created, using the + // MoveWindow (or equivalent) function. + static WebWidgetHost* Create(HWND parent_window, WebWidgetDelegate* delegate); + + static WebWidgetHost* FromWindow(HWND hwnd); + + HWND window_handle() const { return hwnd_; } + WebWidget* webwidget() const { return webwidget_; } + + void DidInvalidateRect(const gfx::Rect& rect); + void DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect); + void SetCursor(HCURSOR cursor); + + void DiscardBackingStore(); + + protected: + WebWidgetHost(); + ~WebWidgetHost(); + + // Per-class wndproc. Returns true if the event should be swallowed. + virtual bool WndProc(UINT message, WPARAM wparam, LPARAM lparam); + + void Paint(); + void Resize(LPARAM lparam); + void MouseEvent(UINT message, WPARAM wparam, LPARAM lparam); + void WheelEvent(WPARAM wparam, LPARAM lparam); + void KeyEvent(UINT message, WPARAM wparam, LPARAM lparam); + void CaptureLostEvent(); + void SetFocus(bool enable); + + void TrackMouseLeave(bool enable); + void ResetScrollRect(); + void PaintRect(const gfx::Rect& rect); + + void set_painting(bool value) { +#ifndef NDEBUG + painting_ = value; +#endif + } + + static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + + HWND hwnd_; + WebWidget* webwidget_; + scoped_ptr<gfx::PlatformCanvas> canvas_; + + // specifies the portion of the webwidget that needs painting + gfx::Rect paint_rect_; + + // specifies the portion of the webwidget that needs scrolling + gfx::Rect scroll_rect_; + int scroll_dx_; + int scroll_dy_; + + bool track_mouse_leave_; + +#ifndef NDEBUG + bool painting_; +#endif +}; + +#endif // WEBKIT_TOOLS_TEST_SHELL_WEBWIDGET_HOST_H__ |