mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
222 lines
8.1 KiB
TypeScript
222 lines
8.1 KiB
TypeScript
import * as cp from 'node:child_process';
|
|
import * as path from 'node:path';
|
|
|
|
import { ifdescribe } from './lib/spec-helpers';
|
|
|
|
ifdescribe(process.platform === 'darwin' && process.mas)('Mac App Store build', () => {
|
|
describe('private API usage', () => {
|
|
// Get paths to all Electron binaries
|
|
const getElectronBinaries = () => {
|
|
const contentsPath = path.dirname(path.dirname(process.execPath));
|
|
return {
|
|
mainProcess: process.execPath,
|
|
framework: path.join(contentsPath, 'Frameworks', 'Electron Framework.framework', 'Electron Framework'),
|
|
helpers: {
|
|
main: path.join(contentsPath, 'Frameworks', 'Electron Helper.app', 'Contents', 'MacOS', 'Electron Helper'),
|
|
gpu: path.join(contentsPath, 'Frameworks', 'Electron Helper (GPU).app', 'Contents', 'MacOS', 'Electron Helper (GPU)'),
|
|
plugin: path.join(contentsPath, 'Frameworks', 'Electron Helper (Plugin).app', 'Contents', 'MacOS', 'Electron Helper (Plugin)'),
|
|
renderer: path.join(contentsPath, 'Frameworks', 'Electron Helper (Renderer).app', 'Contents', 'MacOS', 'Electron Helper (Renderer)')
|
|
}
|
|
};
|
|
};
|
|
|
|
const checkBinaryForPrivateAPIs = (binaryPath: string, binaryName: string) => {
|
|
// Use nm without -U to get all symbols (both defined and undefined)
|
|
const nmResult = cp.spawnSync('nm', ['-g', binaryPath], {
|
|
encoding: 'utf-8',
|
|
maxBuffer: 50 * 1024 * 1024
|
|
});
|
|
|
|
if (nmResult.error) {
|
|
throw new Error(`Failed to run nm on ${binaryName}: ${nmResult.error.message}`);
|
|
}
|
|
|
|
const symbols = nmResult.stdout;
|
|
|
|
// List of known private APIs that should not be present in MAS builds
|
|
// These are from the mas_avoid_private_macos_api_usage.patch
|
|
const privateAPIs = [
|
|
'abort_report_np',
|
|
'pthread_fchdir_np',
|
|
'pthread_chdir_np',
|
|
'SetApplicationIsDaemon',
|
|
'_LSSetApplicationLaunchServicesServerConnectionStatus',
|
|
'CGSSetWindowCaptureExcludeShape',
|
|
'CGRegionCreateWithRect',
|
|
'CTFontCopyVariationAxesInternal',
|
|
'AudioDeviceDuck',
|
|
'CGSMainConnectionID',
|
|
'IOBluetoothPreferenceSetControllerPowerState',
|
|
'CGSSetDenyWindowServerConnections',
|
|
'CGFontRenderingGetFontSmoothingDisabled',
|
|
'CTFontDescriptorIsSystemUIFont',
|
|
'sandbox_init_with_parameters',
|
|
'sandbox_create_params',
|
|
'sandbox_set_param',
|
|
'sandbox_free_params',
|
|
'sandbox_compile_string',
|
|
'sandbox_apply',
|
|
'sandbox_free_profile',
|
|
'sandbox_extension_issue_file',
|
|
'sandbox_extension_consume',
|
|
'sandbox_extension_release',
|
|
'_CSCheckFixDisable',
|
|
'responsibility_get_pid_responsible_for_pid',
|
|
'_NSAppendToKillRing',
|
|
'_NSPrependToKillRing',
|
|
'_NSYankFromKillRing',
|
|
'__NSNewKillRingSequence',
|
|
'__NSInitializeKillRing',
|
|
'_NSSetKillRingToYankedState'
|
|
];
|
|
|
|
const foundPrivateAPIs: string[] = [];
|
|
|
|
for (const api of privateAPIs) {
|
|
// Check if the symbol appears in the nm output
|
|
// Look for ' U ' (undefined) or ' T ' (defined in text section) followed by the API
|
|
// nm output format is like: " U _symbol_name"
|
|
const regex = new RegExp(`\\s+[UT]\\s+(_${api})\\b`);
|
|
const match = symbols.match(regex);
|
|
if (match) {
|
|
// Keep the full symbol name including the underscore
|
|
foundPrivateAPIs.push(match[1]);
|
|
}
|
|
}
|
|
|
|
return foundPrivateAPIs;
|
|
};
|
|
|
|
it('should not use private macOS APIs in main process', function () {
|
|
this.timeout(60000);
|
|
|
|
const binaries = getElectronBinaries();
|
|
const foundPrivateAPIs = checkBinaryForPrivateAPIs(binaries.mainProcess, 'Electron main process');
|
|
|
|
if (foundPrivateAPIs.length > 0) {
|
|
throw new Error(
|
|
`Found private macOS APIs in main process:\n${foundPrivateAPIs.join('\n')}\n\n` +
|
|
'These APIs are not allowed in Mac App Store builds and will cause rejection.'
|
|
);
|
|
}
|
|
|
|
// Also check for private framework linkage
|
|
const otoolResult = cp.spawnSync('otool', ['-L', binaries.mainProcess], {
|
|
encoding: 'utf-8'
|
|
});
|
|
|
|
if (otoolResult.error) {
|
|
throw new Error(`Failed to run otool: ${otoolResult.error.message}`);
|
|
}
|
|
|
|
const frameworks = otoolResult.stdout;
|
|
|
|
if (frameworks.includes('PrivateFrameworks')) {
|
|
throw new Error(
|
|
'Found linkage to PrivateFrameworks which is not allowed in Mac App Store builds:\n' +
|
|
frameworks
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should not use private macOS APIs in Electron Framework', function () {
|
|
this.timeout(60000);
|
|
|
|
// Check the Electron Framework binary (mentioned in issue #49616)
|
|
const binaries = getElectronBinaries();
|
|
const foundAPIs = checkBinaryForPrivateAPIs(binaries.framework, 'Electron Framework');
|
|
|
|
if (foundAPIs.length > 0) {
|
|
throw new Error(
|
|
`Found private macOS APIs in Electron Framework:\n${foundAPIs.join('\n')}\n\n` +
|
|
'These APIs are not allowed in Mac App Store builds and will cause rejection.\n' +
|
|
'See patches/chromium/mas_avoid_private_macos_api_usage.patch.patch'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should not use private macOS APIs in helper processes', function () {
|
|
this.timeout(60000);
|
|
|
|
const binaries = getElectronBinaries();
|
|
const allFoundAPIs: Record<string, string[]> = {};
|
|
|
|
for (const [helperName, helperPath] of Object.entries(binaries.helpers)) {
|
|
const displayName = `Electron Helper${helperName !== 'main' ? ` (${helperName.charAt(0).toUpperCase() + helperName.slice(1)})` : ''}`;
|
|
const foundAPIs = checkBinaryForPrivateAPIs(helperPath, displayName);
|
|
|
|
if (foundAPIs.length > 0) {
|
|
allFoundAPIs[displayName] = foundAPIs;
|
|
}
|
|
}
|
|
|
|
if (Object.keys(allFoundAPIs).length > 0) {
|
|
const errorLines = Object.entries(allFoundAPIs).map(([helper, apis]) =>
|
|
`${helper}:\n ${apis.join('\n ')}`
|
|
);
|
|
|
|
throw new Error(
|
|
`Found private macOS APIs in helper processes:\n\n${errorLines.join('\n\n')}\n\n` +
|
|
'These APIs are not allowed in Mac App Store builds and will cause rejection.'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should not reference private Objective-C classes', function () {
|
|
this.timeout(60000);
|
|
|
|
// Check for private Objective-C classes (appear as _OBJC_CLASS_$_ClassName)
|
|
const privateClasses = [
|
|
'NSAccessibilityRemoteUIElement',
|
|
'CAContext'
|
|
];
|
|
|
|
const binaries = getElectronBinaries();
|
|
const binariesToCheck = [
|
|
{ path: binaries.mainProcess, name: 'Electron main process' },
|
|
{ path: binaries.framework, name: 'Electron Framework' },
|
|
{ path: binaries.helpers.main, name: 'Electron Helper' },
|
|
{ path: binaries.helpers.renderer, name: 'Electron Helper (Renderer)' }
|
|
];
|
|
|
|
const foundClasses: Record<string, string[]> = {};
|
|
|
|
for (const binary of binariesToCheck) {
|
|
const nmResult = cp.spawnSync('nm', ['-g', binary.path], {
|
|
encoding: 'utf-8',
|
|
maxBuffer: 50 * 1024 * 1024
|
|
});
|
|
|
|
if (nmResult.error) {
|
|
throw new Error(`Failed to run nm on ${binary.name}: ${nmResult.error.message}`);
|
|
}
|
|
|
|
const symbols = nmResult.stdout;
|
|
const foundInBinary: string[] = [];
|
|
|
|
for (const className of privateClasses) {
|
|
// Look for _OBJC_CLASS_$_ClassName pattern
|
|
if (symbols.includes(`_OBJC_CLASS_$_${className}`)) {
|
|
foundInBinary.push(className);
|
|
}
|
|
}
|
|
|
|
if (foundInBinary.length > 0) {
|
|
foundClasses[binary.name] = foundInBinary;
|
|
}
|
|
}
|
|
|
|
if (Object.keys(foundClasses).length > 0) {
|
|
const errorLines = Object.entries(foundClasses).map(([binary, classes]) =>
|
|
`${binary}:\n ${classes.join('\n ')}`
|
|
);
|
|
|
|
throw new Error(
|
|
`Found references to private Objective-C classes:\n\n${errorLines.join('\n\n')}\n\n` +
|
|
'These are not allowed in Mac App Store builds and will cause rejection.'
|
|
);
|
|
}
|
|
});
|
|
});
|
|
});
|