Skip to content

fix: bridge asset picker and input component performance cp-12.20.0 #33507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

micaelae
Copy link
Member

@micaelae micaelae commented Jun 5, 2025

Description

Issues

  • Search query input changes are not actually getting debounced bc the debounced setter is not memoized. This causes the picker to lag when a user types

Open in GitHub Codespaces

Related issues

Fixes: #33466 (partially)

Manual testing steps

  1. Go to this page...

Screenshots/Recordings

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@metamaskbot metamaskbot added the team-swaps-and-bridge For issues with Swaps or Bridging label Jun 5, 2025
@micaelae micaelae marked this pull request as ready for review June 5, 2025 23:35
@micaelae micaelae requested a review from a team as a code owner June 5, 2025 23:35
@metamaskbot
Copy link
Collaborator

metamaskbot commented Jun 5, 2025

✨ Files requiring CODEOWNER review ✨

🔄 @MetaMask/swaps-engineers (8 files, +300 -187)
  • 📁 ui/
    • 📁 hooks/
      • 📁 bridge/
        • 📁 __snapshots__/
          • 📄 useTokensWithFiltering.test.ts.snap +147 -1
          • 📄 useBridging.ts +0 -17
          • 📄 useTokensWithFiltering.test.ts +52 -3
          • 📄 useTokensWithFiltering.ts +73 -74
    • 📁 pages/
      • 📁 bridge/
        • 📁 prepare/
          • 📄 bridge-input-group.tsx +7 -31
          • 📄 prepare-bridge-page.tsx +21 -37
        • 📁 quotes/
          • 📄 multichain-bridge-quote-card.tsx +0 -21
          • 📄 index.tsx +0 -3

🖥️ @MetaMask/wallet-ux (5 files, +81 -50)
  • 📁 ui/
    • 📁 components/
      • 📁 multichain/
        • 📁 asset-picker-amount/
          • 📁 asset-picker/
            • 📄 asset-picker.tsx +24 -19
            • 📄 bridge-asset-picker-button.tsx +26 -6
          • 📁 asset-picker-modal/
            • 📄 asset-picker-modal-nft-tab.tsx +0 -3
            • 📄 asset-picker-modal-search.tsx +1 -4
            • 📄 asset-picker-modal.tsx +30 -18

@metamaskbot
Copy link
Collaborator

Builds ready [21a1279]
UI Startup Metrics (1231 ± 72 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1231109314397212751359
load106794812626911031217
domContentLoaded106094012507011001210
domInteractive17135871631
firstPaint72787126042910861215
backgroundConnect84304812
firstReactRender20163532027
getState1564982128
initialActions001001
loadScripts81369899768847960
setupStore85182811
WebpackHomeuiStartup21181641261722022392561
load16581290206518417701974
domContentLoaded16521287206018317661964
domInteractive151280101342
firstPaint1686742761203301
backgroundConnect21105982537
firstReactRender1184335494105328
getState214336531345
initialActions317134
loadScripts16451285205918617641953
setupStore5363179720312
FirefoxBrowserifyHomeuiStartup13181173175310913851513
load1170102815109412041337
domContentLoaded1170102815099412041337
domInteractive993421232111154
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20145772039
firstReactRender23204632327
getState10520020812
initialActions002001
loadScripts1152101214859311881310
setupStore848211623
WebpackHomeuiStartup1498136318389715351710
load1302117815879213441498
domContentLoaded1302117715879213441498
domInteractive713297107688
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20154252127
firstReactRender40296544245
getState84324812
initialActions002111
loadScripts1284116115689213201479
setupStore7522289
Benchmark value 16 exceeds gate value 15 for chrome browserify home mean getState
Benchmark value 1217 exceeds gate value 1190 for chrome browserify home p95 load
Benchmark value 1210 exceeds gate value 1180 for chrome browserify home p95 domContentLoaded
Benchmark value 1215 exceeds gate value 1180 for chrome browserify home p95 firstPaint
Benchmark value 960 exceeds gate value 940 for chrome browserify home p95 loadScripts
Benchmark value 53 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 2561 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 312 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 40 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 24ms | Sum of p95 exceeds: 466ms
Sum of all benchmark exceeds: 490ms

Bundle size diffs
  • background: 0 Bytes (0%)
  • ui: 32 Bytes (0%)
  • common: 0 Bytes (0%)

Copy link
Contributor

@davidmurdoch davidmurdoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left a comment suggesting a possible (untested) fix for some cleanup. I actually think it might be more necessary than I originally said, as it will also cancel the request when searchQuery changes, which we do want.

@metamaskbot
Copy link
Collaborator

Builds ready [9c7b31b]
UI Startup Metrics (1234 ± 64 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1234110215156412791326
load106493313756611111158
domContentLoaded105792513726611001147
domInteractive16133541629
firstPaint779140116840110961149
backgroundConnect84295924
firstReactRender20166652125
getState1464981928
initialActions001000
loadScripts812680111665856903
setupStore85172911
WebpackHomeuiStartup21331648258622622912523
load16621298197917817851934
domContentLoaded16561293196117617801925
domInteractive161278101342
firstPaint1657137162213280
backgroundConnect2811365442643
firstReactRender14644369112285337
getState184332441331
initialActions622922936
loadScripts16521292196017517781913
setupStore3663287520300
FirefoxBrowserifyHomeuiStartup14091193187116815321780
load12441020169615413371598
domContentLoaded12431020169615413361598
domInteractive1023547045114150
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect241385132755
firstReactRender24215442531
getState11419420916
initialActions001001
loadScripts12231002167415313111582
setupStore11418321734
WebpackHomeuiStartup1563138717909516491723
load1356118515709514181533
domContentLoaded1355118415709514181532
domInteractive78321512086118
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect22164762342
firstReactRender43307044547
getState95314920
initialActions001011
loadScripts1336116715509514021518
setupStore85293812
Benchmark value 1235 exceeds gate value 1234 for chrome browserify home mean uiStartup
Benchmark value 25 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 36 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 2523 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 300 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 1410 exceeds gate value 1405 for firefox browserify home mean uiStartup
Benchmark value 1244 exceeds gate value 1239 for firefox browserify home mean domContentLoaded
Benchmark value 11 exceeds gate value 9 for firefox browserify home mean setupStore
Benchmark value 1780 exceeds gate value 1660 for firefox browserify home p95 uiStartup
Benchmark value 1598 exceeds gate value 1495 for firefox browserify home p95 load
Benchmark value 1598 exceeds gate value 1495 for firefox browserify home p95 domContentLoaded
Benchmark value 1582 exceeds gate value 1475 for firefox browserify home p95 loadScripts
Benchmark value 34 exceeds gate value 27 for firefox browserify home p95 setupStore
Benchmark value 43 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 22ms | Sum of p95 exceeds: 751ms
Sum of all benchmark exceeds: 773ms

Bundle size diffs
  • background: 0 Bytes (0%)
  • ui: 143 Bytes (0%)
  • common: 0 Bytes (0%)

GustavoRSSilva
GustavoRSSilva previously approved these changes Jun 6, 2025
Copy link
Contributor

@MajorLift MajorLift Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The extra state variable and state update using useEffect seem unnecessary.

When something can be calculated from the existing props or state, don’t put it in state. Instead, calculate it during rendering. This makes your code faster (you avoid the extra “cascading” updates), simpler (you remove some code), and less error-prone (you avoid bugs caused by different state variables getting out of sync with each other).

-- https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state

diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
index 7778e01d30..222e10f93f 100644
--- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
+++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
@@ -157,28 +157,27 @@ export function AssetPickerModal({
   const prevNeedsSolanaAccountRef = useRef(false);
 
   const [searchQuery, setSearchQuery] = useState('');
-  const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery);
   const debouncedSetSearchQuery = useCallback(
-    debounce((value) => {
-      setDebouncedSearchQuery(value);
-    }, 200),
+    debounce((value) => value, 200),
     [],
   );
+  const debouncedSearchQuery =
+    debouncedSetSearchQuery(searchQuery) ?? searchQuery;
 
   const abortControllerRef = useRef<AbortController | null>(null);
 
-  // Cleanup abort controller and debounce on unmount
+  // Cleanup abort controller on unmount
   useEffect(() => {
     return () => {
       abortControllerRef.current?.abort();
       abortControllerRef.current = null;
-      debouncedSetSearchQuery.cancel();
     };
   }, []);
 
+  // clean up the debounced function on unmount or when `searchQuery` changes
   useEffect(() => {
-    debouncedSetSearchQuery(searchQuery);
-  }, [searchQuery, debouncedSetSearchQuery]);
+    return () => debouncedSetSearchQuery.cancel();
+  }, [searchQuery]);
 
   const swapsBlockedTokens = useSelector(getSwapsBlockedTokens);
   const memoizedSwapsBlockedTokens = useMemo(() => {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call - will try this out

Copy link
Contributor

@MajorLift MajorLift Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be passing the debounced value elsewhere in the file as well? There might be some performance gains to be had from reducing the frequency of child component props changes.

diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
index 7778e01d30..f895896b99 100644
--- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
+++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
@@ -516,7 +514,7 @@ export function AssetPickerModal({
 
   // This fetches the metadata for the asset if it is not already in the filteredTokenList
   const unlistedAssetMetadata = useAssetMetadata(
-    searchQuery,
+    debouncedSearchQuery,
     filteredTokenList.length === 0,
     abortControllerRef,
     selectedNetwork?.chainId,
@@ -666,7 +664,7 @@ export function AssetPickerModal({
             <AssetPickerModalTabs {...tabProps}>
               <React.Fragment key={TabName.TOKENS}>
                 <Search
-                  searchQuery={searchQuery}
+                  searchQuery={debouncedSearchQuery}
                   onChange={(value) => {
                     // Cancel previous asset metadata fetch
                     abortControllerRef.current?.abort();
@@ -696,12 +694,12 @@ export function AssetPickerModal({
               </React.Fragment>
               <AssetPickerModalNftTab
                 key={TabName.NFTS}
-                searchQuery={searchQuery}
+                searchQuery={debouncedSearchQuery}
                 onClose={onClose}
                 renderSearch={() => (
                   <Search
                     isNFTSearch
-                    searchQuery={searchQuery}
+                    searchQuery={debouncedSearchQuery}
                     onChange={(value) => setSearchQuery(value)}
                   />
                 )}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We pass the searchQuery instead of the debounced value here so that the input fields render the typed value right away. If we used the debounced value, it would look like the ui takes some time after typing to update the input field

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation! Makes sense to keep the ui responsive, and it shouldn't affect memory usage either way, so lgtm to keep it as is.

@MajorLift
Copy link
Contributor

MajorLift commented Jun 6, 2025

@Gudahtt I attempted to optimize the expensive for loop in filteredTokenList. There wasn't a good way to leverage early exits, so I did the following:

  • Reduce the overhead of recreating some functions and data structures
    • Better for maintainability and minimal performance improvement might be possible, but it shouldn't reduce re-renders.
  • Cache the result of the most expensive operation in each loop (creating tokenWithBalanceData).
    • I've included a cleanup fn for clearing the cache on unmount, so it might not help for our scenario here where we're switching between networks frequently.
    • Memory usage might actually increase, although if there's a memory leak coming from fetch calls the caching might help mitigate it.

Haven't benchmarked this yet, but it doesn't seem to break functionality at least.

diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
index 7778e01d30..5cad0ee345 100644
--- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
+++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
@@ -157,28 +157,31 @@ export function AssetPickerModal({
   const prevNeedsSolanaAccountRef = useRef(false);
 
   const [searchQuery, setSearchQuery] = useState('');
-  const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery);
   const debouncedSetSearchQuery = useCallback(
-    debounce((value) => {
-      setDebouncedSearchQuery(value);
-    }, 200),
+    debounce((value) => value, 200),
+    [],
+  );
+  const debouncedSearchQuery =
+    debouncedSetSearchQuery(searchQuery) ?? searchQuery;
+
+  const tokensWithBalanceDataCache = useMemo(
+    () => new Map<`${string}:${string}`, AssetWithDisplayData<ERC20Asset>>(),
     [],
   );
-
   const abortControllerRef = useRef<AbortController | null>(null);
 
-  // Cleanup abort controller and debounce on unmount
+  // Cleanup abort controller on unmount
   useEffect(() => {
     return () => {
       abortControllerRef.current?.abort();
       abortControllerRef.current = null;
-      debouncedSetSearchQuery.cancel();
+      tokensWithBalanceDataCache.clear();
     };
   }, []);
 
+  // clean up the debounced function on unmount or when `searchQuery` changes
   useEffect(() => {
-    debouncedSetSearchQuery(searchQuery);
-  }, [searchQuery, debouncedSetSearchQuery]);
+    return () => debouncedSetSearchQuery.cancel();
+  }, [searchQuery]);
 
   const swapsBlockedTokens = useSelector(getSwapsBlockedTokens);
   const memoizedSwapsBlockedTokens = useMemo(() => {
@@ -416,24 +419,23 @@ export function AssetPickerModal({
     ],
   );
 
-  const filteredTokenList = useMemo(() => {
-    const filteredTokens: (
-      | AssetWithDisplayData<ERC20Asset>
-      | AssetWithDisplayData<NativeAsset>
-    )[] = [];
-    // List of token identifiers formatted like `chainId:address`
-    const filteredTokensAddresses = new Set<string | undefined>();
-    const getTokenKey = (address?: string | null, tokenChainId?: string) =>
+  // List of token identifiers formatted like `chainId:address`
+  const filteredTokensAddresses = useMemo(
+    () => new Set<string | undefined>(),
+    [],
+  );
+
+  const getTokenKey = useCallback(
+    (address?: string | null, tokenChainId?: string) =>
       `${address?.toLowerCase() ?? zeroAddress()}:${
         tokenChainId ?? currentChainId
-      }`;
+      }` as `${string}:${string}`,
+    [currentChainId],
+  );
 
-    // Default filter predicate for whether a token should be included in displayed list
-    const shouldAddToken = (
-      symbol: string,
-      address?: string | null,
-      tokenChainId?: string,
-    ) => {
+  // Default filter predicate for whether a token should be included in displayed list
+  const defaultShowAddToken = useCallback(
+    (symbol: string, address?: string | null, tokenChainId?: string) => {
       const trimmedSearchQuery = debouncedSearchQuery.trim().toLowerCase();
       const isMatchedBySearchQuery = Boolean(
         !trimmedSearchQuery ||
@@ -449,12 +451,27 @@ export function AssetPickerModal({
           isMatchedBySearchQuery &&
           !filteredTokensAddresses.has(getTokenKey(address, tokenChainId)),
       );
-    };
+    },
+    [
+      debouncedSearchQuery,
+      filteredTokensAddresses,
+      getTokenKey,
+      isMultiselectEnabled,
+      selectedChainIds,
+      selectedNetwork?.chainId,
+    ],
+  );
+
+  const filteredTokenList = useMemo(() => {
+    const filteredTokens: (
+      | AssetWithDisplayData<ERC20Asset>
+      | AssetWithDisplayData<NativeAsset>
+    )[] = [];
 
     // If filteredTokensGenerator is passed in, use it to generate the filtered tokens
     // Otherwise use the default tokenGenerator
     const tokenGenerator = (customTokenListGenerator ?? tokenListGenerator)(
-      shouldAddToken,
+      defaultShowAddToken,
     );
 
     for (const token of tokenGenerator) {
@@ -462,25 +479,33 @@ export function AssetPickerModal({
         continue;
       }
 
-      filteredTokensAddresses.add(getTokenKey(token.address, token.chainId));
-
-      const tokenWithBalanceData =
-        !customTokenListGenerator && isStrictHexString(token.address)
-          ? getRenderableTokenData(
-              token.address
-                ? ({
-                    ...token,
-                    ...evmTokenMetadataByAddress[token.address.toLowerCase()],
-                    type: AssetType.token,
-                  } as AssetWithDisplayData<ERC20Asset>)
-                : token,
-              tokenConversionRates,
-              conversionRate,
-              currentCurrency,
-              token.chainId,
-              evmTokenMetadataByAddress,
-            )
-          : (token as unknown as AssetWithDisplayData<ERC20Asset>);
+      const tokenKey = getTokenKey(token.address, token.chainId);
+      filteredTokensAddresses.add(tokenKey);
+
+      let tokenWithBalanceData;
+      if (tokensWithBalanceDataCache.has(tokenKey)) {
+        tokenWithBalanceData = tokensWithBalanceDataCache.get(tokenKey);
+      } else {
+        tokenWithBalanceData =
+          !customTokenListGenerator && isStrictHexString(token.address)
+            ? getRenderableTokenData(
+                token.address
+                  ? ({
+                      ...token,
+                      ...evmTokenMetadataByAddress[token.address.toLowerCase()],
+                      type: AssetType.token,
+                    } as AssetWithDisplayData<ERC20Asset>)
+                  : token,
+                tokenConversionRates,
+                conversionRate,
+                currentCurrency,
+                token.chainId,
+                evmTokenMetadataByAddress,
+              )
+            : (token as unknown as AssetWithDisplayData<ERC20Asset>);
+
+        tokensWithBalanceDataCache.set(tokenKey, tokenWithBalanceData);
+      }
 
       // Add selected asset to the top of the list if it is the selected asset
       if (
@@ -497,12 +522,14 @@ export function AssetPickerModal({
       }
     }
 
+    filteredTokensAddresses.clear();
+
     return filteredTokens;
   }, [
-    currentChainId,
-    debouncedSearchQuery,
-    isMultiselectEnabled,
-    selectedChainIds,
+    tokensWithBalanceDataCache,
+    filteredTokensAddresses,
+    getTokenKey,
+    defaultShowAddToken,
     selectedNetwork?.chainId,
     customTokenListGenerator,
     tokenListGenerator,

@Gudahtt
Copy link
Member

Gudahtt commented Jun 6, 2025

Neat! Maybe we can try that in a subequent PR @MajorLift ?

@micaelae micaelae requested a review from a team as a code owner June 6, 2025 20:47
}
// Only tokens on the active chain are processed here here
const sharedFields = {
...token,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to outside the hook to remove the closure that was retaining references to the token data

@metamaskbot
Copy link
Collaborator

Builds ready [59e374d]
UI Startup Metrics (1214 ± 75 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1214107915527512441343
load105493514187310831178
domContentLoaded104893114097410761172
domInteractive16133251630
firstPaint71180118142010631161
backgroundConnect74223818
firstReactRender20163842030
getState1456992027
initialActions001001
loadScripts805693115573837941
setupStore85173815
WebpackHomeuiStartup21711746266022023372539
load16641317206217217881946
domContentLoaded16571313205017017791932
domInteractive15124781439
firstPaint1747050069203303
backgroundConnect261267103246
firstReactRender16244395117305362
getState145188181427
initialActions318146
loadScripts16541312203816817771921
setupStore5273199525312
FirefoxBrowserifyHomeuiStartup1338117816099313851519
load1184104614278212341359
domContentLoaded1184104614278212341358
domInteractive933417926101160
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect211255102249
firstReactRender23205242327
getState10418318814
initialActions001001
loadScripts1165103414128212151344
setupStore8418618615
WebpackHomeuiStartup1494134417479515701690
load1296115415228713551455
domContentLoaded1295115315228713541455
domInteractive75482742679110
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect20154852130
firstReactRender40345444345
getState84315819
initialActions001011
loadScripts1278113715078713361438
setupStore9511912811
Benchmark value 941 exceeds gate value 940 for chrome browserify home p95 loadScripts
Benchmark value 53 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 2539 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 312 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 41 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 24ms | Sum of p95 exceeds: 333ms
Sum of all benchmark exceeds: 357ms

Bundle size diffs [🚀 Bundle size reduced!]
  • background: -126 Bytes (0%)
  • ui: 15.49 KiB (0.22%)
  • common: -5.84 KiB (-0.07%)

@metamaskbot
Copy link
Collaborator

Builds ready [d1d928c]
UI Startup Metrics (1220 ± 74 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyHomeuiStartup1220110914777412621398
load105994512876911011195
domContentLoaded105293712807010931186
domInteractive16133341628
firstPaint651128126342710661164
backgroundConnect84305822
firstReactRender19163932024
getState1453871927
initialActions001001
loadScripts806697100967847940
setupStore85172812
WebpackHomeuiStartup20921604255022522112482
load16401239194318717841903
domContentLoaded16341235193218617771897
domInteractive15115381339
firstPaint1717236459209288
backgroundConnect20105482238
firstReactRender1204336596109331
getState224415611338
initialActions315134
loadScripts16311233192118517741895
setupStore4373158818306
FirefoxBrowserifyHomeuiStartup13361171188511213961544
load1183102915039512431348
domContentLoaded1182102815039512431348
domInteractive973327432104145
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect2313143192155
firstReactRender24205252429
getState11418325826
initialActions001001
loadScripts1162101614428812241334
setupStore74798612
WebpackHomeuiStartup15421335195512616201785
load13341163167910814051540
domContentLoaded13341163167910814051540
domInteractive80342302588123
firstPaintNaNNaNNaNNaNNaNNaN
backgroundConnect22148192332
firstReactRender40315144347
getState11518118929
initialActions002111
loadScripts13151148166510813831524
setupStore13526233827
Benchmark value 1399 exceeds gate value 1365 for chrome browserify home p95 uiStartup
Benchmark value 1196 exceeds gate value 1190 for chrome browserify home p95 load
Benchmark value 1186 exceeds gate value 1180 for chrome browserify home p95 domContentLoaded
Benchmark value 22 exceeds gate value 18 for chrome browserify home p95 backgroundConnect
Benchmark value 941 exceeds gate value 940 for chrome browserify home p95 loadScripts
Benchmark value 43 exceeds gate value 32 for chrome webpack home mean setupStore
Benchmark value 2483 exceeds gate value 2454 for chrome webpack home p95 uiStartup
Benchmark value 306 exceeds gate value 65 for chrome webpack home p95 setupStore
Benchmark value 12 exceeds gate value 11 for firefox browserify home mean getState
Benchmark value 26 exceeds gate value 24 for firefox browserify home p95 getState
Benchmark value 41 exceeds gate value 38 for firefox webpack home mean firstReactRender
Sum of mean exceeds: 15ms | Sum of p95 exceeds: 323ms
Sum of all benchmark exceeds: 338ms

Bundle size diffs
  • background: 0 Bytes (0%)
  • ui: 82 Bytes (0%)
  • common: 0 Bytes (0%)

@micaelae micaelae enabled auto-merge June 7, 2025 00:55
@micaelae micaelae changed the title fix: memoize debounced searchQuery setter cp-12.20.0 fix: bridge asset picker and input component performance cp-12.20.0 Jun 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
team-swaps-and-bridge For issues with Swaps or Bridging
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: The extension exhibits performance degradation, then crashes with the "MetaMask Lavamoat show has crashed." error
6 participants