chromium/third_party/blink/renderer/build/scripts/update_css_ranking.py

# This script is used to update the CSS ranking. The CSS ranking will affect
# the grouping of CSS properties in Computed Style.
# Usage: Run `python update_css_ranking.py` to update the default
#        CSS ranking file and API.
#        Run `python update_css_ranking.py <ranking_file>` to update
#        the ranking to another file with the default ranking API.
#        Run `python update_css_ranking.py <ranking_file> <ranking_api_link>`
#        to update the ranking from <ranking_api_link> API to <ranking_file>

import json
import sys
import cluster
import json5_generator
import math
try:
    from urllib.request import urlopen
except ImportError:
    from urllib2 import urlopen

CSS_RANKING_API = "http://www.chromestatus.com/data/csspopularity"
CSS_RANKING_FILE = "../../core/css/css_properties_ranking.json5"
CSS_PROPERTIES = "../../core/css/css_properties.json5"
CONFIG_FILE = "../../core/css/css_group_config.json5"


def reformat_properties_name(css_properties):
    for i in range(len(css_properties)):
        if css_properties[i][:5] == "alias":
            css_properties[i] = css_properties[i][5:]
        if css_properties[i][:6] == "webkit":
            css_properties[i] = "-" + css_properties[i]


def update_css_ranking(css_ranking_file, css_ranking_api):
    """Create the css_properties_ranking.json5 for uses in Computed Style grouping

    Args:
        css_ranking_file: file directory to css_properties_ranking.json5
        css_ranking_api: url to CSS ranking api

    """
    css_ranking = json.loads(urlopen(css_ranking_api).read())
    css_ranking_content = {"properties": {}, "data": []}
    css_ranking_content["data"] = [
        property_["property_name"] for property_ in sorted(
            css_ranking, key=lambda x: -float(x["day_percentage"]))
    ]

    # Pseudo-properties are normally not part of this list, and thus get put fairly far down.
    # However, IsStackingContextWithoutContainment is accessed and mutated fairly often
    # in the case of e.g. the “transform” property being mutated. If it is put down in rare data,
    # incremental computation of inline style will end up being significantly less efficient
    # in such cases, as copy-on-write will be invoked on the mutated subobject. (See e.g.
    # the MotionMark “multiply” subtest, which does this a lot.) Thus, we give it special treatment
    # and push it to the top of the list.
    css_ranking_content["data"].insert(0,
                                       "IsStackingContextWithoutContainment")

    reformat_properties_name(css_ranking_content["data"])

    with open(css_ranking_file, "w") as fw:
        fw.write(
            "// DO NOT EDIT: This file is generated by manually running:\n"
            "// third_party/blink/renderer/build/scripts/update_css_ranking.py\n"
            "//\n"
            "// The popularity ranking of all css properties the first properties is the most\n"
            "// used property according to: https://www.chromestatus.com/metrics/css/popularity\n"
        )
        json.dump(css_ranking_content, fw, indent=4, sort_keys=False)


def find_partition_rule(css_property_set,
                        all_properties,
                        n_cluster,
                        transform=lambda x: x):
    """Find partition rule for a set of CSS property based on its popularity

    Args:
        css_property_set: list of CSS properties and their popularity of form
                          [(css_property_name, popularity_score)..]
        n_cluster: number of cluster to divide the set into
        all_properties: all CSS properties and its score
        transform: data transform function to transform the popularity score,
                   default value is the identity function

    Returns:
        partition rule for css_property_set
    """
    _, cluster_alloc, _ = cluster.k_means(
        [transform(p[1]) for p in css_property_set], n_cluster=n_cluster)
    return [
        all_properties[css_property_set[i][0]]
        for i in range(len(cluster_alloc) - 1)
        if cluster_alloc[i] != cluster_alloc[i + 1]
    ] + [1.0]


def produce_partition_rule(config_file, css_ranking_api):
    """Find the partition rule for the groups and print them to config_file

    Args:
        config_file: the file to write the parameters to
        css_ranking_api: url to CSS ranking api

    """
    css_ranking = sorted(json.loads(urlopen(css_ranking_api).read()),
                         key=lambda x: -x["day_percentage"])
    total_css_properties = len(css_ranking)
    css_ranking_dictionary = dict(
        [(x["property_name"], x["day_percentage"] * 100) for x in css_ranking])
    css_ranking_cdf = dict(
        zip([x["property_name"] for x in css_ranking], [
            float(i) / total_css_properties
            for i in range(total_css_properties)
        ]))
    css_properties = json5_generator.Json5File.load_from_files(
        [CSS_PROPERTIES]).name_dictionaries

    rare_non_inherited_properties = sorted(
        [(x["name"].original, css_ranking_dictionary[x["name"].original])
         for x in css_properties
         if not x["inherited"] and x["field_group"] is not None and "*" in
         x["field_group"] and x["name"].original in css_ranking_dictionary],
        key=lambda x: -x[1])
    rare_inherited_properties = sorted(
        [(x["name"].original, css_ranking_dictionary[x["name"].original])
         for x in css_properties
         if x["inherited"] and x["field_group"] is not None and "*" in
         x["field_group"] and x["name"].original in css_ranking_dictionary],
        key=lambda x: -x[1])

    rni_properties_rule = find_partition_rule(
        rare_non_inherited_properties, css_ranking_cdf, n_cluster=3)

    ri_properties_rule = find_partition_rule(
        rare_inherited_properties,
        css_ranking_cdf,
        n_cluster=2,
        transform=lambda x: math.log(x + 10e-6))

    with open(config_file, 'w') as fw:
        fw.write(
            "// DO NOT EDIT: This file is generated by manually running:\n"
            "// third_party/blink/renderer/build/scripts/update_css_ranking.py\n"
            "//\n"
            "// The grouping parameter is a cumulative distribution "
            "over the whole set of ranked\n"
            "// CSS properties.\n")
        json.dump({
            "parameters": {},
            "data": [{
                "name": "rare_non_inherited_properties_rule",
                "cumulative_distribution": rni_properties_rule
            },
                     {
                         "name": "rare_inherited_properties_rule",
                         "cumulative_distribution": ri_properties_rule
                     }]
        },
                  fw,
                  indent=4)


if __name__ == '__main__':
    assert len(sys.argv) < 4, "Too many parameters"

    if len(sys.argv) == 1:
        update_css_ranking(CSS_RANKING_FILE, CSS_RANKING_API)
        produce_partition_rule(CONFIG_FILE, CSS_RANKING_API)
    elif len(sys.argv) == 2:
        update_css_ranking(sys.argv[1], CSS_RANKING_API)
        produce_partition_rule(CONFIG_FILE, CSS_RANKING_API)
    elif len(sys.argv) == 3:
        update_css_ranking(sys.argv[1], sys.argv[2])
        produce_partition_rule(CONFIG_FILE, sys.argv[2])