Fix login form issue when using password auto-fill (#5047)

Signed-off-by: Vlad Timofeev <11474041+vlad-timofeev@users.noreply.github.com>
This commit is contained in:
Vlad 2024-04-15 05:40:05 -04:00 committed by GitHub
parent 04bb873191
commit 07ff2a9d3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 117 additions and 2 deletions

View File

@ -6,10 +6,12 @@
"license": "EPL-2.0",
"scripts": {
"build": "compile ui",
"test": "jest --passWithNoTests --silent",
"build:docs": "api-extractor run --local",
"format": "format src",
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},

View File

@ -0,0 +1,28 @@
import { makeSequential } from '../mutex'
describe('mutex', () => {
let results: number[]
beforeEach(() => {
results = []
})
async function waitAndPushResult (millis: number): Promise<void> {
await new Promise<void>((resolve) => {
setTimeout(resolve, millis)
})
results.push(millis)
}
it('expect sequential execution', async () => {
const myUpdate = makeSequential(waitAndPushResult)
await Promise.all([myUpdate(50), myUpdate(30), myUpdate(40), myUpdate(10), myUpdate(20)])
expect(results).toEqual([50, 30, 40, 10, 20])
})
it('expect parallel execution', async () => {
const myUpdate = waitAndPushResult
await Promise.all([myUpdate(50), myUpdate(30), myUpdate(40), myUpdate(10), myUpdate(20)])
expect(results).toEqual([10, 20, 30, 40, 50])
})
})

View File

@ -31,6 +31,7 @@
import { onMount } from 'svelte'
import { BottomAction, getHref } from '..'
import login from '../plugin'
import { makeSequential } from '../mutex'
import Providers from './Providers.svelte'
interface Field {
@ -66,7 +67,7 @@
$: $themeStore.language && validate($themeStore.language)
async function validate (language: string): Promise<boolean> {
const validate = makeSequential(async function validateAsync (language: string): Promise<boolean> {
if (ignoreInitialValidation) return true
for (const field of fields) {
const v = object[field.name]
@ -101,7 +102,7 @@
}
status = OK
return true
}
})
validate($themeStore.language)
let inAction = false

View File

@ -0,0 +1,84 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
/**
Class that allows synchronization of async functions to prevent race conditions.
Inspired by https://stackoverflow.com/a/51086893
**/
class Mutex {
currentLock: Promise<void>
constructor () {
// initially the lock is not held
this.currentLock = Promise.resolve()
}
/**
Acquires the lock. Usage example:
```
let mutex = new Mutex()
async function synchronizedFunction() {
const unlockMutex = await mutex.lock()
try {
// critical section
} finally {
unlockMutex()
}
}
```
@return {Promise<VoidFunction>} promise that must be awaited in order to acquire the lock.
When the promise is fulfulled it returns a function that must be invoked to release the lock.
**/
async lock (): Promise<VoidFunction> {
// function which invocation releases the lock
let releaseLock: VoidFunction
// this Promise is fulfilled as soon as the function above is invoked
const afterReleasePromise = new Promise<void>((resolve) => {
releaseLock = () => {
resolve()
}
})
// Caller gets a promise that resolves
// only after the current outstanding lock resolves
const blockingPromise = this.currentLock.then(() => releaseLock)
// Don't allow the next request until the new promise is done
this.currentLock = afterReleasePromise
// Return the new promise
return await blockingPromise
}
}
type AnyFunction<R> = (...args: any[]) => Promise<R>
/**
This function wraps around the Mutex implementation above and provides a simple interface.
@return {Promise<VoidFunction>} a sequential version of the passed function. This version guarantees
that its invocations are executed one by one.
**/
export function makeSequential<R, T extends AnyFunction<R>> (fn: T): (...args: Parameters<T>) => Promise<R> {
const mutex = new Mutex()
return async function (...args: Parameters<T>): Promise<R> {
const unlockMutex = await mutex.lock()
try {
return await fn(...args)
} finally {
unlockMutex()
}
}
}