Skip to content
This repository was archived by the owner on Nov 12, 2025. It is now read-only.

Commit c5159ac

Browse files
committed
Add handler for unsupported browser APIs in Interactive Canvas
Radio group selects whether there should be a warning or throw an error By default nothing changes Bug: 193062584 Bug: 193666517 Change-Id: I770eb76e98df7d2ed19c312142d5f72a5ccc7fd5
1 parent f55ce9d commit c5159ac

File tree

7 files changed

+150
-1
lines changed

7 files changed

+150
-1
lines changed

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {MatChipsModule} from '@angular/material/chips';
2222
import {MatFormFieldModule} from '@angular/material/form-field';
2323
import {MatIconModule} from '@angular/material/icon';
2424
import {MatInputModule} from '@angular/material/input';
25+
import {MatRadioModule} from '@angular/material/radio';
2526
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
2627
import {MatTabsModule} from '@angular/material/tabs';
2728

@@ -49,6 +50,7 @@ import {TabPreferencesComponent} from './tab-preferences/tab-preferences.compone
4950
MatFormFieldModule,
5051
MatIconModule,
5152
MatInputModule,
53+
MatRadioModule,
5254
MatSlideToggleModule,
5355
MatTabsModule,
5456
],

src/app/chrome-bridge.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ export class ChromeBridgeService {
7171
}
7272
`);
7373
}
74+
// Sync API behavior with webpage.
75+
await this.updateUnsupportedApiBehavior(
76+
await this.preferences.getUnsupportedApiBehavior()
77+
);
7478
}
7579

7680
/**
@@ -350,4 +354,13 @@ export class ChromeBridgeService {
350354
}
351355
}, 500);
352356
}
357+
358+
/**
359+
* Method that is called by `tab-preferences` when the `unsupportedApiBehavior`
360+
* value is changed, in order to broadcast this change with the webpage.
361+
* @param newValue The updated warning level
362+
*/
363+
async updateUnsupportedApiBehavior(newValue = 'off') {
364+
await this.broadcastMessage('Ext-UnsupportedApiBehavior', newValue);
365+
}
353366
}

src/app/preferences.service.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ interface ExtensionPreferences {
2929
* Flag that, when enabled, sends logs in webpage context.
3030
*/
3131
flagDebugClient: boolean;
32+
/**
33+
* How to handle when the webpage calls an API method that will not work in
34+
* Interactive Canvas.
35+
* @see https://developers.google.com/assistant/interactivecanvas/web-apps#guidelines_and_restrictions
36+
*/
37+
unsupportedApiBehavior: 'off' | 'warn' | 'error';
3238
}
3339

3440
type ExtensionPreference = keyof ExtensionPreferences;
@@ -61,6 +67,14 @@ export class PreferencesService {
6167
return this.writePreference<boolean>('flagDebugClient', value);
6268
}
6369

70+
async getUnsupportedApiBehavior() {
71+
return this.readPreference<string>('unsupportedApiBehavior');
72+
}
73+
74+
async setUnsupportedApiBehavior(value: string) {
75+
this.writePreference<string>('unsupportedApiBehavior', value);
76+
}
77+
6478
private async readPreference<T>(key: ExtensionPreference): Promise<T> {
6579
return new Promise(res => {
6680
chrome.storage.sync.get(key, (result: Record<string, T>) => {

src/app/tab-preferences/tab-preferences.component.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,24 @@ <h1>Preferences</h1>
3232
These features are not available during remote debugging.
3333
</span>
3434

35-
<h2>Extension Flags</h2>
35+
<!-- Global controls -->
36+
<h2>Extension Preferences</h2>
3637
<mat-slide-toggle [checked]="preferencesDebugClient" (change)="onChangeDebugClient($event)">
3738
Enable client-side logging
3839
</mat-slide-toggle>
40+
3941
<mat-slide-toggle [checked]="preferencesDebugExtension" (change)="onChangeDebugExtension($event)">
4042
Enable extension debug logging
4143
</mat-slide-toggle>
44+
45+
<span>Behavior for
46+
<a href="https://developers.google.com/assistant/interactivecanvas/web-apps#guidelines_and_restrictions">
47+
unavailable Interactive Canvas methods
48+
</a>:
49+
</span>
50+
<br>
51+
<mat-radio-group aria-label="Unsupported API warning level" [value]="preferencesUnsupportedApi" (change)="onChangeUnsupportedApi($event)">
52+
<mat-radio-button value="off">Ignore</mat-radio-button>
53+
<mat-radio-button value="warn">Log Warning</mat-radio-button>
54+
<mat-radio-button value="error">Throw Error</mat-radio-button>
55+
</mat-radio-group>

src/app/tab-preferences/tab-preferences.component.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import {Component, OnInit} from '@angular/core';
1818
import {MatSlideToggleChange} from '@angular/material/slide-toggle';
19+
import {MatRadioChange} from '@angular/material/radio';
1920
import {ChromeBridgeService} from '../chrome-bridge.service';
2021
import {PreferencesService} from '../preferences.service';
2122

@@ -33,6 +34,8 @@ export class TabPreferencesComponent implements OnInit {
3334
preferences: PreferencesService;
3435
preferencesDebugClient = false;
3536
preferencesDebugExtension = false;
37+
preferencesUnsupportedApi = 'off';
38+
3639
isRemoteTarget = false;
3740
chromeBridge: ChromeBridgeService;
3841

@@ -49,6 +52,8 @@ export class TabPreferencesComponent implements OnInit {
4952
this.preferencesDebugClient = await this.preferences.getFlagDebugClient();
5053
this.preferencesDebugExtension =
5154
await this.preferences.getFlagDebugExtension();
55+
this.preferencesUnsupportedApi =
56+
(await this.preferences.getUnsupportedApiBehavior()) || 'off';
5257
}
5358

5459
/**
@@ -84,4 +89,16 @@ export class TabPreferencesComponent implements OnInit {
8489
this.preferencesDebugExtension = checked;
8590
await this.preferences.setFlagDebugExtension(checked);
8691
}
92+
93+
/**
94+
* Click handler when the Unsupported API level radio button is changed.
95+
* @param event Toggle event
96+
*/
97+
async onChangeUnsupportedApi(event: MatRadioChange) {
98+
const {value} = event;
99+
this.preferencesUnsupportedApi = value;
100+
await this.preferences.setUnsupportedApiBehavior(value);
101+
// Sync this changed value with webpage
102+
await this.chromeBridge.updateUnsupportedApiBehavior(value);
103+
}
87104
}

src/styles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,8 @@ h1 {
6868
--toggle-thumb-color: rgb(96, 125, 139); /* #607D8B */
6969
--label-unselected-text-color: rgba(255, 255, 255, 0.7);
7070
}
71+
72+
a:visited {
73+
color: cyan;
74+
}
7175
}

src/webpage-script.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,86 @@ async function getImgData(blob: Blob): Promise<string> {
410410
});
411411
}
412412

413+
/**
414+
* A setting for how to behave when an unsupported API is used. Updated via
415+
* `Ext-UnsupportedApiBehavior` message. It is a preference (see `preferences.service`)
416+
* and updated in the Preferences tab.
417+
*/
418+
let unsupportedApiBehavior = 'off';
419+
420+
/**
421+
* Returns the names of every property of an object that is a function.
422+
* @param object An object with methods and optionally fields
423+
*/
424+
function getMethodsOfObject(object: Record<string, Function>) {
425+
const properties = Object.getOwnPropertyNames(object);
426+
return properties.filter(p => typeof object[p] === 'function');
427+
}
428+
429+
/**
430+
* Adds an Interactive Canvas warning to the specific method.
431+
* @param fn Function that should have additional warning
432+
* @param binder The object to which this function belongs
433+
*/
434+
// eslint-disable-next-line
435+
function addMethodWarning(fn: (...args: any[]) => unknown, binder: Record<string, Function>) {
436+
const boundFunction = fn.bind(binder);
437+
const methodName = fn.name;
438+
const msg =
439+
`The method "${methodName}" will not execute in an Interactive ` +
440+
'Canvas environment. See ' +
441+
'https://developers.google.com/assistant/interactivecanvas/web-apps#guidelines_and_restrictions ' +
442+
'to learn more.';
443+
binder[methodName] = (...args: unknown[]) => {
444+
if (unsupportedApiBehavior === 'warn') {
445+
console.warn(msg);
446+
} else if (unsupportedApiBehavior === 'error') {
447+
throw new Error(msg);
448+
}
449+
boundFunction(...args);
450+
};
451+
}
452+
453+
/**
454+
* Adds an Interactive Canvas warning to the specific property.
455+
* @param readOnlyField String-based name of the property
456+
* @param owner The object to which the field belongs
457+
*/
458+
// eslint-disable-next-line
459+
function addPropertyWarning(readOnlyField: string, owner: any) {
460+
const msg =
461+
`The field "${readOnlyField}" will not execute in an Interactive ` +
462+
'Canvas environment. See ' +
463+
'https://developers.google.com/assistant/interactivecanvas/web-apps#guidelines_and_restrictions ' +
464+
'to learn more.';
465+
const boundProperty = owner[readOnlyField];
466+
Object.defineProperty(owner, readOnlyField, {
467+
get: () => {
468+
if (unsupportedApiBehavior === 'warn') {
469+
console.warn(msg);
470+
} else if (unsupportedApiBehavior === 'error') {
471+
throw new Error(msg);
472+
}
473+
return boundProperty;
474+
},
475+
});
476+
}
477+
478+
/**
479+
* Add handler to every API method that will not work in Interactive Canvas.
480+
* This will result in either a warning or throw an error depending on
481+
* preference.
482+
* @see https://developers.google.com/assistant/interactivecanvas/web-apps#guidelines_and_restrictions
483+
*/
484+
function addUnsupportedApiWarnings() {
485+
addPropertyWarning('localStorage', window);
486+
addPropertyWarning('geolocation', window.navigator);
487+
addPropertyWarning('mediaDevices', window.navigator);
488+
addPropertyWarning('cookie', document);
489+
addPropertyWarning('indexedDB', window);
490+
addPropertyWarning('webkitSpeechRecognition', window);
491+
}
492+
413493
window.requestAnimationFrame(() => {
414494
const hasInteractiveCanvas = window.interactiveCanvas !== undefined;
415495

@@ -447,6 +527,7 @@ window.requestAnimationFrame(() => {
447527
};
448528

449529
window.interactiveCanvasProcessSdk = processSdk;
530+
addUnsupportedApiWarnings();
450531
});
451532

452533
document.addEventListener('message', (e: Event) => {
@@ -472,5 +553,9 @@ document.addEventListener('message', (e: Event) => {
472553
window.interactiveCanvasProcessSdk();
473554
break;
474555
}
556+
case 'Ext-UnsupportedApiBehavior': {
557+
const {data} = eventData;
558+
unsupportedApiBehavior = data;
559+
}
475560
}
476561
});

0 commit comments

Comments
 (0)