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

[Android] Re-introduce autofill in iframe #652

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
647 changes: 360 additions & 287 deletions dist/autofill-debug.js

Large diffs are not rendered by default.

598 changes: 328 additions & 270 deletions dist/autofill.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions docs/playwright-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ await emailPage.clickIntoInput()

```shell
npm run test:integration
# Shows the UI, thus allowing await page.pause()
npm run test:integration:showui
# Skips file compilation and uses higher concurrency
npm run test:integration:fast
# Skips file compilation and uses higher concurrency, and shows the UI
npm run test:integration:fast:showui
```

## Running only flaky tests
Expand Down
178 changes: 84 additions & 94 deletions docs/runtime.android.md
Original file line number Diff line number Diff line change
@@ -1,121 +1,111 @@
## ~`getRuntimeConfiguration()`~
## String replacement
On Android, the only string replacement needed is the platform name. We substitute this:

on android devices, this data is retrieved from the following string-replacements:
```js
// INJECT userPreferences HERE
```

Internally, we force it into the following shape in order to conform to the following schema definition:
- [Runtime Configuration Schema](https://github.com/duckduckgo/content-scope-scripts/blob/shane/unify-config/src/schema/runtime-configuration.schema.json)
with this

**strings to replace**
```
// INJECT contentScope HERE
// INJECT userUnprotectedDomains HERE
// INJECT userPreferences HERE
```js
userPreferences = {
"debug": false,
"platform": {
"name": "android"
}
}
```

Directly replace the lines above in the following way:
## `getRuntimeConfiguration()`

`str.replace('// INJECT contentScope HERE', 'contentScope = {JSON_HERE}') + ';'`
See: [../src/schema/runtimeConfiguration.json](../src/deviceApiCalls/schemas/runtimeConfiguration.json).

For example, the 3 variables should look like this (don't forget the semicolon at the end of each!)
This includes the configurations needed for autofill to know what options are available on the specific page we're on, for example whether the domain is blocklisted by remote config, or whether a specific feature is turned on/off either remotely or by the user.

```javascript
// INJECT contentScope HERE
contentScope = {
"features": {
"autofill": {
"state": "enabled",
"exceptions": []
}
},
"unprotectedTemporary": []
};
```
Compared to other platforms, on Android we include `availableInputTypes` in the initial runtime configuration. We plan to adopt this to all platforms to reduce message passing.

```javascript
// INJECT userUnprotectedDomains HERE
userUnprotectedDomains = [];
```
**Example:**

```javascript
// INJECT userPreferences HERE
userPreferences = {
"debug": false,
"platform": {
"name": "android"
},
"features": {
"autofill": {
"settings": {
"featureToggles": {
"inputType_credentials": true,
"inputType_identities": false,
"inputType_creditCards": false,
"emailProtection": true,
"password_generation": false,
"credentials_saving": true
```json
{
"success": {
"availableInputTypes": {
"email": false,
"credentials": {
"username": true,
"password": true
},
"identities": {
"firstName": false,
"middleName": false,
"lastName": false,
"birthdayDay": false,
"birthdayMonth": false,
"birthdayYear": false,
"addressStreet": false,
"addressStreet2": false,
"addressCity": false,
"addressProvince": false,
"addressPostalCode": false,
"addressCountryCode": false,
"phone": false,
"emailAddress": false
},
"creditCards": {
"cardName": false,
"cardSecurityCode": false,
"expirationMonth": false,
"expirationYear": false,
"cardNumber": false
}
},
"contentScope": {
"features": {
"autofill": {
"state": "enabled",
"exceptions": []
}
},
"unprotectedTemporary": []
},
"userUnprotectedDomains": [],
"userPreferences": {
"debug": false,
"platform": {
"name": "android"
},
"features": {
"autofill": {
"settings": {
"featureToggles": {
"inputType_credentials": true,
"inputType_identities": false,
"inputType_creditCards": false,
"emailProtection": true,
"password_generation": true,
"unknown_username_categorization": true,
"credentials_saving": true
}
}
}
}
}
}
};
}
```

---

## `getAvailableInputTypes()`

This represents which input types we can autofill for the current user.


**strings to replace**
```
// INJECT availableInputTypes HERE
```

Directly replace the line above in the following way:

`str.replace('// INJECT availableInputTypes HERE', 'contentScope = {JSON_HERE}') + ';'`

See: [../src/schema/availableInputTypes.json](../src/deviceApiCalls/schemas/availableInputTypes.json).

This represents which input types we can autofill for the current user. Values are `true` if we can autofill the
field type with at least one item. For example, if we have two credential items and the first only has a username
and the second only has a password, both fields will be `true`. The email value is the same as returned by the
and the second only has a password, both fields will be `true`. The email value is the same as returned by the
`isSignedIn()` method. At this time `identities` and `creditCards` are optional as they are not supported on Android.

```javascript
// INJECT availableInputTypes HERE
availableInputTypes = {
"email": true,
"credentials": {
"username": true,
"password": true
},
"identities": {
"firstName": false,
"middleName": false,
"lastName": false,
"birthdayDay": false,
"birthdayMonth": false,
"birthdayYear": false,
"addressStreet": false,
"addressStreet2": false,
"addressCity": false,
"addressProvince": false,
"addressPostalCode": false,
"addressCountryCode": false,
"phone": false,
"emailAddress": false
},
"creditCards": {
"cardName": false,
"cardSecurityCode": false,
"expirationMonth": false,
"expirationYear": false,
"cardNumber": false
}
}
```
On Android, this data is passed with the initial runtime configurations, so the call just returns the data we already received.

---

Expand Down Expand Up @@ -165,11 +155,11 @@ window.BrowserAutofill.storeFormData(JSON.stringify(data))

---

## `window.BrowserAutofill.getAutofillData(request)`
## `window.ddgGetAutofillData(request)`

- Autofill will send `request` as a string of JSON
- See: [../src/schema/request.getAutofillData.schema.json](../src/deviceApiCalls/schemas/getAutofillData.params.json)
- Response Message via: `window.postMessage(response)`
- Response Message via the listener to: `window.ddgGetAutofillData`
- See: [../src/schema/response.getAutofillData.schema.json](../src/deviceApiCalls/schemas/getAutofillData.result.json)

**`request`** example
Expand Down
102 changes: 50 additions & 52 deletions docs/runtime.ios.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,72 @@
## ~`getRuntimeConfiguration()`~

on Apple devices, this data is retrieved from the following string-replacements

- [BrowserServices Kit String replacements](https://github.com/duckduckgo/BrowserServicesKit/blob/main/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift#L54-L56)
## String replacement
On iOS and macOS, we string-replace these variables:

Internally, we force it into the following shape in order to conform to the following schema definition:
- [Runtime Configuration Schema](https://github.com/duckduckgo/content-scope-scripts/blob/shane/unify-config/src/schema/runtime-configuration.schema.json)

**strings to replace**
```
// INJECT contentScope HERE
// INJECT userUnprotectedDomains HERE
```js
// INJECT userPreferences HERE
// INJECT isApp HERE
// INJECT isTopFrame HERE
// INJECT supportsTopFrame HERE
// INJECT hasModernWebkitAPI HERE
// INJECT webkitMessageHandlerNames HERE
```

Directly replace the lines above in the following way:
- `userPreferences`: sets the platform name
- `isApp`: true when is macOS
- `isTopFrame`: true when the script is being rendered in the overlay webview
- `supportsTopFrame`: true on macOS when the overlay webview is supported (now on all versions)
- `hasModernWebkitAPI`: now true on all versions, since we've dropped macOS Catalina
- `webkitMessageHandlerNames`: an array of all the expected call names, if we attempt a call that's not in the list, the script will error

`str.replace('// INJECT contentScope HERE', 'contentScope = {JSON_HERE}') + ';'`
## ~`getRuntimeConfiguration()`~

For example, the 3 variables should look like this (don't forget the semicolon at the end of each!)
See: [../src/schema/runtimeConfiguration.json](../src/deviceApiCalls/schemas/runtimeConfiguration.json).

```javascript
// INJECT contentScope HERE
contentScope = {
"features": {
"autofill": {
"state": "enabled",
"exceptions": []
}
},
"unprotectedTemporary": []
};
```
This includes the configurations needed for autofill to know what options are available on the specific page we're on, for example whether the domain is blocklisted by remote config, or whether a specific feature is turned on/off either remotely or by the user.

```javascript
// INJECT userUnprotectedDomains HERE
userUnprotectedDomains = [];
```
**Example:**

```javascript
// INJECT userPreferences HERE
userPreferences = {
"debug": false,
"platform": {
"name": "android"
},
"features": {
"autofill": {
"settings": {
"featureToggles": {
"inputType_credentials": true,
"inputType_identities": false,
"inputType_creditCards": false,
"emailProtection": true,
"password_generation": false,
"credentials_saving": true
```json
{
"success": {
"contentScope": {
"features": {
"autofill": {
"state": "enabled",
"exceptions": []
}
},
"unprotectedTemporary": []
},
"userUnprotectedDomains": [],
"userPreferences": {
"debug": false,
"platform": {
"name": "ios"
},
"features": {
"autofill": {
"settings": {
"featureToggles": {
"inputType_credentials": true,
"inputType_identities": false,
"inputType_creditCards": false,
"emailProtection": true,
"password_generation": true,
"credentials_saving": true
}
}
}
}
}
}
};
}
```

---

## `getAvailableInputTypes()`

see:

- [../src/deviceApiCalls/schemas/getAvailableInputTypes.result.json](../src/deviceApiCalls/schemas/getAvailableInputTypes.result.json)
see: [../src/deviceApiCalls/schemas/getAvailableInputTypes.result.json](../src/deviceApiCalls/schemas/getAvailableInputTypes.result.json)

This represents which input types we can autofill for the current user. Values are `true` if we can autofill the
field type with at least one item. For example, if we have two credential items and the first only has a username
Expand Down
16 changes: 15 additions & 1 deletion integration-test/helpers/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,20 @@ export async function defaultIOSScript (page) {
.applyTo(page)
}

/**
* @param {import("@playwright/test").Page} page
*/
export async function defaultAndroidScript (page) {
return createAutofillScript()
.replaceAll({
userPreferences: {
platform: {name: 'android'}
}
})
.platform('android')
.applyTo(page)
}

/**
* @param {import("@playwright/test").Page} page
*/
Expand Down Expand Up @@ -313,7 +327,7 @@ export async function addMocksAsAttachments (page, test, testInfo) {
const lines = [`name: ${name}`]
if (platform === 'android') {
lines.push('sent as json string: \n\n' + JSON.stringify(params))
params = JSON.parse(/** @type {any} */(params))
params = typeof params === 'string' ? JSON.parse(/** @type {any} */(params)) : params
}
lines.push(`\n\nparams: \n\n` + JSON.stringify(params, null, 2))
lines.push(`\n\nresponse: \n\n` + JSON.stringify(response, null, 2))
Expand Down
1 change: 1 addition & 0 deletions integration-test/helpers/harness.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface CredentialsMock {
* ```
*/
interface MockBuilder<State, Mocks extends Record<string, any>> {
withRuntimeConfigOverrides(overrides: RuntimeConfiguration): MockBuilder<State, Mocks>
// Set the private email address
withPrivateEmail(email: string): MockBuilder<State, Mocks>
// Set the personal email address
Expand Down
Loading
Loading