interface_exposure_checker.js (5281B)
1 function entryDisabled( 2 entry, 3 { 4 isNightly, 5 isEarlyBetaOrEarlier, 6 isRelease, 7 isDesktop, 8 isWindows, 9 isMac, 10 isLinux, 11 isAndroid, 12 isAarch64, 13 isInsecureContext, 14 isFennec, 15 isCrossOriginIsolated, 16 isSessionHistoryInParent, 17 } 18 ) { 19 return ( 20 entry.nightly === !isNightly || 21 (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || 22 entry.desktop === !isDesktop || 23 entry.windows === !isWindows || 24 entry.mac === !isMac || 25 entry.linux === !isLinux || 26 (entry.android === !isAndroid && !entry.nightlyAndroid) || 27 entry.aarch64 === !isAarch64 || 28 entry.fennecOrDesktop === (isAndroid && !isFennec) || 29 entry.fennec === !isFennec || 30 entry.release === !isRelease || 31 // The insecureContext test is very purposefully converting 32 // entry.insecureContext to boolean, so undefined will convert to 33 // false. That way entries without an insecureContext annotation 34 // will get treated as "insecureContext: false", which means exposed 35 // only in secure contexts. 36 (isInsecureContext && !entry.insecureContext) || 37 entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || 38 entry.crossOriginIsolated === !isCrossOriginIsolated || 39 entry.sessionHistoryInParent === !isSessionHistoryInParent || 40 entry.disabled 41 ); 42 } 43 44 function createInterfaceMap(data, interfaceGroups) { 45 var interfaceMap = {}; 46 47 /** @param {any[]} interfaceGroup */ 48 function checkSorted(interfaceGroup) { 49 /** @type {(entry) => string} */ 50 let getName = entry => (typeof entry === "string" ? entry : entry.name); 51 52 // slice(1) to start from index 1 (index 0 has nothing to compare with) 53 for (let [index, entry] of interfaceGroup.slice(1).entries()) { 54 let x = getName(interfaceGroup[index]); 55 let y = getName(entry); 56 ok( 57 x <= y, 58 `The interface group is not sorted! ${y} must come before ${x}!` 59 ); 60 } 61 } 62 63 function addInterfaces(interfaces) { 64 for (var entry of interfaces) { 65 if (typeof entry === "string") { 66 interfaceMap[entry] = !data.isInsecureContext; 67 } else { 68 ok(!("pref" in entry), "Bogus pref annotation for " + entry.name); 69 interfaceMap[entry.name] ||= !entryDisabled(entry, data); 70 } 71 } 72 } 73 74 for (let interfaceGroup of interfaceGroups) { 75 checkSorted(interfaceGroup); 76 addInterfaces(interfaceGroup); 77 } 78 79 return interfaceMap; 80 } 81 82 function runTest( 83 parentName, 84 parent, 85 { data, interfaceGroups, testFunctions = [] } 86 ) { 87 var interfaceMap = createInterfaceMap(data, interfaceGroups); 88 for (var name of Object.getOwnPropertyNames(parent)) { 89 // Ignore functions on the global that are part of the test (harness). 90 if (parent === self && testFunctions.includes(name)) { 91 continue; 92 } 93 ok( 94 interfaceMap[name], 95 "If this is failing: DANGER, are you sure you want to expose the new interface " + 96 name + 97 " to all webpages as a property on '" + 98 parentName + 99 "'? Do not make a change to this file without a " + 100 " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)" 101 ); 102 103 ok( 104 name in parent, 105 `${name} is exposed as an own property on '${parentName}' but tests false for "in" in the global scope` 106 ); 107 ok( 108 Object.getOwnPropertyDescriptor(parent, name), 109 `${name} is exposed as an own property on '${parentName}' but has no property descriptor in the global scope` 110 ); 111 112 delete interfaceMap[name]; 113 } 114 for (var name of Object.keys(interfaceMap)) { 115 const not = interfaceMap[name] ? "" : " NOT"; 116 ok( 117 name in parent === interfaceMap[name], 118 `${name} should${not} be defined on ${parentName}` 119 ); 120 if (!interfaceMap[name]) { 121 delete interfaceMap[name]; 122 } 123 } 124 is( 125 Object.keys(interfaceMap).length, 126 0, 127 "The following interface(s) are not enumerated: " + 128 Object.keys(interfaceMap).join(", ") 129 ); 130 } 131 132 if (typeof window !== "undefined") { 133 window.getHelperData = () => { 134 const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( 135 "resource://gre/modules/AppConstants.sys.mjs" 136 ); 137 const sysinfo = SpecialPowers.Services.sysinfo; 138 const appinfo = SpecialPowers.Services.appinfo; 139 140 return { 141 isNightly: AppConstants.NIGHTLY_BUILD, 142 isEarlyBetaOrEarlier: AppConstants.EARLY_BETA_OR_EARLIER, 143 isRelease: AppConstants.RELEASE_OR_BETA, 144 isDesktop: !/Mobile|Tablet/.test(navigator.userAgent), 145 isMac: AppConstants.platform == "macosx", 146 isWindows: AppConstants.platform == "win", 147 isAndroid: AppConstants.platform == "android", 148 isLinux: AppConstants.platform == "linux", 149 isAarch64: sysinfo.get("arch") === "aarch64", 150 isInsecureContext: !window.isSecureContext, 151 // Currently, MOZ_APP_NAME is always "fennec" for all mobile builds, so we can't use AppConstants for this 152 isFennec: 153 AppConstants.platform == "android" && 154 SpecialPowers.Cc["@mozilla.org/android/bridge;1"].getService( 155 SpecialPowers.Ci.nsIGeckoViewBridge 156 ).isFennec, 157 isCrossOriginIsolated: window.crossOriginIsolated, 158 isSessionHistoryInParent: appinfo.sessionHistoryInParent, 159 }; 160 }; 161 }