• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2021 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * This file deals with caching traces in the browser's Cache storage. The
17 * traces are cached so that the UI can gracefully reload a trace when the tab
18 * containing it is discarded by Chrome(e.g. because the tab was not used for a
19 * long time) or when the user accidentally hits reload.
20 */
21import {assertExists} from '../base/logging';
22import {TraceArrayBufferSource, TraceSource} from './state';
23
24const TRACE_CACHE_NAME = 'cached_traces';
25const TRACE_CACHE_SIZE = 10;
26
27export async function cacheTrace(
28    traceSource: TraceSource, traceUuid: string): Promise<boolean> {
29  let trace, title = '', fileName = '', url = '', contentLength = 0,
30             localOnly = false;
31  switch (traceSource.type) {
32    case 'ARRAY_BUFFER':
33      trace = traceSource.buffer;
34      title = traceSource.title;
35      fileName = traceSource.fileName || '';
36      url = traceSource.url || '';
37      contentLength = traceSource.buffer.byteLength;
38      localOnly = traceSource.localOnly || false;
39      break;
40    case 'FILE':
41      trace = await traceSource.file.arrayBuffer();
42      title = traceSource.file.name;
43      contentLength = traceSource.file.size;
44      break;
45    default:
46      return false;
47  }
48  assertExists(trace);
49
50  const headers = new Headers([
51    ['x-trace-title', title],
52    ['x-trace-url', url],
53    ['x-trace-filename', fileName],
54    ['x-trace-local-only', `${localOnly}`],
55    ['content-type', 'application/octet-stream'],
56    ['content-length', `${contentLength}`],
57    [
58      'expires',
59      // Expires in a week from now (now = upload time)
60      (new Date((new Date()).getTime() + (1000 * 60 * 60 * 24 * 7)))
61          .toUTCString()
62    ]
63  ]);
64  const traceCache = await caches.open(TRACE_CACHE_NAME);
65  await deleteStaleEntries(traceCache);
66  await traceCache.put(
67      `/_${TRACE_CACHE_NAME}/${traceUuid}`, new Response(trace, {headers}));
68  return true;
69}
70
71export async function tryGetTrace(traceUuid: string):
72    Promise<TraceArrayBufferSource|undefined> {
73  await deleteStaleEntries(await caches.open(TRACE_CACHE_NAME));
74  const response = await caches.match(
75      `/_${TRACE_CACHE_NAME}/${traceUuid}`, {cacheName: TRACE_CACHE_NAME});
76
77  if (!response) return undefined;
78  return {
79    type: 'ARRAY_BUFFER',
80    buffer: await response.arrayBuffer(),
81    title: response.headers.get('x-trace-title') || '',
82    fileName: response.headers.get('x-trace-filename') || undefined,
83    url: response.headers.get('x-trace-url') || undefined,
84    uuid: traceUuid,
85    localOnly: response.headers.get('x-trace-local-only') === 'true'
86  };
87}
88
89async function deleteStaleEntries(traceCache: Cache) {
90  /*
91   * Loop through stored caches and invalidate all but the most recent 10.
92   */
93  const keys = await traceCache.keys();
94  const storedTraces: Array<{key: Request, date: Date}> = [];
95  for (const key of keys) {
96    const existingTrace = assertExists(await traceCache.match(key));
97    const expiryDate =
98        new Date(assertExists(existingTrace.headers.get('expires')));
99    if (expiryDate < new Date()) {
100      await traceCache.delete(key);
101    } else {
102      storedTraces.push({key, date: expiryDate});
103    }
104  }
105
106  if (storedTraces.length <= TRACE_CACHE_SIZE) return;
107
108  /*
109   * Sort the traces descending by time, such that most recent ones are placed
110   * at the beginning. Then, take traces from TRACE_CACHE_SIZE onwards and
111   * delete them from cache.
112   */
113  const oldTraces =
114      storedTraces.sort((a, b) => b.date.getTime() - a.date.getTime())
115          .slice(TRACE_CACHE_SIZE);
116  for (const oldTrace of oldTraces) {
117    await traceCache.delete(oldTrace.key);
118  }
119}
120