diff options
Diffstat (limited to 'devtools/client/shared/remote-debugging/version-checker.js')
-rw-r--r-- | devtools/client/shared/remote-debugging/version-checker.js | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/devtools/client/shared/remote-debugging/version-checker.js b/devtools/client/shared/remote-debugging/version-checker.js new file mode 100644 index 0000000000..7023a5bcd3 --- /dev/null +++ b/devtools/client/shared/remote-debugging/version-checker.js @@ -0,0 +1,154 @@ +/* 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; |