Skip to content

Commit

Permalink
Android: enable iframe support (#536)
Browse files Browse the repository at this point in the history
* WIP

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Progress for in-context signup, still WIP

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* WIP Email Protection stuff

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Wire Email Protection into Android transport

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Improve getAlias API

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Email Protection progress

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Just add builds from a WIP

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* WIP build to fix multiple calls

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Move logic to messaging.js

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Update integration tests

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Improve logging

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Improve playwright test documentation

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Commit compiled files

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Update docs

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Improve docs

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Update getAlias to not call if in-context failed

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* More comments

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Explicitly return undefined

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

* Ensure password gen feature flag is respected

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>

---------

Signed-off-by: Emanuele Feliziani <feliziani.emanuele@gmail.com>
  • Loading branch information
GioSensation authored Apr 9, 2024
1 parent 3d293c5 commit 8b046b1
Show file tree
Hide file tree
Showing 36 changed files with 2,088 additions and 1,629 deletions.
606 changes: 345 additions & 261 deletions dist/autofill-debug.js

Large diffs are not rendered by default.

557 changes: 313 additions & 244 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
177 changes: 83 additions & 94 deletions docs/runtime.android.md
Original file line number Diff line number Diff line change
@@ -1,121 +1,110 @@
## ~`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,
"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 +154,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

0 comments on commit 8b046b1

Please sign in to comment.