diff --git a/.cloudbuild/deploy.yaml b/.cloudbuild/deploy.yaml index 8b99456aa..735535fde 100644 --- a/.cloudbuild/deploy.yaml +++ b/.cloudbuild/deploy.yaml @@ -17,10 +17,6 @@ steps: env: - 'NODE_OPTIONS="--max_old_space_size=8192"' # https://github.com/GoogleChrome/developer.chrome.com/issues/2439 - - name: node:16.14.2 - entrypoint: npm - args: ['run', 'algolia'] - - name: 'gcr.io/cloud-builders/gcloud' entrypoint: 'bash' args: diff --git a/.cloudbuild/external.yaml b/.cloudbuild/external.yaml index 5f8d862eb..69e25ba0d 100644 --- a/.cloudbuild/external.yaml +++ b/.cloudbuild/external.yaml @@ -9,11 +9,6 @@ steps: entrypoint: npm args: ['ci'] - - name: node:16.14.2 - id: 'Configure secrets' - entrypoint: npm - args: ['run', 'cloud-secrets'] - - name: node:16.14.2 id: 'Build external data' entrypoint: npm @@ -34,6 +29,8 @@ steps: - name: node:16.14.2 id: 'Build eleventy in dev mode to confirm' entrypoint: npm + env: + - 'NODE_OPTIONS="--max_old_space_size=8192"' args: ['run', 'eleventy'] # This does NOT set `NODE_ENV=production`, as we don't need the full build. @@ -49,3 +46,15 @@ options: env: - 'PROJECT_ID=$PROJECT_ID' - 'NODE_OPTIONS=--unhandled-rejections=strict' + secretEnv: ['GITHUB_APP_ID', 'GITHUB_APP_KEY', 'GITHUB_APP_INSTALLATION_ID', 'YOUTUBE_API_KEY'] + +availableSecrets: + secretManager: + - versionName: projects/chrome-apps-doc/secrets/GITHUB_APP_ID/versions/1 + env: 'GITHUB_APP_ID' + - versionName: projects/chrome-apps-doc/secrets/GITHUB_APP_KEY/versions/4 + env: 'GITHUB_APP_KEY' + - versionName: projects/chrome-apps-doc/secrets/GITHUB_APP_INSTALLATION_ID/versions/1 + env: 'GITHUB_APP_INSTALLATION_ID' + - versionName: projects/chrome-apps-doc/secrets/YOUTUBE_API_KEY/versions/1 + env: 'YOUTUBE_API_KEY' \ No newline at end of file diff --git a/.cloudbuild/stage.yaml b/.cloudbuild/stage.yaml index 9e01edcd3..ce801b696 100644 --- a/.cloudbuild/stage.yaml +++ b/.cloudbuild/stage.yaml @@ -1,12 +1,12 @@ timeout: 2700s # Set build timeout to 45 mins steps: - id: 'install' - name: node:18 + name: node:16 entrypoint: npm args: ['ci'] - id: 'prepare' - name: node:18 + name: node:16 entrypoint: bash args: - '-c' @@ -15,7 +15,7 @@ steps: npm run stage:github announceDeploymentStart - id: 'build' - name: node:18 + name: node:16 env: - 'CI=true' - 'NODE_OPTIONS="--max_old_space_size=8192"' @@ -52,7 +52,7 @@ steps: fi - id: 'finalize' - name: node:18 + name: node:16 entrypoint: bash args: - '-c' diff --git a/.cloudbuild/stagingCleanUp.yaml b/.cloudbuild/stagingCleanUp.yaml new file mode 100644 index 000000000..2da27f429 --- /dev/null +++ b/.cloudbuild/stagingCleanUp.yaml @@ -0,0 +1,25 @@ +# Runs in dcc-staging and cleans up stale instances and bucket items by comparing open +# PRs to currently deployed instances/folders +timeout: 2700s +steps: + - id: 'install' + name: node:18 + entrypoint: npm + args: ['ci'] + + - id: 'clean' + name: node:18 + entrypoint: bash + args: + - '-c' + - | + npm run stage:github cleanUpGoogleCloud + +availableSecrets: + secretManager: + - versionName: projects/dcc-staging/secrets/GITHUB_APP_ID/versions/1 + env: 'GITHUB_APP_ID' + - versionName: projects/dcc-staging/secrets/GITHUB_APP_KEY/versions/1 + env: 'GITHUB_APP_KEY' + - versionName: projects/dcc-staging/secrets/GITHUB_APP_INSTALLATION_ID/versions/1 + env: 'GITHUB_APP_INSTALLATION_ID' \ No newline at end of file diff --git a/.cloudbuild/updateAlgoliaIndex.yaml b/.cloudbuild/updateAlgoliaIndex.yaml new file mode 100644 index 000000000..6785a3a62 --- /dev/null +++ b/.cloudbuild/updateAlgoliaIndex.yaml @@ -0,0 +1,21 @@ +timeout: 600s # set build timeout to 10 mins +steps: + - name: node:16.14.2 + entrypoint: npm + args: ['ci'] + + - name: node:16.14.2 + entrypoint: npm + args: ['run', 'cloud-secrets'] + + - name: node:16.14.2 + entrypoint: npm + args: ['run', 'algolia'] + +substitutions: + _EXTRA_GCLOUD_ARGS: # default empty + +options: + machineType: 'E2_HIGHCPU_32' + env: + - 'PROJECT_ID=$PROJECT_ID' diff --git a/.eleventy.js b/.eleventy.js index afa20006e..16478be35 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -28,6 +28,7 @@ const {DetailsSummary} = require('./site/_shortcodes/DetailsSummary'); const {Empty} = require('./site/_shortcodes/Empty'); const {IFrame} = require('./site/_shortcodes/IFrame'); const {Glitch} = require('./site/_shortcodes/Glitch'); +const {AndroidBrowserSupportTable} = require('./site/_shortcodes/AndroidBrowserSupportTable'); const {Hreflang} = require('./site/_shortcodes/Hreflang'); const {Img} = require('./site/_shortcodes/Img'); const {Label} = require('./site/_shortcodes/Label'); @@ -133,6 +134,7 @@ module.exports = eleventyConfig => { eleventyConfig.addNunjucksAsyncFilter('minifyHtml', minifyHtml); // Add shortcodes + eleventyConfig.addShortcode('AndroidBrowserSupportTable', AndroidBrowserSupportTable); eleventyConfig.addShortcode('InlineCss', InlineCss); eleventyConfig.addShortcode('Codepen', Codepen); eleventyConfig.addShortcode('IFrame', IFrame); diff --git a/.github/chrome-devrel-bot.json b/.github/chrome-devrel-bot.json index 89fc89aef..61b1180af 100644 --- a/.github/chrome-devrel-bot.json +++ b/.github/chrome-devrel-bot.json @@ -5,15 +5,15 @@ "check_name" : "Content team approval", "teams" : [], "users" : [ + "PaulKinlan", "malchata", "rachelandrew", "heyawhite", "jpmedley", "mihajlija", "sofiayem", - "AaronForinton", + "nancymic2", "anusmitaray", - "IanStanion-google", "AmySteam" ], "paths" : [ @@ -26,18 +26,19 @@ "users" : [ "PaulKinlan", "matthiasrohmer", - "devnook" + "devnook", + "tunetheweb" ], "paths" : [ - "*.js", - "*.json", - "*.css", - "*.scss", - "*.html", - "*.htm", - "*.njk", - "*.toml", - "*.sh" + "**/*.js", + "**/*.json", + "**/*.css", + "**/*.scss", + "**/*.html", + "**/*.htm", + "**/*.njk", + "**/*.toml", + "**/*.sh" ] }, { @@ -45,16 +46,18 @@ "teams" : [], "users" : [ "PaulKinlan", - "jeffposnick", - "devnook", "malchata", "rachelandrew", "heyawhite", "jpmedley", "mihajlija", "sofiayem", - "AaronForinton", - "matthiasrohmer" + "nancymic2", + "anusmitaray", + "AmySteam", + "matthiasrohmer", + "devnook", + "tunetheweb" ], "paths" : [ "site/_data/**.yml", @@ -64,9 +67,18 @@ ] }, { - "check_name" : "Content (es) team approval", + "check_name" : "Content team or Spanish reader approval", "teams" : [], "users" : [ + "malchata", + "rachelandrew", + "heyawhite", + "jpmedley", + "mihajlija", + "sofiayem", + "nancymic2", + "anusmitaray", + "AmySteam", "tropicadri" ], "paths" : [ diff --git a/.huskyrc.js b/.huskyrc.js index e1562f4c7..7b769a233 100644 --- a/.huskyrc.js +++ b/.huskyrc.js @@ -1,5 +1,5 @@ module.exports = { hooks: { - "pre-push": "npm run lint" + "pre-commit": "npx lint-staged" } }; \ No newline at end of file diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..ee161c239 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,6 @@ +{ + "*.scss": "npm run lint:scss", + "*.ts": "npm run lint:types", + "*.md": "npm run lint:md", + "*.js": "npm run lint:js" +} diff --git a/algolia.js b/algolia.js index aea97d0de..eb2b4ca76 100644 --- a/algolia.js +++ b/algolia.js @@ -14,10 +14,13 @@ * limitations under the License. */ require('dotenv').config(); + const {default: algoliasearch} = require('algoliasearch'); -const fs = require('fs'); +const {default: fetch} = require('node-fetch'); const {sizeof} = require('sizeof'); +const algoliaIndexSource = 'https://developers.chrome.com/algolia.json'; + const maxChunkSizeInBytes = 10000000; // 10,000,000 /** @@ -53,8 +56,14 @@ async function index() { return; } - const raw = fs.readFileSync('dist/algolia.json', 'utf-8'); - const algoliaData = JSON.parse(raw); + let algoliaData = []; + try { + const raw = await fetch(algoliaIndexSource); + algoliaData = await raw.json(); + } catch (err) { + console.error('Could not load algolia index from prod.', err); + return; + } // Set date of when object is being added to algolia algoliaData.map(e => { diff --git a/external/build/chrome-deprecations.js b/external/build/chrome-deprecations.js new file mode 100644 index 000000000..ae23a8127 --- /dev/null +++ b/external/build/chrome-deprecations.js @@ -0,0 +1,143 @@ +const {default: fetch} = require('node-fetch'); +const fs = require('fs').promises; +const path = require('path'); + +const DEPRECATION_ENDPOINT = + 'https://chromestatus.com/api/v0/features?q=feature_type=3'; + +const CHANNELS_ENDPOINT = 'https://chromestatus.com/api/v0/channels'; + +/** + * Fetches channel data from an endpoint URL. + * @async + * @param {string} endpoint - The URL of the endpoint to fetch data from. + * @returns {Promise} - An array of channel data objects. + **/ + +async function fetchData(endpoint) { + let data = {}; + + try { + data = await fetch(endpoint); + + // Remove first 5 chars to have a correctly formatted JSON + data = (await data.text()).substring(5); + data = JSON.parse(data.toString()); + } catch (error) { + console.error('Error fetching the data', error); + } + return data; +} + +/** + * Fetches all the deprecations from a list of features + * + * @return {Promise} A promise that resolves in an array containing + * all the deprecations + */ +async function fetchDeprecations() { + let deprecations = await fetchData(DEPRECATION_ENDPOINT); + deprecations = deprecationsSort(deprecations?.features); + return deprecations; +} + +/** + * Fetches all channels info needed to get depracation + * dates starting from a Chrome version + * + * @param {number | undefined} start oldest version needed + * @param {number | undefined} end newest version needed + * @return {Promise} A promise that resolves in an array containing + * all the channel infos needed + */ +async function fetchChannels(start, end) { + const params = new URLSearchParams(); + if (start !== undefined && end !== undefined) { + params.set('start', start.toString()); + params.set('end', end.toString()); + } + const queryString = params.toString(); + + const channels = fetchData(`${CHANNELS_ENDPOINT}?${queryString}`); + + return channels; +} + +/** + * Starting from an array of deprecations, removes the ones that have + * no Chrome version associated and then orders them chronologically + * + * @param {object[]} deprecations a list of deprecations to filter and order + * @return {number[]} an array containing all the versions involved in the + * deprecation list + */ +function getVersions(deprecations) { + const versions = deprecations + .map(f => f.browsers.chrome.desktop) + .filter(f => f !== null) + .sort((a, b) => parseInt(a) - parseInt(b)); + + return versions; +} + +/** + * Confronts deprecations to sort them chronologically using Array.sort() + * + * @param {object[]} deprecations + * @return {object[]} + */ +function deprecationsSort(deprecations) { + return deprecations.sort((a, b) => { + if (!b.browsers.chrome.desktop) return -1; + if (!a.browsers.chrome.desktop) return 1; + return ( + parseInt(b.browsers.chrome.desktop) - parseInt(a.browsers.chrome.desktop) + ); + }); +} + +/** + *Formats an array of deprecations using the given channels data. + * @param {Object[]} deprecations - An array of deprecations to format. + * @param {Object} channels - An object containing channel data. + * @returns {Object[]} - An array of formatted deprecations. + **/ + +function formatDeprecations(deprecations, channels) { + const formattedDeprecations = deprecations.map(deprecation => { + const channel = channels[deprecation.browsers.chrome.desktop]; + let date = 'N/A'; + if (channel && channel.stable_date) { + date = channel.stable_date.slice(0, -9); + } + + const formattedDeprecation = { + id: deprecation.id, + version: deprecation.browsers.chrome.desktop, + name: deprecation.name, + date: date, + }; + + return formattedDeprecation; + }); + + return formattedDeprecations; +} + +async function run() { + const deprecations = await fetchDeprecations(); + const versions = getVersions(deprecations); + const channels = await fetchChannels(versions.at(0), versions.at(-1)); + + const targetFile = path.join(__dirname, '../data/chrome-deprecations.json'); + try { + fs.writeFile( + targetFile, + JSON.stringify(formatDeprecations(deprecations, channels)) + ); + } catch (error) { + console.error('Error writing the data', error); + } +} + +run(); diff --git a/external/build/extension-samples.js b/external/build/extension-samples.js new file mode 100644 index 000000000..5f3391b99 --- /dev/null +++ b/external/build/extension-samples.js @@ -0,0 +1,67 @@ +/** + * @fileoverview Fetches chrome extension samples list from GitHub artifacts + * and writes to storage. + * https://github.com/GoogleChrome/chrome-extensions-samples + */ + +const { + getGitHubApiClient, +} = require('../../gulp-tasks/stageGitHub/lib/gitHubApi.js'); +const AdmZip = require('adm-zip'); +const fs = require('fs'); +const path = require('path'); + +const REPO_OWNER = 'GoogleChrome'; +const REPO_NAME = 'chrome-extensions-samples'; + +async function run() { + const client = await getGitHubApiClient(); + + const response = await client.request( + `GET /repos/${REPO_OWNER}/${REPO_NAME}/actions/artifacts` + ); + + if (response.status !== 200) { + throw new Error( + `Could not fetch artifacts. Status code: ${response.status}` + ); + } + + const artifacts = response.data.artifacts.filter( + artifact => artifact.name === 'extension-samples.json' && !artifact.expired + ); + + if (!artifacts.length) { + throw new Error('No artifacts found.'); + } + + const sortedArtifacts = artifacts.sort((a, b) => { + return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); + }); + + const latestArtifactId = sortedArtifacts[0].id; + + const artifactResponse = await client.request( + `GET /repos/${REPO_OWNER}/${REPO_NAME}/actions/artifacts/${latestArtifactId}/zip` + ); + + if (artifactResponse.status !== 200) { + throw new Error( + `Could not fetch artifact data. Status code: ${artifactResponse.status}` + ); + } + + const zip = new AdmZip(Buffer.from(artifactResponse.data)); + + const zipEntries = zip + .getEntries() + .filter(entry => entry.entryName === 'extension-samples.json') + .map(entry => entry.getData()); + + fs.writeFileSync( + path.join(__dirname, '../data/extension-samples.json'), + zipEntries[0] + ); +} + +run(); diff --git a/external/build/external-posts.js b/external/build/external-posts.js new file mode 100644 index 000000000..8c5f8304a --- /dev/null +++ b/external/build/external-posts.js @@ -0,0 +1,28 @@ +/** + * @fileoverview Fetch the RSS feeds. + */ + +const fs = require('fs'); +const path = require('path'); +const {rssFeeds} = require('webdev-infra/utils/rss-feeds'); + +async function run() { + const raw = fs.readFileSync('./site/_data/authorsData.json', 'utf8'); + const authorsData = JSON.parse(raw); + const feeds = {}; + + for (const author in authorsData) { + if (authorsData[author].external) { + feeds[author] = authorsData[author].external; + } + } + + const authorsFeeds = await rssFeeds(feeds); + + fs.writeFileSync( + path.join(__dirname, '../data/external-posts.json'), + JSON.stringify(authorsFeeds) + ); +} + +run(); diff --git a/external/build/lib/dts-parse.js b/external/build/lib/dts-parse.js index cf69c47e2..cd17047e3 100644 --- a/external/build/lib/dts-parse.js +++ b/external/build/lib/dts-parse.js @@ -757,12 +757,20 @@ class Transform { // we haven't seen any other platforms, this might be chromeOsOnly. if (text === 'chromeos' && chromeOsOnly === undefined) { chromeOsOnly = true; + } else if (text === 'lacros') { + // We don't currently have a lacros specific pill, so we don't need + // to do much here, but we should avoid falling in to the next case + // and unsetting chromeOsOnly. } else { - // The first time we see a platform that's not chromeos, we know the - // feature isn't chromeOsOnly. + // The first time we see a platform that's not chromeos or lacros, + // we know the feature isn't chromeOsOnly. chromeOsOnly = false; } break; + case 'chrome-install-location': + if (text === 'policy') { + out.requiresPolicyInstall = true; + } } }); @@ -792,7 +800,7 @@ class Transform { const raw = text.split(' ')[0].replace(/\\_/g, '_'); const value = JSON.parse(raw); - const rest = text.substr(raw.length + 1).trim(); + const rest = text.substring(text.indexOf(' ') + 1).trim(); enums.push({value, description: rest}); }); diff --git a/external/build/tweets.js b/external/build/tweets.js deleted file mode 100644 index 3a89cb69a..000000000 --- a/external/build/tweets.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @fileoverview Fetches the most recent tweet from ChromiumDev and writes to storage. - */ - -require('dotenv').config(); - -const {default: fetch} = require('node-fetch'); -const fs = require('fs'); -const path = require('path'); - -const tweetCount = 1; -const url = `https://api.twitter.com/1.1/statuses/user_timeline.json?user_id=113713261&count=${tweetCount}&include_rts=false&exclude_replies=true&tweet_mode=extended&include_ext_alt_text=true`; - -async function run() { - if (!process.env.TWITTER_BEARER) { - if (process.env.CI) { - return; // do nothing, the fallback data will win - } - throw new Error('No `TWITTER_BEARER` environment var for production'); - } - - const r = await fetch(url, { - headers: { - Authorization: `Bearer ${process.env.TWITTER_BEARER}`, - }, - }); - - if (!r.ok) { - throw new Error(`Could not fetch tweets, status: ${r.status}`); - } - - const json = await r.json(); - - if (json['errors']) { - const error = json['errors'][0]; - throw new Error(`${error.code}: ${error.message}`); - } - - const targetFile = path.join(__dirname, '../data/tweets.json'); - fs.writeFileSync(targetFile, JSON.stringify(json)); -} - -run(); diff --git a/external/build/workbox-types.js b/external/build/workbox-types.js index 69bb31b47..f006e7f03 100644 --- a/external/build/workbox-types.js +++ b/external/build/workbox-types.js @@ -57,7 +57,7 @@ async function run() { try { // webpack is a peerDependency of workbox-webpack-plugin, and needs to be // manually installed. - childProcess.execFileSync('npm', ['install', 'webpack'], { + childProcess.execFileSync('npm', ['install', 'webpack@5.75.0'], { cwd: t.name, stdio: 'inherit', }); diff --git a/external/build/youtube-playlist.js b/external/build/youtube-playlist.js index d1312eaef..c24f01481 100644 --- a/external/build/youtube-playlist.js +++ b/external/build/youtube-playlist.js @@ -17,8 +17,6 @@ * @fileoverview Fetches the most recent playlists by specified channels from YouTube */ -require('dotenv').config(); - const fs = require('fs').promises; const path = require('path'); const ms = require('ms'); @@ -189,7 +187,7 @@ async function checkDataTimestamp() { * specified in the config. */ async function run() { - if (!process.env.YOUTUBE_API_KEY) { + if (!API_KEY) { throw new Error('No `YOUTUBE_API_KEY` environment var for production'); } diff --git a/external/testdata/chrome-deprecations.json b/external/testdata/chrome-deprecations.json new file mode 100644 index 000000000..ec8edf620 --- /dev/null +++ b/external/testdata/chrome-deprecations.json @@ -0,0 +1,478 @@ +[ + { + "id": 6239658726391808, + "version": 119, + "name": "Deprecate non-standard `shadowroot` attribute for declarative shadow DOM", + "date": "2023-11-07" + }, + { + "id": 4631626228695040, + "version": 117, + "name": "[WebRTC] Callback-based legacy getStats()", + "date": "2023-09-12" + }, + { + "id": 5080569152536576, + "version": 115, + "name": "Deprecate module size limit for WebAssembly.Module()", + "date": "2023-07-18" + }, + { + "id": 5428079583297536, + "version": 115, + "name": "Deprecate the `document.domain` setter.", + "date": "2023-07-18" + }, + { + "id": 5182904243585024, + "version": 113, + "name": "Remove WebGPU limit maxFragmentCombinedOutputResources", + "date": "2023-05-02" + }, + { + "id": 5187711071158272, + "version": 113, + "name": "Remove support for Web Push Notifications using FCM Sender IDs", + "date": "2023-05-02" + }, + { + "id": 5203057325899776, + "version": 113, + "name": "Secure Payment Confirmation: Rename rp --> rpId in CollectedClientAdditionalPaymentData", + "date": "2023-05-02" + }, + { + "id": 5099285054488576, + "version": 111, + "name": "Deprecate and remove PaymentInstruments", + "date": "2023-03-07" + }, + { + "id": 6286595631087616, + "version": 111, + "name": "Deprecate and remove: connect-src CSP bypass in Web Payment API", + "date": "2023-03-07" + }, + { + "id": 5190978431352832, + "version": 111, + "name": "Merchant identity in \"canmakepayment\" event", + "date": "2023-03-07" + }, + { + "id": 5175124599767040, + "version": 110, + "name": "Deprecate and remove WebSQL in non-secure contexts", + "date": "2023-02-07" + }, + { + "id": 5659629104136192, + "version": 110, + "name": "Features: Remove window.webkitStorageInfo", + "date": "2023-02-07" + }, + { + "id": 5726124632965120, + "version": 109, + "name": "Deprecate Event.path", + "date": "2023-01-10" + }, + { + "id": 4906826915643392, + "version": 108, + "name": "Deprecate and remove window.defaultStatus and window.defaultstatus", + "date": "2022-11-29" + }, + { + "id": 4560781148946432, + "version": 108, + "name": "Deprecate and remove ImageDecoderInit.premultiplyAlpha.", + "date": "2022-11-29" + }, + { + "id": 5029730789621760, + "version": 108, + "name": "Removal of navigateEvent.restoreScroll()", + "date": "2022-11-29" + }, + { + "id": 5169970311856128, + "version": 108, + "name": "Removal of navigateEvent.transitionWhile()", + "date": "2022-11-29" + }, + { + "id": 5184046454800384, + "version": 108, + "name": "[WebRTC] Deprecate and Remove mediaConstraint's googIPv6", + "date": "2022-11-29" + }, + { + "id": 6244547273687040, + "version": 107, + "name": "Expect-CT", + "date": "2022-10-25" + }, + { + "id": 5534966262792192, + "version": 106, + "name": "Deprecate non-ASCII characters in cookie domain attributes", + "date": "2022-09-27" + }, + { + "id": 5176235376246784, + "version": 106, + "name": "Deprecate Persistent Quota", + "date": "2022-09-27" + }, + { + "id": 6302414934114304, + "version": 106, + "name": "Remove HTTP/2 push", + "date": "2022-09-27" + }, + { + "id": 5096490737860608, + "version": 105, + "name": "CSS default keyword is disallowed in custom identifiers", + "date": "2022-08-30" + }, + { + "id": 5166018807726080, + "version": 105, + "name": "Gesture Scroll DOM events", + "date": "2022-08-30" + }, + { + "id": 5816343679991808, + "version": 104, + "name": "Block iframe contexts navigating to filesystem: URLs", + "date": "2022-08-02" + }, + { + "id": 5694492182052864, + "version": 104, + "name": "Removing Legacy Client Hint Mode", + "date": "2022-08-02" + }, + { + "id": 5759004926017536, + "version": 104, + "name": "U2F Security Key API removal (Cryptotoken Component Extension)", + "date": "2022-08-02" + }, + { + "id": 4878376799043584, + "version": 103, + "name": "Remove Battery Status API on Insecure Origins", + "date": "2022-06-21" + }, + { + "id": 5823036655665152, + "version": 102, + "name": "[WebRTC] Deprecate and Remove Plan B", + "date": "2022-05-24" + }, + { + "id": 5948593429020672, + "version": 102, + "name": "Calling PaymentRequest.show without user activation", + "date": "2022-05-24" + }, + { + "id": 5684870116278272, + "version": 101, + "name": "Deprecate and remove WebSQL in third-party contexts", + "date": "2022-04-26" + }, + { + "id": 5730051011117056, + "version": 100, + "name": "The \"basic-card\" method of PaymentRequest API", + "date": "2022-03-29" + }, + { + "id": 5667793157488640, + "version": 99, + "name": "Deprecating minor WebCodecs spec violations", + "date": "2022-03-01" + }, + { + "id": 5639265565278208, + "version": 99, + "name": "Remove font-family -webkit-standard", + "date": "2022-03-01" + }, + { + "id": 5695324321480704, + "version": 98, + "name": "Remove SDES key exchange for WebRTC", + "date": "2022-02-01" + }, + { + "id": 5679790780579840, + "version": 95, + "name": "Deprecate support for URLs with non-IPv4 hostnames ending in numbers", + "date": "2021-10-19" + }, + { + "id": 5650158039597056, + "version": 95, + "name": "WebAssembly cross-origin module sharing", + "date": "2021-10-19" + }, + { + "id": 6246151319715840, + "version": 95, + "name": "Remove FTP support", + "date": "2021-10-19" + }, + { + "id": 6192449487634432, + "version": 94, + "name": "Application Cache", + "date": "2021-09-21" + }, + { + "id": 6678134168485888, + "version": 93, + "name": "Remove 3DES in TLS", + "date": "2021-08-31" + }, + { + "id": 5717324021628928, + "version": 92, + "name": "Payment handlers for standardized payment method identifiers.", + "date": "2021-07-20" + }, + { + "id": 4735675582644224, + "version": 91, + "name": "Remove webkitBeforeTextInserted & webkitEditableContentChanged JS events", + "date": "2021-05-25" + }, + { + "id": 5742693948850176, + "version": 90, + "name": "Remove Content Security Policy directive 'plugin-types'", + "date": "2021-04-13" + }, + { + "id": 4925917174431744, + "version": 89, + "name": "Remove prefixed events for ", + "date": "2021-03-02" + }, + { + "id": 5740835259809792, + "version": 89, + "name": "Comma separator in iframe allow attribute", + "date": "2021-03-02" + }, + { + "id": 5643527180517376, + "version": 88, + "name": "Flash Player Support", + "date": "2021-01-19" + }, + { + "id": 5759116003770368, + "version": 84, + "name": "TLS 1.0 and TLS 1.1", + "date": "2020-07-14" + }, + { + "id": 5691978677223424, + "version": 84, + "name": "Blocking insecure downloads from secure (HTTPS) contexts", + "date": "2020-07-14" + }, + { + "id": 5021976655560704, + "version": 78, + "name": "XSS Auditor", + "date": "2019-10-22" + }, + { + "id": 6750456638341120, + "version": 63, + "name": "Shadow-Piercing descendant combinator, '/deep/'", + "date": "2017-12-05" + }, + { + "id": 5718547946799104, + "version": 55, + "name": "Block the load of cross-origin, parser-blocking scripts inserted via document.write() for users on 2G", + "date": "2016-11-29" + }, + { + "id": 5652436521844736, + "version": null, + "name": "Block the load of cross-origin, parser-blocking scripts inserted via document.write() for users on slow connections", + "date": "N/A" + }, + { + "id": 5436853517811712, + "version": null, + "name": "Restrict \"private network requests\" for subresources from public websites to secure contexts. ", + "date": "N/A" + }, + { + "id": 5148698084376576, + "version": null, + "name": "Remove alert(), confirm(), and prompt for cross origin iframes", + "date": "N/A" + }, + { + "id": 5698580851458048, + "version": null, + "name": "Remove special handling of localhost6 and localhost6.localdomain6 hosts", + "date": "N/A" + }, + { + "id": 5668106045227008, + "version": null, + "name": "Special handling of localhost.localdomain host", + "date": "N/A" + }, + { + "id": 5769608873115648, + "version": null, + "name": "Remove SpeechRecognitionEvent's interpretation and emma attributes", + "date": "N/A" + }, + { + "id": 5686951464140800, + "version": null, + "name": "Text encoding detection for XHR JSON response", + "date": "N/A" + }, + { + "id": 5196871120191488, + "version": null, + "name": "Remove font-family -webkit-", + "date": "N/A" + }, + { + "id": 5171427683598336, + "version": null, + "name": "Deprecated and remove font-family: -webkit-pictograph", + "date": "N/A" + }, + { + "id": 5778154275733504, + "version": null, + "name": "Remove author access to :-internal-autofill-previewed and :-internal-autofill-selected", + "date": "N/A" + }, + { + "id": 5668230711476224, + "version": null, + "name": "Human-readable names for Bluetooth assigned numbers", + "date": "N/A" + }, + { + "id": 5632429577469952, + "version": null, + "name": "Remove support for loading media via filesystem:// URLs on Android", + "date": "N/A" + }, + { + "id": 5157284681351168, + "version": null, + "name": "Deprecate 's ability to specify URLs to ", + "date": "N/A" + }, + { + "id": 6283184588193792, + "version": null, + "name": "Deprecate element's functionality", + "date": "N/A" + }, + { + "id": 5160086884843520, + "version": null, + "name": "Removal of X-Requested-With in WebView", + "date": "N/A" + }, + { + "id": 5141814818897920, + "version": null, + "name": "Remove -webkit-perspective-origin-[x,y]", + "date": "N/A" + }, + {"id": 5182546583748608, "version": null, "name": "test 1", "date": "N/A"}, + {"id": 5171075833397248, "version": null, "name": "Test 1", "date": "N/A"}, + { + "id": 5129184450445312, + "version": null, + "name": "Remove ImageEncodeOptions colorSpace and pixelFormat", + "date": "N/A" + }, + { + "id": 5128825141198848, + "version": null, + "name": "data: URL in SVGUseElement", + "date": "N/A" + }, + { + "id": 5090735022407680, + "version": null, + "name": "Calling getDisplayMedia() without user activation", + "date": "N/A" + }, + { + "id": 5087526916718592, + "version": null, + "name": "Remove Prefetch 5-minute Rule", + "date": "N/A" + }, + { + "id": 6539024213213184, + "version": null, + "name": "Deprecate and remove client hint User-Agent full version", + "date": "N/A" + }, + { + "id": 5134293578285056, + "version": null, + "name": "Deprecate and Remove WebSQL", + "date": "N/A" + }, + { + "id": 5199363708551168, + "version": null, + "name": "Block-all-mixed-content CSP directive", + "date": "N/A" + }, + { + "id": 5137018030391296, + "version": null, + "name": "Deprecate and remove \"window-placement\" alias for permission and permission policy \"window-management\"", + "date": "N/A" + }, + { + "id": 5198065940561920, + "version": null, + "name": "[WebRTC] Unship deprecated \"track\" and \"stream\" stats from getStats()", + "date": "N/A" + }, + { + "id": 4832850040324096, + "version": null, + "name": "Deprecate TLS SHA-1 server signatures", + "date": "N/A" + }, + { + "id": 5287151366832128, + "version": null, + "name": "JSON comments in importmap and webbundle scripts", + "date": "N/A" + }, + { + "id": 5066630972833792, + "version": null, + "name": "Remove non-standard -webkit-appearance keywords", + "date": "N/A" + } +] diff --git a/external/testdata/extension-samples.json b/external/testdata/extension-samples.json new file mode 100644 index 000000000..2bfe376eb --- /dev/null +++ b/external/testdata/extension-samples.json @@ -0,0 +1,1383 @@ +[ + { + "type": "API_SAMPLE", + "name": "action", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/action", + "apis": [ + { + "type": "event", + "namespace": "runtime", + "propertyName": "onInstalled" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "isEnabled" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "disable" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "enable" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "setPopup" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "getPopup" + }, + { + "type": "event", + "namespace": "action", + "propertyName": "onClicked" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "getBadgeText" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "setBadgeText" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "getBadgeTextColor" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "setBadgeTextColor" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "getBadgeBackgroundColor" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "setBadgeBackgroundColor" + }, + { + "type": "method", + "namespace": "runtime", + "propertyName": "getURL" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "setIcon" + }, + { + "type": "method", + "namespace": "runtime", + "propertyName": "getManifest" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "setTitle" + }, + { + "type": "method", + "namespace": "action", + "propertyName": "getTitle" + } + ], + "title": "Action API Demo", + "description": "", + "permissions": [] + }, + { + "type": "API_SAMPLE", + "name": "alarms", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/alarms", + "apis": [ + { + "type": "event", + "namespace": "alarms", + "propertyName": "onAlarm" + }, + { + "type": "method", + "namespace": "alarms", + "propertyName": "clear" + }, + { + "type": "method", + "namespace": "alarms", + "propertyName": "create" + }, + { + "type": "method", + "namespace": "alarms", + "propertyName": "clearAll" + }, + { + "type": "method", + "namespace": "alarms", + "propertyName": "getAll" + }, + { + "type": "event", + "namespace": "runtime", + "propertyName": "onInstalled" + }, + { + "type": "type", + "namespace": "runtime", + "propertyName": "OnInstalledReason" + }, + { + "type": "event", + "namespace": "action", + "propertyName": "onClicked" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + } + ], + "title": "Alarms API Demo", + "description": "", + "permissions": [ + "alarms" + ] + }, + { + "type": "API_SAMPLE", + "name": "contentSettings", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/contentSettings", + "apis": [ + { + "type": "method", + "namespace": "tabs", + "propertyName": "query" + } + ], + "title": "Content settings", + "description": "Shows the content settings for the current site.", + "permissions": [ + "contentSettings", + "activeTab" + ] + }, + { + "type": "API_SAMPLE", + "name": "basic", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/contextMenus/basic", + "apis": [ + { + "type": "event", + "namespace": "contextMenus", + "propertyName": "onClicked" + }, + { + "type": "event", + "namespace": "runtime", + "propertyName": "onInstalled" + }, + { + "type": "method", + "namespace": "contextMenus", + "propertyName": "create" + }, + { + "type": "property", + "namespace": "runtime", + "propertyName": "lastError" + } + ], + "title": "Context Menus Sample", + "description": "Shows some of the features of the Context Menus API", + "permissions": [ + "contextMenus" + ] + }, + { + "type": "API_SAMPLE", + "name": "global_context_search", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/contextMenus/global_context_search", + "apis": [ + { + "type": "event", + "namespace": "runtime", + "propertyName": "onInstalled" + }, + { + "type": "method", + "namespace": "contextMenus", + "propertyName": "create" + }, + { + "type": "event", + "namespace": "contextMenus", + "propertyName": "onClicked" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + }, + { + "type": "event", + "namespace": "storage", + "propertyName": "onChanged" + }, + { + "type": "method", + "namespace": "contextMenus", + "propertyName": "remove" + }, + { + "type": "property", + "namespace": "storage", + "propertyName": "sync" + } + ], + "title": "Global Google Search", + "description": "Use the context menu to search a different country's Google", + "permissions": [ + "contextMenus", + "storage" + ] + }, + { + "type": "API_SAMPLE", + "name": "cookie-clearer", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/cookies/cookie-clearer", + "apis": [ + { + "type": "method", + "namespace": "tabs", + "propertyName": "query" + }, + { + "type": "method", + "namespace": "cookies", + "propertyName": "getAll" + }, + { + "type": "method", + "namespace": "cookies", + "propertyName": "remove" + } + ], + "title": "Cookie Clearer", + "description": "", + "permissions": [ + "cookies" + ] + }, + { + "type": "API_SAMPLE", + "name": "no-cookies", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/declarativeNetRequest/no-cookies", + "apis": [ + { + "type": "event", + "namespace": "declarativeNetRequest", + "propertyName": "onRuleMatchedDebug" + } + ], + "title": "No Cookies", + "description": "Removes 'Cookie' headers.", + "permissions": [ + "declarativeNetRequest", + "declarativeNetRequestFeedback" + ] + }, + { + "type": "API_SAMPLE", + "name": "url-blocker", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/declarativeNetRequest/url-blocker", + "apis": [ + { + "type": "event", + "namespace": "declarativeNetRequest", + "propertyName": "onRuleMatchedDebug" + } + ], + "title": "URL Blocker", + "description": "Blocks all main frame requests to example.com.", + "permissions": [ + "declarativeNetRequest", + "declarativeNetRequestFeedback" + ] + }, + { + "type": "API_SAMPLE", + "name": "url-redirect", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/declarativeNetRequest/url-redirect", + "apis": [ + { + "type": "event", + "namespace": "declarativeNetRequest", + "propertyName": "onRuleMatchedDebug" + } + ], + "title": "URL Redirect", + "description": "Redirects MV2 documents to equivalent MV3 documents on developer.chrome.com.", + "permissions": [ + "declarativeNetRequest", + "declarativeNetRequestFeedback", + "declarativeNetRequestWithHostAccess" + ] + }, + { + "type": "API_SAMPLE", + "name": "default_command_override", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/default_command_override", + "apis": [ + { + "type": "event", + "namespace": "commands", + "propertyName": "onCommand" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "query" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "update" + } + ], + "title": "Tab Flipper", + "description": "Press Ctrl+Shift+Right or Ctrl+Shift+Left (Command+Shift+Right or Command+Shift+Left on a Mac) to flip through window tabs", + "permissions": [] + }, + { + "type": "API_SAMPLE", + "name": "favicon", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/favicon", + "apis": [ + { + "type": "method", + "namespace": "runtime", + "propertyName": "getURL" + } + ], + "title": "Favicon API in a popup", + "description": "", + "permissions": [ + "favicon" + ] + }, + { + "type": "API_SAMPLE", + "name": "showHistory", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/history/showHistory", + "apis": [ + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + }, + { + "type": "method", + "namespace": "history", + "propertyName": "search" + }, + { + "type": "method", + "namespace": "history", + "propertyName": "getVisits" + } + ], + "title": "Typed URL History", + "description": "Reads your history and uses the transition type of each history item to filter out a list of the top ten pages you go to specifically by typing the URL.", + "permissions": [ + "history" + ] + }, + { + "type": "API_SAMPLE", + "name": "il8n", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/il8n", + "apis": [ + { + "type": "method", + "namespace": "i18n", + "propertyName": "getMessage" + } + ], + "title": "il8n API Example", + "description": "", + "permissions": [ + "activeTab" + ] + }, + { + "type": "API_SAMPLE", + "name": "new-tab-search", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/omnibox/new-tab-search", + "apis": [ + { + "type": "event", + "namespace": "omnibox", + "propertyName": "onInputEntered" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + } + ], + "title": "Omnibox - New Tab Search", + "description": "Type 'nt' plus a search term into the Omnibox to open search in new tab.", + "permissions": [] + }, + { + "type": "API_SAMPLE", + "name": "blank_ntp", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/override/blank_ntp", + "apis": [], + "title": "Blank new tab page", + "description": "Override the new tab page with a blank one", + "permissions": [] + }, + { + "type": "API_SAMPLE", + "name": "printing", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/printing", + "apis": [ + { + "type": "method", + "namespace": "printing", + "propertyName": "submitJob" + }, + { + "type": "property", + "namespace": "runtime", + "propertyName": "lastError" + }, + { + "type": "method", + "namespace": "printing", + "propertyName": "cancelJob" + }, + { + "type": "method", + "namespace": "printing", + "propertyName": "getPrinters" + }, + { + "type": "method", + "namespace": "printing", + "propertyName": "getPrinterInfo" + }, + { + "type": "event", + "namespace": "printing", + "propertyName": "onJobStatusChanged" + } + ], + "title": "Print Extension", + "description": "Sends print job directly to the printers installed on the Chromebook", + "permissions": [ + "printing" + ] + }, + { + "type": "API_SAMPLE", + "name": "sandbox", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/sandbox/sandbox", + "apis": [ + { + "type": "event", + "namespace": "runtime", + "propertyName": "onInstalled" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + } + ], + "title": "Sandboxed Frame Sample", + "description": "", + "permissions": [] + }, + { + "type": "API_SAMPLE", + "name": "sandboxed-content", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/sandbox/sandboxed-content", + "apis": [ + { + "type": "event", + "namespace": "runtime", + "propertyName": "onInstalled" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + } + ], + "title": "Sandboxed Content Sample", + "description": "", + "permissions": [] + }, + { + "type": "API_SAMPLE", + "name": "scripting", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/scripting", + "apis": [ + { + "type": "event", + "namespace": "action", + "propertyName": "onClicked" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "create" + }, + { + "type": "event", + "namespace": "webNavigation", + "propertyName": "onDOMContentLoaded" + }, + { + "type": "property", + "namespace": "storage", + "propertyName": "local" + }, + { + "type": "method", + "namespace": "scripting", + "propertyName": "executeScript" + }, + { + "type": "event", + "namespace": "runtime", + "propertyName": "onMessage" + }, + { + "type": "method", + "namespace": "scripting", + "propertyName": "getRegisteredContentScripts" + }, + { + "type": "method", + "namespace": "scripting", + "propertyName": "unregisterContentScripts" + }, + { + "type": "method", + "namespace": "runtime", + "propertyName": "sendMessage" + }, + { + "type": "method", + "namespace": "scripting", + "propertyName": "registerContentScripts" + } + ], + "title": "Scripting API Demo", + "description": "", + "permissions": [ + "scripting", + "webNavigation", + "storage" + ] + }, + { + "type": "API_SAMPLE", + "name": "tabCapture", + "repo_link": "https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/tabCapture", + "apis": [ + { + "type": "property", + "namespace": "runtime", + "propertyName": "lastError" + }, + { + "type": "method", + "namespace": "tabCapture", + "propertyName": "getMediaStreamId" + }, + { + "type": "event", + "namespace": "runtime", + "propertyName": "onMessage" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "query" + }, + { + "type": "method", + "namespace": "runtime", + "propertyName": "getURL" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "remove" + }, + { + "type": "event", + "namespace": "action", + "propertyName": "onClicked" + }, + { + "type": "method", + "namespace": "windows", + "propertyName": "create" + }, + { + "type": "event", + "namespace": "tabs", + "propertyName": "onUpdated" + }, + { + "type": "method", + "namespace": "tabs", + "propertyName": "sendMessage" + } + ], + "title": "Tab Capture Example", + "description": "Capture a tab and play in a