├── w3c.json ├── devtools-network-panel.png ├── img ├── resource-timing-cache.png ├── resource-timing-fetch.png ├── resource-timing-race.png ├── resource-timing-network.png └── resource-timing-overview.png ├── devtools-application-panel.png ├── LICENSE.md ├── CONTRIBUTING.md ├── security-privacy-questionnaire.md ├── update-from-chrome-ot.md ├── update-from-chrome-m116.md ├── final-form.md ├── README.md └── resource-timing-api.md /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [80485] 3 | , "contacts": ["cwilso"] 4 | , "repo-type": "cg-report" 5 | } 6 | -------------------------------------------------------------------------------- /devtools-network-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/devtools-network-panel.png -------------------------------------------------------------------------------- /img/resource-timing-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/img/resource-timing-cache.png -------------------------------------------------------------------------------- /img/resource-timing-fetch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/img/resource-timing-fetch.png -------------------------------------------------------------------------------- /img/resource-timing-race.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/img/resource-timing-race.png -------------------------------------------------------------------------------- /devtools-application-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/devtools-application-panel.png -------------------------------------------------------------------------------- /img/resource-timing-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/img/resource-timing-network.png -------------------------------------------------------------------------------- /img/resource-timing-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WICG/service-worker-static-routing-api/HEAD/img/resource-timing-overview.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors 2 | under the 3 | [W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). 4 | 5 | Contributions to Specifications are made under the 6 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 7 | 8 | Contributions to Test Suites are made under the 9 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) 10 | 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the W3C Web Platform Incubator Community Group, governed by the [W3C Community License 4 | Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To make substantive contributions, 5 | you must join the CG. 6 | 7 | If you are not the sole contributor to a contribution (pull request), please identify all 8 | contributors in the pull request comment. 9 | 10 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 11 | 12 | ``` 13 | +@github_username 14 | ``` 15 | 16 | If you added a contributor by mistake, you can remove them in a comment with: 17 | 18 | ``` 19 | -@github_username 20 | ``` 21 | 22 | If you are making a pull request on behalf of someone else but you had no part in designing the 23 | feature, you can remove yourself with the above syntax. 24 | -------------------------------------------------------------------------------- /security-privacy-questionnaire.md: -------------------------------------------------------------------------------- 1 | # Answers to the Self-Review Questionnaire: Security and Privacy 2 | 3 | https://www.w3.org/TR/security-privacy-questionnaire/ 4 | 5 | ### 2.1. What information might this feature expose to Web sites or other parties, and for what purposes is that exposure necessary? 6 | 7 | No additional information will be exposed by the proposal. This is just offloading simple things that the fetch handler does. 8 | 9 | ### 2.2. Do features in your specification expose the minimum amount of information necessary to enable their intended uses? 10 | 11 | Yes. However, the proposal does not change how the browser handles information. 12 | 13 | ### 2.3. How do the features in your specification deal with personal information, personally-identifiable information (PII), or information derived from them? 14 | 15 | The proposal does not change how the browser handles PII. 16 | 17 | ### 2.4. How do the features in your specification deal with sensitive information? 18 | 19 | n/a 20 | 21 | ### 2.5. Do the features in your specification introduce new state for an origin that persists across browsing sessions? 22 | 23 | Yes. The proposal asks a user agent to remember how to handle subsequent navigation with ServiceWorkers. 24 | 25 | ### 2.6. Do the features in your specification expose information about the underlying platform to origins? 26 | 27 | No. 28 | 29 | ### 2.7. Does this specification allow an origin to send data to the underlying platform? 30 | 31 | If static routing rules are considered “data”, yes. 32 | 33 | ### 2.8. Do features in this specification enable access to device sensors? 34 | 35 | No. 36 | 37 | ### 2.9. Do features in this specification enable new script execution/loading mechanisms? 38 | 39 | Yes. A rule can make the script loaded from the network or cache without executing JavaScript in ServiceWorkers. 40 | 41 | ### 2.10. Do features in this specification allow an origin to access other devices? 42 | 43 | No. 44 | 45 | ### 2.11. Do features in this specification allow an origin some measure of control over a user agent’s native UI? 46 | 47 | No. However, the router rules are shown to chrome://serviceworker-internals and/or devtools for debuggability. 48 | 49 | ### 2.12. What temporary identifiers do the features in this specification create or expose to the web? 50 | 51 | n/a 52 | 53 | ### 2.13. How does this specification distinguish between behavior in first-party and third-party contexts? 54 | 55 | This follows how a ServiceWorker fetch handler behaves. 56 | 57 | ### 2.14. How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode? 58 | 59 | It will not bring any difference with and without the proposal. 60 | 61 | ### 2.15. Does this specification have both "Security Considerations" and "Privacy Considerations" sections? 62 | 63 | No. However, the original Service Worker specification considers it in 64 | [6. Security Considerations](https://www.w3.org/TR/service-workers/#security-considerations). 65 | The proposal does not change any security requirements and privacy requirements explained there. 66 | 67 | ### 2.16. Do features in your specification enable origins to downgrade default security protections? 68 | 69 | No. 70 | 71 | ### 2.17. How does your feature handle non-"fully active" documents? 72 | 73 | The proposal does not change the behavior of non-”fully active” documents. 74 | 75 | ### 2.18. What should this questionnaire have asked? 76 | 77 | No additional questions are needed. 78 | -------------------------------------------------------------------------------- /update-from-chrome-ot.md: -------------------------------------------------------------------------------- 1 | # What has been changed between the Chrome origin trial and M123? 2 | 3 | As you can find in the [Chrome 123 beta blog](https://developer.chrome.com/blog/chrome-123-beta#service_worker_static_routing_api), 4 | Chrome M123 includes the ServiceWorker static routing API. Based on feedback from the Origin Trial and progress in the implementation, 5 | the following things have been updated since M117. 6 | You can find the difference between M116 and M117 in [What has been changed between M116 and later?](https://github.com/WICG/service-worker-static-routing-api/blob/main/update-from-chrome-m116.md) 7 | 8 | ## API surface change 9 | 10 | `InstallEvent.addRoutes()` replaces `InstallEvent.registerRouter()`. The function signature is not changed, and you can just rename `registerRouter()` to 11 | `addRoutes()`. `registerRouter()` will be removed soon after M123. 12 | 13 | The difference between them is whether it is allowed to be called multiple times or not. RegisterRouter() was explicitly designed to be called once, 14 | because we would like to limit the case where the router registrations are distributed across multiple event handlers. However, we received feedback 15 | from the developer community that there are some cases where service worker scripts are delivered from a third party origin, and that can be provided 16 | as a SDK. It is reasonable to assume that both first party and third party want to use the Static Routing API and register some routes. The new API shape 17 | was discussed based on the community feedback ([issue comments](https://github.com/w3c/ServiceWorker/issues/1373#issuecomment-1667123029) and 18 | [github issue](https://github.com/WICG/service-worker-static-routing-api/issues/10)). 19 | 20 | ## Behavior change on URLPattern condition 21 | 22 | The router condition accepts the `urlPattern` field, which the input is eventually converted into the [URLPattern](https://urlpattern.spec.whatwg.org/) 23 | object. Since M116, both URLPattern API and the Service Worker static routing API are updated, and we believe the current behavior is much more intuitive. 24 | 25 | Two major updates: 26 | 27 | 1. Use [inherit left, wildcard right](https://github.com/whatwg/urlpattern/pull/198) behavior in base URL. 28 | 1. The main ServiceWorker script URL as a baseURL will be implicitly set when the input is a dictionary or a string, representing [URLPatternInput](https://urlpattern.spec.whatwg.org/#typedefdef-urlpatterninput). If the input is the URLPattern object constructed with `new URLPattern()`, the baseURL inheritance wouldn’t be applied. e.g. a pattern like `“/articles/*”` or `{‘pathname’: ‘*.png’}` will be affected. 29 | 30 | ## New conditions and sources 31 | 32 | We added some new conditions and sources since M116, and now following conditions and sources are available to use: 33 | 34 | * `condition`: 35 | * `urlPattern`: A [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) object, [a dictionary](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern#input), or a string. See [Behavior change on URLPattern condition](#behavior-change-on-urlpattern-condition) for details. 36 | * `requestMethod`: A string containing a Request [method](https://developer.mozilla.org/docs/Web/API/Request/method). 37 | * `requestMode`: A string containing a Request [mode](https://developer.mozilla.org/docs/Web/API/Request/mode). 38 | * `requestDestination`: A string containing a Request [destination](https://developer.mozilla.org/docs/Web/API/Request/destination). 39 | * `runningStatus`: a string, either `”running”` or `”not-running”`. 40 | * `source`: 41 | * `"cache"` 42 | * `"fetch-event"` 43 | * `"race-network-and-fetch-handler"` 44 | * an object with the string `cacheName` property: The string tells the cache storage’s cacheName 45 | 46 | e.g. 47 | Directly goes to network for the POST method 48 | ```js 49 | { 50 | condition: {requestMethod: ‘POST’}, 51 | source: ‘network’ 52 | } 53 | ``` 54 | 55 | e.g. 56 | Use cache storage for scripts, and fallbacks to network if cache misses. 57 | ```js 58 | { 59 | condition: {requestDestination: ‘script’}, 60 | source: ‘cache’’ 61 | } 62 | ``` 63 | 64 | e.g. 65 | Run competition between network fetch and a fetch-handler for navigational requests. 66 | ```js 67 | { 68 | condition: {requestMode: ‘navigate’}, 69 | source: ‘race-network-and-fetch-handler’ 70 | } 71 | ``` 72 | 73 | e.g. 74 | Use cache storage named “pictures” for files whose name ends with “.png” or “.jpg”. 75 | ```js 76 | { 77 | condition: { 78 | or: [ 79 | {urlPattern: “*.png”}, 80 | {urlPattern: “*.jpg”} 81 | ] 82 | }, 83 | source: { 84 | cacheName: “pictures” 85 | } 86 | } 87 | ``` 88 | 89 | e.g. 90 | Directory fetch from the network for subresources served via a content delivery network (CDN) named "cdn.example.com". 91 | ```js 92 | { 93 | condition: {urlPattern: new URLPattern({hostname: "cdn.example.com"})}, 94 | source: "network" 95 | } 96 | ``` 97 | 98 | ## DevTools support 99 | 100 | ### Show registered router rules 101 | 102 | Registered router rules are displayed in the **Service Worker** Tab of the **Application** panel. 103 | 104 | ![DevTools Application Panel](devtools-application-panel.png "DevTools: Application panel") 105 | 106 | In the **Network** Panel, if the request matches the registered rule, this is indicated in the size column. 107 | When holding the pointer over the size column, the registered router ID is shown. Corresponding rules are displayed in the application tab. 108 | 109 | ![Devtools Network Panel](devtools-network-panel.png "DevTools: Network panel") 110 | -------------------------------------------------------------------------------- /update-from-chrome-m116.md: -------------------------------------------------------------------------------- 1 | # What has been changed between Chrome M116 and later? 2 | 3 | Chrome’s origin trial on the static routing API starts from [M116](https://groups.google.com/a/chromium.org/g/blink-dev/c/_H8rqHW9ERQ/m/KLnRZaz3AAAJ). 4 | It has been implemented as a minimum viable product, and it only has a limited feature. From M117, a full set of 5 | [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) has been supported, which brings a behavior difference from M116. 6 | 7 | ## M116 `urlPattern` behavior 8 | 9 | In M116, the `urlPattern` condition is recognized as a pathname matching rule. It matches with a request URL regardless of protocol, username, password, 10 | hostname, port, query, and hash. The pathname matching will be applied to cross-origin requests automatically. i.e. if you write a pattern like “\*.txt”, 11 | it matches all accesses to a file that ends with “.txt” regardless of where it is under the ServiceWorker’s control. 12 | For example, if you write a following rule: 13 | 14 | ``` 15 | addEventListener('install', (event) => { 16 | event.registerRouter({ 17 | condition: { 18 | urlPattern: "*.jpg" 19 | }, 20 | source: "network" 21 | }); 22 | }) 23 | ``` 24 | 25 | Network fallback will happen for all of the following URLs: 26 | 27 | * `http://same.origin.example.org/images/icon.jpg`, 28 | * `http://some.example.org/images/icon.jpg?token=12345`, 29 | * `https://the.other.example.org/images/icon.jpg#hash`, 30 | * and `https://cross.origin.example.com/top.jpg`. 31 | 32 | ## M117 `urlPattern` behavior 33 | 34 | From M117 and later, the `urlPattern` condition takes [URLPatternInput]( 35 | https://chromium.googlesource.com/chromium/src/+/02c1d179486e02c6c11fcc14031e494879a2ab5f/third_party/blink/renderer/modules/url_pattern/url_pattern.idl#5) 36 | instead of `USVString`, and is used for creating [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) for matching. 37 | If it is `USVString`, the ServiceWorker script’s base URL is implicitly used as a base URL argument passed to URLPattern. i.e. 38 | `new URLPattern(, )` is the pattern used for matching. If you do not want the implicit base URL, 39 | please use `URLPatternInit`. If it is URLPatternInit, it is passed to URLPattern as-is. i.e. `new URLPattern()` is the pattern used 40 | for matching. For example, if you write a following rule and the ServiceWorker script is located in `https://www.example.com/util/sw.js`: 41 | 42 | ``` 43 | addEventListener('install', (event) => { 44 | event.registerRouter({ 45 | condition: { 46 | urlPattern: "*.jpg" 47 | }, 48 | source: "network" 49 | }); 50 | }) 51 | ``` 52 | (Yes, the same pattern as above) 53 | 54 | Network fallback will happen for the following URLs: 55 | 56 | * `https://www.example.com/util/top.jpg`, 57 | * and `https://www.example.com/util/images/icon.jpg`. 58 | 59 | However, it does not cause network fallback for followings: 60 | 61 | * `http://www.example.com/images/icon.jpg`, 62 | * `http://www.example.com/util/icon.jpg?token=12345`, 63 | * `https://www.example.com/util/icon.jpg#hash`, 64 | * and `https://cross.origin.example.com/util/icon.jpg`. 65 | 66 | That is because the pattern matches with a `.jpg` file under `https://www.example.com/util/` without a query string or a hashtag. 67 | 68 | ### A workaround to make it work for both M116 and M117 69 | 70 | If you need the equivalent behavior with M116, the `urlPattern` field need to be `urlPattern: new URLPattern({pathname: ‘*.jpg’})` instead. 71 | If you only need to match the URL within the same hostname without username, password, query string, or hashtag, you can also write like 72 | `urlPattern: “/*.jpg”`. Even for this case, you need to write “/” prefix explicitly to prevent it being recognized as a path under a ServiceWorker 73 | script’s directory. 74 | 75 | To make the rule work for both M116 and M117+, we recommend setting the rule in M117+ way first then fallback to M116. i.e. 76 | 77 | ``` 78 | addEventListener('install', (event) => { 79 | event.registerRouter({ // Try M117+ way first. 80 | condition: { 81 | urlPattern: {pathname: "*.jpg"} 82 | }, 83 | source: "network" 84 | }).catch(() => { // Fallback to M116 way. 85 | event.registerRouter({ 86 | condition: { 87 | urlPattern: "*.jpg" 88 | }, 89 | source: "network" 90 | }) 91 | }); 92 | }) 93 | ``` 94 | 95 | You can also write this like: 96 | 97 | ``` 98 | addEventListener('install', async event => { 99 | try { 100 | await event.registerRouter({ // Try the M117+ way first. 101 | condition: { 102 | urlPattern: {pathname: "*.jpg"} 103 | }, 104 | source: "network" 105 | }) 106 | } catch(() => { // Fallback to the M116 way. 107 | event.registerRouter({ 108 | condition: { 109 | urlPattern: "*.jpg" 110 | }, 111 | source: "network" 112 | }); 113 | } 114 | }); 115 | ``` 116 | 117 | ## FAQ 118 | 119 | ### How can I convert the M116’s urlPattern condition to M117’s? 120 | 121 | In M116, Chrome only checks the pathname matching. Thus, it can be simply rewritten like this. 122 | 123 | * M116: `urlPattern: “”` 124 | * M117: `urlPattern: new URLPattern({pathname: “”})` 125 | 126 | Note that URLPattern automatically sets wildcards for unset fields, and matches anything. 127 | 128 | **Example:** 129 | 130 | * M116: `urlPattern: “*.html”` 131 | * M117: `urlPattern: new URLPattern({pathname: “*.html”})`, or you can also write like `urlPattern: {pathname: “*.html”}` 132 | 133 | ### How can I test the urlPattern condition? 134 | 135 | To test URLPattern behavior, you can actually construct a [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) 136 | instance and execute the `test` method on Chrome. For example, if you want to check URLPattern pattern `{pathname: “*.png”}`, 137 | it will be tested like: 138 | 139 | ``` 140 | p = new URLPattern({pathname: “*.png”}); 141 | p.test(“https://www.example.com/icon.png”) // true 142 | p.test(“https://www.example.com/top.html”) // false 143 | ``` 144 | 145 | If you want to check URLPattern `*.png` in M117+ style with the ServiceWorker script base URL is `https://www.example.org/code/sw.js`, 146 | it will be tested like: 147 | 148 | ``` 149 | p = new URLPattern(“*.png”, “https://www.example.org/code/sw.js”); 150 | p.test(“https://www.example.org/code/icon.png”) // true 151 | p.test(“https://www.example.org/icon.png”) // false 152 | ``` 153 | 154 | If you want to test M116’s condition, please see "[How can I convert the M116’s urlPattern condition to M117’s?](#how-can-i-convert-the-m116s-urlpattern-condition-to-m117s)". 155 | -------------------------------------------------------------------------------- /final-form.md: -------------------------------------------------------------------------------- 1 | # ServiceWorker static routing API final form 2 | 3 | This is for explaining the final form of the routing API. Current explainer only covers the initial step. 4 | However, it looks more complex than it should be without showing how the API would be in the future. 5 | This document explains WebIDL and examples if all features are fulfilled. 6 | 7 | ## WebIDL 8 | 9 | ```idl 10 | // This follows Jake's proposal. Router is implemented in the InstallEvent. 11 | interface InstallEvent { 12 | // `addRoutes` is used to define static routes. 13 | // Not matching all rules means fallback to the regular process. 14 | // i.e. fetch handler. 15 | // Promise is rejected for invalid `rules` e.g. syntax error. 16 | Promise addRoutes((RouterRule or sequence) rules); 17 | } 18 | 19 | // RouterRule defines the condition and source of the static routes. One entry contains one rule. 20 | dictionary RouterRule { 21 | (RouterCondition or sequence) condition; 22 | // If no options are needed, developers may write RouterSourceEnum shortly. 23 | (RouterSource or RouterSourceEnum or sequence) source; 24 | } 25 | 26 | // Defines when to respond from the source. 27 | dictionary RouterCondition { 28 | }; 29 | 30 | directory RouterUrlPatternCondition : RouterCondition { 31 | // A relative path pattern string usable for URLPattern, which implicitly set baseURL 32 | // from the current origin that the ServiceWorker script belongs. 33 | (USVString or URLPatternInit) urlPattern; 34 | }; 35 | 36 | dictionary RouterRequestCondition : RouterCondition { 37 | RequestMethod requestMethod; 38 | RequestMode requestMode; 39 | RequestDestination requestDestination; 40 | }; 41 | 42 | dictionary RouterTimeCondition : RouterCondition { 43 | unsigned long long timeFrom = 0; 44 | unsigned long long timeTo = Infinity; 45 | }; 46 | 47 | dictionary RouterRunningStatusCondition : RouterCondition { 48 | RunningStatusEnum runningStatus; 49 | }; 50 | 51 | enum RunningStatusEnum { "running", "not-running" } 52 | 53 | dictionary RouterNetworkQualityCondition : RouterCondition { 54 | // https://wicg.github.io/netinfo/#dom-networkinformation-rtt 55 | unsigned long rttLessThan; 56 | unsigned long rttGreaterThan; 57 | }; 58 | 59 | dictionary RouterAndCondition : RouterCondition { 60 | sequence and; 61 | }; 62 | 63 | dictionary RouterOrCondition : RouterCondition { 64 | sequence or; 65 | }; 66 | 67 | dictionary RouterNotCondition : RouterCondition { 68 | RouterCondition not; 69 | }; 70 | 71 | // Defines where the response comes from. 72 | dictionary RouterSource { 73 | RouterSourceEnum type; 74 | RouterSourceBehaviorEnum behaviorEnum = "finish-with-success"; 75 | }; 76 | 77 | enum RouterSourceEnum { "network", "cache", "fetch-event", "race-network-and-fetch-handler", "race-network-and-cache" }; 78 | 79 | // Behavior on successful response. 80 | // finish-with-success: finish getting a response if the source is successful. (default) 81 | // continue-discarding-latter-results: response with the successful result, while discarding the latter 82 | // source results. 83 | enum RouterSourceBehaviorEnum { "finish-with-success", "continue-discarding-latter-results" } 84 | 85 | dictionary RouterNetworkSource : RouterSource { 86 | // If response has found and want to update cache with it, 87 | // set cache name here. 88 | DOMString? updatedCacheName; 89 | boolean? cacheErrorResponse; 90 | }; 91 | 92 | dictionary RouterCacheSource : RouterSource { 93 | // cache name. 94 | DOMstring cacheName; 95 | 96 | // A specific request to be used for looking up the cache. 97 | // Otherwise, the current request will be used. 98 | Request? request; 99 | }; 100 | 101 | dictionary RouterFetchEventSource : RouterSource { 102 | // ID to be used as a routerCallbackId in the fetch event. 103 | DOMString? id; 104 | }; 105 | 106 | dictionary RouterRaceNetworkAndCacheSource : RouterSource { 107 | // cache name. 108 | DOMstring raceNetworkAndCacheCacheName; 109 | }; 110 | ``` 111 | 112 | ## Examples 113 | 114 | ### Offline-first 115 | 116 | ```js 117 | // offline first for all same-origin URLs whose suffix is .png and .css. 118 | addEventListener('install', (event) => { 119 | event.addRoutes({ 120 | condition: { 121 | or: [ 122 | { 123 | urlPattern: "/**/*.png" 124 | }, 125 | { 126 | urlPattern: "/**/*.css" 127 | }, 128 | ] 129 | }, 130 | source: [ 131 | { 132 | cacheName: "static resources" 133 | }, 134 | "network" 135 | ] 136 | }); 137 | }); 138 | ``` 139 | 140 | ### Online-first 141 | 142 | ```js 143 | // online first for all same-origin URLs that start with "/articles". 144 | addEventListener('install', (event) => { 145 | event.addRoutes({ 146 | condition: { 147 | urlPattern: "/articles/*" 148 | }, 149 | source: [ 150 | "network", 151 | { 152 | cacheName: "articles" 153 | }, 154 | { 155 | cacheName: "articles", 156 | request: "/articles/offline" 157 | } 158 | ] 159 | }); 160 | }); 161 | ``` 162 | 163 | ### Ignore ServiceWorker for post requests 164 | 165 | ```js 166 | // not use ServiceWorker for posting to 'form'. 167 | addEventListener('install', (event) => { 168 | event.addRoutes({ 169 | condition: { 170 | and: [ 171 | { 172 | urlPattern: "/form/*" 173 | }, 174 | { 175 | requestMethod: "post" 176 | } 177 | ], 178 | }, 179 | source: "network" 180 | }); 181 | }); 182 | ``` 183 | 184 | ### Use service worker iif running 185 | 186 | ```js 187 | // Use ServiceWorker for URLs that start with "/articles", if the service worker is currently running. 188 | addEventListener('install', (event) => { 189 | event.addRoutes({ 190 | condition: { 191 | and: [ 192 | { 193 | urlPattern: "/articles/*" 194 | }, 195 | { 196 | runningStatus: "running", 197 | } 198 | ], 199 | }, 200 | source: [ "fetch-event", "network" ] 201 | }); 202 | }); 203 | ``` 204 | 205 | ### stale-while-revalidate 206 | 207 | ```js 208 | // stale-while-revalidate same-origin URLs that start with "/articles". 209 | addEventListener('install', (event) => { 210 | event.addRoutes({ 211 | condition: { 212 | urlPattern: "/articles/*" 213 | }, 214 | source: [ 215 | { 216 | cacheName: "articles", 217 | behavior: "continue-discarding-latter-results", 218 | }, 219 | { 220 | updatedCacheName: "articles", 221 | } 222 | ] 223 | }); 224 | }); 225 | ``` 226 | 227 | ### race network and fetch handler 228 | 229 | ```js 230 | // race for all same-origin URLs that start with "/articles". 231 | addEventListener('install', (event) => { 232 | event.addRoutes({ 233 | condition: { 234 | urlPattern: "/articles/*" 235 | }, 236 | source: "race-network-and-fetch-handler" 237 | }); 238 | }); 239 | ``` 240 | 241 | ### race network and cache storage 242 | 243 | To deal with devices with slow storage access, web developer may want to race 244 | network and cache storage. In this case, if the cache storage access is too slow 245 | or the cache storage lookup fails, the network response is used. 246 | 247 | ```js 248 | // race for all same-origin URLs that start with "/articles". 249 | addEventListener('install', (event) => { 250 | event.addRoutes({ 251 | condition: { 252 | urlPattern: "/articles/*" 253 | }, 254 | source: "race-network-and-cache" 255 | }); 256 | }); 257 | ``` 258 | 259 | In case the cache storage name need to specified, it can be written like: 260 | 261 | ```js 262 | // race for all same-origin URLs that start with "/articles". 263 | addEventListener('install', (event) => { 264 | event.addRoutes({ 265 | condition: { 266 | urlPattern: "/articles/*" 267 | }, 268 | source: { 269 | raceNetworkAndCacheCacheName: "articles" 270 | } 271 | }); 272 | }); 273 | ``` 274 | 275 | The response will be the first OK response from either of network or cache storage. i.e. 200 response for network, cache lookup success for 276 | cache storage. If either of responses are not OK, the other response will be used. If both are not OK, network error will be passed. 277 | 278 | ### Avoid using ServiceWorker for non app-shell resources 279 | ```js 280 | // load non app shell resources from network. 281 | addEventListener('install', (event) => { 282 | event.addRoutes({ 283 | condition: { 284 | not: {urlPattern: "/app-shell/*"} 285 | }, 286 | source: "network" 287 | }); 288 | }); 289 | ``` 290 | 291 | ### Avoid using ServiceWorker for good network connection 292 | ```js 293 | // If network round-trip time is good enough, use network directly. 294 | addEventListener('install', (event) => { 295 | event.addRoutes({ 296 | condition: { 297 | // RTT <= 150 ms. (It is actually represented like "not > 150ms") 298 | {not: {rttGreaterThan: 150}} 299 | }, 300 | source: "network" 301 | }); 302 | // Otherwise, fallback to fetch-handler (default). 303 | }); 304 | ``` 305 | 306 | It can also be written like: 307 | ```js 308 | // If network round-trip time is good enough, use network directly. 309 | addEventListener('install', (event) => { 310 | event.addRoutes([{ 311 | condition: { 312 | // RTT > 150 ms. 313 | rttGreaterThan: 150 314 | }, 315 | source: "fetch-event" 316 | }, 317 | { // fallback to "network" by default. 318 | condition: { 319 | urlPattern: new URLPattern() 320 | }, 321 | source: "network" 322 | }]); 323 | }); 324 | ``` 325 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Explainer: ServiceWorker Static Routing API 2 | 3 | ## Authors 4 | 5 | * Yoshisato Yanagisawa ([@yoshisatoyanagisawa](https://github.com/yoshisatoyanagisawa)) 6 | * Shunya Shishido ([@sisidovski](https://github.com/sisidovski)) 7 | 8 | ## Participate 9 | 10 | * https://github.com/w3c/ServiceWorker/issues/1373 11 | 12 | ## Background 13 | 14 | [ServiceWorker](https://www.w3.org/TR/service-workers/) is a web platform feature that brings application-like experience to users. 15 | It works in the background of a web browser to provide a cache feature, offline support, and other functionality. 16 | 17 | ## A problem and a solution 18 | 19 | Starting ServiceWorkers is known to be a slow process, and web users need to wait for its startup if the ServiceWorker intercepts 20 | loading the page resources. At the same time, the ServiceWorker brings flexibility to the transport layer, and it behaves as 21 | a client-side proxy. Developers can implement offline support or provide client-side content modification with it. 22 | Since using the ServiceWorker has a certain overhead, we need to use it wisely. 23 | 24 | Currently, ServiceWorkers intercept all navigation for pages within their scope, which can cause a performance penalty. 25 | To avoid this penalty, we need a way to specify when ServiceWorkers and JavaScript should not be run. 26 | 27 | ### NavigationPreload 28 | 29 | [NavigationPreload](https://developer.mozilla.org/en-US/docs/Web/API/NavigationPreloadManager) was introduced in Chrome 59 30 | (June, 2017). To avoid the navigation being blocked by the ServiceWorker startup, the browser sends a request to a network while 31 | starting a ServiceWorker to hide a performance penalty to start a ServiceWorker. 32 | 33 | However, there is still a delay while the ServiceWorker startup. This may happen on slow devices, which can be unacceptable for 34 | partners who care about web performance. Additionally, this feature changes the execution order, which can make it difficult 35 | to analyze and debug issues. As a result, some partners have stopped using navigation preload. 36 | 37 | Unlike navigation preload, the static routing API proposed below allows developers to exclude the ServiceWorker from the navigation 38 | critical path where it is configured. We can avoid the delay for the slow ServiceWorker start-up in that case. The execution 39 | order is easy to understand and predict. 40 | 41 | ## A proposal 42 | 43 | We propose a new version of declarative routing API originally proposed by Jake Archibald in 2018-2019 44 | ([github](https://github.com/w3c/ServiceWorker/issues/1373), 45 | [site](https://jakearchibald.com/2019/service-worker-declarative-router/)). 46 | It defines set of static routing rules for the following purpose: 47 | 48 | * be able to bypass a ServiceWorker for particular requests. 49 | * speed up simple offline-first, online-first routes by avoiding ServiceWorker startup time. 50 | * be polyfillable. 51 | * be extensible. 52 | 53 | Our proposal is heavily based on the proposal, with minor changes to use the modern primitives (URLPattern), 54 | and simplify the API surface. This is [a full picture of the proposal](final-form.md). However, since it is too large, 55 | we will start from something minimal (Refer to the FAQ section for the detailed list of changes applied in this proposal). 56 | 57 | From the user's perspective, this proposal brings performance improvement by skipping waiting for ServiceWorker where it is 58 | not needed. Even with slow devices, users do not need to wait for the page load blocked by slow ServiceWorker start-up. 59 | Not directly covered by this explainer, but we have offline first support in our big picture, and more pages can be used 60 | offline when it is realized. 61 | 62 | ### WebIDL 63 | 64 | Below is the WebIDL of the API. You may find it more complicated than what it can do now. That is because it is designed to 65 | allow evolution to [the full picture](final-form.md) in the future. 66 | 67 | ```webidl 68 | // This follows Jake's proposal. Router is implemented in the InstallEvent. 69 | interface InstallEvent : ExtendableEvent { 70 | // `addRoutes()` is used to define static routes. 71 | // Not matching all rules means fallback to the regular process. 72 | // i.e. fetch handler. 73 | // Promise is rejected for invalid `rules` e.g. syntax error. 74 | Promise addRoutes((RouterRule or sequence) rules); 75 | } 76 | 77 | // RouterRule defines the condition and source of the static routes. One entry contains one rule. 78 | dictionary RouterRule { 79 | // In the first version, only the single condition and source should be enough because we only 80 | // support urlPattern, and "network". 81 | RouterCondition condition; 82 | RouterSourceEnum source; 83 | } 84 | 85 | // Defines when to respond from the source. 86 | dictionary RouterCondition { 87 | // https://wicg.github.io/urlpattern/#typedefdef-urlpatterninput 88 | // For a USVString input, a ServiceWorker script's URL is used as a base URL. 89 | URLPatternInput urlPattern; 90 | }; 91 | 92 | enum RouterSourceEnum { "network" }; 93 | ``` 94 | 95 | Note that the rules are evalauted sequentially with added order. It means that if `addRoutes()` is called multiple times, 96 | rules added ealier will be evaluated ealier. 97 | 98 | ### Examples 99 | 100 | #### Bypassing ServiceWorker for particular resources 101 | 102 | ```js 103 | // Go straight to the network and bypass invoking "fetch" handlers for URLs that start with '/form/'. 104 | addEventListener('install', (event) => { 105 | event.addRoutes({ 106 | condition: { 107 | urlPattern: new URLPattern({pathname: "/form/*"}) 108 | }, 109 | source: "network" 110 | }); 111 | }) 112 | 113 | // Go straight to the network and bypass invoking "fetch" handlers for URLs that start 114 | // with '/videos/' and '/images/'. 115 | addEventListener('install', (event) => { 116 | event.addRoutes([{ 117 | condition: { 118 | urlPattern: new URLPattern({pathname: "/images/*"}) 119 | }, 120 | source: "network" 121 | }, 122 | { 123 | condition: { 124 | urlPattern: new URLPattern({pathname: "/videos/*"}) 125 | }, 126 | source: "network" 127 | }]); 128 | }); 129 | ``` 130 | 131 | ## Origin Trial 132 | 133 | We (Google Chrome team) will start the origin trial, which allows developers to opt-in the feature on their sites. Regarding the introduction and basic registration process, please refer to [this page](https://developer.chrome.com/docs/web-platform/origin-trials/). 134 | 135 | Once a token string is generated from [the dashboard](https://developer.chrome.com/origintrials), developers need to set the HTTP response header to their ServiceWorker files. 136 | 137 | ``` 138 | Origin-Trial: TOKEN_GOES_HERE 139 | ``` 140 | 141 | One important thing to mention here is that the header has to be set to **the ServiceWorker script, not to the page**. Also, HTML meta tags are not supported. The feature will take effect after the ServiceWorker registration. 142 | 143 | For local testing, you can enable the feature by flipping the `Service Worker Static Router` flag from chrome://flags. 144 | 145 | ## FAQ 146 | 147 | ### Is a demo available for the API? 148 | 149 | Please visit https://sw-static-routing-demo.glitch.me/ with the latest Chrome. 150 | 151 | ### How is the proposal different from Jake’s original proposal? 152 | 153 | We propose `addRoutes()` to set routes with specified routes instead of `add()` and `get()`. Unlike `add()` or `get()`, 154 | `addRoutes()` can take a list of rules in addition to a single rule. It can be called multiple times in case if needed 155 | (e.g. imported third-party service worker script already added some routes). Web developers need to use the browser mechanisms like 156 | devtools to check the latest router rules. 157 | `addRoutes()` is a part of the `install` event [^1]. 158 | Since `addRoutes()` is only the method to set the router rules, we put it as a part of the `install` event. 159 | When the `install` listener is executed, no routes are set. Web developers can call `addRoutes()` to set 160 | routes at that time. 161 | 162 | Our proposal uses [`URLPattern`](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern), which was not available when Jake made 163 | the original proposal. It is natural evolution to use `URLPattern` instead of URL related conditions in the proposal. 164 | 165 | [^1]: We followed [Jake's proposal](https://github.com/w3c/ServiceWorker/issues/1373#issuecomment-451436409) to use the install event. 166 | 167 | #### Summary 168 | 169 | * Introduce `addRoutes()` method, and won’t provide `add()` or `get()` methods. 170 | * `addRoutes()` method sets ServiceWorker routes with specified routes. 171 | To allow third party services to use the API, the method can be called multiple times. 172 | * URL related conditions are merged into `URLPattern`. 173 | 174 | ### How does it work if there is no fetch handler? 175 | 176 | For the "network" source, we can safely ignore the routes if there is no fetch handler, except for a debugging purpose. 177 | In [the full picture](final-form.md), we introduce "cache", "fetch-event", and "race-network-and-fetch-handler" sources. 178 | The "cache" source should look up a request from the cache storage even if there is no fetch handler. 179 | Moreover, it does not need to run the fetch handlers regardless of cache hit or cache miss. 180 | On the other hand, the "fetch-event" and "race-network-and-fetch-handler" sources run the fetch handlers. 181 | `addRoutes()` should return a promise rejected with a *TypeError* if these sources are used while no fetch handlers are set. 182 | 183 | ### How does it work with [empty fetch listeners](https://github.com/yoshisatoyanagisawa/service-worker-skip-no-op-fetch-handler)? 184 | 185 | If [all the fetch listeners are empty functions](https://w3c.github.io/ServiceWorker/#all-fetch-listeners-are-empty-algorithm), 186 | routes that only have the "network" source can be ignored, except for a debugging purpose. 187 | Like the no fetch handler case above, the "cache" source should look up the cache storage regardless of the empty fetch handler or not. 188 | However, unlike the no fetch handler case above, 189 | `addRoutes()` should return a promise resolved with undefined for the "fetch-event" and "race-network-and-fetch-handler" sources 190 | because the fetch handler exists. 191 | During the navigation, the fetch handler may not need to run for these sources because they are empty, 192 | which is allowed by https://github.com/w3c/ServiceWorker/pull/1674. 193 | 194 | ### How does it work with Navigation Preload? 195 | 196 | Routes are evaluated before Navigation Preload. If no routes are matched, navigation falls back to the regular ServiceWorker 197 | fetch handler path, and Navigation Preload would be used if it is configured. 198 | 199 | ### How would the full picture be rolled out? 200 | 201 | [The full picture](final-form.md) is large, and it is not easy to implement it at once. We plan to gradually roll it out with 202 | the following order: 203 | 204 | * `RouterURLPatternCondition`, `RouterNetworkSource` (this proposal) 205 | * `RouterRequestCondition`, `RouterAndCondition`, `RouterNotCondition` 206 | * `RouterRunningStatusCondition`, `RouterOrCondition` 207 | * `RouterTimeCondition` 208 | * `RouterCacheSource`, `RouterFetchSource`, `RouterSourceBehaviorEnum`, allowing sequence of sources. (offline/online-first support) 209 | * Stale-While-Revalidate support. 210 | * race-network-and-fetch-event support 211 | 212 | ### How Chrome implements this? 213 | 214 | The Google Chrome team starts the Origin Trial from M116, and the implementation has slightly been changed from M117. 215 | It has been explained in [a separate document](update-from-chrome-m116.md). 216 | 217 | There was `registerRouter()`. For ease of understanding the latest routes, it can be called once. However, it made it difficult for 218 | the third-party services to add routes. To solve the situation, the method has been renamed to `addRoutes()` and can be called multiple times. 219 | -------------------------------------------------------------------------------- /resource-timing-api.md: -------------------------------------------------------------------------------- 1 | # Explainer for timing info for Static Routing API 2 | 3 | ## Authors 4 | 5 | - Keita Suzuki ([@quasi-mod](https://github.com/quasi-mod)) 6 | - Yoshisato Yanagisawa ([@yoshisatoyanagisawa](https://github.com/yoshisatoyanagisawa)) 7 | 8 | 9 | ## Participate 10 | 11 | * [Link to issue](https://github.com/WICG/service-worker-static-routing-api/issues/19) 12 | 13 | ## Abstract 14 | 15 | * Introduce timing info for the ServiceWorker Static Routing API in Resource Timing API and Navigation Timing API. 16 | 17 | ## User needs 18 | 19 | To bring better user experience with reducing frustration on rendering the web 20 | sites, it is good to use resource timing API to evaluate their page performance. As explained in 21 | the ServiceWorker static routing API's explainer, one of the motivations to introduce the API to 22 | the site is page performance improvement. 23 | 24 | By utilizing the resource timing feature for the static routing API, developers gain the ability 25 | to gauge the latency introduced by the API itself. This includes measuring aspects like router evaluation 26 | time and cache lookup duration. Additionally, developers can verify if the initially matched source was 27 | ultimately used to fetch the resource, and if not, identify the alternative source that was employed. 28 | 29 | 30 | ## Background 31 | 32 | 33 | ### The ServiceWorker Static Routing API 34 | 35 | The startup of [ServiceWorkers](https://www.w3.org/TR/service-workers/), a web platform feature that brings application-like 36 | experience to users, is known to be a slow process. If the ServiceWorker intercepts loading the page 37 | resources, web users may need to wait for the startup to complete in order for the page loading to start. 38 | 39 | [Service Worker Static Routing API](https://github.com/WICG/service-worker-static-routing-api) was introduced to mitigate this issue by letting the 40 | developers selectively choose whether the ServiceWorker should intercept the 41 | navigation, and allow them to specify when to not run ServiceWorker. 42 | In addition, it allows the developers to offload simple ServiceWorker operations 43 | like cache look up. i.e. they can return resources from CacheStorage without running 44 | ServiceWorkers. 45 | 46 | ### Timing Info of ServiceWorkers 47 | 48 | Service Worker provides [timing information](https://w3c.github.io/ServiceWorker/#service-worker-timing) to mark certain points 49 | in time. This is exposed and used by the [navigation timing API](https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming) 50 | as well as the [resource timing API](https://w3c.github.io/resource-timing/). It currently records two times: 51 | 52 | * Start time 53 | * Fetch event dispatch time 54 | 55 | However, it currently does not have any fields related to the ServiceWorker Static 56 | Routing API. Developers would benefit from having fields that provide information such as: 57 | 58 | * the matched route (the route that the Static Routing API evaluated) 59 | * the actual source from which the resource was retrieved 60 | * the time it took to match the route 61 | 62 | This information will allow developers to measure the latency incurred by the 63 | API such as router evaluation time or time required to conduct cache lookup, 64 | or determine if the matched source is the final source used (can find out if the 65 | matched source failed to get the resource or not, and which source was used as 66 | the alternative). 67 | 68 | ## Proposal 69 | 70 | ![Overview of ResourceTiming](./img/resource-timing-overview.png "Resource Timing: Overview") 71 | 72 | We add the following two timing information: 73 | 74 | * [Currently Under discussion] workerRouterEvaluationStart 75 | * A `DOMHighResTimeStamp`, initially 0 76 | * Time to start matching a request with registered router rules 77 | * This field is currently under discussion, and will not be included in the initial spec change 78 | * We will revisit this field once the static routing is adopted widely to determine with more data 79 | * workerCacheLookupStart 80 | * A `DOMHighResTimeStamp`, initially 0 81 | * Time to start looking up the cache storage when acessing from Cache API 82 | * Recorded whenever the response is coming from Cache API 83 | * Includes access when "cache" rule is specified in static routing API, or from SW fetch-event. 84 | 85 | In addition to the timestamp information, we also add the following two route source information: 86 | 87 | * workerMatchedRouterSource 88 | * A `RouterSource`, initially empty string 89 | * The enum string of the matched source (the source of result of router evaluation) 90 | * This shall match to "network", "cache", "fetch-event", or "race-network-and-fetch-handler". If no rule is matched, it shall be an empty string. 91 | * workerFinalRouterSource 92 | * A `RouterSource`, initially empty string 93 | * The enum string of the used source 94 | * This shall match to "network", "cache", or "fetch-event" 95 | * When a matched router source exists, this should match to the MatchedRouterSource, unless in "race-network-and-fetch-handler", where the winner of the race will be the final source (either "network" or "fetch-event"). Otherwise, it should remain as an empty string. 96 | 97 | 98 | ### Example code 99 | 100 | #### Fetch Rule Specified 101 | 102 | ![ResourceTiming (fetch-event)](./img/resource-timing-fetch.png "Resource Timing: fetch-event") 103 | 104 | ```js 105 | // Add route inside ServiceWorker 106 | addEventListener('install', (event) => { 107 | event.addRoutes({ 108 | condition: { 109 | urlPattern: {pathname: "/form/*"} 110 | }, 111 | source: "fetch-event" 112 | }); 113 | }) 114 | ``` 115 | 116 | ```js 117 | // Measure routerEvaluationTime 118 | let timing = performance.getEntriesByType('navigation'); 119 | let routerEvaluationTime = 0.0; 120 | switch (timing.finalRouteSource) { 121 | case "network": 122 | // Indicates that the fetch fallback to network. 123 | routerEvaluationTime = timing.fetchStart - timing.workerRouterEvaluationStart; 124 | break; 125 | case 'fetch-event': 126 | routerEvaluationTime = timing.workerStart - timing.workerRouterEvaluationStart; 127 | break; 128 | case "cache": 129 | // UNREACHABLE 130 | break; 131 | } 132 | ``` 133 | 134 | #### Network Rule Specified 135 | 136 | ![ResourceTiming (network)](./img/resource-timing-network.png "Resource Timing: network") 137 | 138 | ```js 139 | // Add route inside ServiceWorker 140 | addEventListener('install', (event) => { 141 | event.addRoutes({ 142 | condition: { 143 | urlPattern: {pathname: "/form/*"} 144 | }, 145 | source: "network" 146 | }); 147 | }) 148 | ``` 149 | 150 | ```js 151 | // Measure routerEvaluationTime 152 | let timing = performance.getEntriesByType('navigation'); 153 | let routerEvaluationTime = 0.0; 154 | switch (timing.finalRouteSource) { 155 | case "network": 156 | // Routed to network 157 | routerEvaluationTime = timing.fetchStart - timing.workerRouterEvaluationStart; 158 | break; 159 | case "cache": 160 | case "fetch-event": 161 | // UNREACHABLE 162 | break; 163 | } 164 | ``` 165 | 166 | #### Race Rule Specified 167 | 168 | ![ResourceTiming (race-network-and-fetch-event)](./img/resource-timing-race.png "Resource Timing: race-network-and-fetch-event") 169 | 170 | ```js 171 | // Add route inside ServiceWorker 172 | addEventListener('install', (event) => { 173 | event.addRoutes({ 174 | condition: { 175 | urlPattern: {pathname: "/form/*"} 176 | }, 177 | source: "race-network-and-fetch-event" 178 | }); 179 | }) 180 | ``` 181 | 182 | ```js 183 | // Measure routerEvaluationTime 184 | let timing = performance.getEntriesByType('navigation'); 185 | let routerEvaluationTime = 0.0; 186 | switch (timing.finalRouteSource) { 187 | case "network": 188 | // Indicates that the network has won the race, 189 | // or the fetch event has failed. 190 | routerEvaluationTime = timing.fetchStart - timing.workerRouterEvaluationStart; 191 | break; 192 | case 'fetch-event': 193 | // Indicates that the fetch has won the race. 194 | routerEvaluationTime = timing.workerStart - timing.workerRouterEvaluationStart; 195 | break; 196 | case "cache": 197 | // UNREACHABLE 198 | break; 199 | } 200 | ``` 201 | 202 | #### Cache Rule Specified 203 | 204 | ![ResourceTiming (cache)](./img/resource-timing-cache.png "Resource Timing: cache") 205 | 206 | ```js 207 | // Add route inside ServiceWorker 208 | addEventListener('install', (event) => { 209 | event.addRoutes({ 210 | condition: { 211 | urlPattern: {pathname: "/form/*"} 212 | }, 213 | source: "cache" 214 | }); 215 | }) 216 | ``` 217 | 218 | ```js 219 | // Measure routerEvaluationTime and cacheLookupTime 220 | let timing = performance.getEntriesByType('navigation'); 221 | let routerEvaluationTime = 0.0; 222 | let cacheLookupTime = 0.0; 223 | switch (timing.FinalRouteSource) { 224 | case "network": 225 | // Cache miss. Fallback to network. 226 | routerEvaluationTime = timing.cacheLookupStart - timing.routerEvaluationStart; 227 | cacheLookupTime = time.fetchStart - time.workerCacheLookupStart; 228 | break; 229 | case "cache": 230 | // Cache Hit. 231 | routerEvaluationTime = 232 | timing.cacheLookupStart - timing.workerRouterEvaluationStart; 233 | cacheLookupTime = 234 | time.responseStart - time.cacheLookupStart; 235 | case "fetch-event": 236 | // UNREACHABLE 237 | break; 238 | } 239 | ``` 240 | 241 | ### Recorded timing per matched route 242 | 243 | As mentioned above, the recorded fields will be different depending on the matched source. The fields to be recorded per source is as follows (✔ indicates recorded, ✘ indicates not recorded). 244 | 245 | 246 | 247 | 248 | 250 | 252 | 254 | 256 | 257 | 258 | 260 | 262 | 264 | 266 | 268 | 269 | 270 | 272 | 274 | 276 | 278 | 279 | 280 | 282 | 284 | 286 | 288 | 289 | 290 | 292 | 294 | 296 | 298 | 299 | 300 | 302 | 304 | 306 | 308 | 309 |
249 | RouterEvaluationStart 251 | CacheLookupStart 253 | fetchStart 255 |
Matched Source 259 | Fetch 261 | ✔ 263 | ✘ 265 | ✔ 267 |
Network 271 | ✔ 273 | ✘ 275 | ✔ 277 |
Race (Network vs Fetch) 281 | ✔ 283 | ✘ 285 | ✔ 287 |
Cache 291 | ✔ 293 | ✔ 295 | ✘ 297 |
None Matched 301 | ✔ 303 | ✘ 305 | ✔ 307 |
310 | 311 | 312 | 313 | ### Correspondence of the Matched Source Type and the Actual Source Type 314 | 315 | In some situations, the actual source type will be different from the matched source 316 | type. This includes cases such as "race-network-and-fetch-event" where the result 317 | of the race will be the actual route, or "cache" where a cache miss occurs. 318 | 319 | The full list of correspondence of the matched source type and the actual source 320 | type is as follows: 321 | 322 | 323 | 324 | 325 | 327 | 329 | 330 | 331 | 333 | 335 | 337 | 338 | 339 | 341 | 343 | 345 | 348 | 350 | 351 | 352 | 354 | 356 | 358 | 360 | 361 | 362 | 364 | 368 | 370 | 372 | 373 | 374 | 376 | 378 | 381 | 383 | 384 | 385 | 387 | 389 | 391 | 393 | 394 |
326 | Actual Source 328 |
Fetch 332 | Network 334 | Cache 336 |
Matched Source 340 | Fetch 342 | Fetch (Success) 344 | Network

346 | (Fallback: Fetch handler is invalid) 347 |

N/A 349 |
Network 353 | N/A 355 | Network (Success) 357 | N/A 359 |
Race (Network vs Fetch) 363 | Fetch 365 |

366 | (Fetch win) 367 |

Network

(Network win or Fetch fallback) 369 |

N/A 371 |
Cache 375 | N/A 377 | Network

379 | (Fallback: Cache Missed) 380 |

Cache

(Cache hit) 382 |

None matched 386 | N/A (null) 388 | N/A (null) 390 | N/A 392 |
395 | 396 | 397 | ## Security and Privacy Concerns 398 | 399 | ### Security Concerns 400 | The proposed features extend the widely used [Resource Timing API](https://www.w3.org/TR/resource-timing/#sec-security), inheriting 401 | its security considerations. 402 | 403 | As covered in the resource timing specification, the main security concern is 404 | the use of high resolution timers. The timing field of the resource timing is 405 | defined to use high resolution timers. In addition, this could expose 406 | timing information about how the other origins loaded as well, leading to 407 | inferring information about the user's activity across origins. 408 | 409 | To mitigate these risks, the same-origin policy is enforced by default, with 410 | specific attributes set to zero, as defined in the HTTP Fetch standard. 411 | Resource providers can selectively expose timing data by utilizing the 412 | Timing-Allow-Origin HTTP response header, granting access to specified domains. 413 | 414 | ### Privacy Concerns 415 | 416 | Similar to the security concerns, privacy concern also inherits the same issue 417 | as the Resource Timing API. It has a potential of fingerprinting. 418 | While statistical fingerprinting poses a privacy risk, where malicious websites 419 | can infer a user's browsing history by analyzing resource loading times, the 420 | Resource Timing API doesn't significantly exacerbate this issue. 421 | Existing mechanisms, like the load event, already allow for limited cache 422 | timing measurements. Furthermore, cross-origin restrictions in HTTP Fetch, 423 | which is enforced by the Timing-Allow-Origin header, prevent this API from 424 | exposing additional information that could be exploited for fingerprinting. 425 | 426 | ## Discussions 427 | 428 | 429 | ### What to include in actual source when there is no matched source 430 | 431 | When no matching rule is found, `workerMatchedRouterSource` is an empty string, and we use 432 | the ServiceWorker to fetch the resources. To indicate this case, there is a 433 | discussion on what the `workerFinalRouterSource` field should contain. We came up with 434 | two possible solutions: 435 | 436 | 1. Set empty string to `workerFinalRouterSource` as well 437 | 2. Set the actual source to `workerFinalRouteSource` ("fetch-event" or "network", if it falls back) 438 | 439 | We are currently planning to pursue Solution 1. 440 | 441 | Solution 2 does expose more information to developers, but the exposed information 442 | is independent from ServiceWorker Static Routing API as it is about whether 443 | ServiceWorker fetch has succeeded or not. Although this should be taken into 444 | consideration in the future, we concluded that such information would be out of 445 | scope of Timing Info for the API. 446 | 447 | 448 | ### `fetchStart` on Cache Hit 449 | 450 | When the cache is specified as the source and the resource is found in the cache 451 | (cache hit), no fetch operation is performed. 452 | To align the behavior with other fields, we will set `fetchStart` to `responseStart` 453 | when the resource is from cache. 454 | 455 | ### `deliveryType` when final router source is from cache 456 | 457 | When the final router source is from cache, the `deliveryType` property should also 458 | be set. Currently, `deliveryType` has `cache` as one of the values. However, this 459 | points to the [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching), and not 460 | [Cache API (CacheStorage)](https://developer.mozilla.org/en-US/docs/Web/API/Cache) result. To avoid confusion, 461 | we should introduce `cache-storage` value to `deliveryType`, where the value is set when 462 | the response comes from Cache API. 463 | The deliveryType `cache-storage` is set either when the response is from the cache rule (with cache hit) in 464 | static routing API, or when the response is coming from fetch handler response, 465 | coming from the CacheStorage. 466 | --------------------------------------------------------------------------------