| // Copyright (C) 2021 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /** |
| * This file deals with caching traces in the browser's Cache storage. The |
| * traces are cached so that the UI can gracefully reload a trace when the tab |
| * containing it is discarded by Chrome(e.g. because the tab was not used for a |
| * long time) or when the user accidentally hits reload. |
| */ |
| import {assertExists} from '../base/logging'; |
| import {TraceArrayBufferSource, TraceSource} from './state'; |
| |
| const TRACE_CACHE_NAME = 'cached_traces'; |
| const TRACE_CACHE_SIZE = 10; |
| |
| export async function cacheTrace( |
| traceSource: TraceSource, traceUuid: string): Promise<boolean> { |
| let trace, title = '', fileName = '', url = '', contentLength = 0, |
| localOnly = false; |
| switch (traceSource.type) { |
| case 'ARRAY_BUFFER': |
| trace = traceSource.buffer; |
| title = traceSource.title; |
| fileName = traceSource.fileName || ''; |
| url = traceSource.url || ''; |
| contentLength = traceSource.buffer.byteLength; |
| localOnly = traceSource.localOnly || false; |
| break; |
| case 'FILE': |
| trace = await traceSource.file.arrayBuffer(); |
| title = traceSource.file.name; |
| contentLength = traceSource.file.size; |
| break; |
| default: |
| return false; |
| } |
| assertExists(trace); |
| |
| const headers = new Headers([ |
| ['x-trace-title', title], |
| ['x-trace-url', url], |
| ['x-trace-filename', fileName], |
| ['x-trace-local-only', `${localOnly}`], |
| ['content-type', 'application/octet-stream'], |
| ['content-length', `${contentLength}`], |
| [ |
| 'expires', |
| // Expires in a week from now (now = upload time) |
| (new Date((new Date()).getTime() + (1000 * 60 * 60 * 24 * 7))) |
| .toUTCString() |
| ] |
| ]); |
| const traceCache = await caches.open(TRACE_CACHE_NAME); |
| await deleteStaleEntries(traceCache); |
| await traceCache.put( |
| `/_${TRACE_CACHE_NAME}/${traceUuid}`, new Response(trace, {headers})); |
| return true; |
| } |
| |
| export async function tryGetTrace(traceUuid: string): |
| Promise<TraceArrayBufferSource|undefined> { |
| await deleteStaleEntries(await caches.open(TRACE_CACHE_NAME)); |
| const response = await caches.match( |
| `/_${TRACE_CACHE_NAME}/${traceUuid}`, {cacheName: TRACE_CACHE_NAME}); |
| |
| if (!response) return undefined; |
| return { |
| type: 'ARRAY_BUFFER', |
| buffer: await response.arrayBuffer(), |
| title: response.headers.get('x-trace-title') || '', |
| fileName: response.headers.get('x-trace-filename') || undefined, |
| url: response.headers.get('x-trace-url') || undefined, |
| uuid: traceUuid, |
| localOnly: response.headers.get('x-trace-local-only') === 'true' |
| }; |
| } |
| |
| async function deleteStaleEntries(traceCache: Cache) { |
| /* |
| * Loop through stored caches and invalidate all but the most recent 10. |
| */ |
| const keys = await traceCache.keys(); |
| const storedTraces: Array<{key: Request, date: Date}> = []; |
| for (const key of keys) { |
| const existingTrace = assertExists(await traceCache.match(key)); |
| const expiryDate = |
| new Date(assertExists(existingTrace.headers.get('expires'))); |
| if (expiryDate < new Date()) { |
| await traceCache.delete(key); |
| } else { |
| storedTraces.push({key, date: expiryDate}); |
| } |
| } |
| |
| if (storedTraces.length <= TRACE_CACHE_SIZE) return; |
| |
| /* |
| * Sort the traces descending by time, such that most recent ones are placed |
| * at the beginning. Then, take traces from TRACE_CACHE_SIZE onwards and |
| * delete them from cache. |
| */ |
| const oldTraces = |
| storedTraces.sort((a, b) => b.date.getTime() - a.date.getTime()) |
| .slice(TRACE_CACHE_SIZE); |
| for (const oldTrace of oldTraces) { |
| await traceCache.delete(oldTrace.key); |
| } |
| } |