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
30import logging
31import re
32
33from webkitpy.common.host import Host
34from webkitpy.common.webkit_finder import WebKitFinder
35from HTMLParser import HTMLParser
36
37
38_log = logging.getLogger(__name__)
39
40
41def convert_for_webkit(new_path, filename, host=Host()):
42    """ Converts a file's |contents| so it will function correctly in its |new_path| in Webkit.
43
44    Returns the list of modified properties and the modified text if the file was modifed, None otherwise."""
45    contents = host.filesystem.read_binary_file(filename)
46    converter = _W3CTestConverter(new_path, filename, host)
47    if filename.endswith('.css'):
48        return converter.add_webkit_prefix_to_unprefixed_properties(contents)
49    else:
50        converter.feed(contents)
51        converter.close()
52        return converter.output()
53
54
55class _W3CTestConverter(HTMLParser):
56    def __init__(self, new_path, filename, host=Host()):
57        HTMLParser.__init__(self)
58
59        self._host = host
60        self._filesystem = self._host.filesystem
61        self._webkit_root = WebKitFinder(self._filesystem).webkit_base()
62
63        self.converted_data = []
64        self.converted_properties = []
65        self.in_style_tag = False
66        self.style_data = []
67        self.filename = filename
68
69        resources_path = self.path_from_webkit_root('LayoutTests', 'resources')
70        resources_relpath = self._filesystem.relpath(resources_path, new_path)
71        self.resources_relpath = resources_relpath
72
73        # These settings might vary between WebKit and Blink
74        self._css_property_file = self.path_from_webkit_root('Source', 'core', 'css', 'CSSProperties.in')
75
76        self.prefixed_properties = self.read_webkit_prefixed_css_property_list()
77
78        self.prefixed_properties = self.read_webkit_prefixed_css_property_list()
79        prop_regex = '([\s{]|^)(' + "|".join(prop.replace('-webkit-', '') for prop in self.prefixed_properties) + ')(\s+:|:)'
80        self.prop_re = re.compile(prop_regex)
81
82    def output(self):
83        return (self.converted_properties, ''.join(self.converted_data))
84
85    def path_from_webkit_root(self, *comps):
86        return self._filesystem.abspath(self._filesystem.join(self._webkit_root, *comps))
87
88    def read_webkit_prefixed_css_property_list(self):
89        prefixed_properties = []
90        unprefixed_properties = set()
91
92        contents = self._filesystem.read_text_file(self._css_property_file)
93        for line in contents.splitlines():
94            if re.match('^(#|//|$)', line):
95                # skip comments and preprocessor directives
96                continue
97            prop = line.split()[0]
98            # Find properties starting with the -webkit- prefix.
99            match = re.match('-webkit-([\w|-]*)', prop)
100            if match:
101                prefixed_properties.append(match.group(1))
102            else:
103                unprefixed_properties.add(prop.strip())
104
105        # Ignore any prefixed properties for which an unprefixed version is supported
106        return [prop for prop in prefixed_properties if prop not in unprefixed_properties]
107
108    def add_webkit_prefix_to_unprefixed_properties(self, text):
109        """ Searches |text| for instances of properties requiring the -webkit- prefix and adds the prefix to them.
110
111        Returns the list of converted properties and the modified text."""
112
113        converted_properties = set()
114        text_chunks = []
115        cur_pos = 0
116        for m in self.prop_re.finditer(text):
117            text_chunks.extend([text[cur_pos:m.start()], m.group(1), '-webkit-', m.group(2), m.group(3)])
118            converted_properties.add(m.group(2))
119            cur_pos = m.end()
120        text_chunks.append(text[cur_pos:])
121
122        for prop in converted_properties:
123            _log.info('  converting %s', prop)
124
125        # FIXME: Handle the JS versions of these properties and GetComputedStyle, too.
126        return (converted_properties, ''.join(text_chunks))
127
128    def convert_style_data(self, data):
129        converted = self.add_webkit_prefix_to_unprefixed_properties(data)
130        if converted[0]:
131            self.converted_properties.extend(list(converted[0]))
132        return converted[1]
133
134    def convert_attributes_if_needed(self, tag, attrs):
135        converted = self.get_starttag_text()
136        if tag in ('script', 'link'):
137            target_attr = 'src'
138            if tag != 'script':
139                target_attr = 'href'
140            for attr_name, attr_value in attrs:
141                if attr_name == target_attr:
142                    new_path = re.sub('/resources/testharness',
143                                      self.resources_relpath + '/testharness',
144                                      attr_value)
145                    converted = re.sub(attr_value, new_path, converted)
146                    new_path = re.sub('/common/vendor-prefix',
147                                      self.resources_relpath + '/vendor-prefix',
148                                      attr_value)
149                    converted = re.sub(attr_value, new_path, converted)
150
151        for attr_name, attr_value in attrs:
152            if attr_name == 'style':
153                new_style = self.convert_style_data(attr_value)
154                converted = re.sub(attr_value, new_style, converted)
155            if attr_name == 'class' and 'instructions' in attr_value:
156                # Always hide instructions, they're for manual testers.
157                converted = re.sub(' style=".*?"', '', converted)
158                converted = re.sub('\>', ' style="display:none">', converted)
159
160        self.converted_data.append(converted)
161
162    def handle_starttag(self, tag, attrs):
163        if tag == 'style':
164            self.in_style_tag = True
165        self.convert_attributes_if_needed(tag, attrs)
166
167    def handle_endtag(self, tag):
168        if tag == 'style':
169            self.converted_data.append(self.convert_style_data(''.join(self.style_data)))
170            self.in_style_tag = False
171            self.style_data = []
172        self.converted_data.extend(['</', tag, '>'])
173
174    def handle_startendtag(self, tag, attrs):
175        self.convert_attributes_if_needed(tag, attrs)
176
177    def handle_data(self, data):
178        if self.in_style_tag:
179            self.style_data.append(data)
180        else:
181            self.converted_data.append(data)
182
183    def handle_entityref(self, name):
184        self.converted_data.extend(['&', name, ';'])
185
186    def handle_charref(self, name):
187        self.converted_data.extend(['&#', name, ';'])
188
189    def handle_comment(self, data):
190        self.converted_data.extend(['<!-- ', data, ' -->'])
191
192    def handle_decl(self, decl):
193        self.converted_data.extend(['<!', decl, '>'])
194
195    def handle_pi(self, data):
196        self.converted_data.extend(['<?', data, '>'])
197
198