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