github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/webapp/components/product-info.js (about) 1 /** 2 * Copyright 2018 The WPT Dashboard Project. All rights reserved. 3 * Use of this source code is governed by a BSD-style license that can be 4 * found in the LICENSE file. 5 */ 6 const DisplayNames = (() => { 7 let m = new Map(); 8 ['chrome', 'chrome-experimental'].forEach(n => m.set(n, 'Chrome')); 9 ['edge', 'edge-experimental'].forEach(n => m.set(n, 'Edge')); 10 ['firefox', 'firefox-experimental'].forEach(n => m.set(n, 'Firefox')); 11 ['safari', 'safari-experimental'].forEach(n => m.set(n, 'Safari')); 12 m.set('android_webview', 'WebView'); 13 m.set('chrome_android', 'ChromeAndroid'); 14 m.set('chrome_ios', 'ChromeIOS'); 15 m.set('chromium', 'Chromium'); 16 m.set('deno', 'Deno'); 17 m.set('firefox_android', 'Firefox Android'); 18 m.set('flow', 'Flow'); 19 m.set('node.js', 'Node.js'); 20 m.set('servo', 'Servo'); 21 m.set('uc', 'UC Browser'); 22 m.set('wktr', 'macOS WebKit'); 23 m.set('webkitgtk', 'WebKitGTK'); 24 // Platforms 25 m.set('android', 'Android'); 26 m.set('linux', 'Linux'); 27 m.set('mac', 'macOS'); 28 m.set('win', 'Windows'); 29 // Channels 30 m.set('stable', 'Stable'); 31 m.set('beta', 'Beta'); 32 m.set('experimental', 'Experimental'); 33 m.set('dev', 'Dev'); // Chrome 34 m.set('preview', 'Technology Preview'); // Safari 35 m.set('nightly', 'Nightly'); // Firefox 36 // Sources 37 m.set('azure', 'Azure Pipelines'); 38 m.set('buildbot', 'Buildbot'); 39 m.set('msedge', 'MS Edge'); 40 m.set('taskcluster', 'Taskcluster'); 41 return m; 42 })(); 43 const versionPatterns = Object.freeze({ 44 Major: /(\d+)/, 45 MajorAndMinor: /(\d+\.\d+)/, 46 Node: /(\d+\.\d+\.\d+(?:-[a-zA-Z]+)?)/, 47 }); 48 49 // The set of all browsers known to the wpt.fyi UI. 50 const AllBrowserNames = Object.freeze(['android_webview', 'chrome_android', 'chrome_ios', 'chrome', 51 'chromium', 'deno', 'edge', 'firefox_android', 'firefox', 'flow', 'node.js', 'safari', 'servo', 'webkitgtk', 'wktr']); 52 53 // The list of default browsers used in cases where the user has not otherwise 54 // chosen a set of browsers (e.g. which browsers to show runs for). Stored as 55 // an ordered list so that the first entry can be used as a consistent default. 56 const DefaultBrowserNames = Object.freeze(['chrome', 'edge', 'firefox', 'safari']); 57 const DefaultProductSpecs = DefaultBrowserNames; 58 59 // The above sets, encoded as product objects. This avoids repeatedly calling 60 // parseProductSpec when product objects are needed. 61 const AllProducts = AllBrowserNames.map(p => Object.freeze(parseProductSpec(p))); 62 const DefaultProducts = DefaultProductSpecs.map(p => Object.freeze(parseProductSpec(p))); 63 64 const CommitTypes = new Set(['pr_head', 'master']); 65 const Channels = new Set(['stable', 'beta', 'experimental']); 66 const Sources = new Set(['buildbot', 'taskcluster', 'msedge', 'azure']); 67 const Platforms = new Set(['linux', 'win', 'mac', 'ios', 'android']); 68 const SemanticLabels = [ 69 { property: '_channel', values: Channels }, 70 { property: '_source', values: Sources }, 71 ]; 72 73 function parseProductSpec(spec) { 74 // @sha (optional) 75 let revision = ''; 76 const atIndex = spec.indexOf('@'); 77 if (atIndex > 0) { 78 revision = spec.substr(atIndex + 1); 79 spec = spec.substr(0, atIndex); 80 } 81 // [foo,bar] labels syntax (optional) 82 let labels = []; 83 const arrayIndex = spec.indexOf('['); 84 if (arrayIndex > 0) { 85 let labelsStr = spec.substr(arrayIndex + 1); 86 if (labelsStr[labelsStr.length - 1] !== ']') { 87 throw 'Expected closing bracket'; 88 } 89 const seenLabels = new Set(); 90 labelsStr = labelsStr.substr(0, labelsStr.length - 1); 91 for (const label of labelsStr.split(',')) { 92 if (!seenLabels.has(label)) { 93 seenLabels.add(label); 94 labels.push(label); 95 } 96 } 97 spec = spec.substr(0, arrayIndex); 98 } 99 // product 100 const product = parseProduct(spec); 101 product.revision = revision; 102 product.labels = labels; 103 return product; 104 } 105 106 function parseProduct(name) { 107 // -version (optional) 108 let version; 109 const dashIndex = name.indexOf('-'); 110 if (dashIndex > 0) { 111 version = name.substr(dashIndex + 1); 112 name = name.substr(0, dashIndex); 113 } 114 return { 115 browser_name: name, 116 browser_version: version, 117 }; 118 } 119 120 function productFromRun(run) { 121 const product = { 122 browser_name: run.browser_name, 123 browser_version: run.browser_version, 124 labels: run.labels, 125 revision: run.revision, 126 }; 127 return product; 128 } 129 130 // eslint-disable-next-line no-unused-vars 131 const ProductInfo = (superClass) => class extends superClass { 132 static get properties() { 133 return { 134 // Polymer templates can only access variables in the scope of the owning 135 // class. Forward some declarations so that subclasses can use them in 136 // template parameters. 137 allProducts: { 138 type: Array, 139 value: AllProducts, 140 readOnly: true, 141 } 142 }; 143 } 144 145 displayName(name) { 146 return DisplayNames.get(name) || name; 147 } 148 149 displayLabels(labels) { 150 if (labels && labels instanceof Array) { 151 return labels.join(', '); 152 } 153 return ''; 154 } 155 156 displayMetadataLogo(productName) { 157 // Special case for metadata; an empty product name maps to the WPT logo. 158 if (productName === '') { 159 productName = 'wpt'; 160 } 161 return this.displayLogo(productName); 162 } 163 164 displayLogo(name, labels) { 165 if (!name) { 166 return; 167 } 168 labels = new Set(labels); 169 // Special case for Chrome nightly, which is in fact Chromium ToT: 170 if (name === 'chrome' && labels.has('nightly') && !labels.has('canary')) { 171 name = 'chromium'; 172 173 } else if (name === 'android_webview') { 174 return `/static/${name}.png`; 175 176 } else if (name === 'chrome_android' || name === 'chrome_ios') { 177 // TODO(kyle): A temporary workaround; remove this check when 178 // chrome_android and chrome_ios is mapped to chrome on wptrunner. 179 return '/static/chrome_64x64.png'; 180 } else if (name === 'firefox_android') { 181 // For now use the geckoview logo for Firefox for Android, 182 // although it would be better to have some variant of the Firefox logo. 183 return '/static/geckoview_64x64.png'; 184 185 } else if (name !== 'chromium' && name !== 'deno' && name !== 'flow' && name !== 'node.js' && name !== 'servo' && name !== 'wktr') { // Products without per-channel logos. 186 let channel; 187 const candidates = ['beta', 'dev', 'canary', 'nightly', 'preview']; 188 for (const label of candidates) { 189 if (labels.has(label)) { 190 channel = label; 191 break; 192 } 193 } 194 if (channel) { 195 name = `${name}-${channel}`; 196 } 197 } 198 return `/static/${name}_64x64.png`; 199 } 200 201 sourceName(product) { 202 if (product.labels) { 203 return this.displayName(product.labels.find(s => Sources.has(s))); 204 } 205 return ''; 206 } 207 208 minorIsSignificant(browserName) { 209 return browserName === 'deno' || browserName === 'flow' || browserName === 'safari' || browserName === 'webkitgtk'; 210 } 211 212 /** 213 * Truncate a software version identifier to include only the most 214 * salient information for the specified browser. 215 */ 216 shortVersion(browserName, browserVersion) { 217 let pattern; 218 if (browserName === 'node.js') { 219 pattern = versionPatterns.Node; 220 } else { 221 pattern = this.minorIsSignificant(browserName) 222 ? versionPatterns.MajorAndMinor 223 : versionPatterns.Major; 224 } 225 const match = pattern.exec(browserVersion); 226 227 if (!match) { 228 return browserVersion; 229 } 230 231 return match[1]; 232 } 233 234 parseProductSpec(spec) { 235 return parseProductSpec(spec); 236 } 237 238 parseProduct(name) { 239 return parseProduct(name); 240 } 241 242 getSpec(product, withRevision=true) { 243 let spec = product.browser_name; 244 if (product.browser_version) { 245 spec += `-${product.browser_version}`; 246 } 247 if (product.labels && product.labels.length) { 248 spec += `[${product.labels.join(',')}]`; 249 } 250 if (withRevision && product.revision && !this.computeIsLatest(product.revision)) { 251 spec += `@${product.revision}`; 252 } 253 return spec; 254 } 255 256 computeIsLatest(sha) { 257 if (Array.isArray(sha)) { 258 return !sha.length || sha.length === 1 && this.computeIsLatest(sha[0]); 259 } 260 return !sha || sha === 'latest'; 261 } 262 }; 263 264 export { 265 AllBrowserNames, 266 AllProducts, 267 DisplayNames, 268 DefaultBrowserNames, 269 DefaultProductSpecs, 270 DefaultProducts, 271 CommitTypes, 272 Channels, 273 Platforms, 274 Sources, 275 SemanticLabels, 276 ProductInfo, 277 parseProductSpec, 278 parseProduct, 279 productFromRun, 280 };