blob: 75bad733191986022cee6820bceff1058f660303 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (C) 2013 Adobe Systems Incorporated. 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 THE COPYRIGHT HOLDER "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 HOLDER 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 json
import logging
import os
import re
import sys
from webkitpy.common.host import Host
from webkitpy.common.webkit_finder import WebKitFinder
if sys.version_info > (3, 0):
from html.parser import HTMLParser
else:
from HTMLParser import HTMLParser
_log = logging.getLogger(__name__)
def convert_for_webkit(new_path, filename, reference_support_info, host=Host(), convert_test_harness_links=True, webkit_test_runner_options=''):
""" Converts a file's |contents| so it will function correctly in its |new_path| in Webkit.
Returns the list of modified properties and the modified text if the file was modifed, None otherwise."""
contents = host.filesystem.read_text_file(filename)
# WebKit does not have a www test domain.
contents = contents.replace('{{domains[www]}}', '{{hosts[alt][]}}')
converter = _W3CTestConverter(new_path, filename, reference_support_info, host, convert_test_harness_links, webkit_test_runner_options)
if filename.endswith('.css'):
return converter.add_webkit_prefix_to_unprefixed_properties_and_values(contents)
elif filename.endswith('.js'):
return ([], [], contents)
else:
converter.feed(contents)
converter.close()
return converter.output()
class _W3CTestConverter(HTMLParser):
def __init__(self, new_path, filename, reference_support_info, host=Host(), convert_test_harness_links=True, webkit_test_runner_options=''):
if sys.version_info > (3, 0):
HTMLParser.__init__(self, convert_charrefs=False)
else:
HTMLParser.__init__(self)
self._host = host
self._filesystem = self._host.filesystem
self._webkit_root = WebKitFinder(self._filesystem).webkit_base()
self.converted_data = []
self.converted_properties = []
self.converted_property_values = []
self.in_style_tag = False
self.style_data = []
self.filename = filename
self.reference_support_info = reference_support_info
self.webkit_test_runner_options = webkit_test_runner_options
self.has_started = False
resources_path = self.path_from_webkit_root('LayoutTests', 'resources')
resources_relpath = self._filesystem.relpath(resources_path, new_path)
self.new_test_harness_path = resources_relpath.replace(os.sep, '/')
self.convert_test_harness_links = convert_test_harness_links
# These settings might vary between WebKit and Blink
css_property_file = self.path_from_webkit_root('Source', 'WebCore', 'css', 'CSSProperties.json')
css_property_value_file = self.path_from_webkit_root('Source', 'WebCore', 'css', 'CSSValueKeywords.in')
self.test_harness_re = re.compile('/resources/testharness')
self.prefixed_properties = self.read_webkit_prefixed_css_property_list(css_property_file)
prop_regex = r'([\s{]|^)(' + "|".join(prop.replace('-webkit-', '') for prop in self.prefixed_properties) + r')(\s+:|:)'
self.prop_re = re.compile(prop_regex)
self.prefixed_property_values = self.legacy_read_webkit_prefixed_css_property_list(css_property_value_file)
prop_value_regex = r'(:\s*|^\s*)(' + "|".join(value.replace('-webkit-', '') for value in self.prefixed_property_values) + r')(\s*;|\s*}|\s*$)'
self.prop_value_re = re.compile(prop_value_regex)
def output(self):
return (self.converted_properties, self.converted_property_values, ''.join(self.converted_data))
def path_from_webkit_root(self, *comps):
return self._filesystem.abspath(self._filesystem.join(self._webkit_root, *comps))
def read_webkit_prefixed_css_property_list(self, file_name):
contents = self._filesystem.read_text_file(file_name)
if not contents:
return []
properties = json.loads(contents)['properties']
property_names = []
for property_name, property_dict in properties.items():
property_names.append(property_name)
if 'codegen-properties' in property_dict:
codegen_options = property_dict['codegen-properties']
if 'aliases' in codegen_options:
property_names.extend(codegen_options['aliases'])
prefixed_properties = []
unprefixed_properties = set()
for property_name in property_names:
# Find properties starting with the -webkit- prefix.
match = re.match(r'-webkit-([\w|-]*)', property_name)
if match:
prefixed_properties.append(match.group(1))
else:
unprefixed_properties.add(property_name)
# Ignore any prefixed properties for which an unprefixed version is supported
return [prop for prop in prefixed_properties if prop not in unprefixed_properties]
def legacy_read_webkit_prefixed_css_property_list(self, file_name):
contents = self._filesystem.read_text_file(file_name)
prefixed_properties = []
unprefixed_properties = set()
for line in contents.splitlines():
if re.match('^(#|//)', line) or len(line.strip()) == 0:
# skip comments and preprocessor directives and empty lines.
continue
# Property name is always first on the line.
property_name = line.split(' ', 1)[0]
# Find properties starting with the -webkit- prefix.
match = re.match(r'-webkit-([\w|-]*)', property_name)
if match:
prefixed_properties.append(match.group(1))
else:
unprefixed_properties.add(property_name.strip())
# Ignore any prefixed properties for which an unprefixed version is supported
return [prop for prop in prefixed_properties if prop not in unprefixed_properties]
def add_webkit_prefix_to_unprefixed_properties_and_values(self, text):
""" Searches |text| for instances of properties and values requiring the -webkit- prefix and adds the prefix to them.
Returns the list of converted properties, values and the modified text."""
converted_properties = self.add_webkit_prefix_following_regex(text, self.prop_re)
converted_property_values = self.add_webkit_prefix_following_regex(converted_properties[1], self.prop_value_re)
# FIXME: Handle the JS versions of these properties and values and GetComputedStyle, too.
return (converted_properties[0], converted_property_values[0], converted_property_values[1])
def add_webkit_prefix_following_regex(self, text, regex):
converted_list = set()
text_chunks = []
cur_pos = 0
for m in regex.finditer(text):
text_chunks.extend([text[cur_pos:m.start()], m.group(1), '-webkit-', m.group(2), m.group(3)])
converted_list.add(m.group(2))
cur_pos = m.end()
text_chunks.append(text[cur_pos:])
for item in converted_list:
_log.info(' converting %s', item)
return (converted_list, ''.join(text_chunks))
def convert_reference_relpaths(self, text):
""" Searches |text| for instances of files in reference_support_info and updates the relative path to be correct for the new ref file location"""
converted = text
for path in self.reference_support_info['files']:
if converted.find(path) != -1:
# FIXME: This doesn't handle an edge case where simply removing the relative path doesn't work.
# See http://webkit.org/b/135677 for details.
new_path = re.sub(re.escape(self.reference_support_info['reference_relpath']), '', path, 1)
converted = re.sub(re.escape(path), new_path, converted)
return converted
def convert_style_data(self, data):
converted = self.add_webkit_prefix_to_unprefixed_properties_and_values(data)
if converted[0]:
self.converted_properties.extend(list(converted[0]))
if converted[1]:
self.converted_property_values.extend(list(converted[1]))
if self.reference_support_info is None or self.reference_support_info == {}:
return converted[2]
return self.convert_reference_relpaths(converted[2])
def convert_attributes_if_needed(self, tag, attrs):
converted = self.get_starttag_text()
if self.convert_test_harness_links and tag in ('script', 'link'):
attr_name = 'src'
if tag != 'script':
attr_name = 'href'
for attr in attrs:
if attr[0] == attr_name:
new_path = re.sub(self.test_harness_re, self.new_test_harness_path + '/testharness', attr[1])
converted = re.sub(re.escape(attr[1]), new_path, converted)
for attr in attrs:
if attr[0] == 'style':
new_style = self.convert_style_data(attr[1])
converted = re.sub(re.escape(attr[1]), new_style, converted)
# Convert relative paths
src_tags = ('script', 'style', 'img', 'frame', 'iframe', 'input', 'layer', 'textarea', 'video', 'audio')
if self.reference_support_info is not None and self.reference_support_info != {}:
if tag in src_tags:
for attr_name, attr_value in attrs:
if attr_name == 'src':
new_path = self.convert_reference_relpaths(attr_value)
converted = re.sub(re.escape(attr_value), new_path, converted)
if tag == 'link':
for attr_name, attr_value in attrs:
if attr_name == 'href':
new_path = self.convert_reference_relpaths(attr_value)
converted = re.sub(re.escape(attr_value), new_path, converted)
self.converted_data.append(converted)
def add_webkit_test_runner_options_if_needed(self):
if self.has_started:
return
self.has_started = True
if self.webkit_test_runner_options:
self.converted_data[-1] = self.converted_data[-1] + self.webkit_test_runner_options
def handle_starttag(self, tag, attrs):
if tag == 'style':
self.in_style_tag = True
self.convert_attributes_if_needed(tag, attrs)
self.add_webkit_test_runner_options_if_needed()
def handle_endtag(self, tag):
if tag == 'style':
self.converted_data.append(self.convert_style_data(''.join(self.style_data)))
self.in_style_tag = False
self.style_data = []
self.converted_data.extend(['</', tag, '>'])
def handle_startendtag(self, tag, attrs):
self.convert_attributes_if_needed(tag, attrs)
def handle_data(self, data):
if self.in_style_tag:
self.style_data.append(data)
else:
self.converted_data.append(data)
def handle_entityref(self, name):
self.converted_data.extend(['&', name, ';'])
def handle_charref(self, name):
self.converted_data.extend(['&#', name, ';'])
def handle_comment(self, data):
self.converted_data.extend(['<!--', data, '-->'])
self.add_webkit_test_runner_options_if_needed()
def handle_decl(self, decl):
self.converted_data.extend(['<!', decl, '>'])
self.add_webkit_test_runner_options_if_needed()
def handle_pi(self, data):
self.converted_data.extend(['<?', data, '>'])
self.add_webkit_test_runner_options_if_needed()