diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js
index 607515b18fda..698e137d2aeb 100644
--- a/app/components/Views/Browser/index.js
+++ b/app/components/Views/Browser/index.js
@@ -1,5 +1,11 @@
import PropTypes from 'prop-types';
-import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
+import React, {
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
import { View } from 'react-native';
import { captureScreen } from 'react-native-view-shot';
import { connect, useSelector } from 'react-redux';
@@ -29,10 +35,20 @@ import URL from 'url-parse';
import { useMetrics } from '../../hooks/useMetrics';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { appendURLParams } from '../../../util/browser';
-import { THUMB_WIDTH, THUMB_HEIGHT, IDLE_TIME_CALC_INTERVAL, IDLE_TIME_MAX } from './constants';
+import {
+ THUMB_WIDTH,
+ THUMB_HEIGHT,
+ IDLE_TIME_CALC_INTERVAL,
+ IDLE_TIME_MAX,
+} from './constants';
import { useStyles } from '../../hooks/useStyles';
import styleSheet from './styles';
import Routes from '../../../constants/navigation/Routes';
+///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
+import { selectSelectedInternalAccount } from '../../../selectors/accountsController';
+import { isSolanaAccount } from '../../../core/Multichain/utils';
+import { useFocusEffect } from '@react-navigation/native';
+///: END:ONLY_INCLUDE_IF
const MAX_BROWSER_TABS = 5;
@@ -71,27 +87,65 @@ export const Browser = (props) => {
(state) => state.security.dataCollectionForMarketing,
);
- const homePageUrl = useCallback(() =>
- appendURLParams(AppConstants.HOMEPAGE_URL, {
- metricsEnabled: isEnabled(),
- marketingEnabled: isDataCollectionForMarketingEnabled ?? false,
- }).href,
+ ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
+ const currentSelectedAccount = useSelector(selectSelectedInternalAccount);
+ ///: END:ONLY_INCLUDE_IF
+
+ const homePageUrl = useCallback(
+ () =>
+ appendURLParams(AppConstants.HOMEPAGE_URL, {
+ metricsEnabled: isEnabled(),
+ marketingEnabled: isDataCollectionForMarketingEnabled ?? false,
+ }).href,
[isEnabled, isDataCollectionForMarketingEnabled],
);
- const newTab = useCallback((url, linkType) => {
- // if tabs.length > MAX_BROWSER_TABS, show the max browser tabs modal
- if (tabs.length >= MAX_BROWSER_TABS) {
- navigation.navigate(Routes.MODAL.MAX_BROWSER_TABS_MODAL);
- } else {
- // When a new tab is created, a new tab is rendered, which automatically sets the url source on the webview
- createNewTab(url || homePageUrl(), linkType);
- }
- }, [tabs, navigation, homePageUrl, createNewTab]);
+ ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
+ // TODO remove after we release Solana dapp connectivity
+ useFocusEffect(
+ useCallback(() => {
+ if (isSolanaAccount(currentSelectedAccount)) {
+ toastRef?.current?.showToast({
+ variant: ToastVariants.Network,
+ networkImageSource: require('../../../images/solana-logo.png'),
+ labelOptions: [
+ {
+ label: `${strings(
+ 'browser.toast.solana_dapp_connection_coming_soon.title',
+ )} \n`,
+ isBold: true,
+ },
+ {
+ label: `${strings(
+ 'browser.toast.solana_dapp_connection_coming_soon.message',
+ )}`,
+ },
+ ],
+ });
+ }
+ }, [toastRef, currentSelectedAccount]),
+ );
+ ///: END:ONLY_INCLUDE_IF
- const updateTabInfo = useCallback((tabID, info) => {
- updateTab(tabID, info);
- }, [updateTab]);
+ const newTab = useCallback(
+ (url, linkType) => {
+ // if tabs.length > MAX_BROWSER_TABS, show the max browser tabs modal
+ if (tabs.length >= MAX_BROWSER_TABS) {
+ navigation.navigate(Routes.MODAL.MAX_BROWSER_TABS_MODAL);
+ } else {
+ // When a new tab is created, a new tab is rendered, which automatically sets the url source on the webview
+ createNewTab(url || homePageUrl(), linkType);
+ }
+ },
+ [tabs, navigation, homePageUrl, createNewTab],
+ );
+
+ const updateTabInfo = useCallback(
+ (tabID, info) => {
+ updateTab(tabID, info);
+ },
+ [updateTab],
+ );
const hideTabsAndUpdateUrl = (url) => {
navigation.setParams({
@@ -125,7 +179,8 @@ export const Browser = (props) => {
// if it isn't the active tab
if (tab.id !== activeTabId) {
// add idle time for each non-active tab
- newIdleTimes[tab.id] = (newIdleTimes[tab.id] || 0) + IDLE_TIME_CALC_INTERVAL;
+ newIdleTimes[tab.id] =
+ (newIdleTimes[tab.id] || 0) + IDLE_TIME_CALC_INTERVAL;
// if the tab has surpassed the maximum
if (newIdleTimes[tab.id] > IDLE_TIME_MAX) {
// then "archive" it
@@ -257,27 +312,28 @@ export const Browser = (props) => {
],
);
- const takeScreenshot = useCallback((url, tabID) =>
- new Promise((resolve, reject) => {
- captureScreen({
- format: 'jpg',
- quality: 0.2,
- THUMB_WIDTH,
- THUMB_HEIGHT,
- }).then(
- (uri) => {
- updateTab(tabID, {
- url,
- image: uri,
- });
- resolve(true);
- },
- (error) => {
- Logger.error(error, `Error saving tab ${url}`);
- reject(error);
- },
- );
- }),
+ const takeScreenshot = useCallback(
+ (url, tabID) =>
+ new Promise((resolve, reject) => {
+ captureScreen({
+ format: 'jpg',
+ quality: 0.2,
+ THUMB_WIDTH,
+ THUMB_HEIGHT,
+ }).then(
+ (uri) => {
+ updateTab(tabID, {
+ url,
+ image: uri,
+ });
+ resolve(true);
+ },
+ (error) => {
+ Logger.error(error, `Error saving tab ${url}`);
+ reject(error);
+ },
+ );
+ }),
[updateTab],
);
@@ -362,19 +418,32 @@ export const Browser = (props) => {
return null;
};
- const renderBrowserTabWindows = useCallback(() => tabs.filter((tab) => !tab.isArchived).map((tab) => (
-
- )), [tabs, route.params?.showTabs, newTab, homePageUrl, updateTabInfo, showTabs]);
+ const renderBrowserTabWindows = useCallback(
+ () =>
+ tabs
+ .filter((tab) => !tab.isArchived)
+ .map((tab) => (
+
+ )),
+ [
+ tabs,
+ route.params?.showTabs,
+ newTab,
+ homePageUrl,
+ updateTabInfo,
+ showTabs,
+ ],
+ );
return (
({
+ useAccounts: jest.fn().mockReturnValue({
+ evmAccounts: [],
+ accounts: [],
+ ensByAccountAddress: {},
+ }),
+}));
const mockTabs = [
{ id: 1, url: 'about:blank', image: '', isArchived: false },
@@ -28,6 +37,7 @@ const mockInitialState = {
backgroundState: {
...backgroundState,
BrowserController: { tabs: mockTabs },
+ AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
},
},
security: {},
@@ -42,20 +52,25 @@ const mockInitialState = {
browser: {
tabs: mockTabs,
activeTab: 1,
- }
+ },
};
-jest.mock('../../../core/Engine', () => ({
- context: {
- PhishingController: {
- maybeUpdateState: jest.fn(),
- test: jest.fn((url: string) => {
- if (url === 'phishing.com') return { result: true };
- return { result: false };
- }),
+jest.mock('../../../core/Engine', () => {
+ const { MOCK_ACCOUNTS_CONTROLLER_STATE: mockAccountsControllerState } =
+ jest.requireActual('../../../util/test/accountsControllerTestUtils');
+ return {
+ context: {
+ PhishingController: {
+ maybeUpdateState: jest.fn(),
+ test: jest.fn((url: string) => {
+ if (url === 'phishing.com') return { result: true };
+ return { result: false };
+ }),
+ },
+ AccountsController: mockAccountsControllerState,
},
- },
-}));
+ };
+});
jest.mock('react-native/Libraries/Linking/Linking', () => ({
addEventListener: jest.fn(),
@@ -109,7 +124,8 @@ describe('Browser', () => {
- , { state: { ...mockInitialState } },
+ ,
+ { state: { ...mockInitialState } },
);
expect(toJSON()).toMatchSnapshot();
});
@@ -152,7 +168,9 @@ describe('Browser', () => {
{() => (
{
-
+ ,
);
// Check if myFunction was called
- expect(navigationSpy).toHaveBeenCalledWith(Routes.MODAL.MAX_BROWSER_TABS_MODAL);
+ expect(navigationSpy).toHaveBeenCalledWith(
+ Routes.MODAL.MAX_BROWSER_TABS_MODAL,
+ );
// Clean up the spy
navigationSpy.mockRestore();
});
-
it('should mark a tab as archived if it has been idle for too long', async () => {
const mockTabsForIdling = [
{ id: 1, url: 'about:blank', image: '', isArchived: false },
@@ -206,7 +225,7 @@ describe('Browser', () => {
-
+ ,
);
// Wrap the timer advancement in act
diff --git a/locales/languages/en.json b/locales/languages/en.json
index e83b04a9a929..741911c0bccd 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -1738,7 +1738,13 @@
"generic": "This website has been blocked from automatically opening an external application"
},
"ipfs_gateway_off_title": "IPFS gateway is off",
- "ipfs_gateway_off_content": "To see this site, turn on IPFS gateway in Privacy and Security Settings."
+ "ipfs_gateway_off_content": "To see this site, turn on IPFS gateway in Privacy and Security Settings.",
+ "toast": {
+ "solana_dapp_connection_coming_soon": {
+ "title": "Solana dapp connection coming soon",
+ "message": "You can connect to Solana dapps in the extension or trade Solana tokens in-app."
+ }
+ }
},
"backup_alert": {
"title": "Protect your wallet",