setting to exclude proxy from global routing

This commit is contained in:
Fabian Freund
2026-05-27 09:23:18 +02:00
parent 2812c24f35
commit 7275ac9400
27 changed files with 661 additions and 65 deletions
@@ -44,6 +44,16 @@ class GeckoContainerProxyApiImpl : GeckoContainerProxyApi {
)
}
override fun setContainerDirectConnection(contextId: String, scopeId: String) {
ContainerProxyFeature.scheduleRequest(
"setContainerDirectConnection",
JSONObject().apply {
put("contextId", contextId)
put("scopeId", scopeId)
}
)
}
override fun clearContainerProxy(contextId: String) {
ContainerProxyFeature.scheduleRequest("clearContainerProxy", contextId)
}
@@ -8412,6 +8412,7 @@ interface GeckoContainerProxyApi {
fun upsertProxy(proxy: GeckoProxySettings)
fun removeProxy(proxyId: String)
fun setContainerProxy(contextId: String, proxyId: String)
fun setContainerDirectConnection(contextId: String, scopeId: String)
fun clearContainerProxy(contextId: String)
fun removeContainerProxyRelation(contextId: String, proxyId: String)
fun setSiteAssignments(assignments: Map<String, String>)
@@ -8535,6 +8536,25 @@ interface GeckoContainerProxyApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setContainerDirectConnection$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val contextIdArg = args[0] as String
val scopeIdArg = args[1] as String
val wrapped: List<Any?> = try {
api.setContainerDirectConnection(contextIdArg, scopeIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.clearContainerProxy$separatedMessageChannelSuffix", codec)
if (api != null) {
@@ -14,6 +14,7 @@ interface Message {
'upsertProxy' |
'removeProxy' |
'setContainerProxy' |
'setContainerDirectConnection' |
'clearContainerProxy' |
'removeContainerProxyRelation' |
'healthcheck' |
@@ -69,6 +70,15 @@ port.onMessage.addListener((raw: unknown): void => {
store.setContainerProxyRelation(message.args.contextId, message.args.proxyId)
console.log('set container relation ' + message.args.contextId + ' -> ' + message.args.proxyId)
break
case "setContainerDirectConnection":
if (typeof message.args === 'string') {
store.setContainerDirectRelation(message.args)
console.log('set container direct relation ' + message.args)
} else {
store.setContainerDirectRelation(message.args.contextId, message.args.scopeId)
console.log('set container direct relation ' + message.args.contextId + ' scoped to ' + message.args.scopeId)
}
break
case "clearContainerProxy":
store.clearContainerProxyRelation(message.args)
console.log('cleared container relation ' + message.args)
@@ -84,6 +84,7 @@ interface WildcardAssignment {
export class Store {
private proxies: ProxyDao[] = []
private relations: { [key: string]: string[] } = {}
private directRelationScopes: { [key: string]: string } = {}
private siteAssignments: Map<string, string> = new Map<string, string>()
private wildcardAssignments: WildcardAssignment[] = []
@@ -147,15 +148,35 @@ export class Store {
return this.lookupAssignment(uri) !== undefined
}
private hasRelation(contextId: string): boolean {
return Object.prototype.hasOwnProperty.call(this.relations, contextId)
}
/**
* Returns the effective proxy relation for a context ID, mirroring the
* fallback logic in getProxiesForContainer: explicit relation first,
* then 'general' for non-private contexts, then empty.
* Returns the effective proxy relation for a context ID, preserving the
* difference between "no explicit relation" (undefined, may inherit) and
* "explicit direct connection" ([]).
*/
private getEffectiveRelation(contextId: string): string[] {
return this.relations[contextId]
?? ((contextId !== 'private') ? this.relations['general'] : undefined)
?? [];
private getEffectiveRelation(contextId: string): string[] | undefined {
if (this.hasRelation(contextId)) {
return this.relations[contextId]
}
if (contextId !== 'private' && this.hasRelation('general')) {
return this.relations['general']
}
return undefined
}
private getEffectiveDirectScope(contextId: string): string | undefined {
if (this.hasRelation(contextId) && this.relations[contextId].length === 0) {
return this.directRelationScopes[contextId] ?? contextId
}
if (contextId !== 'private' &&
this.hasRelation('general') &&
this.relations['general'].length === 0) {
return this.directRelationScopes['general'] ?? 'general'
}
return undefined
}
isSiteOriginInSameContext(uri: URL, contextId: string): boolean {
@@ -169,10 +190,19 @@ export class Store {
const assignedRelation = this.getEffectiveRelation(assignedContextId);
const currentRelation = this.getEffectiveRelation(contextId);
// Only treat as equivalent if both have actual proxy relations —
// empty relations mean no proxy, and different non-proxied contexts
// should not be considered equivalent.
if (assignedRelation.length > 0 &&
const assignedDirectScope = this.getEffectiveDirectScope(assignedContextId)
const currentDirectScope = this.getEffectiveDirectScope(contextId)
if (assignedDirectScope !== undefined || currentDirectScope !== undefined) {
return assignedDirectScope !== undefined &&
assignedDirectScope === currentDirectScope
}
// Treat proxy-routed contexts as equivalent only when both resolve to an
// explicit non-direct relation. Direct relations are scoped above so two
// unrelated bypassed containers do not collapse into the same context.
if (assignedRelation !== undefined &&
currentRelation !== undefined &&
assignedRelation.length > 0 &&
assignedRelation.length === currentRelation.length &&
assignedRelation.every((id, i) => id === currentRelation[i])) {
return true;
@@ -222,10 +252,17 @@ export class Store {
setContainerProxyRelation(cookieStoreId: string, proxyId: string): void {
this.relations[cookieStoreId] = [proxyId]
delete this.directRelationScopes[cookieStoreId]
}
setContainerDirectRelation(cookieStoreId: string, scopeId: string = cookieStoreId): void {
this.relations[cookieStoreId] = []
this.directRelationScopes[cookieStoreId] = scopeId
}
clearContainerProxyRelation(cookieStoreId: string): void {
delete this.relations[cookieStoreId]
delete this.directRelationScopes[cookieStoreId]
}
removeContainerProxyRelation(cookieStoreId: string, proxyId: string): void {
@@ -172,6 +172,17 @@ describe('Store', () => {
expect(result).to.be.deep.equal([])
})
it('should let an explicit direct relation bypass the general relation', () => {
const isolatedStore = new Store()
isolatedStore.putProxy(someProxyWith('global-proxy'))
isolatedStore.setContainerProxyRelation('general', 'global-proxy')
isolatedStore.setContainerDirectRelation('container1')
const result = isolatedStore.getProxiesForContainer('container1')
expect(result).to.be.null
})
})
describe('wildcard site assignments', function () {
@@ -247,5 +258,24 @@ describe('Store', () => {
const result = store.isSiteOriginInSameContext(new URL('https://blocked.example/another'), 'iso1_blocked')
expect(result).to.be.equal(false)
})
it('should allow direct aliases scoped to the assigned container', async () => {
store.setSiteAssignments(new Map([['https://direct.example/path', 'container_direct']]))
await store.setContainerProxyRelation('general', 'proxy_global')
await store.setContainerDirectRelation('container_direct')
await store.setContainerDirectRelation('iso1_direct', 'container_direct')
const result = store.isSiteOriginInSameContext(new URL('https://direct.example/another'), 'iso1_direct')
expect(result).to.be.equal(true)
})
it('should not allow unrelated direct containers as equivalent', async () => {
store.setSiteAssignments(new Map([['https://direct.example/path', 'container_direct']]))
await store.setContainerDirectRelation('container_direct')
await store.setContainerDirectRelation('other_direct')
const result = store.isSiteOriginInSameContext(new URL('https://direct.example/another'), 'other_direct')
expect(result).to.be.equal(false)
})
})
})
@@ -36,6 +36,13 @@ class GeckoContainerProxyService {
return _apiInstance.setContainerProxy(contextId, proxyId);
}
Future<void> setContainerDirectConnection(
String contextId, {
required String scopeId,
}) {
return _apiInstance.setContainerDirectConnection(contextId, scopeId);
}
Future<void> clearContainerProxy(String contextId) {
return _apiInstance.clearContainerProxy(contextId);
}
@@ -8799,6 +8799,29 @@ class GeckoContainerProxyApi {
);
}
Future<void> setContainerDirectConnection(
String contextId,
String scopeId,
) async {
final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setContainerDirectConnection$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
<Object?>[contextId, scopeId],
);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
_extractReplyValueOrThrow(
pigeonVar_replyList,
pigeonVar_channelName,
isNullValid: true,
);
}
Future<void> clearContainerProxy(String contextId) async {
final pigeonVar_channelName =
'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.clearContainerProxy$pigeonVar_messageChannelSuffix';
@@ -1888,6 +1888,7 @@ abstract class GeckoContainerProxyApi {
void upsertProxy(GeckoProxySettings proxy);
void removeProxy(String proxyId);
void setContainerProxy(String contextId, String proxyId);
void setContainerDirectConnection(String contextId, String scopeId);
void clearContainerProxy(String contextId);
void removeContainerProxyRelation(String contextId, String proxyId);
void setSiteAssignments(Map<String, String> assignments);