/* 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 http://mozilla.org/MPL/2.0/. */ "use strict"; const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); const MS_PER_DAY = 1000 * 60 * 60 * 24; const COMPATIBILITY_STATUS = { COMPATIBLE: "compatible", TOO_OLD: "too-old", TOO_OLD_FENNEC: "too-old-fennec", TOO_RECENT: "too-recent", }; exports.COMPATIBILITY_STATUS = COMPATIBILITY_STATUS; function getDateFromBuildID(buildID) { // Build IDs are a timestamp in the yyyyMMddHHmmss format. // Extract the year, month and day information. const fields = buildID.match(/(\d{4})(\d{2})(\d{2})/); // Date expects 0 - 11 for months const month = Number.parseInt(fields[2], 10) - 1; return new Date(fields[1], month, fields[3]); } function getMajorVersion(platformVersion) { // Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64. return Number.parseInt(platformVersion.match(/\d+/)[0], 10); } /** * Compute the minimum and maximum supported version for remote debugging for the provided * version of Firefox. Backward compatibility policy for devtools supports at most 2 * versions older than the current version. * * @param {String} localVersion * The version of the local Firefox instance, eg "67.0" * @return {Object} * - minVersion {String} the minimum supported version, eg "65.0a1" * - maxVersion {String} the first unsupported version, eg "68.0a1" */ function computeMinMaxVersion(localVersion) { // Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64. const localMajorVersion = getMajorVersion(localVersion); return { // Define the minimum officially supported version of Firefox when connecting to a // remote runtime. (Use ".0a1" to support the very first nightly version) // This matches the release channel's version when we are on nightly, // or 2 versions before when we are on other channels. minVersion: localMajorVersion - 2 + ".0a1", // The maximum version is the first excluded from the support range. That's why we // increase the current version by 1 and use ".0a1" to point to the first Nightly. // We do not support forward compatibility at all. maxVersion: localMajorVersion + 1 + ".0a1", }; } /** * Tells if the remote device is using a supported version of Firefox. * * @param {DevToolsClient} devToolsClient * DevToolsClient instance connected to the target remote Firefox. * @return Object with the following attributes: * * String status, one of COMPATIBILITY_STATUS * COMPATIBLE if the runtime is compatible, * TOO_RECENT if the runtime uses a too recent version, * TOO_OLD if the runtime uses a too old version. * * String minVersion * The minimum supported version. * * String runtimeVersion * The remote runtime version. * * String localID * Build ID of local runtime. A date with like this: YYYYMMDD. * * String deviceID * Build ID of remote runtime. A date with like this: YYYYMMDD. */ async function checkVersionCompatibility(devToolsClient) { const localDescription = { appbuildid: Services.appinfo.appBuildID, platformversion: AppConstants.MOZ_APP_VERSION, }; try { const deviceFront = await devToolsClient.mainRoot.getFront("device"); const description = await deviceFront.getDescription(); return _compareVersionCompatibility(localDescription, description); } catch (e) { // If we failed to retrieve the device description, assume we are trying to connect to // a really old version of Firefox. const localVersion = localDescription.platformversion; const { minVersion } = computeMinMaxVersion(localVersion); return { minVersion, runtimeVersion: "<55", status: COMPATIBILITY_STATUS.TOO_OLD, }; } } exports.checkVersionCompatibility = checkVersionCompatibility; function _compareVersionCompatibility(localDescription, deviceDescription) { const runtimeID = deviceDescription.appbuildid.substr(0, 8); const localID = localDescription.appbuildid.substr(0, 8); const runtimeDate = getDateFromBuildID(runtimeID); const localDate = getDateFromBuildID(localID); const runtimeVersion = deviceDescription.platformversion; const localVersion = localDescription.platformversion; const { minVersion, maxVersion } = computeMinMaxVersion(localVersion); const isTooOld = Services.vc.compare(runtimeVersion, minVersion) < 0; const isTooRecent = Services.vc.compare(runtimeVersion, maxVersion) >= 0; const runtimeMajorVersion = getMajorVersion(runtimeVersion); const localMajorVersion = getMajorVersion(localVersion); const isSameMajorVersion = runtimeMajorVersion === localMajorVersion; let status; if (isTooOld) { if (runtimeMajorVersion === 68 && deviceDescription.os === "Android") { status = COMPATIBILITY_STATUS.TOO_OLD_FENNEC; } else { status = COMPATIBILITY_STATUS.TOO_OLD; } } else if (isTooRecent) { status = COMPATIBILITY_STATUS.TOO_RECENT; } else if (isSameMajorVersion && runtimeDate - localDate > 7 * MS_PER_DAY) { // If both local and remote runtimes have the same major version, compare build dates. // This check is useful for Gecko developers as we might introduce breaking changes // within a Nightly cycle. // Still allow devices to be newer by up to a week. This accommodates those with local // device builds, since their devices will almost always be newer than the client. status = COMPATIBILITY_STATUS.TOO_RECENT; } else { status = COMPATIBILITY_STATUS.COMPATIBLE; } return { localID, localVersion, minVersion, runtimeID, runtimeVersion, status, }; } // Exported for tests. exports._compareVersionCompatibility = _compareVersionCompatibility;