From 6748fee73d7feef2e9a65f1d9ffaf88a448f4e31 Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Wed, 22 Jul 2026 12:44:13 +0200 Subject: [PATCH] [plugins] IJPL-249806 Missing project-trust gate lets Plugins Advertiser query Marketplace pre-trust (cherry picked from commit 00b015bb260b66f11815270962173175dfbbf3bf) IJ-CR-214792 GitOrigin-RevId: 6ae16ea77313cb5e6ea32b563fc125c9c67a3b2b --- .../resources/messages/IdeBundle.properties | 2 +- .../advertiser/PluginFeatureCacheService.kt | 2 + .../CheckSuggestedPluginsAction.kt | 19 +- ...ginAdvertiserEditorNotificationProvider.kt | 17 +- .../PluginsAdvertiserStartupActivity.kt | 221 +++++++++--------- 5 files changed, 132 insertions(+), 129 deletions(-) diff --git a/platform/platform-api/resources/messages/IdeBundle.properties b/platform/platform-api/resources/messages/IdeBundle.properties index 173ba0aa20d4..ac3714591474 100644 --- a/platform/platform-api/resources/messages/IdeBundle.properties +++ b/platform/platform-api/resources/messages/IdeBundle.properties @@ -1896,7 +1896,7 @@ plugins.advertiser.action.try.ultimate=Try {0} plugins.advertiser.action.ignore.ultimate=Dismiss plugins.advertiser.no.suggested.plugins=No suggested plugins found plugins.advertiser.no.compatible.plugins.found=No compatible plugins found -plugins.advertiser.check.progress=Checking suggested plugins for project +plugins.advertiser.check.progress=Checking Suggested Plugins for Project plugins.advertiser.ultimate.features.detected=Features covered by Ultimate Subscription {0} are detected plugins.advertiser.missing.feature={0,choice,1#Plugin|2#Plugins} {4} supporting {1} ''{2}'' {0,choice,1#is|2#are} currently {3,choice,0#disabled|1#not installed}. plugins.advertiser.missing.features={0,choice,1#Plugin|2#Plugins} {3} supporting features ({1}) {0,choice,1#is|2#are} currently {2,choice,0#disabled|1#not installed}. diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/advertiser/PluginFeatureCacheService.kt b/platform/platform-impl/src/com/intellij/ide/plugins/advertiser/PluginFeatureCacheService.kt index 9903202b9c45..109c894c7bff 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/advertiser/PluginFeatureCacheService.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/advertiser/PluginFeatureCacheService.kt @@ -4,6 +4,7 @@ package com.intellij.ide.plugins.advertiser import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.components.Service import com.intellij.openapi.components.service +import com.intellij.openapi.components.serviceAsync import com.intellij.platform.settings.CacheTag import com.intellij.platform.settings.Setting import com.intellij.platform.settings.objectSerializer @@ -21,6 +22,7 @@ import org.jetbrains.annotations.ApiStatus class PluginFeatureCacheService { companion object { fun getInstance(): PluginFeatureCacheService = service() + suspend fun getInstanceAsync(): PluginFeatureCacheService = serviceAsync() } private val factory = settingDescriptorFactory(PluginManagerCore.CORE_ID) diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/CheckSuggestedPluginsAction.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/CheckSuggestedPluginsAction.kt index e1271090d2b6..3b5f47c64d2f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/CheckSuggestedPluginsAction.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/CheckSuggestedPluginsAction.kt @@ -2,23 +2,24 @@ package com.intellij.openapi.updateSettings.impl.pluginsAdvertisement import com.intellij.ide.IdeBundle +import com.intellij.ide.trustedProjects.TrustedProjects import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import com.intellij.platform.ide.progress.runWithModalProgressBlocking internal class CheckSuggestedPluginsAction : DumbAwareAction() { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - - runWithModalProgressBlocking(project, IdeBundle.message("plugins.advertiser.check.progress")) { - PluginsAdvertiserStartupActivity().checkSuggestedPlugins(project = project, includeIgnored = true) - } - } + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { - e.presentation.isEnabledAndVisible = e.project != null + e.presentation.isEnabledAndVisible = e.project?.let { TrustedProjects.isProjectTrusted(it) } ?: false } - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + override fun actionPerformed(e: AnActionEvent) { + val project = e.project?.takeIf { TrustedProjects.isProjectTrusted(it) } ?: return + + runWithModalProgressBlocking(project, IdeBundle.message("plugins.advertiser.check.progress")) { + checkSuggestedPlugins(project = project, includeIgnored = true) + } + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.kt index 1ca7a7990d7d..1eb0228752bf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.kt @@ -10,6 +10,7 @@ import com.intellij.ide.plugins.PluginManagerCore import com.intellij.ide.plugins.advertiser.PluginData import com.intellij.ide.plugins.marketplace.MarketplaceRequests import com.intellij.ide.plugins.pluginRequiresUltimatePluginButItsDisabled +import com.intellij.ide.trustedProjects.TrustedProjects import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.EDT @@ -36,6 +37,7 @@ import com.intellij.ui.EditorNotificationPanel import com.intellij.ui.EditorNotificationProvider import com.intellij.ui.EditorNotifications import com.intellij.ui.HyperlinkLabel +import com.intellij.util.application import fleet.util.Either import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -56,14 +58,14 @@ import javax.swing.JLabel class PluginAdvertiserEditorNotificationProvider : EditorNotificationProvider, DumbAware { override fun collectNotificationData(project: Project, file: VirtualFile): Function? { - val app = ApplicationManager.getApplication() - if (app.isUnitTestMode || app.isHeadlessEnvironment || tryUltimateIsDisabled()) { + if (!TrustedProjects.isProjectTrusted(project)) return null + + if (application.isHeadlessEnvironment || tryUltimateIsDisabled()) { return null } - val providedSuggestion = SUGGESTION_EP_NAME.extensionList.asSequence() - .mapNotNull { it.getSuggestion(project, file) } - .firstOrNull() + val providedSuggestion = SUGGESTION_EP_NAME.extensionList + .firstNotNullOfOrNull { it.getSuggestion(project, file) } val suggestionChoice = getSuggestionData(project = project, activeProductCode = service().build.productCode, @@ -307,8 +309,7 @@ private fun logSuggestionShown(project: Project, pluginIds: Collection } private fun logSuggestedProducts(project: Project, suggestedIdes: List) { - for (ide in suggestedIdes) { - val productCode = ide.productCode + for ((_, productCode) in suggestedIdes) { if (!loggedIdeSuggestions.contains(productCode)) { FUSEventSource.EDITOR.logIdeSuggested(project, productCode) loggedIdeSuggestions.add(productCode) @@ -332,7 +333,7 @@ private fun getSuggestionData( } } -private fun getSuggestionDataByDetector(project: Project, suggestion: PluginAdvertisedByFileContent): AdvertiserSuggestion? { +private fun getSuggestionDataByDetector(project: Project, suggestion: PluginAdvertisedByFileContent): AdvertiserSuggestion { val implementationName = "${FILE_HANDLER_KIND}:${suggestion.fileHandler.id}" return AdvertiserSuggestion( diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiserStartupActivity.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiserStartupActivity.kt index 4490dbd9e987..bdce1f836b04 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiserStartupActivity.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiserStartupActivity.kt @@ -12,116 +12,43 @@ import com.intellij.ide.plugins.advertiser.PluginFeatureMap import com.intellij.ide.plugins.marketplace.MarketplaceRequests import com.intellij.ide.plugins.newui.PluginNodeModelBuilderFactory import com.intellij.ide.plugins.newui.PluginUiModel +import com.intellij.ide.trustedProjects.TrustedProjects import com.intellij.notification.NotificationType import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.EDT import com.intellij.openapi.components.serviceAsync -import com.intellij.openapi.diagnostic.ControlFlowException +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.rethrowControlFlowException import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.progress.runBlockingMaybeCancellable +import com.intellij.openapi.extensions.ExtensionNotApplicableException import com.intellij.openapi.project.Project import com.intellij.openapi.startup.ProjectActivity -import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.util.registry.RegistryManager import com.intellij.ui.EditorNotifications import com.intellij.util.SystemProperties import com.intellij.util.io.computeDetached import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext -import java.util.concurrent.CancellationException import java.util.concurrent.TimeUnit -import kotlin.coroutines.coroutineContext -private val isTestingMode : Boolean by lazy { +private fun isTestingMode(): Boolean { val application = ApplicationManager.getApplication() - application.isUnitTestMode || application.isHeadlessEnvironment || SystemProperties.getBooleanProperty("idea.is.playback", false) + return application.isHeadlessEnvironment || SystemProperties.getBooleanProperty("idea.is.playback", false) } internal class PluginsAdvertiserStartupActivity : ProjectActivity { - - suspend fun checkSuggestedPlugins(project: Project, includeIgnored: Boolean) { - if (isTestingMode) { - return - } - - val customPlugins = computeDetached { RepositoryHelper.loadPluginsFromCustomRepositories(null, PluginNodeModelBuilderFactory) } - - coroutineContext.ensureActive() - - val extensionService = PluginFeatureCacheService.getInstance() - val oldExtensions = extensionService.extensions.get() - - val pluginAdvertiserService = PluginAdvertiserService.getInstance(project) - pluginAdvertiserService.collectDependencyUnknownFeatures(includeIgnored) - val unknownFeatures = UnknownFeaturesCollector.getInstance(project).unknownFeatures - - if (oldExtensions != null && unknownFeatures.isEmpty()) { - if (includeIgnored) { - coroutineContext.ensureActive() - - withContext(Dispatchers.EDT) { - getPluginSuggestionNotificationGroup() - .createNotification(IdeBundle.message("plugins.advertiser.no.suggested.plugins"), NotificationType.INFORMATION) - .setDisplayId("advertiser.no.plugins") - .notify(project) - } - } - } - - try { - val customPluginIds = customPlugins.map { it.pluginId.idString }.toSet() - if (oldExtensions == null - || oldExtensions.isOutdated - || includeIgnored) { - @Suppress("DEPRECATION") - extensionService.extensions.set(PluginFeatureMap( - featureMap = getFeatureMapFromMarketPlace(customPluginIds = customPluginIds, featureType = "com.intellij.fileTypeFactory"), - lastUpdateTime = if (oldExtensions != null) System.currentTimeMillis() else 0L, - )) - coroutineContext.ensureActive() - EditorNotifications.getInstance(project).updateAllNotifications() - } - - val oldDependencies = extensionService.dependencies.get() - if (oldDependencies == null - || oldDependencies.isOutdated - || includeIgnored) { - extensionService.dependencies.set(PluginFeatureMap( - featureMap = getFeatureMapFromMarketPlace(customPluginIds = customPluginIds, featureType = DEPENDENCY_SUPPORT_FEATURE), - lastUpdateTime = if (oldDependencies != null) System.currentTimeMillis() else 0L, - )) - } - - // update information about file handlers when forced - if (includeIgnored && PluginAdvertiserExtensionsStateService.getInstance().updateCompatibleFileHandlers()) { - EditorNotifications.getInstance(project).updateAllNotifications() - } - - coroutineContext.ensureActive() - - if (unknownFeatures.isNotEmpty()) { - pluginAdvertiserService.run( - customPlugins = customPlugins, - unknownFeatures = unknownFeatures, - includeIgnored = includeIgnored - ) - } - - notifyUnbundledPlugins(project) - } - catch (e: CancellationException) { - throw e - } - catch (e: Exception) { - if (e !is ControlFlowException) { - thisLogger().info(e) - } + init { + if (isTestingMode()) { + throw ExtensionNotApplicableException.create() } } override suspend fun execute(project: Project) { - if (!Registry.`is`("ide.show.plugin.suggestions.on.open", true)) { + if (!RegistryManager.getInstanceAsync().`is`("ide.show.plugin.suggestions.on.open")) { + thisLogger().debug("Plugin advertiser plugin suggestions disabled on open") return } @@ -129,41 +56,113 @@ internal class PluginsAdvertiserStartupActivity : ProjectActivity { } } -internal fun findSuggestedPlugins(project: Project, customRepositories: Map>): List { - return runBlockingMaybeCancellable { - if (isTestingMode) { - return@runBlockingMaybeCancellable emptyList() +private val LOG = logger() + +internal suspend fun checkSuggestedPlugins(project: Project, includeIgnored: Boolean) { + if (!TrustedProjects.isProjectTrusted(project)) return + + LOG.debug("Checking plugin advertiser plugin suggestions") + + val customPlugins = computeDetached { RepositoryHelper.loadPluginsFromCustomRepositories(null, PluginNodeModelBuilderFactory) } + + currentCoroutineContext().ensureActive() + + val extensionService = PluginFeatureCacheService.getInstanceAsync() + val oldExtensions = extensionService.extensions.get() + + val pluginAdvertiserService = PluginAdvertiserService.getInstance(project) + pluginAdvertiserService.collectDependencyUnknownFeatures(includeIgnored) + val unknownFeatures = project.serviceAsync().unknownFeatures + + if (oldExtensions != null && unknownFeatures.isEmpty()) { + if (includeIgnored) { + currentCoroutineContext().ensureActive() + + withContext(Dispatchers.EDT) { + getPluginSuggestionNotificationGroup() + .createNotification(IdeBundle.message("plugins.advertiser.no.suggested.plugins"), NotificationType.INFORMATION) + .setDisplayId("advertiser.no.plugins") + .notify(project) + } } + } - val customPlugins = ArrayList() - for (value in customRepositories.values) { - customPlugins.addAll(value) - } - - val pluginAdvertiserService = project.serviceAsync() - pluginAdvertiserService.collectDependencyUnknownFeatures(true) - + try { val customPluginIds = customPlugins.map { it.pluginId.idString }.toSet() - val extensionService = serviceAsync() - val oldDependencies = extensionService.dependencies.get() - extensionService.dependencies.set(PluginFeatureMap( - getFeatureMapFromMarketPlace(customPluginIds, DEPENDENCY_SUPPORT_FEATURE), - if (oldDependencies != null) System.currentTimeMillis() else 0L, - )) - val unknownFeatures = UnknownFeaturesCollector.getInstance(project).unknownFeatures - if (unknownFeatures.isNotEmpty()) { - return@runBlockingMaybeCancellable pluginAdvertiserService.fetch(customPlugins, unknownFeatures, true) + if (oldExtensions == null + || oldExtensions.isOutdated + || includeIgnored) { + @Suppress("DEPRECATION") + extensionService.extensions.set(PluginFeatureMap( + featureMap = getFeatureMapFromMarketPlace(customPluginIds = customPluginIds, featureType = "com.intellij.fileTypeFactory"), + lastUpdateTime = if (oldExtensions != null) System.currentTimeMillis() else 0L, + )) + currentCoroutineContext().ensureActive() + EditorNotifications.getInstance(project).updateAllNotifications() } - return@runBlockingMaybeCancellable emptyList() + val oldDependencies = extensionService.dependencies.get() + if (oldDependencies == null + || oldDependencies.isOutdated + || includeIgnored) { + extensionService.dependencies.set(PluginFeatureMap( + featureMap = getFeatureMapFromMarketPlace(customPluginIds = customPluginIds, featureType = DEPENDENCY_SUPPORT_FEATURE), + lastUpdateTime = if (oldDependencies != null) System.currentTimeMillis() else 0L, + )) + } + + // update information about file handlers when forced + if (includeIgnored && PluginAdvertiserExtensionsStateService.getInstance().updateCompatibleFileHandlers()) { + EditorNotifications.getInstance(project).updateAllNotifications() + } + + currentCoroutineContext().ensureActive() + + if (unknownFeatures.isNotEmpty()) { + pluginAdvertiserService.run( + customPlugins = customPlugins, + unknownFeatures = unknownFeatures, + includeIgnored = includeIgnored + ) + } + + notifyUnbundledPlugins(project) + } + catch (e: Exception) { + rethrowControlFlowException(e) + LOG.warn(e) } } -private suspend fun getFeatureMapFromMarketPlace(customPluginIds: Set, featureType: String): Map { - if (isTestingMode) { - return emptyMap() +internal suspend fun findSuggestedPlugins(project: Project, customRepositories: Map>): List { + if (isTestingMode()) return emptyList() + + val customPlugins = ArrayList() + for (value in customRepositories.values) { + customPlugins.addAll(value) } + val pluginAdvertiserService = project.serviceAsync() + pluginAdvertiserService.collectDependencyUnknownFeatures(true) + + val customPluginIds = customPlugins.map { it.pluginId.idString }.toSet() + val extensionService = serviceAsync() + val oldDependencies = extensionService.dependencies.get() + extensionService.dependencies.set(PluginFeatureMap( + getFeatureMapFromMarketPlace(customPluginIds, DEPENDENCY_SUPPORT_FEATURE), + if (oldDependencies != null) System.currentTimeMillis() else 0L, + )) + val unknownFeatures = UnknownFeaturesCollector.getInstance(project).unknownFeatures + if (unknownFeatures.isNotEmpty()) { + return pluginAdvertiserService.fetch(customPlugins, unknownFeatures, true) + } + + return emptyList() +} + +private suspend fun getFeatureMapFromMarketPlace(customPluginIds: Set, featureType: String): Map { + if (isTestingMode()) return emptyMap() + val params = mapOf("featureType" to featureType) val features = MarketplaceRequests.getInstance().getFeatures(params) return features