Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/browser/extensions/webcompat/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 25 kB image not shown  

Quelle  codegen.py

  Sprache: Python
 

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import json
import os
import re
import shutil
import unicodedata
from functools import cache

import jsonschema
import mozpack.path as mozpath
from mozpack.files import FileFinder


@cache
def interventions_schema():
    schema_path = mozpath.join(
        os.path.dirname(__file__),
        "intervention_schema.json",
    )
    with open(schema_path) as schema_fd:
        return json.load(schema_fd)


def load_intervention_json(json_fd):
    try:
        config = json.load(json_fd)
    except json.decoder.JSONDecodeError as e:
        raise ValueError(f"{mozpath.basename(json_fd.path)} is invalid JSON: {e}")

    try:
        jsonschema.validate(instance=config, schema=interventions_schema())
    except jsonschema.exceptions.ValidationError as e:
        raise ValueError(
            f"{mozpath.basename(json_fd.path)} is invalid intervention JSON: {e}"
        )

    return config


def clear_dir(path):
    if not os.path.isdir(path) and not os.path.islink(path):
        raise Exception(f"{path} is not a directory")
    for filename in os.listdir(path):
        file_path = mozpath.join(path, filename)
        try:
            if os.path.isfile(file_path) or os.path.islink(file_path):
                os.unlink(file_path)
            elif os.path.isdir(file_path):
                shutil.rmtree(file_path)
        except Exception as e:
            print(f"Failed to delete {file_path}. Reason: {e}")


def safe_filename(raw):
    subbed = re.sub(
        "_+",
        "_",
        raw
        .replace(" ""_")
        .replace("\\""_")
        .replace("/""_")
        .replace(os.path.sep, "_")
        .replace("(""")
        .replace(")"""),
    )
    normalized = unicodedata.normalize("NFD", subbed)
    return "".join([c for c in normalized if not unicodedata.combining(c)])


def maybe_lstrip(str, value):
    if str.startswith(str):
        return str[len(value) :].lstrip()
    return str


def clean_script_template(script, filename):
    # drop any license header, linter globals line, and "use strict".
    script = script.rstrip()
    while True:
        script = script.lstrip()
        if script.startswith("/*"):
            script = script.partition("*/")[2]
        elif script.startswith("//"):
            script = script.partition("\n")[2]
        elif script.startswith('"use strict";'):
            script = script[13:]
        else:
            break
    if not script:
        raise ValueError(f"{filename} template does not seem to be a proper template")
    if (
        not script.startswith("{")
        and not script.startswith("try")
        and not script.startswith("if")
    ):
        script = "\n  ".join(script.splitlines())
        script = f"{{\n  {script}\n}}"
    return script


SPECIAL_META_KEYS = ["all_frames""match_origin_as_fallback"]


class special_js_script_checker:
    def check(self, intervention, src_json_filename):
        data = intervention.pop(self.section, None)

        if data is None:
            return NoneNone

        metas = {name: False for name in SPECIAL_META_KEYS}
        if type(data) is dict:
            for key in SPECIAL_META_KEYS:
                metas[key] = data.get(key, False)

        return metas, self._get_params_from_data(data, src_json_filename)


class check_hide_alerts_section(special_js_script_checker):
    def __init__(self):
        self.section = "hide_alerts"
        self.json_key = "alerts"
        self.source = "hide_alerts.js"

    def _get_params_from_data(self, data, src_json_filename):
        if type(data) is list:
            alertsToHide = data
        elif type(data) is dict and "alerts" in data:
            alertsToHide = data["alerts"]
        else:
            raise ValueError(
                f"Unexpected data in {self.section} in {src_json_filename}: {str(data)}"
            )

        for alert in alertsToHide:
            if alert.lower() != alert:
                raise ValueError(
                    f"Please use lowercase values for {self.section} values (not `{alert}`) in {src_json_filename}"
                )

        return {"alertsToHide": alertsToHide}


class check_hide_messages_section(special_js_script_checker):
    def __init__(self):
        self.section = "hide_messages"
        self.json_key = "messages"
        self.source = "hide_messages.js"

    def _get_params_from_data(self, data, src_json_filename):
        if type(data) is list:
            messagesToHide = data
        elif type(data) is dict and "message" in data:
            messagesToHide = [data]
        elif type(data) is dict and "messages" in data:
            messagesToHide = data["messages"]
        else:
            raise ValueError(
                f"Unexpected data in {self.section} in {src_json_filename}: {str(data)}"
            )

        return {"messagesToHide": messagesToHide}


class check_modify_meta_viewport_section(special_js_script_checker):
    def __init__(self):
        self.section = "modify_meta_viewport"
        self.json_key = "modify"
        self.source = "modify_meta_viewport.js"

    def _get_params_from_data(self, data, src_json_filename):
        if type(data) is dict and "modify" in data:
            metaViewportChanges = data["modify"]
        elif type(data) is dict:
            metaViewportChanges = data
        else:
            raise ValueError(
                f"Unexpected data in {self.section} in {src_json_filename}: {str(data)}"
            )

        return {"metaViewportChanges": metaViewportChanges}


def bake_params_into_script_template(template, params={}):
    for name, value in params.items():
        template = template.replace(
            f'"param:{name}"', json.dumps(value, sort_keys=True)
        )
    return template


@cache
def get_script_template(filename, *dirs_to_try):
    for dir in dirs_to_try:
        path_to_try = mozpath.join(dir, filename)
        if os.path.isfile(path_to_try) and os.access(path_to_try, os.R_OK):
            with open(path_to_try) as template_fd:
                return clean_script_template(template_fd.read(), filename)

    raise ValueError(
        f"Could not access expected template {filename} in {' or '.join(dirs_to_try)}"
    )


def build_logger_script(config, templates_dir):
    domain_to_bug_numbers = {}
    for bug_number, data in config.get("bugs", {}).items():
        for match in data.get("matches", []):
            domain = match.partition("://")[2].split("/")[0].replace("*.""")
            domain_to_bug_numbers.setdefault(domain, set())
            domain_to_bug_numbers[domain].add(bug_number)

    bugInfo = [[k, sorted(list(v))] for k, v in domain_to_bug_numbers.items()]
    return bake_params_into_script_template(
        get_script_template("log_console_message.js", templates_dir),
        {"bugInfo": bugInfo},
    )


def determine_generated_content_scripts_for_intervention(
    config, bug_number, src_json_filename, interventions_dir
):
    injections_dir = mozpath.normpath(
        mozpath.join(
            interventions_dir,
            "..",
            "..",
            "injections",
        )
    )

    return determine_generated_css_content_scripts_for_intervention(
        config, bug_number, src_json_filename
    ) | determine_generated_js_content_scripts_for_intervention(
        config, bug_number, src_json_filename, injections_dir
    )


def determine_generated_js_content_scripts_for_intervention(
    config, bug_number, src_json_filename, injections_dir
):
    # The JSON files for interventions may used generic JS scripts, including
    # special ones with special info in the JSON, like this:
    #
    #  "interventions": [
    #    {
    #      "hide_messages": [{ "container": ".header.caution", "message": "unsupported browser"}],
    #      "hide_alerts": { "all_frames": true, "alerts: ["Chrome"] },
    #      "modify_meta_viewport": {
    #         "interactive-widget": "resizes-content",
    #      },
    #      "content_scripts": {
    #        "js": ["use_chrome_useragent.js"]
    #      }
    #    },
    #
    # We want to combine these into one final content-scripts:
    #
    #    {
    #      "content_scripts": {
    #        "js": ["injections/generated/bug12345_whatever.com.js"]
    #      }
    #    },

    files_to_generate = {}
    generated_filenames_cache = {}

    special_checkers = [
        check_hide_alerts_section(),
        check_hide_messages_section(),
        check_modify_meta_viewport_section(),
    ]

    label = safe_filename(config["label"])
    for intervention in config["interventions"]:
        final_metas = None

        content_scripts = intervention.get("content_scripts", {})
        if content_scripts:
            final_metas = {}
            for key in SPECIAL_META_KEYS:
                final_metas[key] = content_scripts.get(key, False)

        js = content_scripts.get("js", [])
        generate_from_sources = []
        if js and any(not script_filename.startswith("bug"for script_filename in js):
            generate_from_sources = [
                {
                    "params": {},
                    "source": script_filename,
                }
                for script_filename in js
            ]

        for checker in special_checkers:
            metas, params = checker.check(intervention, src_json_filename)
            if not metas and not params:
                continue

            if final_metas is None:
                final_metas = metas
            elif final_metas != metas:
                raise ValueError(
                    f"cannot mix true/false values of {SPECIAL_META_KEYS} in the same intervention in {src_json_filename}"
                )

            generate_from_sources.append({
                "params": params,
                "source": checker.source,
            })

        if generate_from_sources:
            cache_key = json.dumps(generate_from_sources, sort_keys=True)
            generated_filename = generated_filenames_cache.get(cache_key, None)
            if not generated_filename:
                suffix = ""
                next_generated_script_num = len(generated_filenames_cache)
                if next_generated_script_num:
                    suffix = f"-{next_generated_script_num}"
                generated_filename = safe_filename(
                    f"bug{bug_number}-{label}{suffix}.js"
                )
                generated_filenames_cache[cache_key] = generated_filename

            content_scripts = intervention.setdefault("content_scripts", {})
            js = content_scripts.setdefault("js", [])
            content_scripts["js"] = [f"injections/generated/{generated_filename}"]
            for name, value in final_metas.items():
                if value:
                    content_scripts[name] = True
            files_to_generate[generated_filename] = generate_from_sources

    return files_to_generate


def determine_generated_css_content_scripts_for_intervention(
    config, bug_number, src_json_filename
):
    # The JSON files for interventions may contain css sections like this:
    #
    #  "label": "whatever.com",
    #  "css": {
    #      "fix_broken_slider": "css text 1",
    #      "remove_extra_scrollbars": "css text 2"
    #  },
    #  "interventions": [
    #    {
    #      "css": ["fix_broken_slider"]
    #    },
    #    {
    #      "css": {
    #        "all_frames": true,
    #        "match_origin_as_fallback": true,
    #        "which": ["fix_broken_slider", "remove_extra_scrollbars"]
    #      }
    #    }
    #  ]
    #
    # These must be replaced with corresponding content_scripts sections while building the
    # final run.js (and the files it references must also be generated):
    #
    #  "label": "whatever.com",
    #  "interventions": [
    #    {
    #      "content_scripts": {
    #        "css": ["injections/generated/bug12345_whatever.com_fix_broken_slider.css"]
    #      }
    #    },
    #    {
    #      "content_scripts": {
    #        "all_frames": true,
    #        "match_origin_as_fallback": true,
    #        "css": ["injections/generated/bug12345_whatever.com_fix_broken_slider.css",
    #                "injections/generated/bug12345_whatever.com_remove_extra_scrollbars.css"]
    #      }
    #    }
    #  ]

    files_to_generate = {}

    if "css" in config and (type(config["css"]) is not dict or not config["css"]):
        raise ValueError(
            f"css section should be a non-empty object or be removed from {src_json_filename}"
        )

    css_files = config.pop("css"None)
    if not css_files:
        for intervention in config["interventions"]:
            if intervention.get("css"):
                raise ValueError(
                    f"css wanted, but none specified for {src_json_filename}"
                )
        return files_to_generate

    actually_used_files = set()

    for cssText in css_files.values():
        if type(cssText) is not str or not cssText:
            raise ValueError(
                f"css text should be a non-empty string in {src_json_filename}"
            )

    label = safe_filename(config["label"])
    for intervention in config["interventions"]:
        css = intervention.pop("css"None)

        if css is None:
            continue

        if type(css) is list:
            css = {"which": css}
        elif type(css) is not dict:
            raise ValueError(
                f"css sections should be a non-empty object or list or be removed from interventions in {src_json_filename}"
            )

        which_css_files_to_add = css.pop("which"None)
        if not which_css_files_to_add or type(which_css_files_to_add) is not list:
            raise ValueError(
                f"intervention with missing `which` key or invalid array of desired css files in {src_json_filename}"
            )

        for file in which_css_files_to_add:
            if type(file) is not str or not file:
                raise ValueError(
                    f"Empty or non-string filename not listed in intervention css section of {src_json_filename}"
                )
            if not css_files.get(file):
                raise ValueError(
                    f"{file} is not listed in css section of {src_json_filename}"
                )
            actually_used_files.add(file)

        metas = {}
        for key in SPECIAL_META_KEYS:
            metas[key] = css.pop(key, False)
            if not isinstance(metas[key], bool):
                raise ValueError(
                    f"{key} must be `true` or `false` in {src_json_filename}"
                )

        unknown_keys = "','".join(css.keys())
        if unknown_keys:
            raise ValueError(
                f"unknown key(s) '{unknown_keys}' in css section of {src_json_filename}"
            )

        content_scripts = intervention.get("content_scripts"None)
        if content_scripts:
            for key in SPECIAL_META_KEYS:
                if content_scripts.get(key, False) != metas[key]:
                    raise ValueError(
                        f"cannot mix value of {key} in css and content_scripts sections in {src_json_filename}"
                    )

        content_scripts = intervention.setdefault("content_scripts", {})

        css = content_scripts.setdefault("css", [])
        for filename in which_css_files_to_add:
            final_filename = safe_filename(f"bug{bug_number}-{label}-{filename}.css")
            css.append(f"injections/generated/{final_filename}")
            files_to_generate[final_filename] = [{"contents": css_files[filename]}]

        for key in SPECIAL_META_KEYS:
            if metas[key]:
                content_scripts[key] = True

    extras = set(css_files.keys()).difference(actually_used_files)
    if extras:
        raise ValueError(
            f"Extra css fragments specified which aren't used in {src_json_filename}: "
            + ", ".join(list(extras))
        )

    return files_to_generate


def generate_run_js(
    output_fd,
    template_path,
    interventions_dir,
    *_preprocessed_intervention_files_mozbuild,
):
    preprocessed_intervention_files_mozbuild = [
        f
        for f in _preprocessed_intervention_files_mozbuild
        if not f.endswith("/codegen.py")
    ]

    with open(template_path) as template_fd:
        input_files = list(FileFinder(interventions_dir).find("*.json"))

        filenames_json_files_expect_to_generate = set()

        final_interventions = {}
        actually_referenced_non_generated_files = set()
        for json_filename, json_fd in input_files:
            bug_number = mozpath.splitext(mozpath.basename(json_filename))[0].split(
                "-"
            )[0]

            config = load_intervention_json(json_fd)

            final_interventions[bug_number] = config

            # Do some sanity checks first
            listed_bugs = config.get("bugs", {}).keys()
            if len(listed_bugs) < 2 and bug_number.split("_")[0not in listed_bugs:
                raise ValueError(
                    f"Bug number in the filename ({bug_number}) does not match bugs section of {json_filename}"
                )

            for intervention in config["interventions"]:
                content_scripts = intervention.get("content_scripts", {})
                listed_files = set()
                for type in ["css""js"]:
                    for non_generated_filename in content_scripts.get(type, []):
                        actually_referenced_non_generated_files.add(
                            non_generated_filename
                        )
                        if f"injections/{type}/" in non_generated_filename:
                            raise ValueError(
                                f"Please remove the unneeded 'injections/{type}/' from '{non_generated_filename}' intervention in {json_filename}"
                            )
                        if not non_generated_filename.endswith(f".{type}"):
                            raise ValueError(
                                f"{non_generated_filename} does not end in .{type} in {json_filename}"
                            )
                        if non_generated_filename in listed_files:
                            raise ValueError(
                                f"{non_generated_filename} is listed twice in same intervention in {json_filename}"
                            )
                        listed_files.add(non_generated_filename)
                        actual_path = mozpath.normpath(
                            mozpath.join(
                                interventions_dir,
                                "..",
                                "..",
                                "injections",
                                type,
                                non_generated_filename,
                            )
                        )
                        if not os.path.isfile(actual_path) or not os.access(
                            actual_path, os.R_OK
                        ):
                            raise ValueError(
                                f"{non_generated_filename} is not an accessible file in {json_filename} (expected at {actual_path})"
                            )
                        if os.path.splitext(actual_path)[1] != "." + type:
                            raise ValueError(
                                f"File extension for {actual_path} should be .{type} in {json_filename}"
                            )

            # Now remove each css section in this JSON and ensure that a corresponding
            # content_scripts section exists, with the files named as they will be after
            # they are generated by generate_css_intervention. Also double-check that we
            # will not be stomping over any already-existing non-generated files.

            generated_files = determine_generated_content_scripts_for_intervention(
                config, bug_number, json_filename, interventions_dir
            )

            for filename in generated_files:
                filenames_json_files_expect_to_generate.add(filename)

        # Halt if preprocessed_intervention_files.mozbuild needs to be updated
        filenames_listed_in_mozbuild = set(preprocessed_intervention_files_mozbuild)
        extra_generated_files = sorted(
            filenames_json_files_expect_to_generate.difference(
                filenames_listed_in_mozbuild
            )
        )
        missing_generated_files = sorted(
            filenames_listed_in_mozbuild.difference(
                filenames_json_files_expect_to_generate
            )
        )
        if extra_generated_files or missing_generated_files:
            msg = ""
            if missing_generated_files:
                msg += "\nPlease remove: " + ", ".join(missing_generated_files)
            if extra_generated_files:
                msg += "\nPlease add: " + ", ".join(extra_generated_files)
            raise ValueError(
                "preprocessed_intervention_files.mozbuild is out of date:" + msg
            )

        # Check if any non-generated css/js files aren't being used anymore.
        non_generated_files_path = mozpath.normpath(
            mozpath.join(interventions_dir, "..""..""injections")
        )
        non_generated_files = set(
            name
            for name, _ in FileFinder(
                mozpath.join(non_generated_files_path, "css")
            ).find("*")
        )
        non_generated_files.update(
            set(
                name
                for name, _ in FileFinder(
                    mozpath.join(non_generated_files_path, "js")
                ).find("*")
            )
        )
        extra_non_generated_files = sorted(
            non_generated_files.difference(actually_referenced_non_generated_files)
        )
        # Generic css/js files are bundled in case we need them for remote updates,
        # so don't ask for them to be removed even if they seem unreferenced.
        extra_non_generated_files = [
            filename
            for filename in extra_non_generated_files
            if filename.startswith("bug")
        ]
        if extra_non_generated_files:
            raise ValueError(
                "Please remove these files which are not referenced in any intervention JSON file: "
                + ", ".join(extra_non_generated_files)
            )

        # Emit the final run.json
        interventions_json = json.dumps(
            dict(sorted(final_interventions.items())), indent=2, sort_keys=True
        )

        raw = template_fd.read()
        subbed = raw.replace(
            "// Note that this variable is expanded during build-time. See bz2019069 for details.\n",
            "",
        )
        subbed = raw.replace(
            "AVAILABLE_INTERVENTIONS = {}",
            f"AVAILABLE_INTERVENTIONS = {interventions_json}",
        )
        output_fd.write(subbed)


def generate_file(outfile, interventions_dir, *ignored):
    desired_filename = mozpath.basename(outfile.name)
    is_css = outfile.name.endswith(".css")
    is_js = outfile.name.endswith(".js")
    if not is_css and not is_js:
        raise ValueError(f"Do not know how to generate {outfile.name}")

    bug_number = (
        mozpath
        .splitext(mozpath.basename(desired_filename))[0]
        .split("-")[0]
        .lstrip("bug")
    )

    expected_json_filename = "-".join(
        mozpath.splitext(desired_filename.lstrip("bug"))[0].split("-")[0:2]
    )
    json_files = list(
        FileFinder(interventions_dir).find(f"{expected_json_filename}*.json")
    )
    if not json_files:
        raise ValueError(
            f"no json intervention file starting with {expected_json_filename}"
        )
    if len(json_files) > 1:
        json_files = " or ".join(f[0for f in json_files)
        raise ValueError(
            f"multiple json intervention files starting with {expected_json_filename}.. not sure which to use to generate {desired_filename} from {json_files}"
        )

    json_filename, json_fd = json_files[0]
    config = load_intervention_json(json_fd)

    generated_files = determine_generated_content_scripts_for_intervention(
        config, bug_number, json_filename, interventions_dir
    )

    needed_parts = generated_files.get(desired_filename, None)

    if needed_parts is None or not needed_parts:
        raise ValueError(
            f"No needed parts found to generate {desired_filename} for {json_filename}"
        )

    generated_parts = []

    templates_dir = mozpath.normpath(
        mozpath.join(
            interventions_dir,
            "..",
            "..",
            "templates",
        )
    )

    js_injections_dir = mozpath.normpath(
        mozpath.join(templates_dir, "..""injections""js")
    )

    for part_info in needed_parts:
        if part_info.get("contents"None):
            # css files go here
            generated_parts.append(part_info["contents"])
            continue

        params = part_info["params"]
        source_filename = part_info["source"]
        template = get_script_template(
            source_filename, templates_dir, js_injections_dir
        )
        generated_parts.append(bake_params_into_script_template(template, params))

    if not generated_parts:
        raise ValueError(
            f"Failed to generate anything for {outfile.name} in {json_filename}"
        )

    all_parts = "\n\n".join(generated_parts)
    if is_js:
        if "__webcompat_spoof_platform" in all_parts:
            all_parts += "\n\ndelete window.__webcompat_spoof_platform;"
        if "window.__webcompat" in all_parts.replace("__webcompat_spoof_platform"""):
            all_parts += "\n\n" + build_logger_script(config, templates_dir)

    outfile.write(
        "/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. */\n\n"
    )
    outfile.write(all_parts)


def main(*args):  # mach requires this
    pass

Messung V0.5 in Prozent
C=95 H=95 G=94

¤ Dauer der Verarbeitung: 0.8 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.