| # 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 logging | |
| import re | |
| from webkitpy.common.host import Host | |
| from webkitpy.common.webkit_finder import WebKitFinder | |
| from HTMLParser import HTMLParser | |
| _log = logging.getLogger(__name__) | |
| def convert_for_webkit(new_path, filename, reference_support_info, host=Host()): | |
| """Converts a file's contents so the Blink layout test runner can run it. | |
| Args: | |
| new_path: Absolute path where file will be copied to in the Chromium repo. | |
| filename: Absolute path to where the file is. | |
| reference_support_info: Dict of information about a related reference HTML, if any. | |
| Returns: | |
| A pair of (list of modified CSS properties, modified text). | |
| """ | |
| # Conversion is not necessary for any tests in wpt now; see http://crbug.com/654081. | |
| contents = host.filesystem.read_binary_file(filename) | |
| try: | |
| contents = contents.decode('utf-8') | |
| except UnicodeDecodeError: | |
| contents = contents.decode('utf-16') | |
| converter = _W3CTestConverter(new_path, filename, reference_support_info, host) | |
| if filename.endswith('.css'): | |
| return converter.add_webkit_prefix_to_unprefixed_properties(contents) | |
| converter.feed(contents) | |
| converter.close() | |
| return converter.output() | |
| class _W3CTestConverter(HTMLParser): | |
| """A HTMLParser subclass which converts a HTML file as it is parsed. | |
| After the feed() method is called, the converted document will be stored | |
| in converted_data, and can be retrieved with the output() method. | |
| """ | |
| def __init__(self, new_path, filename, reference_support_info, host=Host()): | |
| 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.in_style_tag = False | |
| self.style_data = [] | |
| self.filename = filename | |
| self.reference_support_info = reference_support_info | |
| resources_path = self.path_from_webkit_root('LayoutTests', 'resources') | |
| resources_relpath = self._filesystem.relpath(resources_path, new_path) | |
| self.resources_relpath = resources_relpath | |
| # These settings might vary between WebKit and Blink. | |
| # Only -webkit-text-emphasis is currently needed. See: | |
| # https://bugs.chromium.org/p/chromium/issues/detail?id=614955#c1 | |
| self.prefixed_properties = [ | |
| '-webkit-text-emphasis', | |
| '-webkit-text-emphasis-color', | |
| '-webkit-text-emphasis-position', | |
| '-webkit-text-emphasis-style', | |
| ] | |
| prop_regex = r'([\s{]|^)(' + '|'.join( | |
| prop.replace('-webkit-', '') for prop in self.prefixed_properties) + r')(\s+:|:)' | |
| self.prop_re = re.compile(prop_regex) | |
| def output(self): | |
| return (self.converted_properties, ''.join(self.converted_data)) | |
| def path_from_webkit_root(self, *comps): | |
| return self._filesystem.abspath(self._filesystem.join(self._webkit_root, *comps)) | |
| def add_webkit_prefix_to_unprefixed_properties(self, text): | |
| """Searches |text| for instances of properties requiring the -webkit- prefix and adds the prefix to them. | |
| Returns the list of converted properties and the modified text. | |
| """ | |
| converted_properties = set() | |
| text_chunks = [] | |
| cur_pos = 0 | |
| for match in self.prop_re.finditer(text): | |
| text_chunks.extend([ | |
| text[cur_pos:match.start()], | |
| match.group(1), '-webkit-', | |
| match.group(2), | |
| match.group(3) | |
| ]) | |
| converted_properties.add(match.group(2)) | |
| cur_pos = match.end() | |
| text_chunks.append(text[cur_pos:]) | |
| for prop in converted_properties: | |
| _log.info(' converting %s', prop) | |
| # FIXME: Handle the JS versions of these properties and GetComputedStyle, too. | |
| return (converted_properties, ''.join(text_chunks)) | |
| def convert_reference_relpaths(self, text): | |
| """Converts reference file paths found in the given text. | |
| Searches |text| for instances of files in |self.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 path in text: | |
| # FIXME: This doesn't handle an edge case where simply removing the relative path doesn't work. | |
| # See crbug.com/421584 for details. | |
| new_path = re.sub(self.reference_support_info['reference_relpath'], '', path, 1) | |
| converted = re.sub(path, new_path, text) | |
| return converted | |
| def convert_style_data(self, data): | |
| converted = self.add_webkit_prefix_to_unprefixed_properties(data) | |
| if converted[0]: | |
| self.converted_properties.extend(list(converted[0])) | |
| if self.reference_support_info is None or self.reference_support_info == {}: | |
| return converted[1] | |
| return self.convert_reference_relpaths(converted[1]) | |
| def convert_attributes_if_needed(self, tag, attrs): | |
| """Converts attributes in a start tag in HTML. | |
| The converted tag text is appended to |self.converted_data|. | |
| """ | |
| converted = self.get_starttag_text() | |
| for attr_name, attr_value in attrs: | |
| if attr_name == 'style': | |
| new_style = self.convert_style_data(attr_value) | |
| converted = re.sub(re.escape(attr_value), new_style, converted) | |
| src_tags = ('script', 'img', 'style', 'frame', 'iframe', 'input', 'layer', 'textarea', 'video', 'audio') | |
| if tag in src_tags and self.reference_support_info is not None and self.reference_support_info != {}: | |
| 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) | |
| self.converted_data.append(converted) | |
| def parse_endtag(self, i): | |
| # parse_endtag is being overridden here instead of handle_endtag | |
| # so we can get the original end tag text with the original | |
| # capitalization | |
| endpos = HTMLParser.parse_endtag(self, i) | |
| self.converted_data.extend([self.rawdata[i:endpos]]) | |
| return endpos | |
| def handle_starttag(self, tag, attrs): | |
| if tag == 'style': | |
| self.in_style_tag = True | |
| self.convert_attributes_if_needed(tag, attrs) | |
| 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 = [] | |
| 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, '-->']) | |
| def handle_decl(self, decl): | |
| self.converted_data.extend(['<!', decl, '>']) | |
| def handle_pi(self, data): | |
| self.converted_data.extend(['<?', data, '>']) |