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}