diff --git a/package-lock.json b/package-lock.json index fd1622d3e..5b2895ded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9068,6 +9068,11 @@ "dev": true, "license": "MIT" }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==" + }, "node_modules/deepmerge": { "version": "4.3.1", "dev": true, @@ -21867,6 +21872,7 @@ "@google-psat/i18n": "*", "@google-psat/library-detection": "*", "classnames": "^2.3.2", + "deep-object-diff": "^1.1.9", "fast-xml-parser": "^4.3.2", "file-saver": "^2.0.5", "p-queue": "^7.3.4", @@ -21906,6 +21912,7 @@ "@google-psat/design-system": "*", "@google-psat/i18n": "*", "classnames": "^2.5.1", + "deep-object-diff": "^1.1.9", "escape-string-regexp": "^4.0.0", "react": "^18.2.0", "use-context-selector": "^1.4.1" diff --git a/package.json b/package.json index 6a3c4388a..0a8e37cc5 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build-storybook": "storybook build", "build:all": "npm-run-all **:build ", "tsc-packages:build": "npm run i18n:build && npm run common:build && npm run library-detection:build && npm run design-system:build && npm run analysis-utils:build", - "tsc-packages:dev": "npm run i18n:dev && npm run common:dev && npm run library-detection:dev && npm run design-system:dev && npm run analysis-utils:dev", + "tsc-packages:dev": "run-p i18n:dev common:dev library-detection:dev design-system:dev analysis-utils:dev", "build-cli": "npm run tsc-packages:build && npm run cli-dashboard:build && npm run cli:build", "publish:all:local": "npm run build:all && npm run publish:local --workspaces", "unpublish:all:local": "npm run unpublish:local --workspaces", @@ -27,7 +27,8 @@ "analysis-utils:prebuild": "npm run build:remove -w @google-psat/analysis-utils", "analysis-utils:dev": "npm run dev -w @google-psat/analysis-utils", "analysis-utils:build": "npm run analysis-utils:prebuild && npm run build -w @google-psat/analysis-utils", - "cli:dev": "npm run tsc-packages:dev && npm run dev -w @google-psat/cli", + "cli:predev": "npm run tsc-packages:dev && npm run dev -w @google-psat/cli", + "cli:dev": "run-p tsc-packages:dev cli:predev", "cli:prebuild": "npm run build:remove -w @google-psat/cli", "cli:build": "cross-env NODE_ENV=production npm run build -w @google-psat/cli", "cli:start": "npm install && npm run cli:dev", @@ -37,7 +38,8 @@ "cli-dashboard:prebuild": "npm run build:remove -w @google-psat/cli-dashboard", "cli-dashboard:build": "npm run cli-dashboard:prebuild && cross-env NODE_ENV=production npm run build -w @google-psat/cli-dashboard", "cookie-db:update": "node scripts/update-cookie-db.cjs", - "ext:dev": "npm run tsc-packages:dev && npm run dev -w @google-psat/extension", + "ext:predev":"npm run dev -w @google-psat/extension", + "ext:dev": "run-p tsc-packages:dev ext:predev", "ext:prebuild": "npm run build:remove -w @google-psat/extension", "ext:build": "npm run tsc-packages:build && npm run ext:prebuild && cross-env NODE_ENV=production npm run build -w @google-psat/extension", "ext:start": "npm install && npm run ext:dev", diff --git a/packages/common/src/cookies.types.ts b/packages/common/src/cookies.types.ts index 30a1d348d..c013f0293 100644 --- a/packages/common/src/cookies.types.ts +++ b/packages/common/src/cookies.types.ts @@ -112,7 +112,7 @@ export type CookieData = { }; analytics?: CookieAnalytics | null; url?: string; - headerType?: 'response' | 'request' | 'javascript'; + headerType?: 'response' | 'request' | 'javascript' | 'http'; isFirstParty?: boolean | null; frameIdList?: Array; blockedReasons?: BlockedReason[]; diff --git a/packages/extension/package.json b/packages/extension/package.json index 45279421c..fef97e775 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -31,6 +31,7 @@ "@google-psat/i18n": "*", "@google-psat/library-detection": "*", "classnames": "^2.3.2", + "deep-object-diff": "^1.1.9", "fast-xml-parser": "^4.3.2", "file-saver": "^2.0.5", "p-queue": "^7.3.4", diff --git a/packages/extension/src/store/synchnorousCookieStore.ts b/packages/extension/src/store/synchnorousCookieStore.ts index b3b59598b..2a75d1e9c 100644 --- a/packages/extension/src/store/synchnorousCookieStore.ts +++ b/packages/extension/src/store/synchnorousCookieStore.ts @@ -547,11 +547,31 @@ class SynchnorousCookieStore { ) { sentMessageAnyWhere = true; + const newCookieData: { + [cookieKey: string]: CookieData; + } = {}; + + Object.keys(this.tabsData[tabId]).forEach((key) => { + newCookieData[key] = { + ...this.tabsData[tabId][key], + networkEvents: { + requestEvents: [], + responseEvents: [], + }, + url: '', + headerType: ['request', 'response'].includes( + this.tabsData[tabId][key]?.headerType ?? '' + ) + ? 'http' + : 'javascript', + }; + }); + await chrome.runtime.sendMessage({ type: NEW_COOKIE_DATA, payload: { tabId, - cookieData: this.tabsData[tabId], + cookieData: newCookieData, extraData: { extraFrameData: this.tabs[tabId].frameIDURLSet, }, diff --git a/packages/extension/src/view/devtools/components/cookies/cookieLanding/index.tsx b/packages/extension/src/view/devtools/components/cookies/cookieLanding/index.tsx index 98e280227..7e0469ad7 100644 --- a/packages/extension/src/view/devtools/components/cookies/cookieLanding/index.tsx +++ b/packages/extension/src/view/devtools/components/cookies/cookieLanding/index.tsx @@ -16,7 +16,7 @@ /** * External dependencies */ -import React, { useMemo } from 'react'; +import React, { memo, useMemo } from 'react'; import { LibraryDetection, useLibraryDetectionContext, @@ -123,4 +123,4 @@ const AssembledCookiesLanding = () => { ); }; -export default AssembledCookiesLanding; +export default memo(AssembledCookiesLanding); diff --git a/packages/extension/src/view/devtools/components/cookies/cookiesListing/useCookieListing/index.tsx b/packages/extension/src/view/devtools/components/cookies/cookiesListing/useCookieListing/index.tsx index ef1af39dd..9f2ee3c61 100644 --- a/packages/extension/src/view/devtools/components/cookies/cookiesListing/useCookieListing/index.tsx +++ b/packages/extension/src/view/devtools/components/cookies/cookiesListing/useCookieListing/index.tsx @@ -431,7 +431,9 @@ const useCookieListing = (domainsInAllowList: Set) => { case I18n.getMessage('jS'): return value === 'javascript'; case I18n.getMessage('http'): - return value === 'request' || value === 'response'; + return ( + value === 'request' || value === 'response' || value === 'http' + ); default: return true; } diff --git a/packages/extension/src/view/devtools/components/cookies/index.tsx b/packages/extension/src/view/devtools/components/cookies/index.tsx index a8972890f..f8acb3372 100644 --- a/packages/extension/src/view/devtools/components/cookies/index.tsx +++ b/packages/extension/src/view/devtools/components/cookies/index.tsx @@ -16,7 +16,7 @@ /** * External dependencies. */ -import React from 'react'; +import React, { memo } from 'react'; import { Button, CookiesLanding, @@ -105,4 +105,4 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => { ); }; -export default Cookies; +export default memo(Cookies); diff --git a/packages/extension/src/view/devtools/stateProviders/cookie/cookieProvider.tsx b/packages/extension/src/view/devtools/stateProviders/cookie/cookieProvider.tsx index 50b190f75..3a989bb25 100644 --- a/packages/extension/src/view/devtools/stateProviders/cookie/cookieProvider.tsx +++ b/packages/extension/src/view/devtools/stateProviders/cookie/cookieProvider.tsx @@ -43,6 +43,7 @@ import { useSettings } from '../settings'; import { getTab } from '../../../../utils/getTab'; import getFramesForCurrentTab from '../../../../utils/getFramesForCurrentTab'; import Context, { type CookieStoreContext } from './context'; +import { diff } from 'deep-object-diff'; const Provider = ({ children }: PropsWithChildren) => { const [loading, setLoading] = useState(true); @@ -91,15 +92,22 @@ const Provider = ({ children }: PropsWithChildren) => { const currentTargets = await chrome.debugger.getTargets(); - setTabFrames((prevState) => - getFramesForCurrentTab( + setTabFrames((prevState) => { + const updatedTabFrames = getFramesForCurrentTab( prevState, currentTabFrames, currentTargets, extraFrameData ?? {}, isUsingCDP - ) - ); + ); + const isThereDiff = diff(prevState ?? {}, updatedTabFrames); + + if (Object.keys(isThereDiff).length === 0) { + return prevState; + } + + return updatedTabFrames; + }); }, [isUsingCDP] ); @@ -297,7 +305,16 @@ const Provider = ({ children }: PropsWithChildren) => { if (tabId.toString() === message.payload.tabId.toString()) { if (isCurrentTabBeingListenedToRef.current) { setTabToRead(tabId.toString()); - setTabCookies(Object.keys(data).length > 0 ? data : null); + setTabCookies((prevState) => { + if (Object.keys(data).length > 0) { + const isThereDiff = diff(prevState ?? {}, data); + if (Object.keys(isThereDiff).length === 0) { + return prevState; + } + return data; + } + return null; + }); await getAllFramesForCurrentTab(frameData); } else { setTabFrames(null); @@ -414,36 +431,48 @@ const Provider = ({ children }: PropsWithChildren) => { }; }, []); - return ( - - {children} - - ); + const memoisedValue = useMemo(() => { + return { + state: { + tabCookies, + tabUrl, + tabFrames, + loading, + selectedFrame, + isCurrentTabBeingListenedTo: isCurrentTabBeingListenedToRef.current, + returningToSingleTab, + contextInvalidated, + isInspecting, + canStartInspecting, + tabToRead, + frameHasCookies, + }, + actions: { + setSelectedFrame, + changeListeningToThisTab, + getCookiesSetByJavascript, + setIsInspecting, + setContextInvalidated, + setCanStartInspecting, + }, + }; + }, [ + canStartInspecting, + changeListeningToThisTab, + contextInvalidated, + frameHasCookies, + getCookiesSetByJavascript, + isInspecting, + loading, + returningToSingleTab, + selectedFrame, + tabCookies, + tabFrames, + tabToRead, + tabUrl, + ]); + + return {children}; }; export default Provider; diff --git a/packages/library-detection/package.json b/packages/library-detection/package.json index 722a2d663..a56c1113d 100644 --- a/packages/library-detection/package.json +++ b/packages/library-detection/package.json @@ -35,6 +35,7 @@ "@google-psat/design-system": "*", "@google-psat/i18n": "*", "classnames": "^2.5.1", + "deep-object-diff": "^1.1.9", "escape-string-regexp": "^4.0.0", "react": "^18.2.0", "use-context-selector": "^1.4.1" diff --git a/packages/library-detection/src/core/hooks/useLibraryDetection.ts b/packages/library-detection/src/core/hooks/useLibraryDetection.ts index e71a20531..31eea80a9 100644 --- a/packages/library-detection/src/core/hooks/useLibraryDetection.ts +++ b/packages/library-detection/src/core/hooks/useLibraryDetection.ts @@ -21,7 +21,7 @@ import { executeTaskInDevToolWorker, LIBRARY_DETECTION_WORKER_TASK, } from '@google-psat/common'; - +import { diff } from 'deep-object-diff'; /** * Internal dependencies. */ @@ -91,8 +91,13 @@ const useLibraryDetection = () => { matches, realtimeComputationResult ); + const diffedLibraryMatches = diff(data, matches); + + if (Object.keys(diffedLibraryMatches).length > 0) { + return data; + } - return data; + return matches; }); } } diff --git a/packages/library-detection/src/core/stateProvider/libraryDetectionProvider.tsx b/packages/library-detection/src/core/stateProvider/libraryDetectionProvider.tsx index fde4b5933..4ca7901d1 100644 --- a/packages/library-detection/src/core/stateProvider/libraryDetectionProvider.tsx +++ b/packages/library-detection/src/core/stateProvider/libraryDetectionProvider.tsx @@ -63,24 +63,30 @@ const LibraryDetectionProvider = ({ children }: PropsWithChildren) => { error !== 'net::ERR_ABORTED' ) { setErrorOccured(true); + } else if ( + frameId === 0 && + _tabId === chrome.devtools.inspectedWindow.tabId && + error === 'net::ERR_ABORTED' + ) { + setIsCurrentTabLoading(false); } }, [] ); - // It is attached, next time the tab is updated or reloaded. - const onTabUpdate = useCallback( - (changingTabId: number, changeInfo: chrome.tabs.TabChangeInfo) => { - if (Number(changingTabId) === Number(tabId)) { - if (changeInfo.status === 'complete') { - setIsCurrentTabLoading(false); - } else if (changeInfo.status === 'loading') { - setLibraryMatches(initialLibraryMatches); - setIsCurrentTabLoading(true); - setShowLoader(true); - setLoadedBeforeState(false); - setIsInitialDataUpdated(false); - } + const onCompletedListener = useCallback( + ({ + frameId, + frameType, + tabId: changingTabId, + }: chrome.webNavigation.WebNavigationFramedCallbackDetails) => { + if ( + frameId === 0 && + frameType === 'outermost_frame' && + Number(changingTabId) === Number(tabId) + ) { + setIsCurrentTabLoading(false); + setErrorOccured(false); } }, [tabId] @@ -102,28 +108,19 @@ const LibraryDetectionProvider = ({ children }: PropsWithChildren) => { [tabId] ); - const onCompleted = useCallback( - ({ frameId }: chrome.webNavigation.WebNavigationFramedCallbackDetails) => { - if (frameId === 0) { - setErrorOccured(false); - } - }, - [] - ); - useEffect(() => { - chrome.tabs.onUpdated.removeListener(onTabUpdate); - chrome.tabs.onUpdated.addListener(onTabUpdate); + chrome.webNavigation.onCompleted.addListener(onCompletedListener); chrome.webNavigation.onErrorOccurred.addListener(onErrorOccuredListener); chrome.webNavigation.onBeforeNavigate.addListener(onNavigatedListener); - chrome.webNavigation.onCompleted.addListener(onCompleted); return () => { - chrome.tabs.onUpdated.removeListener(onTabUpdate); + chrome.webNavigation.onCompleted.removeListener(onCompletedListener); chrome.webNavigation.onBeforeNavigate.removeListener(onNavigatedListener); - chrome.webNavigation.onCompleted.removeListener(onCompleted); + chrome.webNavigation.onErrorOccurred.removeListener( + onErrorOccuredListener + ); }; - }, [onTabUpdate, onErrorOccuredListener, onNavigatedListener, onCompleted]); + }, [onErrorOccuredListener, onNavigatedListener, onCompletedListener]); return (