Skip to content

Commit 27ffa52

Browse files
Add patching changes
1 parent 847694d commit 27ffa52

File tree

4 files changed

+83
-27
lines changed

4 files changed

+83
-27
lines changed

injected/src/config-feature.js

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { immutableJSONPatch } from 'immutable-json-patch';
22
import { camelcase, computeEnabledFeatures, matchHostname, parseFeatureSettings } from './utils.js';
3+
import { URLPattern } from 'urlpattern-polyfill';
34

45
export default class ConfigFeature {
56
/** @type {import('./utils.js').RemoteConfig | undefined} */
@@ -48,19 +49,55 @@ export default class ConfigFeature {
4849
* @protected
4950
*/
5051
matchDomainFeatureSetting(featureKeyName) {
51-
const domain = this.args?.site.domain;
52-
if (!domain) return [];
5352
const domains = this._getFeatureSettings()?.[featureKeyName] || [];
5453
return domains.filter((rule) => {
55-
if (Array.isArray(rule.domain)) {
56-
return rule.domain.some((domainRule) => {
57-
return matchHostname(domain, domainRule);
58-
});
59-
}
60-
return matchHostname(domain, rule.domain);
54+
return this.matchConditionalChanges(rule);
6155
});
6256
}
6357

58+
/**
59+
* Used to match conditional changes for a settings feature.
60+
* @typedef {object} ConditionBlock
61+
* @property {string[] | string} domain?
62+
* @property {object} urlPattern?
63+
*/
64+
65+
/**
66+
* Takes a conditional block and returns true if it applies.
67+
* All conditions must be met to return true.
68+
* @param {ConditionBlock} conditionBlock
69+
* @returns {boolean}
70+
*/
71+
matchConditionalChanges(conditionBlock) {
72+
// Check domain condition
73+
if (conditionBlock.domain && !this._matchDomainConditional(conditionBlock)) {
74+
return false;
75+
}
76+
77+
// Check URL pattern condition
78+
if (conditionBlock.urlPattern) {
79+
const pattern = new URLPattern(conditionBlock.urlPattern);
80+
if (!pattern.test(this.args?.site.url)) {
81+
return false;
82+
}
83+
}
84+
85+
// All conditions are met
86+
return true;
87+
}
88+
89+
_matchDomainConditional(conditionBlock) {
90+
if (!conditionBlock.domain) return false;
91+
const domain = this.args?.site.domain;
92+
if (!domain) return false;
93+
if (Array.isArray(conditionBlock.domain)) {
94+
return conditionBlock.domain.some((domainRule) => {
95+
return matchHostname(domain, domainRule);
96+
});
97+
}
98+
return matchHostname(domain, conditionBlock.domain);
99+
}
100+
64101
/**
65102
* Return the settings object for a feature
66103
* @param {string} [featureName] - The name of the feature to get the settings for; defaults to the name of the feature
@@ -131,13 +168,14 @@ export default class ConfigFeature {
131168
*/
132169
getFeatureSetting(featureKeyName, featureName) {
133170
let result = this._getFeatureSettings(featureName);
134-
if (featureKeyName === 'domains') {
135-
throw new Error('domains is a reserved feature setting key name');
171+
if (featureKeyName in ['domains', 'conditionalChanges']) {
172+
throw new Error(`${featureKeyName} is a reserved feature setting key name`);
136173
}
137-
const domainMatch = [...this.matchDomainFeatureSetting('domains')].sort((a, b) => {
138-
return a.domain.length - b.domain.length;
139-
});
140-
for (const match of domainMatch) {
174+
// We only support one of these keys at a time, where conditionalChanges takes precedence
175+
// TODO should we rename these?
176+
// TODO should we only support conditionalChanges to support other types of settings?
177+
const conditionalMatch = this.matchDomainFeatureSetting('conditionalChanges') || this.matchDomainFeatureSetting('domains');
178+
for (const match of conditionalMatch) {
141179
if (match.patchSettings === undefined) {
142180
continue;
143181
}

injected/src/utils.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,31 +118,39 @@ export function hasThirdPartyOrigin(scriptOrigins) {
118118
}
119119

120120
/**
121-
* Best guess effort of the tabs hostname; where possible always prefer the args.site.domain
122-
* @returns {string|null} inferred tab hostname
121+
* @returns {URL | null}
123122
*/
124-
export function getTabHostname() {
125-
let framingOrigin = null;
123+
export function getTabUrl() {
124+
let framingURLString = null;
126125
try {
127126
// @ts-expect-error - globalThis.top is possibly 'null' here
128-
framingOrigin = globalThis.top.location.href;
127+
framingURLString = globalThis.top.location.href;
129128
} catch {
130-
framingOrigin = globalThis.document.referrer;
129+
framingURLString = globalThis.document.referrer;
131130
}
132131

133132
// Not supported in Firefox
134133
if ('ancestorOrigins' in globalThis.location && globalThis.location.ancestorOrigins.length) {
135134
// ancestorOrigins is reverse order, with the last item being the top frame
136-
framingOrigin = globalThis.location.ancestorOrigins.item(globalThis.location.ancestorOrigins.length - 1);
135+
framingURLString = globalThis.location.ancestorOrigins.item(globalThis.location.ancestorOrigins.length - 1);
137136
}
138137

138+
let framingURL;
139139
try {
140140
// @ts-expect-error - framingOrigin is possibly 'null' here
141-
framingOrigin = new URL(framingOrigin).hostname;
141+
framingURL = new URL(framingURLString)
142142
} catch {
143-
framingOrigin = null;
143+
framingURL = null;
144144
}
145-
return framingOrigin;
145+
return framingURL;
146+
}
147+
148+
/**
149+
* Best guess effort of the tabs hostname; where possible always prefer the args.site.domain
150+
* @returns {string|null} inferred tab hostname
151+
*/
152+
export function getTabHostname() {
153+
return getTabUrl()?.hostname || null;
146154
}
147155

148156
/**
@@ -524,6 +532,7 @@ export function computeLimitedSiteObject() {
524532
const topLevelHostname = getTabHostname();
525533
return {
526534
domain: topLevelHostname,
535+
url: getTabUrl()
527536
};
528537
}
529538

package-lock.json

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"stylelint-csstree-validator": "^3.0.0",
5050
"typedoc": "^0.27.9",
5151
"typescript": "^5.8.2",
52-
"typescript-eslint": "^8.27.0"
52+
"typescript-eslint": "^8.27.0",
53+
"urlpattern-polyfill": "^10.0.0"
5354
},
5455
"dependencies": {
5556
"immutable-json-patch": "^6.0.1"

0 commit comments

Comments
 (0)