Home | History | Annotate | Download | only in w3c
      1 #!/usr/bin/env python
      2 
      3 # Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
      4 #
      5 # Redistribution and use in source and binary forms, with or without
      6 # modification, are permitted provided that the following conditions
      7 # are met:
      8 #
      9 # 1. Redistributions of source code must retain the above
     10 #    copyright notice, this list of conditions and the following
     11 #    disclaimer.
     12 # 2. Redistributions in binary form must reproduce the above
     13 #    copyright notice, this list of conditions and the following
     14 #    disclaimer in the documentation and/or other materials
     15 #    provided with the distribution.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
     18 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     20 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
     21 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
     22 # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     23 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     24 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
     26 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
     27 # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     28 # SUCH DAMAGE.
     29 
     30 import logging
     31 import re
     32 
     33 from webkitpy.common.host import Host
     34 from webkitpy.common.webkit_finder import WebKitFinder
     35 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup, Tag
     36 
     37 
     38 _log = logging.getLogger(__name__)
     39 
     40 
     41 class W3CTestConverter(object):
     42 
     43     def __init__(self):
     44         self._host = Host()
     45         self._filesystem = self._host.filesystem
     46         self._webkit_root = WebKitFinder(self._filesystem).webkit_base()
     47 
     48         # These settings might vary between WebKit and Blink
     49         self._css_property_file = self.path_from_webkit_root('Source', 'core', 'css', 'CSSPropertyNames.in')
     50         self._css_property_split_string = 'alias_for='
     51 
     52         self.prefixed_properties = self.read_webkit_prefixed_css_property_list()
     53 
     54         prop_regex = '([\s{]|^)(' + "|".join(prop.replace('-webkit-', '') for prop in self.prefixed_properties) + ')(\s+:|:)'
     55         self.prop_re = re.compile(prop_regex)
     56 
     57     def path_from_webkit_root(self, *comps):
     58         return self._filesystem.abspath(self._filesystem.join(self._webkit_root, *comps))
     59 
     60     def read_webkit_prefixed_css_property_list(self):
     61         prefixed_properties = []
     62         unprefixed_properties = set()
     63 
     64         contents = self._filesystem.read_text_file(self._css_property_file)
     65         for line in contents.splitlines():
     66             if re.match('^(#|//)', line):
     67                 # skip comments and preprocessor directives
     68                 continue
     69             fields = line.split(self._css_property_split_string)
     70             for prop in fields:
     71                 # Find properties starting with the -webkit- prefix.
     72                 match = re.match('-webkit-([\w|-]*)', prop)
     73                 if match:
     74                     prefixed_properties.append(match.group(1))
     75                 else:
     76                     unprefixed_properties.add(prop.strip())
     77 
     78         # Ignore any prefixed properties for which an unprefixed version is supported
     79         return [prop for prop in prefixed_properties if prop not in unprefixed_properties]
     80 
     81     def convert_for_webkit(self, new_path, filename):
     82         """ Converts a file's |contents| so it will function correctly in its |new_path| in Webkit.
     83 
     84         Returns the list of modified properties and the modified text if the file was modifed, None otherwise."""
     85         contents = self._filesystem.read_binary_file(filename)
     86         if filename.endswith('.css'):
     87             return self.convert_css(contents, filename)
     88         return self.convert_html(new_path, contents, filename)
     89 
     90     def convert_css(self, contents, filename):
     91         return self.add_webkit_prefix_to_unprefixed_properties(contents, filename)
     92 
     93     def convert_html(self, new_path, contents, filename):
     94         doc = BeautifulSoup(contents)
     95         did_modify_paths = self.convert_testharness_paths(doc, new_path, filename)
     96         converted_properties_and_content = self.convert_prefixed_properties(doc, filename)
     97         return converted_properties_and_content if (did_modify_paths or converted_properties_and_content[0]) else None
     98 
     99     def convert_testharness_paths(self, doc, new_path, filename):
    100         """ Update links to testharness.js in the BeautifulSoup |doc| to point to the copy in |new_path|.
    101 
    102         Returns whether the document was modified."""
    103 
    104         # Look for the W3C-style path to any testharness files - scripts (.js) or links (.css)
    105         pattern = re.compile('/resources/testharness')
    106         script_tags = doc.findAll(src=pattern)
    107         link_tags = doc.findAll(href=pattern)
    108         testharness_tags = script_tags + link_tags
    109 
    110         if not testharness_tags:
    111             return False
    112 
    113         resources_path = self.path_from_webkit_root('LayoutTests', 'resources')
    114         resources_relpath = self._filesystem.relpath(resources_path, new_path)
    115 
    116         for tag in testharness_tags:
    117             # FIXME: We need to handle img, audio, video tags also.
    118             attr = 'src'
    119             if tag.name != 'script':
    120                 attr = 'href'
    121 
    122             if not attr in tag.attrMap:
    123                 # FIXME: Figure out what to do w/ invalid tags. For now, we return False
    124                 # and leave the document unmodified, which means that it'll probably fail to run.
    125                 _log.error("Missing an attr in %s" % filename)
    126                 return False
    127 
    128             old_path = tag[attr]
    129             new_tag = Tag(doc, tag.name, tag.attrs)
    130             new_tag[attr] = re.sub(pattern, resources_relpath + '/testharness', old_path)
    131 
    132             self.replace_tag(tag, new_tag)
    133 
    134         return True
    135 
    136     def convert_prefixed_properties(self, doc, filename):
    137         """ Searches a BeautifulSoup |doc| for any CSS properties requiring the -webkit- prefix and converts them.
    138 
    139         Returns the list of converted properties and the modified document as a string """
    140 
    141         converted_properties = []
    142 
    143         # Look for inline and document styles.
    144         inline_styles = doc.findAll(style=re.compile('.*'))
    145         style_tags = doc.findAll('style')
    146         all_styles = inline_styles + style_tags
    147 
    148         for tag in all_styles:
    149 
    150             # Get the text whether in a style tag or style attribute.
    151             style_text = ''
    152             if tag.name == 'style':
    153                 if not tag.contents:
    154                     continue
    155                 style_text = tag.contents[0]
    156             else:
    157                 style_text = tag['style']
    158 
    159             updated_style_text = self.add_webkit_prefix_to_unprefixed_properties(style_text, filename)
    160 
    161             # Rewrite tag only if changes were made.
    162             if updated_style_text[0]:
    163                 converted_properties.extend(list(updated_style_text[0]))
    164 
    165                 new_tag = Tag(doc, tag.name, tag.attrs)
    166                 new_tag.insert(0, updated_style_text[1])
    167 
    168                 self.replace_tag(tag, new_tag)
    169 
    170         # FIXME: Doing the replace in the parsed document and then writing it back out
    171         # is normalizing the HTML, which may in fact alter the intent of some tests.
    172         # We should probably either just do basic string-replaces, or have some other
    173         # way of flagging tests that are sensitive to being rewritten.
    174         # https://bugs.webkit.org/show_bug.cgi?id=119159
    175 
    176         return (converted_properties, doc.prettify())
    177 
    178     def add_webkit_prefix_to_unprefixed_properties(self, text, filename):
    179         """ Searches |text| for instances of properties requiring the -webkit- prefix and adds the prefix to them.
    180 
    181         Returns the list of converted properties and the modified text."""
    182 
    183         converted_properties = set()
    184         text_chunks = []
    185         cur_pos = 0
    186         for m in self.prop_re.finditer(text):
    187             text_chunks.extend([text[cur_pos:m.start()], m.group(1), '-webkit-', m.group(2), m.group(3)])
    188             converted_properties.add(m.group(2))
    189             cur_pos = m.end()
    190         text_chunks.append(text[cur_pos:])
    191 
    192         for prop in converted_properties:
    193             _log.info('  converting %s', prop)
    194 
    195         # FIXME: Handle the JS versions of these properties and GetComputedStyle, too.
    196         return (converted_properties, text)
    197 
    198     def replace_tag(self, old_tag, new_tag):
    199         index = old_tag.parent.contents.index(old_tag)
    200         old_tag.parent.insert(index, new_tag)
    201         old_tag.extract()
    202