places/api/history.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use super::apply_observation;
6use crate::db::PlacesDb;
7use crate::error::*;
8use crate::observation::VisitObservation;
9use crate::types::*;
10use types::Timestamp;
11use url::Url;
12// This module can become, roughly: PlacesUtils.history()
13
14pub fn can_add_url(_url: &Url) -> Result<bool> {
15 Ok(true)
16}
17
18// eg: PlacesUtils.history.insert({url: "http", title: ..., visits: [{date: ...}]})
19
20// Structs representing place and visit infos for this API.
21// (Not clear this makes sense - it's a copy of what desktop does just to
22// get started)
23// NOTE THAT THESE STRUCTS are only for demo purposes, showing how
24// PlacesUtils.history.insert() could be implemented using the same shaped
25// objects.
26// They should really be moved into an "examples" folder.
27#[derive(Debug)]
28pub struct AddablePlaceInfo {
29 pub url: Url,
30 pub title: Option<String>,
31 pub visits: Vec<AddableVisit>,
32}
33
34#[derive(Debug)]
35pub struct AddableVisit {
36 pub date: Timestamp,
37 pub transition: VisitType,
38 pub referrer: Option<Url>,
39 pub is_local: bool,
40}
41
42// insert a visit a'la PlacesUtils.history.insert()
43pub fn insert(conn: &mut PlacesDb, place: AddablePlaceInfo) -> Result<()> {
44 for v in place.visits {
45 let obs = VisitObservation::new(place.url.clone())
46 .with_visit_type(v.transition)
47 .with_at(v.date)
48 .with_title(place.title.clone())
49 .with_is_remote(!v.is_local);
50 // .with_referrer(...) ????
51
52 //if place.referrer
53 apply_observation(conn, obs)?;
54 }
55 Ok(())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::api::places_api::test::new_mem_connection;
62
63 #[test]
64 fn test_insert() {
65 let mut c = new_mem_connection();
66 let url = Url::parse("http://example.com").expect("it's a valid url");
67 let date = Timestamp::now();
68 let visits = vec![AddableVisit {
69 date,
70 transition: VisitType::Link,
71 referrer: None,
72 is_local: true,
73 }];
74 let a = AddablePlaceInfo {
75 url,
76 title: None,
77 visits,
78 };
79
80 insert(&mut c, a).expect("should insert");
81
82 // For now, a raw read of the DB.
83 let sql = "SELECT p.id, p.url, p.title,
84 p.visit_count_local, p.visit_count_remote,
85 p.hidden, p.typed, p.frecency,
86 p.last_visit_date_local, p.last_visit_date_remote,
87 p.guid, p.foreign_count, p.url_hash, p.description,
88 p.preview_image_url, p.origin_id,
89 v.is_local, v.from_visit, v.place_id,
90 v.visit_date, v.visit_type
91 FROM moz_places p, moz_historyvisits v
92 WHERE v.place_id = p.id";
93
94 let mut stmt = c.db.prepare(sql).expect("valid sql");
95 let mut rows = stmt.query([]).expect("should execute");
96 let result = rows.next().expect("should get a row");
97 let row = result.expect("expect anything");
98
99 assert_eq!(
100 row.get::<_, String>("url").expect("should work"),
101 "http://example.com/"
102 ); // hrmph - note trailing slash
103 assert_eq!(
104 row.get::<_, Timestamp>("visit_date").expect("should work"),
105 date
106 );
107 assert_ne!(row.get::<_, i32>("frecency").expect("should work"), 0);
108 // XXX - check more.
109 }
110}
111
112/////////////////////////////////////////////
113// Stuff to reimplement nsHistory::VisitUri()
114fn is_recently_visited(_url: &Url) -> bool {
115 // History.cpp keeps an in-memory hashtable of urls visited in the last
116 // 6 minutes to avoid pages which self-refresh from getting many entries.
117 // ie, there's no DB query done here.
118 // TODO: implement this.
119 false
120}
121
122fn add_recently_visited(_url: &Url) {}
123
124// Other "recent" flags:
125// enum RecentEventFlags {
126// RECENT_TYPED = 1 << 0, // User typed in URL recently
127// RECENT_ACTIVATED = 1 << 1, // User tapped URL link recently
128// RECENT_BOOKMARKED = 1 << 2 // User bookmarked URL recently
129// };
130// All of which are just a 15-second in-memory cache, and all of which appear
131// to rely on explicit calls to set the flag. eg:
132// nsNavHistory::MarkPageAsTyped(nsIURI *aURI) just adds to the cache.
133
134// Is this URL the *source* is a redirect? Note that this is different than
135// the redirect flags in the TransitionType, as that is the flag for the
136// *target* of the redirect.
137#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
138pub enum RedirectSourceType {
139 Temporary,
140 Permanent,
141}
142
143// nsIHistory::VisitURI - this is the main interface used by the browser
144// itself to record visits.
145// This differs from the desktop implementation in one major way - instead
146// of using various browser-specific heuristics to compute the VisitType
147// we assume the caller has already done this and passed the correct transition
148// flags in.
149pub fn visit_uri(
150 conn: &mut PlacesDb,
151 url: &Url,
152 last_url: Option<Url>,
153 // To be more honest, this would *not* take a VisitType,
154 // but instead other "internal" nsIHistory flags, from which
155 // it would deduce the VisitType.
156 transition: VisitType,
157 redirect_source: Option<RedirectSourceType>,
158 is_error_page: bool,
159) -> Result<()> {
160 // Silently return if URI is something we shouldn't add to DB.
161 if !can_add_url(url)? {
162 return Ok(());
163 };
164 // Do not save a reloaded uri if we have visited the same URI recently.
165 // (Note that desktop implies `reload` based of the "is it the same as last
166 // and is it recent" check below) - but here we are asking for the
167 // VisitType to be passed in, which explicitly has a value for reload.
168 // Note clear if we should try and unify these.
169 // (and note that if we can, we can drop the recently_visited cache)
170 if let Some(ref last) = last_url {
171 if url == last && is_recently_visited(url) {
172 // it's a reload we don't want to record, although we do want to
173 // update it as being recent.
174 add_recently_visited(url);
175 return Ok(());
176 };
177 }
178 // So add it.
179
180 // XXX - translate the flags passed to this function, along with the
181 // RECENT_* cache above to create the correct Transition type.
182 // call get_hidden_state to see if .hidden should be set.
183
184 // get_hidden_state...
185
186 // EMBED visits are session-persistent and should not go through the database.
187 // They exist only to keep track of isVisited status during the session.
188 if transition == VisitType::Embed {
189 warn!("Embed visit, but in-memory storage of these isn't done yet");
190 return Ok(());
191 }
192
193 let obs = VisitObservation::new(url.clone())
194 .with_is_error(is_error_page)
195 .with_visit_type(transition)
196 .with_is_redirect_source(redirect_source.map(|_r| true))
197 .with_is_permanent_redirect_source(
198 redirect_source.map(|r| r == RedirectSourceType::Permanent),
199 );
200 apply_observation(conn, obs)
201}