Differential Revision: https://phabricator.services.mozilla.com/D311562
736 lines
26 KiB
Python
736 lines
26 KiB
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", "user_styles"]
|
|
|
|
|
|
class special_js_script_checker:
|
|
def check(self, intervention, src_json_filename):
|
|
data = intervention.pop(self.section, None)
|
|
|
|
if data is None:
|
|
return None, None
|
|
|
|
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,
|
|
# "user_styles": true, // cssOrigin = "user" (otherwise is default of "author")
|
|
# "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("_")[0] not 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[0] for 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
|