basic history working

This commit is contained in:
Fabian Freund
2025-09-18 12:53:03 +02:00
parent 9676ccae3d
commit 2ff97e226c
12 changed files with 454 additions and 190 deletions
+8
View File
@@ -28,6 +28,7 @@ part of 'routes.dart';
path: 'search/:tabType/:searchText', path: 'search/:tabType/:searchText',
), ),
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'), TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
TypedGoRoute<HistoryRoute>(name: 'HistoryRoute', path: 'history'),
TypedGoRoute<ContextMenuRoute>( TypedGoRoute<ContextMenuRoute>(
name: 'ContextMenuRoute', name: 'ContextMenuRoute',
path: 'context_menu', path: 'context_menu',
@@ -178,3 +179,10 @@ class OpenSharedContentRoute extends GoRouteData with _$OpenSharedContentRoute {
return DialogPage(builder: (_) => OpenSharedContent(sharedUrl: $extra)); return DialogPage(builder: (_) => OpenSharedContent(sharedUrl: $extra));
} }
} }
class HistoryRoute extends GoRouteData with _$HistoryRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
return const HistoryScreen();
}
}
+1
View File
@@ -31,6 +31,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/dialog
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart'; import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:weblibre/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart'; import 'package:weblibre/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart';
import 'package:weblibre/features/geckoview/features/history/presentation/screens/history.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/screens/search.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/screens/search.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart';
+26
View File
@@ -295,6 +295,12 @@ RouteBase get $browserRoute => GoRouteData.$route(
factory: _$TorProxyRoute._fromState, factory: _$TorProxyRoute._fromState,
), ),
GoRouteData.$route(
path: 'history',
name: 'HistoryRoute',
factory: _$HistoryRoute._fromState,
),
GoRouteData.$route( GoRouteData.$route(
path: 'context_menu', path: 'context_menu',
name: 'ContextMenuRoute', name: 'ContextMenuRoute',
@@ -419,6 +425,26 @@ mixin _$TorProxyRoute on GoRouteData {
void replace(BuildContext context) => context.replace(location); void replace(BuildContext context) => context.replace(location);
} }
mixin _$HistoryRoute on GoRouteData {
static HistoryRoute _fromState(GoRouterState state) => HistoryRoute();
@override
String get location => GoRouteData.$location('/history');
@override
void go(BuildContext context) => context.go(location);
@override
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
@override
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
@override
void replace(BuildContext context) => context.replace(location);
}
mixin _$ContextMenuRoute on GoRouteData { mixin _$ContextMenuRoute on GoRouteData {
static ContextMenuRoute _fromState(GoRouterState state) => static ContextMenuRoute _fromState(GoRouterState state) =>
ContextMenuRoute(state.extra as String); ContextMenuRoute(state.extra as String);
@@ -257,6 +257,13 @@ class BrowserBottomAppBar extends HookConsumerWidget {
leadingIcon: const Icon(Icons.settings), leadingIcon: const Icon(Icons.settings),
child: const Text('Settings'), child: const Text('Settings'),
), ),
MenuItemButton(
onPressed: () async {
await HistoryRoute().push(context);
},
leadingIcon: const Icon(Icons.history),
child: const Text('History'),
),
Consumer( Consumer(
builder: (context, childRef, child) { builder: (context, childRef, child) {
final browserExtensions = childRef.watch( final browserExtensions = childRef.watch(
@@ -0,0 +1,28 @@
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
part 'history_filter_options.g.dart';
@CopyWith()
class HistoryFilterOptions with FastEquatable {
final DateTime? start;
final DateTime? end;
final Set<VisitType> visitTypes;
HistoryFilterOptions({
required this.start,
required this.end,
required this.visitTypes,
});
HistoryFilterOptions.withDefaults()
: this(
start: null,
end: null,
visitTypes: {VisitType.link, VisitType.typed},
);
@override
List<Object?> get hashParameters => [start, end, visitTypes];
}
@@ -0,0 +1,80 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'history_filter_options.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$HistoryFilterOptionsCWProxy {
HistoryFilterOptions start(DateTime? start);
HistoryFilterOptions end(DateTime? end);
HistoryFilterOptions visitTypes(Set<VisitType> visitTypes);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `HistoryFilterOptions(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// HistoryFilterOptions(...).copyWith(id: 12, name: "My name")
/// ````
HistoryFilterOptions call({
DateTime? start,
DateTime? end,
Set<VisitType> visitTypes,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfHistoryFilterOptions.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfHistoryFilterOptions.copyWith.fieldName(...)`
class _$HistoryFilterOptionsCWProxyImpl
implements _$HistoryFilterOptionsCWProxy {
const _$HistoryFilterOptionsCWProxyImpl(this._value);
final HistoryFilterOptions _value;
@override
HistoryFilterOptions start(DateTime? start) => this(start: start);
@override
HistoryFilterOptions end(DateTime? end) => this(end: end);
@override
HistoryFilterOptions visitTypes(Set<VisitType> visitTypes) =>
this(visitTypes: visitTypes);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `HistoryFilterOptions(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// HistoryFilterOptions(...).copyWith(id: 12, name: "My name")
/// ````
HistoryFilterOptions call({
Object? start = const $CopyWithPlaceholder(),
Object? end = const $CopyWithPlaceholder(),
Object? visitTypes = const $CopyWithPlaceholder(),
}) {
return HistoryFilterOptions(
start: start == const $CopyWithPlaceholder()
? _value.start
// ignore: cast_nullable_to_non_nullable
: start as DateTime?,
end: end == const $CopyWithPlaceholder()
? _value.end
// ignore: cast_nullable_to_non_nullable
: end as DateTime?,
visitTypes: visitTypes == const $CopyWithPlaceholder()
? _value.visitTypes
// ignore: cast_nullable_to_non_nullable
: visitTypes as Set<VisitType>,
);
}
}
extension $HistoryFilterOptionsCopyWith on HistoryFilterOptions {
/// Returns a callable class that can be used as follows: `instanceOfHistoryFilterOptions.copyWith(...)` or like so:`instanceOfHistoryFilterOptions.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$HistoryFilterOptionsCWProxy get copyWith =>
_$HistoryFilterOptionsCWProxyImpl(this);
}
@@ -20,16 +20,38 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/geckoview/features/history/domain/entities/history_filter_options.dart';
part 'providers.g.dart'; part 'providers.g.dart';
@Riverpod() @Riverpod(keepAlive: true)
Future<List<VisitInfo>> browsingHistory( class HistoryFilter extends _$HistoryFilter {
Ref red, { void updateVisitType(VisitType type, bool value) {
required DateTime start, if (value) {
required DateTime end, state = state.copyWith.visitTypes({...state.visitTypes, type});
required Set<VisitType> types, } else {
}) { state = state.copyWith.visitTypes({...state.visitTypes}..remove(type));
final service = GeckoHistoryService(); }
return service.getDetailedVisits(start, end, types); }
@override
HistoryFilterOptions build() {
return HistoryFilterOptions.withDefaults();
}
}
@Riverpod()
Future<List<VisitInfo>> browsingHistory(Ref ref) {
final options = ref.watch(historyFilterProvider);
final service = GeckoHistoryService();
return service
.getDetailedVisits(
options.start ?? DateTime(0),
options.end ?? DateTime(9999),
options.visitTypes,
)
.then(
(visits) => visits..sort((a, b) => b.visitTime.compareTo(a.visitTime)),
);
} }
@@ -6,186 +6,39 @@ part of 'providers.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$browsingHistoryHash() => r'1f644757c60473f00dd94c9d312647baeaec525a'; String _$browsingHistoryHash() => r'a684c34fc370474a771c5fcc7982a200de2294e5';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [browsingHistory]. /// See also [browsingHistory].
@ProviderFor(browsingHistory) @ProviderFor(browsingHistory)
const browsingHistoryProvider = BrowsingHistoryFamily(); final browsingHistoryProvider =
AutoDisposeFutureProvider<List<VisitInfo>>.internal(
/// See also [browsingHistory]. browsingHistory,
class BrowsingHistoryFamily extends Family<AsyncValue<List<VisitInfo>>> { name: r'browsingHistoryProvider',
/// See also [browsingHistory]. debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
const BrowsingHistoryFamily(); ? null
: _$browsingHistoryHash,
/// See also [browsingHistory]. dependencies: null,
BrowsingHistoryProvider call({ allTransitiveDependencies: null,
required DateTime start,
required DateTime end,
required Set<VisitType> types,
}) {
return BrowsingHistoryProvider(start: start, end: end, types: types);
}
@override
BrowsingHistoryProvider getProviderOverride(
covariant BrowsingHistoryProvider provider,
) {
return call(
start: provider.start,
end: provider.end,
types: provider.types,
); );
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'browsingHistoryProvider';
}
/// See also [browsingHistory].
class BrowsingHistoryProvider
extends AutoDisposeFutureProvider<List<VisitInfo>> {
/// See also [browsingHistory].
BrowsingHistoryProvider({
required DateTime start,
required DateTime end,
required Set<VisitType> types,
}) : this._internal(
(ref) => browsingHistory(
ref as BrowsingHistoryRef,
start: start,
end: end,
types: types,
),
from: browsingHistoryProvider,
name: r'browsingHistoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$browsingHistoryHash,
dependencies: BrowsingHistoryFamily._dependencies,
allTransitiveDependencies:
BrowsingHistoryFamily._allTransitiveDependencies,
start: start,
end: end,
types: types,
);
BrowsingHistoryProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.start,
required this.end,
required this.types,
}) : super.internal();
final DateTime start;
final DateTime end;
final Set<VisitType> types;
@override
Override overrideWith(
FutureOr<List<VisitInfo>> Function(BrowsingHistoryRef provider) create,
) {
return ProviderOverride(
origin: this,
override: BrowsingHistoryProvider._internal(
(ref) => create(ref as BrowsingHistoryRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
start: start,
end: end,
types: types,
),
);
}
@override
AutoDisposeFutureProviderElement<List<VisitInfo>> createElement() {
return _BrowsingHistoryProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is BrowsingHistoryProvider &&
other.start == start &&
other.end == end &&
other.types == types;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, start.hashCode);
hash = _SystemHash.combine(hash, end.hashCode);
hash = _SystemHash.combine(hash, types.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
mixin BrowsingHistoryRef on AutoDisposeFutureProviderRef<List<VisitInfo>> { typedef BrowsingHistoryRef = AutoDisposeFutureProviderRef<List<VisitInfo>>;
/// The parameter `start` of this provider. String _$historyFilterHash() => r'271d60ce79f96ac49d450f01d21cbfdad684ce53';
DateTime get start;
/// The parameter `end` of this provider. /// See also [HistoryFilter].
DateTime get end; @ProviderFor(HistoryFilter)
final historyFilterProvider =
/// The parameter `types` of this provider. NotifierProvider<HistoryFilter, HistoryFilterOptions>.internal(
Set<VisitType> get types; HistoryFilter.new,
} name: r'historyFilterProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
class _BrowsingHistoryProviderElement ? null
extends AutoDisposeFutureProviderElement<List<VisitInfo>> : _$historyFilterHash,
with BrowsingHistoryRef { dependencies: null,
_BrowsingHistoryProviderElement(super.provider); allTransitiveDependencies: null,
);
@override
DateTime get start => (origin as BrowsingHistoryProvider).start;
@override
DateTime get end => (origin as BrowsingHistoryProvider).end;
@override
Set<VisitType> get types => (origin as BrowsingHistoryProvider).types;
}
typedef _$HistoryFilter = Notifier<HistoryFilterOptions>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
@@ -1,22 +1,204 @@
import 'package:collection/collection.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart' show DateFormat;
import 'package:nullability/nullability.dart';
import 'package:sliver_tools/sliver_tools.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:weblibre/features/geckoview/features/history/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/history/domain/providers.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart';
class Section extends MultiSliver {
static final _datePattern = DateFormat('yMMMMd').addPattern('Hm');
Section({
Key? key,
required BuildContext context,
required String title,
required List<VisitInfo> items,
}) : super(
key: key,
pushPinnedChildren: true,
children: [
SliverPinnedHeader(
child: Container(
padding: const EdgeInsets.only(left: 24, top: 8),
color: Theme.of(context).canvasColor,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.bodyLarge),
const Divider(),
],
),
),
),
SliverList.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return Column(
key: ValueKey(item.hashCode),
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
leading: UrlIcon([Uri.parse(item.url)], iconSize: 24),
title: item.title.mapNotNull((title) => Text(title)),
subtitle: Text(
item.url,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.only(left: 54),
child: Wrap(
spacing: 8.0,
children: [
Chip(
label: switch (item.visitType) {
VisitType.link => const Text('Followed Link'),
VisitType.typed => const Text('Typed Address'),
VisitType.embed => const Text(
'Embedded Page Element',
),
VisitType.redirectPermanent => const Text(
'Temporary Redirect',
),
VisitType.redirectTemporary => const Text(
'Permanent Redirect',
),
VisitType.download => const Text('Download'),
VisitType.framedLink => const Text('Frame'),
VisitType.reload => const Text('Page Reload'),
VisitType.bookmark => throw UnimplementedError(),
},
),
Chip(
label: Text(
_datePattern.format(
DateTime.fromMillisecondsSinceEpoch(
item.visitTime,
),
),
),
),
],
),
),
],
);
},
),
],
);
}
class HistoryScreen extends HookConsumerWidget { class HistoryScreen extends HookConsumerWidget {
const HistoryScreen({super.key}); const HistoryScreen({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final history = ref.watch( final historyFilter = ref.watch(historyFilterProvider);
browsingHistoryProvider(
start: DateTime(0), final menuController = useMenuController();
end: DateTime.now(),
types: { final historyEntries = ref.watch(browsingHistoryProvider);
VisitType.link,
VisitType.redirectPermanent, return Scaffold(
VisitType.redirectTemporary, appBar: AppBar(
VisitType.typed, actions: [
MenuAnchor(
controller: menuController,
menuChildren: [
...VisitType.values
.whereNot(
(element) => const {VisitType.bookmark}.contains(element),
)
.map(
(type) => CheckboxMenuButton(
value: historyFilter.visitTypes.contains(type),
onChanged: (value) {
if (value != null) {
ref
.read(historyFilterProvider.notifier)
.updateVisitType(type, value);
}
},
child: switch (type) {
VisitType.link => const Text('Followed Links'),
VisitType.typed => const Text('Typed Addresses'),
VisitType.embed => const Text('Embedded Page Elements'),
VisitType.redirectPermanent => const Text(
'Temporary Redirects',
),
VisitType.redirectTemporary => const Text(
'Permanent Redirects',
),
VisitType.download => const Text('Downloads'),
VisitType.framedLink => const Text('Frames'),
VisitType.reload => const Text('Page Reloads'),
VisitType.bookmark => throw UnimplementedError(),
},
),
),
],
child: IconButton(
onPressed: () {
if (menuController.isOpen) {
menuController.close();
} else {
menuController.open();
}
},
icon: const Icon(Icons.more_vert),
),
),
],
),
body: historyEntries.when(
data: (data) {
return RefreshIndicator(
onRefresh: () async {
// ignore: unused_result
await ref.refresh(browsingHistoryProvider.future);
},
child: HookBuilder(
builder: (context) {
final groups = useMemoized(
() => data.groupListsBy(
(element) => timeago.format(
DateTime.fromMillisecondsSinceEpoch(element.visitTime),
),
),
[EquatableValue(data)],
);
return CustomScrollView(
slivers: [
for (final MapEntry(:key, :value) in groups.entries)
Section(context: context, title: key, items: value),
],
);
},
),
);
}, },
error: (error, stackTrace) => Center(
child: FailureWidget(
title: 'Failed to load History',
exception: error,
),
),
loading: () => const Center(child: CircularProgressIndicator()),
), ),
); );
} }
@@ -167,14 +167,33 @@ enum class CookieSameSiteStatus(val raw: Int) {
} }
enum class VisitType(val raw: Int) { enum class VisitType(val raw: Int) {
/** The user followed a link and got a new toplevel window. */
LINK(0), LINK(0),
/**
* The user typed the page's URL in the URL bar or selected it from
* URL bar autocomplete results, clicked on it from a history query
* (from the History sidebar, History menu, or history query in the
* personal toolbar or Places organizer.
*/
TYPED(1), TYPED(1),
/** The user followed a bookmark to get to the page. */
BOOKMARK(2), BOOKMARK(2),
/**
* Some inner content is loaded. This is true of all images on a
* page, and the contents of the iframe. It is also true of any
* content in a frame if the user did not explicitly follow a link
* to get there.
*/
EMBED(3), EMBED(3),
/** Set when the transition was a permanent redirect. */
REDIRECT_PERMANENT(4), REDIRECT_PERMANENT(4),
/** Set when the transition was a temporary redirect. */
REDIRECT_TEMPORARY(5), REDIRECT_TEMPORARY(5),
/** Set when the transition is a download. */
DOWNLOAD(6), DOWNLOAD(6),
/** The user followed a link and got a visit in a frame. */
FRAMED_LINK(7), FRAMED_LINK(7),
/** The user reloaded a page. */
RELOAD(8); RELOAD(8);
companion object { companion object {
@@ -93,14 +93,29 @@ enum CookieSameSiteStatus {
} }
enum VisitType { enum VisitType {
/// The user followed a link and got a new toplevel window.
link, link,
/// The user typed the page's URL in the URL bar or selected it from
/// URL bar autocomplete results, clicked on it from a history query
/// (from the History sidebar, History menu, or history query in the
/// personal toolbar or Places organizer.
typed, typed,
/// The user followed a bookmark to get to the page.
bookmark, bookmark,
/// Some inner content is loaded. This is true of all images on a
/// page, and the contents of the iframe. It is also true of any
/// content in a frame if the user did not explicitly follow a link
/// to get there.
embed, embed,
/// Set when the transition was a permanent redirect.
redirectPermanent, redirectPermanent,
/// Set when the transition was a temporary redirect.
redirectTemporary, redirectTemporary,
/// Set when the transition is a download.
download, download,
/// The user followed a link and got a visit in a frame.
framedLink, framedLink,
/// The user reloaded a page.
reload, reload,
} }
@@ -383,14 +383,37 @@ class Cookie {
} }
enum VisitType { enum VisitType {
/// The user followed a link and got a new toplevel window.
link, link,
/// The user typed the page's URL in the URL bar or selected it from
/// URL bar autocomplete results, clicked on it from a history query
/// (from the History sidebar, History menu, or history query in the
/// personal toolbar or Places organizer.
typed, typed,
/// The user followed a bookmark to get to the page.
bookmark, bookmark,
/// Some inner content is loaded. This is true of all images on a
/// page, and the contents of the iframe. It is also true of any
/// content in a frame if the user did not explicitly follow a link
/// to get there.
embed, embed,
/// Set when the transition was a permanent redirect.
redirectPermanent, redirectPermanent,
/// Set when the transition was a temporary redirect.
redirectTemporary, redirectTemporary,
/// Set when the transition is a download.
download, download,
/// The user followed a link and got a visit in a frame.
framedLink, framedLink,
/// The user reloaded a page.
reload, reload,
} }