Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion tests/lib/feature-query.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava';
import { FeatureQuery } from '../../tools/lib/feature-query.js';
import { FeatureQuery, FeatureQueryModern } from '../../tools/lib/feature-query.js';

test('most stable channel is preferred', (t) => {
const featureQuery = new FeatureQuery({});
Expand All @@ -25,6 +25,28 @@ test('extension features are preferred', (t) => {
);
});

test('extension_types "all" is preferred like an explicit list', (t) => {
const featureQuery = new FeatureQuery({});

// Chrome's feature files may give extension_types as the string "all" rather than an array
// (see feature_compiler.py's `allow_all`). That still has to count as including "extension".
t.deepEqual(
featureQuery.mergeComplexFeature([
{ channel: 'stable', extension_types: ['hosted_app'] },
{ channel: 'stable', min_manifest_version: 3, extension_types: 'all' }
]),
{ channel: 'stable', min_manifest_version: 3, extension_types: 'all' }
);
});

test('MV3+ filter keeps extension_types "all"', (t) => {
// This is the site that dropped runtime.lastError: extension_types: "all" has to pass the
// same as an explicit array containing "extension".
const featureQuery = new FeatureQueryModern({});

t.assert(featureQuery.filter({ channel: 'stable', extension_types: 'all' }));
});

test('non-location specific features are preferred', (t) => {
const featureQuery = new FeatureQuery({});

Expand Down
46 changes: 43 additions & 3 deletions tools/lib/feature-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class FeatureQuery {
}

// Filter by extension (prefer).
const extensionFilter = q.filter(({ extension_types }) => extension_types?.includes('extension'));
const extensionFilter = q.filter(({ extension_types }) => extensionTypesInclude(extension_types, 'extension'));
if (extensionFilter.length === 1) {
return extensionFilter[0];
}
Expand Down Expand Up @@ -214,6 +214,46 @@ export class FeatureQuery {
}


/**
* Checks whether an `extension_types` value includes the given type. Chrome's feature compiler
* allows this field to be the literal string "all" instead of a list, meaning every extension
* type is allowed, so a plain `includes` call would run as a substring test and misread "all" as
* excluding everything.
*
* @param {chromeTypes.MinArray<chromeTypes.ExtensionType>|chromeTypes.All|undefined} extensionTypes
* @param {chromeTypes.ExtensionType} type
* @return {boolean}
*/
export function extensionTypesInclude(extensionTypes, type) {
return extensionTypes === 'all' || (extensionTypes?.includes(type) ?? false);
}

/**
* A FeatureQuery that only allows APIs available in MV3+, used to render the main chrome-types
* bundle (everything except the `-a`/`--all` output, which keeps MV2 and Platform Apps too).
*/
export class FeatureQueryModern extends FeatureQuery {

/**
* @param {chromeTypes.FeatureSpec} f
* @return {boolean}
*/
filter(f) {
// Don't show anything that maxes out before MV3.
if (f.max_manifest_version && f.max_manifest_version < 3) {
return false;
}

// Remove non-extension APIs.
if (f.extension_types && !extensionTypesInclude(f.extension_types, 'extension')) {
return false;
}

return super.filter(f);
}

}

/**
* @param {chromeTypes.FeatureSpec} f
* @return {boolean}
Expand Down Expand Up @@ -243,10 +283,10 @@ function basicFilter(f) {
}

// Only allow a limited set of extension types.
if (Array.isArray(f.extension_types)) {
if (f.extension_types !== undefined) {
/** @type {chromeTypes.ExtensionType[]} */
const allowedTypes = ['extension', 'platform_app'];
const ok = allowedTypes.some((check) => f.extension_types?.includes(check));
const ok = allowedTypes.some((check) => extensionTypesInclude(f.extension_types, check));
if (!ok) {
return false;
}
Expand Down
6 changes: 3 additions & 3 deletions tools/override.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as chromeTypes from '../types/chrome.js';
import * as overrideTypes from '../types/override.js';
import { leastReleasedChannel, mostReleasedChannel } from './lib/channel.js';
import { buildNamespaceAwareMarkdownRewrite } from './lib/comment.js';
import { FeatureQuery } from './lib/feature-query.js';
import { FeatureQuery, extensionTypesInclude } from './lib/feature-query.js';
import { namespaceNameFromId, parentId } from './lib/traverse.js';
import { isDeepEqual } from './lib/equal.js';

Expand Down Expand Up @@ -182,7 +182,7 @@ export class RenderOverride extends EmptyRenderOverride {
* @param {string} id
*/
isPlatformAppsOnly(id) {
return !this.#fq.checkFeature(id, (f) => !f.extension_types || [...f.extension_types].some((t) => t !== "platform_app"));
return !this.#fq.checkFeature(id, (f) => !f.extension_types || f.extension_types === 'all' || f.extension_types.some((t) => t !== "platform_app"));
}

/**
Expand Down Expand Up @@ -546,7 +546,7 @@ export class RenderOverride extends EmptyRenderOverride {

(f.platforms ?? []).forEach((platform) => platforms.add(platform));

if (f.extension_types && !f.extension_types.includes('extension') && f.extension_types.includes('platform_app')) {
if (f.extension_types && !extensionTypesInclude(f.extension_types, 'extension') && extensionTypesInclude(f.extension_types, 'platform_app')) {
isOnlyPlatformApps = true;
}

Expand Down
25 changes: 1 addition & 24 deletions tools/render-tsd.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import * as chromeTypes from '../types/chrome.js';
import * as fs from 'fs';
import mri from 'mri';
import { RenderContext } from './lib/render-context.js';
import { FeatureQuery } from './lib/feature-query.js';
import { FeatureQuery, FeatureQueryModern } from './lib/feature-query.js';
import log from 'fancy-log';
import { RenderOverride } from './override.js';

Expand Down Expand Up @@ -148,27 +148,4 @@ function loadHistory(path) {
}


class FeatureQueryModern extends FeatureQuery {

/**
* @param {chromeTypes.FeatureSpec} f
* @return {boolean}
*/
filter(f) {
// Don't show anything that maxes out before MV3.
if (f.max_manifest_version && f.max_manifest_version < 3) {
return false;
}

// Remove non-extension APIs.
if (f.extension_types && !f.extension_types.includes('extension')) {
return false;
}

return super.filter(f);
}

}


await run();
2 changes: 1 addition & 1 deletion types/chrome.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type MinArray<T> = T[];
export type Channel = 'stable' | 'beta' | 'dev' | 'canary' | 'trunk';
export type Platform = 'chromeos' | 'lacros' | 'linux' | 'mac' | 'win' | 'desktop_android';
export type Context = 'blessed_extension' | 'blessed_web_page' | 'content_script' | 'lock_screen_extension' | 'web_page' | 'webui' | 'webui_untrusted' | 'unblessed_extension';
export type ExtensionType = 'extension' | 'hosted_app' | 'legacy_packaged_app' | 'platform_app' | 'shared_module' | 'theme' | 'login_screen_extension';
export type ExtensionType = 'extension' | 'hosted_app' | 'legacy_packaged_app' | 'platform_app' | 'shared_module' | 'theme' | 'login_screen_extension' | 'chromeos_system_extension';
export type All = 'all';
export type SessionType = 'regular' | 'kiosk' | 'kiosk.autolaunched';
export type Location = 'component' | 'external_component' | 'policy' | 'unpacked';
Expand Down
Loading