1 of 91

2 of 91

Project Fugu

3 of 91

When you build for native, you have choice.

4 of 91

When you build for web, your options are limited.

5 of 91

6 of 91

7 of 91

8 of 91

Users want these features

So developers flock to solutions other than the Web

Mobile hybrid app frameworks like Cordova

Super app platforms for mini apps like WeChat

Desktop hybrid app frameworks like Electron

9 of 91

Audio & Video Capture

Advanced Camera Controls

Recording Media

Real-Time Communication

USB

Credentials

Payments

Network Type & Speed

Online State

Vibration

Battery Status

Local Notifications

Push Messages

Home Screen Installation

Foreground Detection

Permissions

File Reading

Touch Settings

Speech Recognition

Pointing Device Adaptation

Offline Mode

Background Sync

Geolocation

Device Position

Device Motion

Virtual & Augmented Reality

Fullscreen

Screen Orientation & Lock

Presentation Features

Vibration

Device Memory

Bluetooth

Real-Time Communication

Touch Gestures

Storage Quotas

10 of 91

Audio & Video Capture

Advanced Camera Controls

Recording Media

Real-Time Communication

USB

Credentials

Payments

Network Type & Speed

Online State

Vibration

Battery Status

Local Notifications

Push Messages

Home Screen Installation

Foreground Detection

Permissions

File Reading

Contacts

SMS

Serial

Task Scheduling

Touch Settings

Speech Recognition

Offline Mode

Background Sync

Inter-App Communication

NFC

Geolocation

Geofencing

Device Position

Device Motion

Wake Lock

Proximity Sensors

Ambient Light

Virtual & Augmented Reality

Fullscreen

Screen Orientation & Lock

Presentation Features

Task Scheduling

Vibration

Device Memory

Bluetooth

Touch Gestures

Run on Startup

Clipboard Access

Unreliable Socket Communications

Storage Quotas

Clipboard Access

Unlimited Storage

WebHID (HID)

App Icon Badging

Local Fonts Access

Real-Time Communication

Pointing Device Adaptation

11 of 91

Enable web apps to do anything native apps can, by exposing the capabilities of native platforms to the web platform, while maintaining user security, privacy, trust, and other core tenets of the web.

12 of 91

goo.gle/project-fugu

13 of 91

bit.ly/powerful-apis

14 of 91

From idea to new API

15 of 91

From idea to new API

Identify Need

16 of 91

From idea to new API

Write Explainer

17 of 91

From idea to new API

Solicit Feedback

18 of 91

From idea to new API

Formalize Spec

19 of 91

From idea to new API

Formalize Spec

20 of 91

From idea to new API

Ship It

21 of 91

bit.ly/fugu-bugs

22 of 91

goo.gle/fugu-api-tracker

23 of 91

Resources

Web Capabilities page:�goo.gle/capabilities

Fugu Capability process:�goo.gle/fugu-process

24 of 91

Fugu Greetings

25 of 91

26 of 91

The Native File System API

27 of 91

28 of 91

const importImage = async () => {

try {

const handle = await window.showOpenFilePicker();

return handle.getFile();

} catch (err) {

console.error(err.name, err.message);

}

};

29 of 91

const exportImage = async (blob) => {

try {

const handle = await window.showSaveFilePicker();

const writable = await handle.createWritable();

await writable.write(blob);

await writable.close();

} catch (err) {

console.error(err.name, err.message);

}

};

30 of 91

31 of 91

32 of 91

33 of 91

The Contact Picker API

34 of 91

35 of 91

const getContacts = async () => {

const properties = ['name'];

const options = {multiple: true};

try {

return await navigator.contacts.select(properties, options);

} catch (err) {

console.error(err.name, err.message);

}

};

36 of 91

37 of 91

if ('contacts' in navigator) {

import('./contacts.mjs');

}

38 of 91

The Asynchronous Clipboard API

39 of 91

40 of 91

const copy = async (blob) => {

try {

await navigator.clipboard.write([

new ClipboardItem({

[blob.type]: blob,

}),

]);

} catch (err) {

console.error(err.name, err.message);

}

};

41 of 91

const paste = async () => {

try {

const clipboardItems = await navigator.clipboard.read();

for (const clipboardItem of clipboardItems) {

try {

for (const type of clipboardItem.types) {

const blob = await clipboardItem.getType(type);

return blob;

}

} catch (err) {

console.error(err.name, err.message);

}

}

} catch (err) {

console.error(err.name, err.message);

}

};

42 of 91

if (('clipboard' in navigator) &&

('write' in navigator.clipboard)) {

import('./clipboard.mjs');

}

43 of 91

44 of 91

45 of 91

46 of 91

47 of 91

The Badging API

48 of 91

49 of 91

let strokes = 0;

canvas.addEventListener('pointerdown', () => {

navigator.setAppBadge(++strokes);

});

clearButton.addEventListener('click', () => {

strokes = 0;

navigator.setAppBadge(strokes);

});

50 of 91

51 of 91

if ('setAppBadge' in navigator) {

import('./badge.mjs');

}

52 of 91

The Periodic Background Sync API

53 of 91

54 of 91

55 of 91

const registerPeriodicBackgroundSync = async () => {

const registration = await navigator.serviceWorker.ready;

try {

registration.periodicSync.register('image-of-the-day-sync', {

// An interval of one day.

minInterval: 24 * 60 * 60 * 1000,

});

} catch (err) {

console.error(err.name, err.message);

}

};

56 of 91

self.addEventListener('periodicsync', (syncEvent) => {

if (syncEvent.tag === 'image-of-the-day-sync') {

syncEvent.waitUntil((async () => {

const blob = await getImageOfTheDay();

const clients = await self.clients.matchAll();

clients.forEach((client) => {

client.postMessage({

image: blob,

});

});

})());

}

});

57 of 91

// In the client:

const registration = await navigator.serviceWorker.ready;

if (registration && 'periodicSync' in registration) {

import('./periodic_background_sync.mjs');

}

// In the service worker:

if ('periodicSync' in self.registration) {

importScripts('./image_of_the_day.mjs');

}

58 of 91

Notification Triggers API

59 of 91

60 of 91

61 of 91

62 of 91

const targetDate = promptTargetDate();

if (targetDate) {

const registration = await navigator.serviceWorker.ready;

registration.showNotification('Reminder', {

tag: 'reminder',

body: 'It’s time to finish your greeting card!',

showTrigger: new TimestampTrigger(targetDate),

});

}

63 of 91

if (('Notification' in window) &&

('showTrigger' in Notification.prototype)) {

import('./notification_triggers.mjs');

}

64 of 91

The Wake Lock API

65 of 91

66 of 91

67 of 91

let wakeLock = null;

const requestWakeLock = async () => {

wakeLock = await navigator.wakeLock.request('screen');

wakeLock.addEventListener('release', () => {

console.log('Wake Lock was released');

});

console.log('Wake Lock is active');

};

const handleVisibilityChange = () => {

if (wakeLock !== null && document.visibilityState === 'visible') {

requestWakeLock();

}

};

document.addEventListener('visibilitychange', handleVisibilityChange);

document.addEventListener('fullscreenchange', handleVisibilityChange);

68 of 91

if (('wakeLock' in navigator) &&

('request' in navigator.wakeLock)) {

import('./wake_lock.mjs');

}

69 of 91

The Idle Detection API

70 of 91

71 of 91

72 of 91

const idleDetector = new IdleDetector();

idleDetector.addEventListener('change', () => {

const userState = idleDetector.userState;

const screenState = idleDetector.screenState;

console.log(`Idle change: ${userState}, ${screenState}.`);

if (userState === 'idle') {

clearCanvas();

}

});

await idleDetector.start({

threshold: 60000,

signal,

});

73 of 91

if ('IdleDetector' in window) {

import('./idle_detection.mjs');

}

74 of 91

Project Fugu and You

75 of 91

76 of 91

77 of 91

78 of 91

79 of 91

80 of 91

81 of 91

82 of 91

83 of 91

84 of 91

85 of 91

86 of 91

87 of 91

88 of 91

89 of 91

90 of 91

91 of 91

Thank you!