browserHistory.ts ×26

Frontier kind: Code frontier

unlabeled · c_2a6758670a4c

26 tests · 5813 LOC · 30 files · introduces 0 tests · 160 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
26 ranges160 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
873 ranges5813 lines · 30 files · Browse complete extent
All tests (intent)
26 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 160 introduced LOC across 26 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/browserView/common/browserHistory.ts 160 introduced LOC · 26 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- browserHistory.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Emitter, Event } from '../../../base/common/event.js';
7 > import { StringSHA1 } from '../../../base/common/hash.js';
8 > import { Disposable } from '../../../base/common/lifecycle.js';
9 >
10 > /**
11 > * On-disk shape of a single history entry.
12 > * BACKWARDS COMPATIBILE. When evolving this interface, ensure older versions can still be handled gracefully.
13 > */
14 > export interface ISerializedBrowserHistoryEntry {
15 > readonly id: number;
16 > readonly url: string;
17 > /** Epoch ms when the entry was most recently visited. */
18 > readonly time: number;
19 > readonly title: string;
20 > /** Content hash key into the sibling favicons map. */
21 > readonly icon?: string;
22 > /**
23 > * Set when the navigation was initiated by the user (typing in the URL
24 > * bar, picking a suggestion, opening a new tab with a URL) rather than by
25 > * page script or link clicks. Always omitted when false to keep entries
26 > * small.
27 > */
28 > readonly explicit?: true;
29 > }
30 >
31 > /**
32 > * In-memory representation of a history entry. Currently identical to the
33 > * on-disk shape; the split exists so future in-memory-only fields can be
34 > * added here without changing the wire format.
35 > */
36 > export interface IBrowserHistoryEntry extends ISerializedBrowserHistoryEntry { }
37 >
38 > export interface IBrowserHistoryUpdate {
39 > /** URL may be updated e.g. during a redirect or in-page navigation. */
40 > readonly url?: string;
41 > readonly title?: string;
42 > /** Favicon data URI; hashed and deduped against the sibling favicons store. Pass `null` to explicitly clear. */
43 > readonly favicon?: string | null;
44 > }
45 >
46 > /**
47 > * Handle returned by {@link BrowserHistoryStore.add}. `update` and `delete`
48 > * are no-ops once the underlying entry has been evicted.
49 > */
50 > export interface IBrowserHistoryItemHandle {
51 > readonly id: number;
52 > update(patch: IBrowserHistoryUpdate): void;
53 > delete(): void;
54 > }
55 >
56 > /** Returned by {@link BrowserHistoryStore.add} when the store is disabled (max entries = 0). */
57 > const NOOP_HANDLE: IBrowserHistoryItemHandle = Object.freeze({
58 > id: -1,
59 > update: () => { },
60 > delete: () => { },
61 > });
62 >
63 > /**
64 > * On-disk shape of an entries snapshot. See {@link ISerializedBrowserHistoryEntry}
65 > * for the backwards-compatibility rules; the same constraints apply here.
66 > */
67 > export interface ISerializedBrowserHistoryEntriesSnapshot {
68 > readonly items: readonly ISerializedBrowserHistoryEntry[];
69 > }
70 >
71 > /**
72 > * On-disk shape of a favicons snapshot. See {@link ISerializedBrowserHistoryEntry}
73 > * for the backwards-compatibility rules; the same constraints apply here.
74 > */
75 > export interface ISerializedBrowserFaviconsSnapshot {
76 > /** Map from content hash to data URI. */
77 > readonly map: Readonly<Record<string, string>>;
78 > }
79 >
80 > const DEFAULT_MAX_ENTRIES = 200;
81 >
82 > export class BrowserHistoryEntriesStore extends Disposable {
83 >
84 > private _nextId: number = 1;
85 > private _items: IBrowserHistoryEntry[] = [];
86 > private _maxEntries: number;
87 >
88 > private readonly _onDidChange = this._register(new Emitter<void>());
89 > readonly onDidChange: Event<void> = this._onDidChange.event;
90 >
91 > constructor(maxEntries: number = DEFAULT_MAX_ENTRIES) {
92 super();
93 this._maxEntries = maxEntries;
94 }
96 > get items(): readonly IBrowserHistoryEntry[] {
97 return this._items;
98 }
100 > get maxEntries(): number {
101 return this._maxEntries;
102 }
104 > setMaxEntries(max: number): void {
105 if (max < 0 || max === this._maxEntries) {
106 return;
111 }
112 }
114 > add(url: string, title: string, faviconHash: string | undefined, userInitiated: boolean): IBrowserHistoryEntry {
115 const entry: IBrowserHistoryEntry = userInitiated
116 ? { id: this._nextId++, url, time: Date.now(), title, icon: faviconHash, explicit: true }
121 return entry;
122 }
124 > update(id: number, patch: { url?: string; title?: string; faviconHash?: string | null }): boolean {
125 const idx = this._indexOf(id);
126 if (idx === -1) {
143 return true;
144 }
146 > delete(id: number): boolean {
147 const idx = this._indexOf(id);
148 if (idx === -1) {
153 return true;
154 }
156 > clear(): void {
157 if (this._items.length === 0 && this._nextId === 1) {
158 return;
162 this._onDidChange.fire();
163 }
165 > serialize(): ISerializedBrowserHistoryEntriesSnapshot {
166 return { items: this._items.slice() };
167 }
169 > hydrate(snapshot: ISerializedBrowserHistoryEntriesSnapshot | undefined): void {
170 this._items = [];
171 this._nextId = 1;
186 this._onDidChange.fire();
187 }
189 > private _indexOf(id: number): number {
190 // Walk newest-first; mutations target the just-added entry in the common case.
191 for (let i = this._items.length - 1; i >= 0; i--) {
196 return -1;
197 }
199 > private _evictIfNeeded(): boolean {
200 if (this._items.length > this._maxEntries) {
201 this._items.splice(0, this._items.length - this._maxEntries);
204 return false;
205 }
207 >
208 > /**
209 > * Lives separately from {@link BrowserHistoryEntriesStore} so the (large)
210 > * favicon map is only rewritten when an image is added or removed, not on
211 > * every navigation.
212 > */
213 > export class BrowserFaviconsStore extends Disposable {
214
215 private readonly _byHash = new Map<string, string>();
217 private readonly _onDidChange = this._register(new Emitter<void>());
218 readonly onDidChange: Event<void> = this._onDidChange.event;
220 > get(hash: string): string | undefined {
221 return this._byHash.get(hash);
222 }
224 > register(dataUri: string): string {
225 const sha = new StringSHA1();
226 sha.update(dataUri);
232 return hash;
233 }
235 > gc(referenced: ReadonlySet<string>): void {
236 if (this._byHash.size === 0) {
237 return;
248 }
249 }
251 > clear(): void {
252 if (this._byHash.size === 0) {
253 return;
256 this._onDidChange.fire();
257 }
259 > serialize(): ISerializedBrowserFaviconsSnapshot {
260 return { map: Object.fromEntries(this._byHash) };
261 }
263 > hydrate(snapshot: ISerializedBrowserFaviconsSnapshot | undefined): void {
264 this._byHash.clear();
265 if (snapshot?.map && typeof snapshot.map === 'object') {
272 this._onDidChange.fire();
273 }
275 >
276 > /**
277 > * Per-session browser history. The two sub-stores are exposed directly so
278 > * persistence layers can flush them independently.
279 > */
280 > export class BrowserHistoryStore extends Disposable {
281 >
282 > readonly entries: BrowserHistoryEntriesStore;
283 > readonly favicons: BrowserFaviconsStore;
284 >
285 > private readonly _onDidChange = this._register(new Emitter<void>());
286 > readonly onDidChange: Event<void> = this._onDidChange.event;
287 >
288 > constructor(maxEntries?: number) {
289 super();
290 this.entries = this._register(new BrowserHistoryEntriesStore(maxEntries));
297 this._register(this.favicons.onDidChange(() => this._onDidChange.fire()));
298 }
300 > add(url: string, title: string, favicon?: string, userInitiated = false): IBrowserHistoryItemHandle {
301 if (this.entries.maxEntries === 0) {
302 // History disabled: skip favicon hashing and entry creation entirely.
307 return this._handleFor(entry.id);
308 }
310 > setMaxEntries(max: number): void {
311 this.entries.setMaxEntries(max);
312 }
314 > clear(): void {
315 this.entries.clear();
316 this.favicons.clear();
317 }
319 > private _handleFor(id: number): IBrowserHistoryItemHandle {
320 return {
321 id,
339 };
340 }
342 > private _gcFavicons(): void {
343 const referenced = new Set<string>();
344 for (const e of this.entries.items) {
349 this.favicons.gc(referenced);
350 }
352 >
353 function isValidEntry(value: unknown): value is ISerializedBrowserHistoryEntry {
354 if (!value || typeof value !== 'object') {