Skip to content
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

#7784: skip waiting for managed storage after initial wait #7790

Merged
merged 17 commits into from Mar 3, 2024

Conversation

twschiller
Copy link
Contributor

@twschiller twschiller commented Mar 2, 2024

What does this PR do?

Remaining Work

  • Handle onChanged test failures?:
/Users/tschiller/projects/pixiebrix-extension/node_modules/webext-storage/distribution/storage-item.js:39
        chrome.storage.onChanged.addListener(changeHandler);
                       ^

TypeError: Cannot read properties of undefined (reading 'onChanged')
  • Fix test failures due to cleanup abort controller cleanup

Reviewer Tips

  • Review managedStorage.ts
  • Review installer.ts

Future Work

  • Figure out how to use jest fake timers during tests to avoid incurring the delay
  • Submit PR to split storages in the jest-webextension-mock project: Separate storage values by type clarkbw/jest-webextension-mock#183
  • After switch to MV3, can just store the flag/timestamp in session storage (but the current local storage approach will still work)

Checklist

  • Add tests
  • New files added to src/tsconfig.strictNullChecks.json (if possible): N/A
  • Designate a primary reviewer: @fregante

@twschiller twschiller added this to the 1.8.10 milestone Mar 2, 2024
@@ -239,21 +239,6 @@ function useRequiredPartnerAuth(): RequiredPartnerState {
authMethodOverride === "partner-oauth2" ||
authMethodOverride === "partner-token";

console.debug("useRequiredPartnerAuth", {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Accidentally left this in the other PR

@twschiller
Copy link
Contributor Author

twschiller commented Mar 2, 2024

@fregante

This comment was marked as outdated.

@fregante
Copy link
Collaborator

fregante commented Mar 2, 2024

Oh it seems that the whole chrome object is being overridden to just { runtime: { id: 42 } }

Dropping this line fixes the issue, but it breaks the LogTable story

Object.assign(global, { chrome: { runtime: { id: 42 } } });

@@ -321,9 +323,14 @@ async function setUninstallURL(): Promise<void> {
}

function initInstaller() {
browser.runtime.onStartup.addListener(async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that onStartup does not mean "on extension start", but "on browser start"

This means that this won't run when the user first installs the extension or when they re-enable it.

In MV2, all code is run at the start.

in MV3, you might want to try https://github.com/fregante/webext-events/blob/main/source/on-extension-start.md

src/testUtils/testAfterEnv.ts Show resolved Hide resolved
@twschiller
Copy link
Contributor Author

@fregante thanks for the help tracking down the onChanged issue. This should be ready for another review. I ended up using SessionValue instead of onSessionStart to avoid the setInterval

Copy link

codecov bot commented Mar 2, 2024

Codecov Report

Attention: Patch coverage is 59.59596% with 40 lines in your changes are missing coverage. Please review.

Project coverage is 72.31%. Comparing base (4cd4511) to head (9787f65).
Report is 7 commits behind head on main.

Files Patch % Lines
src/store/enterprise/managedStorage.ts 60.78% 20 Missing ⚠️
src/background/installer.ts 23.52% 13 Missing ⚠️
src/mv3/SessionStorage.ts 72.22% 5 Missing ⚠️
src/background/telemetry.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7790      +/-   ##
==========================================
+ Coverage   72.28%   72.31%   +0.03%     
==========================================
  Files        1271     1271              
  Lines       39697    39748      +51     
  Branches     7372     7380       +8     
==========================================
+ Hits        28693    28743      +50     
- Misses      11004    11005       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


type HookState = {
data: Nullishable<ManagedStorageState>;
isLoading: boolean;
};

// NOTE: can't share subscribe methods across generators currently for useAsyncExternalStore because it maintains
// a map of subscriptions to state controllers. See https://github.com/pixiebrix/pixiebrix-extension/issues/7789
function subscribe(callback: () => void): () => void {
Copy link
Contributor Author

@twschiller twschiller Mar 2, 2024

Choose a reason for hiding this comment

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

Moved here from managedStorage to prevent accidentally using across multiple hooks given the useAsyncExternalStore footgun

Comment on lines 336 to 346
// Using our own session value vs. webext-events because onExtensionStart has a 100ms delay
// https://github.com/fregante/webext-events/blob/main/source/on-extension-start.ts#L56

// Can't use onStartup because it doesn't mean "on extension start", but "on browser profile start".
// Events registered onStartup won't run when the user first installs or when they re-enable the extension.
// See: https://developer.chrome.com/docs/extensions/reference/api/runtime#event-onStartup

if (await initialized.get()) {
// Session already initialized
return;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm generally against inlining logic that is reused, e.g. logic like once, memoize, throttle and the whole managed storage initialization logic. Doing otherwise leads to mixing business logic where it's not expected.

I think this call could be:

const initSessionOnce = pMemoize(async () => {
	//
}, {
	// initSessionOnce has no arguments, so only one value is stored
	cache: new SessionMap('managedstorageinit', import.meta.url)
})

or

const oncePerSession = (key, url, fn) => pMemoize(fn, {
	cache: new SessionMap(key, url)
})

const initSessionOnce = oncePerSession('managedstorageinit', import.meta.url, async () => {
	//
});

src/store/enterprise/managedStorage.ts Outdated Show resolved Hide resolved
const waitController = new AbortController();
// Skip polling if it becomes initialized while waiting
initializationTimestamp.onChanged(
waitController.abort,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
waitController.abort,
waitController.abort.bind(waitController),

But in reality this cannot happen. Line 185 (initializationTimestamp.set) cannot be reached by another process while this one is running because waitForInitialManagedStorage is only run once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's related to the misunderstanding here? -- that the timestamp is using local storage to be available across contexts: #7790 (comment)

The abort logic in the waitForInitialManagedStorage handles the case where:

  1. Background page starts up and is starts polling for managed storage
  2. Extension Console Page Starts up and starts polling
  3. Background page page finished polling
  4. The new abort controller allows the Extension Console page quit polling because it knows the Background Page already polled the 4.5 seconds

src/utils/promiseUtils.ts Outdated Show resolved Hide resolved
// Call watchDelayedStorageInitialization to handle case where storage is not immediately available within timeout.
// We might consider putting watchStorageInitialization in initManagedStorage, but having a non-terminating
// interval complicates testing.
void initManagedStorage().then(async () => watchDelayedStorageInitialization());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to installer

@@ -305,8 +304,9 @@ async function collectUserSummary(): Promise<UserSummary> {
}

async function init(): Promise<void> {
if ((await isLinked()) && (await allowsTrack())) {
const client = await getLinkedApiClient();
const client = await maybeGetLinkedApiClient();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix race condition

@@ -58,6 +59,19 @@ export class SessionMap<Value extends JsonValue> {
return `${this.key}::${this.url}::${secondaryKey}` as ManualStorageKey;
}

async has(secondaryKey: string): Promise<boolean> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

has is required to be used a cache

Copy link
Collaborator

@fregante fregante left a comment

Choose a reason for hiding this comment

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

🙌

async () => {
await resetManagedStorageInitializationState();
await initManagedStorage();
void watchForDelayedStorageInitialization();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice. Very readable now

jest.setMock("webext-detect-page", detectPageMock);

// For some reason, throwIfAborted is not available in Jest environment even though it appears to be in JSDOM
// https://github.com/jsdom/jsdom/blob/2f8a7302a43fff92f244d5f3426367a8eb2b8896/lib/jsdom/living/aborting/AbortSignal-impl.js#L24
Copy link
Collaborator

@fregante fregante Mar 3, 2024

Choose a reason for hiding this comment

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

Copy link

github-actions bot commented Mar 3, 2024

No loom links were found in the first post. Please add one there if you'd like to it to appear on Slack.

Do not edit this comment manually.

@twschiller twschiller merged commit 4878e51 into main Mar 3, 2024
16 checks passed
@twschiller twschiller deleted the feature/7784-managed-storage-delay branch March 3, 2024 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Only delay/wait for managed storage once on browser extension startup
2 participants