| # Copyright 2022 The Emscripten Authors. All rights reserved. |
| # Emscripten is available under two separate licenses, the MIT license and the |
| # University of Illinois/NCSA Open Source License. Both these licenses can be |
| # found in the LICENSE file. |
| |
| """Utilities for mapping browser versions to webassembly features.""" |
| |
| import logging |
| from enum import IntEnum, auto |
| |
| from .settings import settings, user_settings |
| from . import diagnostics |
| |
| logger = logging.getLogger('feature_matrix') |
| |
| |
| class Feature(IntEnum): |
| NON_TRAPPING_FPTOINT = auto() |
| SIGN_EXT = auto() |
| BULK_MEMORY = auto() |
| MUTABLE_GLOBALS = auto() |
| JS_BIGINT_INTEGRATION = auto() |
| THREADS = auto() |
| GLOBALTHIS = auto() |
| PROMISE_ANY = auto() |
| |
| |
| default_features = {Feature.SIGN_EXT, Feature.MUTABLE_GLOBALS} |
| |
| min_browser_versions = { |
| Feature.NON_TRAPPING_FPTOINT: { |
| 'chrome': 75, |
| 'firefox': 65, |
| 'safari': 150000, |
| }, |
| Feature.SIGN_EXT: { |
| 'chrome': 74, |
| 'firefox': 62, |
| 'safari': 140100, |
| }, |
| Feature.BULK_MEMORY: { |
| 'chrome': 75, |
| 'firefox': 79, |
| 'safari': 150000, |
| }, |
| Feature.MUTABLE_GLOBALS: { |
| 'chrome': 74, |
| 'firefox': 61, |
| 'safari': 120000, |
| }, |
| Feature.JS_BIGINT_INTEGRATION: { |
| 'chrome': 67, |
| 'firefox': 68, |
| 'safari': 150000, |
| }, |
| Feature.THREADS: { |
| 'chrome': 74, |
| 'firefox': 79, |
| 'safari': 140100, |
| }, |
| Feature.GLOBALTHIS: { |
| 'chrome': 71, |
| 'edge': 79, |
| 'firefox': 65, |
| 'safari': 120100, |
| 'node': 120000, |
| }, |
| Feature.PROMISE_ANY: { |
| 'chrome': 85, |
| 'firefox': 79, |
| 'safari': 140000, |
| 'node': 150000, |
| }, |
| } |
| |
| |
| def caniuse(feature): |
| min_versions = min_browser_versions[feature] |
| |
| def report_missing(setting_name): |
| setting_value = getattr(settings, setting_name) |
| logger.debug(f'cannot use {feature.name} because {setting_name} is too old: {setting_value}') |
| |
| if settings.ENVIRONMENT_MAY_BE_WEB or settings.ENVIRONMENT_MAY_BE_WORKER or \ |
| settings.ENVIRONMENT_MAY_BE_WEBVIEW: |
| if settings.MIN_CHROME_VERSION < min_versions['chrome']: |
| report_missing('MIN_CHROME_VERSION') |
| return False |
| # For edge we just use the same version requirements as chrome since, |
| # at least for modern versions of edge, they share version numbers. |
| if settings.MIN_EDGE_VERSION < min_versions['chrome']: |
| report_missing('MIN_EDGE_VERSION') |
| return False |
| if settings.MIN_FIREFOX_VERSION < min_versions['firefox']: |
| report_missing('MIN_FIREFOX_VERSION') |
| return False |
| if settings.MIN_SAFARI_VERSION < min_versions['safari']: |
| report_missing('MIN_SAFARI_VERSION') |
| return False |
| # IE don't support any non-MVP features |
| if settings.MIN_IE_VERSION != 0x7FFFFFFF: |
| report_missing('MIN_IE_VERSION') |
| return False |
| |
| if settings.ENVIRONMENT_MAY_BE_NODE: |
| if 'node' in min_versions and settings.MIN_NODE_VERSION < min_versions['node']: |
| report_missing('MIN_NODE_VERSION') |
| return False |
| |
| return True |
| |
| |
| BROWSERS = set(['chrome', 'edge', 'firefox', 'safari'] ) |
| |
| |
| # Given a runtime like "chrome" or "node", see if it is relevant for this |
| # build. A flag like ENVIRONMENT=web would make node irrelevant, for example. |
| def is_runtime_relevant(runtime): |
| if runtime in BROWSERS: |
| # Note: This is not 100% precise atm as building for node+pthreads will |
| # mark ENVIRONMENT_MAY_BE_WORKER as true - node.js workers are seen as |
| # both ENVIRONMENT_IS_NODE and ENVIRONMENT_IS_WORKER being true at once. |
| # That has the effect of making it seem like the browser environment is also |
| # possible. (However, code size matters more in the reverse case, so this is |
| # not a big deal.) |
| return settings.ENVIRONMENT_MAY_BE_WEB or \ |
| settings.ENVIRONMENT_MAY_BE_WORKER or \ |
| settings.ENVIRONMENT_MAY_BE_WEBVIEW |
| elif runtime == 'node': |
| return settings.ENVIRONMENT_MAY_BE_NODE |
| return True |
| |
| |
| def enable_feature(feature, reason): |
| """Updates default settings for browser versions such that the given |
| feature is available everywhere. |
| """ |
| for name, min_version in min_browser_versions[feature].items(): |
| if not is_runtime_relevant(name): |
| continue |
| name = f'MIN_{name.upper()}_VERSION' |
| if settings[name] < min_version: |
| if name in user_settings: |
| # If the user explicitly chose an older version we issue a warning. |
| diagnostics.warning( |
| 'compatibility', |
| f'{name}={user_settings[name]} is not compatible with {reason} ' |
| f'({min_version} or above required)') |
| else: |
| # Otherwise we bump the minimum version to accommodate the feature. |
| setattr(settings, name, min_version) |
| |
| |
| # apply minimum browser version defaults based on user settings. if |
| # a user requests a feature that we know is only supported in browsers |
| # from a specific version and above, we can assume that browser version. |
| def apply_min_browser_versions(): |
| if settings.WASM_BIGINT: |
| enable_feature(Feature.JS_BIGINT_INTEGRATION, 'WASM_BIGINT') |
| if settings.PTHREADS: |
| enable_feature(Feature.THREADS, 'pthreads') |
| enable_feature(Feature.BULK_MEMORY, 'pthreads') |
| if settings.AUDIO_WORKLET: |
| enable_feature(Feature.GLOBALTHIS, 'AUDIO_WORKLET') |