container strict mode feature
This commit is contained in:
+4
@@ -72,6 +72,10 @@ class GeckoContainerProxyApiImpl : GeckoContainerProxyApi {
|
||||
ContainerProxyFeature.scheduleRequest("setSiteAssignments", JSONObject(assignments))
|
||||
}
|
||||
|
||||
override fun setStrictContexts(contexts: Map<String, List<String>>) {
|
||||
ContainerProxyFeature.scheduleRequest("setStrictContexts", JSONObject(contexts))
|
||||
}
|
||||
|
||||
override fun healthcheck(callback: (Result<Boolean>) -> Unit) {
|
||||
ContainerProxyFeature.scheduleRequestWithResponse("healthcheck", Unit, object :
|
||||
ResultConsumer<JSONObject> {
|
||||
|
||||
+2
-1
@@ -122,7 +122,8 @@ object ContainerProxyFeature {
|
||||
tabId = components.core.store.state.selectedTabId,
|
||||
originUrl = details.tryGetString("originUrl"),
|
||||
url = details.getString("url"),
|
||||
blocked = details.getBoolean("blocked")
|
||||
blocked = details.getBoolean("blocked"),
|
||||
strict = details.optBoolean("strict", false)
|
||||
)
|
||||
) { _ -> }
|
||||
}
|
||||
|
||||
+43
-5
@@ -4658,7 +4658,14 @@ data class ContainerSiteAssignment (
|
||||
val tabId: String? = null,
|
||||
val originUrl: String? = null,
|
||||
val url: String,
|
||||
val blocked: Boolean
|
||||
val blocked: Boolean,
|
||||
/**
|
||||
* True when the navigation was cancelled because the tab's container is in
|
||||
* strict mode and the target origin is not assigned to it (as opposed to an
|
||||
* ordinary re-open-in-assigned-container block). Strict blocks have no
|
||||
* destination container; the app just surfaces a message.
|
||||
*/
|
||||
val strict: Boolean
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
@@ -4668,7 +4675,8 @@ data class ContainerSiteAssignment (
|
||||
val originUrl = pigeonVar_list[2] as String?
|
||||
val url = pigeonVar_list[3] as String
|
||||
val blocked = pigeonVar_list[4] as Boolean
|
||||
return ContainerSiteAssignment(requestId, tabId, originUrl, url, blocked)
|
||||
val strict = pigeonVar_list[5] as Boolean
|
||||
return ContainerSiteAssignment(requestId, tabId, originUrl, url, blocked, strict)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
@@ -4678,6 +4686,7 @@ data class ContainerSiteAssignment (
|
||||
originUrl,
|
||||
url,
|
||||
blocked,
|
||||
strict,
|
||||
)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -4688,7 +4697,7 @@ data class ContainerSiteAssignment (
|
||||
return true
|
||||
}
|
||||
val other = other as ContainerSiteAssignment
|
||||
return GeckoPigeonUtils.deepEquals(this.requestId, other.requestId) && GeckoPigeonUtils.deepEquals(this.tabId, other.tabId) && GeckoPigeonUtils.deepEquals(this.originUrl, other.originUrl) && GeckoPigeonUtils.deepEquals(this.url, other.url) && GeckoPigeonUtils.deepEquals(this.blocked, other.blocked)
|
||||
return GeckoPigeonUtils.deepEquals(this.requestId, other.requestId) && GeckoPigeonUtils.deepEquals(this.tabId, other.tabId) && GeckoPigeonUtils.deepEquals(this.originUrl, other.originUrl) && GeckoPigeonUtils.deepEquals(this.url, other.url) && GeckoPigeonUtils.deepEquals(this.blocked, other.blocked) && GeckoPigeonUtils.deepEquals(this.strict, other.strict)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
@@ -4698,6 +4707,7 @@ data class ContainerSiteAssignment (
|
||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.originUrl)
|
||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.url)
|
||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.blocked)
|
||||
result = 31 * result + GeckoPigeonUtils.deepHash(this.strict)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -8622,6 +8632,16 @@ interface GeckoContainerProxyApi {
|
||||
fun clearContainerProxy(contextId: String)
|
||||
fun removeContainerProxyRelation(contextId: String, proxyId: String)
|
||||
fun setSiteAssignments(assignments: Map<String, String>)
|
||||
/**
|
||||
* Strict-mode enforcement map. Keys are Gecko cookie-store contexts to
|
||||
* enforce (a strict container's base context plus its isolated tabs'
|
||||
* isolation contexts); each value contains the container base contexts that
|
||||
* site assignments are keyed on. Tabs in these contexts may only load
|
||||
* origins assigned to one of the mapped base contexts (exact match, no proxy
|
||||
* equivalence); any other top-level navigation is cancelled and reported
|
||||
* back with `strict = true`.
|
||||
*/
|
||||
fun setStrictContexts(contexts: Map<String, List<String>>)
|
||||
fun healthcheck(callback: (Result<Boolean>) -> Unit)
|
||||
|
||||
companion object {
|
||||
@@ -8816,6 +8836,24 @@ interface GeckoContainerProxyApi {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setStrictContexts$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val contextsArg = args[0] as Map<String, List<String>>
|
||||
val wrapped: List<Any?> = try {
|
||||
api.setStrictContexts(contextsArg)
|
||||
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.healthcheck$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
@@ -8980,7 +9018,7 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
} else {
|
||||
callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onEngineReadyStateChange(sequenceArg: Long, stateArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||
@@ -10375,7 +10413,7 @@ class GeckoHistoryEvents(private val binaryMessenger: BinaryMessenger, private v
|
||||
}
|
||||
} else {
|
||||
callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+57
-13
@@ -149,19 +149,36 @@ export default class BackgroundMain {
|
||||
}
|
||||
|
||||
const url = URL.parse(options.url);
|
||||
if (url !== null && this.store.isSiteOriginAssigned(url)) {
|
||||
let cookieStoreId: string
|
||||
if (url === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) {
|
||||
cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
|
||||
} else if (tab.cookieStoreId === privateIdentifier) {
|
||||
// Handle private tabs - use 'private' as identifier
|
||||
cookieStoreId = 'private'
|
||||
} else {
|
||||
cookieStoreId = 'general'
|
||||
}
|
||||
let cookieStoreId: string
|
||||
|
||||
if (cookieStoreId == 'private' || this.store.isSiteOriginInSameContext(url, cookieStoreId)) {
|
||||
if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) {
|
||||
cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
|
||||
} else if (tab.cookieStoreId === privateIdentifier) {
|
||||
// Handle private tabs - use 'private' as identifier
|
||||
cookieStoreId = 'private'
|
||||
} else {
|
||||
cookieStoreId = 'general'
|
||||
}
|
||||
|
||||
// Private tabs are never strict. For other tabs, strict enforcement is
|
||||
// keyed on the tab's cookie-store context (base container context, or an
|
||||
// isolation context of a strict container's isolated tab).
|
||||
const isStrict = cookieStoreId !== 'private' &&
|
||||
this.store.isContextStrict(cookieStoreId)
|
||||
|
||||
if (this.store.isSiteOriginAssigned(url)) {
|
||||
// In a strict context the site must be assigned to *this* container's own
|
||||
// base context (exact match, no proxy equivalence). Non-strict contexts
|
||||
// keep the looser proxy/direct-equivalence matching used for routing.
|
||||
const allowed = isStrict
|
||||
? this.store.isSiteOriginStrictlyAllowed(url, cookieStoreId)
|
||||
: (cookieStoreId == 'private' || this.store.isSiteOriginInSameContext(url, cookieStoreId))
|
||||
|
||||
if (allowed) {
|
||||
if (tab.highlighted) {
|
||||
port.postMessage({
|
||||
"type": "assignedSiteRequested",
|
||||
@@ -171,7 +188,8 @@ export default class BackgroundMain {
|
||||
"originUrl": options.originUrl,
|
||||
"url": options.url,
|
||||
"tabId": options.tabId,
|
||||
"blocked": false
|
||||
"blocked": false,
|
||||
"strict": false
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,6 +198,9 @@ export default class BackgroundMain {
|
||||
// even if the tab is not highlighted.
|
||||
return {};
|
||||
} else {
|
||||
// Assigned to a different container: cancel here and let the app re-open
|
||||
// it in its assigned container. This applies to strict contexts too —
|
||||
// the site still doesn't load here, it just routes to where it belongs.
|
||||
//Only send events when tab is selected
|
||||
if (tab.highlighted) {
|
||||
port.postMessage({
|
||||
@@ -190,7 +211,8 @@ export default class BackgroundMain {
|
||||
"originUrl": options.originUrl,
|
||||
"url": options.url,
|
||||
"tabId": options.tabId,
|
||||
"blocked": true
|
||||
"blocked": true,
|
||||
"strict": false
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -199,6 +221,28 @@ export default class BackgroundMain {
|
||||
cancel: true,
|
||||
};
|
||||
}
|
||||
} else if (isStrict) {
|
||||
// Strict mode: this container may only load origins assigned to it. The
|
||||
// origin is not assigned to any container, so cancel the navigation and
|
||||
// report it as a strict block (no destination container).
|
||||
if (tab.highlighted) {
|
||||
port.postMessage({
|
||||
"type": "assignedSiteRequested",
|
||||
"id": options.requestId,
|
||||
"status": "success",
|
||||
"result": {
|
||||
"originUrl": options.originUrl,
|
||||
"url": options.url,
|
||||
"tabId": options.tabId,
|
||||
"blocked": true,
|
||||
"strict": true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
cancel: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
+6
-1
@@ -18,7 +18,8 @@ interface Message {
|
||||
'clearContainerProxy' |
|
||||
'removeContainerProxyRelation' |
|
||||
'healthcheck' |
|
||||
'setSiteAssignments';
|
||||
'setSiteAssignments' |
|
||||
'setStrictContexts';
|
||||
args: any;
|
||||
}
|
||||
|
||||
@@ -92,6 +93,10 @@ port.onMessage.addListener((raw: unknown): void => {
|
||||
console.log('set site assignments ' + JSON.stringify(message.args))
|
||||
store.setSiteAssignments(entries);
|
||||
break
|
||||
case "setStrictContexts":
|
||||
console.log('set strict contexts ' + JSON.stringify(message.args))
|
||||
store.setStrictContexts(new Map(Object.entries(message.args)) as Map<string, string[]>);
|
||||
break
|
||||
case "healthcheck":
|
||||
port.postMessage({
|
||||
"type": "healthcheck",
|
||||
|
||||
@@ -89,6 +89,39 @@ export class Store {
|
||||
private siteAssignments: Map<string, string> = new Map<string, string>()
|
||||
private wildcardAssignments: WildcardAssignment[] = []
|
||||
|
||||
// Maps an enforced cookie-store context (a strict container's base context,
|
||||
// or an isolation context of one of its isolated tabs) to the base contexts
|
||||
// its site assignments are keyed on.
|
||||
private strictContexts: Map<string, Set<string>> = new Map<string, Set<string>>()
|
||||
|
||||
setStrictContexts(contexts: Map<string, string[]>): void {
|
||||
const next = new Map<string, Set<string>>()
|
||||
for (const [contextId, assignmentContexts] of contexts) {
|
||||
next.set(contextId, new Set(assignmentContexts))
|
||||
}
|
||||
this.strictContexts = next
|
||||
}
|
||||
|
||||
isContextStrict(contextId: string): boolean {
|
||||
return this.strictContexts.has(contextId)
|
||||
}
|
||||
|
||||
/**
|
||||
* True when [uri]'s origin is assigned to a base context that [contextId]
|
||||
* enforces. Strict mode requires an exact assignment match (via
|
||||
* [lookupAssignment], which handles exact + wildcard entries) against the
|
||||
* container's base contexts — proxy/direct equivalence is deliberately NOT
|
||||
* consulted, so a site assigned only to a different (even proxy-equivalent)
|
||||
* container does not load here.
|
||||
*/
|
||||
isSiteOriginStrictlyAllowed(uri: URL, contextId: string): boolean {
|
||||
const assignmentContexts = this.strictContexts.get(contextId)
|
||||
const assignedContext = this.lookupAssignment(uri)
|
||||
return assignedContext !== undefined &&
|
||||
assignmentContexts !== undefined &&
|
||||
assignmentContexts.has(assignedContext)
|
||||
}
|
||||
|
||||
setSiteAssignments(sites: Map<string, unknown>): void {
|
||||
const exact = new Map<string, string>()
|
||||
const wildcard: WildcardAssignment[] = []
|
||||
|
||||
@@ -55,6 +55,10 @@ class GeckoContainerProxyService {
|
||||
return _apiInstance.setSiteAssignments(assignments);
|
||||
}
|
||||
|
||||
Future<void> setStrictContexts(Map<String, List<String>> contexts) {
|
||||
return _apiInstance.setStrictContexts(contexts);
|
||||
}
|
||||
|
||||
Future<bool> healthcheck() async {
|
||||
try {
|
||||
return await _apiInstance.healthcheck().timeout(
|
||||
|
||||
@@ -4928,6 +4928,7 @@ class ContainerSiteAssignment {
|
||||
this.originUrl,
|
||||
required this.url,
|
||||
required this.blocked,
|
||||
required this.strict,
|
||||
});
|
||||
|
||||
String requestId;
|
||||
@@ -4940,6 +4941,12 @@ class ContainerSiteAssignment {
|
||||
|
||||
bool blocked;
|
||||
|
||||
/// True when the navigation was cancelled because the tab's container is in
|
||||
/// strict mode and the target origin is not assigned to it (as opposed to an
|
||||
/// ordinary re-open-in-assigned-container block). Strict blocks have no
|
||||
/// destination container; the app just surfaces a message.
|
||||
bool strict;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
requestId,
|
||||
@@ -4947,6 +4954,7 @@ class ContainerSiteAssignment {
|
||||
originUrl,
|
||||
url,
|
||||
blocked,
|
||||
strict,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4961,6 +4969,7 @@ class ContainerSiteAssignment {
|
||||
originUrl: result[2] as String?,
|
||||
url: result[3]! as String,
|
||||
blocked: result[4]! as bool,
|
||||
strict: result[5]! as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4973,7 +4982,7 @@ class ContainerSiteAssignment {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(requestId, other.requestId) && _deepEquals(tabId, other.tabId) && _deepEquals(originUrl, other.originUrl) && _deepEquals(url, other.url) && _deepEquals(blocked, other.blocked);
|
||||
return _deepEquals(requestId, other.requestId) && _deepEquals(tabId, other.tabId) && _deepEquals(originUrl, other.originUrl) && _deepEquals(url, other.url) && _deepEquals(blocked, other.blocked) && _deepEquals(strict, other.strict);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -8634,6 +8643,31 @@ class GeckoContainerProxyApi {
|
||||
;
|
||||
}
|
||||
|
||||
/// Strict-mode enforcement map. Keys are Gecko cookie-store contexts to
|
||||
/// enforce (a strict container's base context plus its isolated tabs'
|
||||
/// isolation contexts); each value contains the container base contexts that
|
||||
/// site assignments are keyed on. Tabs in these contexts may only load
|
||||
/// origins assigned to one of the mapped base contexts (exact match, no proxy
|
||||
/// equivalence); any other top-level navigation is cancelled and reported
|
||||
/// back with `strict = true`.
|
||||
Future<void> setStrictContexts(Map<String, List<String>> contexts) async {
|
||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setStrictContexts$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[contexts]);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
_extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: true,
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
Future<bool> healthcheck() async {
|
||||
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.healthcheck$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
|
||||
@@ -1927,6 +1927,15 @@ abstract class GeckoContainerProxyApi {
|
||||
void removeContainerProxyRelation(String contextId, String proxyId);
|
||||
void setSiteAssignments(Map<String, String> assignments);
|
||||
|
||||
/// Strict-mode enforcement map. Keys are Gecko cookie-store contexts to
|
||||
/// enforce (a strict container's base context plus its isolated tabs'
|
||||
/// isolation contexts); each value contains the container base contexts that
|
||||
/// site assignments are keyed on. Tabs in these contexts may only load
|
||||
/// origins assigned to one of the mapped base contexts (exact match, no proxy
|
||||
/// equivalence); any other top-level navigation is cancelled and reported
|
||||
/// back with `strict = true`.
|
||||
void setStrictContexts(Map<String, List<String>> contexts);
|
||||
|
||||
@async
|
||||
bool healthcheck();
|
||||
}
|
||||
@@ -1985,12 +1994,19 @@ class ContainerSiteAssignment {
|
||||
final String url;
|
||||
final bool blocked;
|
||||
|
||||
/// True when the navigation was cancelled because the tab's container is in
|
||||
/// strict mode and the target origin is not assigned to it (as opposed to an
|
||||
/// ordinary re-open-in-assigned-container block). Strict blocks have no
|
||||
/// destination container; the app just surfaces a message.
|
||||
final bool strict;
|
||||
|
||||
ContainerSiteAssignment({
|
||||
required this.requestId,
|
||||
required this.tabId,
|
||||
required this.originUrl,
|
||||
required this.url,
|
||||
required this.blocked,
|
||||
required this.strict,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user