history_api.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/history/history_api.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/command_line.h"
11#include "base/json/json_writer.h"
12#include "base/lazy_instance.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/message_loop/message_loop.h"
15#include "base/prefs/pref_service.h"
16#include "base/strings/string_number_conversions.h"
17#include "base/strings/utf_string_conversions.h"
18#include "base/task/cancelable_task_tracker.h"
19#include "base/time/time.h"
20#include "base/values.h"
21#include "chrome/browser/chrome_notification_types.h"
22#include "chrome/browser/extensions/activity_log/activity_log.h"
23#include "chrome/browser/history/history_service.h"
24#include "chrome/browser/history/history_service_factory.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/common/chrome_switches.h"
27#include "chrome/common/extensions/api/history.h"
28#include "chrome/common/pref_names.h"
29#include "components/history/core/browser/history_types.h"
30#include "content/public/browser/notification_details.h"
31#include "content/public/browser/notification_source.h"
32#include "extensions/browser/event_router.h"
33#include "extensions/browser/extension_system_provider.h"
34#include "extensions/browser/extensions_browser_client.h"
35
36namespace extensions {
37
38using api::history::HistoryItem;
39using api::history::VisitItem;
40using extensions::ActivityLog;
41
42typedef std::vector<linked_ptr<api::history::HistoryItem> >
43    HistoryItemList;
44typedef std::vector<linked_ptr<api::history::VisitItem> >
45    VisitItemList;
46
47namespace AddUrl = api::history::AddUrl;
48namespace DeleteUrl = api::history::DeleteUrl;
49namespace DeleteRange = api::history::DeleteRange;
50namespace GetVisits = api::history::GetVisits;
51namespace OnVisited = api::history::OnVisited;
52namespace OnVisitRemoved = api::history::OnVisitRemoved;
53namespace Search = api::history::Search;
54
55namespace {
56
57const char kInvalidUrlError[] = "Url is invalid.";
58const char kDeleteProhibitedError[] = "Browsing history is not allowed to be "
59                                      "deleted.";
60
61double MilliSecondsFromTime(const base::Time& time) {
62  return 1000 * time.ToDoubleT();
63}
64
65scoped_ptr<HistoryItem> GetHistoryItem(const history::URLRow& row) {
66  scoped_ptr<HistoryItem> history_item(new HistoryItem());
67
68  history_item->id = base::Int64ToString(row.id());
69  history_item->url.reset(new std::string(row.url().spec()));
70  history_item->title.reset(new std::string(base::UTF16ToUTF8(row.title())));
71  history_item->last_visit_time.reset(
72      new double(MilliSecondsFromTime(row.last_visit())));
73  history_item->typed_count.reset(new int(row.typed_count()));
74  history_item->visit_count.reset(new int(row.visit_count()));
75
76  return history_item.Pass();
77}
78
79scoped_ptr<VisitItem> GetVisitItem(const history::VisitRow& row) {
80  scoped_ptr<VisitItem> visit_item(new VisitItem());
81
82  visit_item->id = base::Int64ToString(row.url_id);
83  visit_item->visit_id = base::Int64ToString(row.visit_id);
84  visit_item->visit_time.reset(
85      new double(MilliSecondsFromTime(row.visit_time)));
86  visit_item->referring_visit_id = base::Int64ToString(row.referring_visit);
87
88  VisitItem::Transition transition = VisitItem::TRANSITION_LINK;
89  switch (row.transition & ui::PAGE_TRANSITION_CORE_MASK) {
90    case ui::PAGE_TRANSITION_LINK:
91      transition = VisitItem::TRANSITION_LINK;
92      break;
93    case ui::PAGE_TRANSITION_TYPED:
94      transition = VisitItem::TRANSITION_TYPED;
95      break;
96    case ui::PAGE_TRANSITION_AUTO_BOOKMARK:
97      transition = VisitItem::TRANSITION_AUTO_BOOKMARK;
98      break;
99    case ui::PAGE_TRANSITION_AUTO_SUBFRAME:
100      transition = VisitItem::TRANSITION_AUTO_SUBFRAME;
101      break;
102    case ui::PAGE_TRANSITION_MANUAL_SUBFRAME:
103      transition = VisitItem::TRANSITION_MANUAL_SUBFRAME;
104      break;
105    case ui::PAGE_TRANSITION_GENERATED:
106      transition = VisitItem::TRANSITION_GENERATED;
107      break;
108    case ui::PAGE_TRANSITION_AUTO_TOPLEVEL:
109      transition = VisitItem::TRANSITION_AUTO_TOPLEVEL;
110      break;
111    case ui::PAGE_TRANSITION_FORM_SUBMIT:
112      transition = VisitItem::TRANSITION_FORM_SUBMIT;
113      break;
114    case ui::PAGE_TRANSITION_RELOAD:
115      transition = VisitItem::TRANSITION_RELOAD;
116      break;
117    case ui::PAGE_TRANSITION_KEYWORD:
118      transition = VisitItem::TRANSITION_KEYWORD;
119      break;
120    case ui::PAGE_TRANSITION_KEYWORD_GENERATED:
121      transition = VisitItem::TRANSITION_KEYWORD_GENERATED;
122      break;
123    default:
124      DCHECK(false);
125  }
126
127  visit_item->transition = transition;
128
129  return visit_item.Pass();
130}
131
132}  // namespace
133
134HistoryEventRouter::HistoryEventRouter(Profile* profile) {
135  const content::Source<Profile> source = content::Source<Profile>(profile);
136  registrar_.Add(this,
137                 chrome::NOTIFICATION_HISTORY_URL_VISITED,
138                 source);
139  registrar_.Add(this,
140                 chrome::NOTIFICATION_HISTORY_URLS_DELETED,
141                 source);
142}
143
144HistoryEventRouter::~HistoryEventRouter() {}
145
146void HistoryEventRouter::Observe(int type,
147                                 const content::NotificationSource& source,
148                                 const content::NotificationDetails& details) {
149  switch (type) {
150    case chrome::NOTIFICATION_HISTORY_URL_VISITED:
151      HistoryUrlVisited(
152          content::Source<Profile>(source).ptr(),
153          content::Details<const history::URLVisitedDetails>(details).ptr());
154      break;
155    case chrome::NOTIFICATION_HISTORY_URLS_DELETED:
156      HistoryUrlsRemoved(
157          content::Source<Profile>(source).ptr(),
158          content::Details<const history::URLsDeletedDetails>(details).ptr());
159      break;
160    default:
161      NOTREACHED();
162  }
163}
164
165void HistoryEventRouter::HistoryUrlVisited(
166    Profile* profile,
167    const history::URLVisitedDetails* details) {
168  scoped_ptr<HistoryItem> history_item = GetHistoryItem(details->row);
169  scoped_ptr<base::ListValue> args = OnVisited::Create(*history_item);
170
171  DispatchEvent(profile, api::history::OnVisited::kEventName, args.Pass());
172}
173
174void HistoryEventRouter::HistoryUrlsRemoved(
175    Profile* profile,
176    const history::URLsDeletedDetails* details) {
177  OnVisitRemoved::Removed removed;
178  removed.all_history = details->all_history;
179
180  std::vector<std::string>* urls = new std::vector<std::string>();
181  for (history::URLRows::const_iterator iterator = details->rows.begin();
182      iterator != details->rows.end(); ++iterator) {
183    urls->push_back(iterator->url().spec());
184  }
185  removed.urls.reset(urls);
186
187  scoped_ptr<base::ListValue> args = OnVisitRemoved::Create(removed);
188  DispatchEvent(profile, api::history::OnVisitRemoved::kEventName, args.Pass());
189}
190
191void HistoryEventRouter::DispatchEvent(
192    Profile* profile,
193    const std::string& event_name,
194    scoped_ptr<base::ListValue> event_args) {
195  if (profile && extensions::EventRouter::Get(profile)) {
196    scoped_ptr<extensions::Event> event(new extensions::Event(
197        event_name, event_args.Pass()));
198    event->restrict_to_browser_context = profile;
199    extensions::EventRouter::Get(profile)->BroadcastEvent(event.Pass());
200  }
201}
202
203HistoryAPI::HistoryAPI(content::BrowserContext* context)
204    : browser_context_(context) {
205  EventRouter* event_router = EventRouter::Get(browser_context_);
206  event_router->RegisterObserver(this, api::history::OnVisited::kEventName);
207  event_router->RegisterObserver(this,
208                                 api::history::OnVisitRemoved::kEventName);
209}
210
211HistoryAPI::~HistoryAPI() {
212}
213
214void HistoryAPI::Shutdown() {
215  EventRouter::Get(browser_context_)->UnregisterObserver(this);
216}
217
218static base::LazyInstance<BrowserContextKeyedAPIFactory<HistoryAPI> >
219    g_factory = LAZY_INSTANCE_INITIALIZER;
220
221// static
222BrowserContextKeyedAPIFactory<HistoryAPI>* HistoryAPI::GetFactoryInstance() {
223  return g_factory.Pointer();
224}
225
226template <>
227void BrowserContextKeyedAPIFactory<HistoryAPI>::DeclareFactoryDependencies() {
228  DependsOn(ActivityLog::GetFactoryInstance());
229  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
230}
231
232void HistoryAPI::OnListenerAdded(const EventListenerInfo& details) {
233  history_event_router_.reset(
234      new HistoryEventRouter(Profile::FromBrowserContext(browser_context_)));
235  EventRouter::Get(browser_context_)->UnregisterObserver(this);
236}
237
238bool HistoryFunction::ValidateUrl(const std::string& url_string, GURL* url) {
239  GURL temp_url(url_string);
240  if (!temp_url.is_valid()) {
241    error_ = kInvalidUrlError;
242    return false;
243  }
244  url->Swap(&temp_url);
245  return true;
246}
247
248bool HistoryFunction::VerifyDeleteAllowed() {
249  PrefService* prefs = GetProfile()->GetPrefs();
250  if (!prefs->GetBoolean(prefs::kAllowDeletingBrowserHistory)) {
251    error_ = kDeleteProhibitedError;
252    return false;
253  }
254  return true;
255}
256
257base::Time HistoryFunction::GetTime(double ms_from_epoch) {
258  // The history service has seconds resolution, while javascript Date() has
259  // milliseconds resolution.
260  double seconds_from_epoch = ms_from_epoch / 1000.0;
261  // Time::FromDoubleT converts double time 0 to empty Time object. So we need
262  // to do special handling here.
263  return (seconds_from_epoch == 0) ?
264      base::Time::UnixEpoch() : base::Time::FromDoubleT(seconds_from_epoch);
265}
266
267HistoryFunctionWithCallback::HistoryFunctionWithCallback() {
268}
269
270HistoryFunctionWithCallback::~HistoryFunctionWithCallback() {
271}
272
273bool HistoryFunctionWithCallback::RunAsync() {
274  AddRef();  // Balanced in SendAysncRepose() and below.
275  bool retval = RunAsyncImpl();
276  if (false == retval)
277    Release();
278  return retval;
279}
280
281void HistoryFunctionWithCallback::SendAsyncResponse() {
282  base::MessageLoop::current()->PostTask(
283      FROM_HERE,
284      base::Bind(&HistoryFunctionWithCallback::SendResponseToCallback, this));
285}
286
287void HistoryFunctionWithCallback::SendResponseToCallback() {
288  SendResponse(true);
289  Release();  // Balanced in RunAsync().
290}
291
292bool HistoryGetVisitsFunction::RunAsyncImpl() {
293  scoped_ptr<GetVisits::Params> params(GetVisits::Params::Create(*args_));
294  EXTENSION_FUNCTION_VALIDATE(params.get());
295
296  GURL url;
297  if (!ValidateUrl(params->details.url, &url))
298    return false;
299
300  HistoryService* hs = HistoryServiceFactory::GetForProfile(
301      GetProfile(), Profile::EXPLICIT_ACCESS);
302  hs->QueryURL(url,
303               true,  // Retrieve full history of a URL.
304               base::Bind(&HistoryGetVisitsFunction::QueryComplete,
305                          base::Unretained(this)),
306               &task_tracker_);
307  return true;
308}
309
310void HistoryGetVisitsFunction::QueryComplete(
311    bool success,
312    const history::URLRow& url_row,
313    const history::VisitVector& visits) {
314  VisitItemList visit_item_vec;
315  if (success && !visits.empty()) {
316    for (history::VisitVector::const_iterator iterator = visits.begin();
317         iterator != visits.end();
318         ++iterator) {
319      visit_item_vec.push_back(make_linked_ptr(
320          GetVisitItem(*iterator).release()));
321    }
322  }
323
324  results_ = GetVisits::Results::Create(visit_item_vec);
325  SendAsyncResponse();
326}
327
328bool HistorySearchFunction::RunAsyncImpl() {
329  scoped_ptr<Search::Params> params(Search::Params::Create(*args_));
330  EXTENSION_FUNCTION_VALIDATE(params.get());
331
332  base::string16 search_text = base::UTF8ToUTF16(params->query.text);
333
334  history::QueryOptions options;
335  options.SetRecentDayRange(1);
336  options.max_count = 100;
337
338  if (params->query.start_time.get())
339    options.begin_time = GetTime(*params->query.start_time);
340  if (params->query.end_time.get())
341    options.end_time = GetTime(*params->query.end_time);
342  if (params->query.max_results.get())
343    options.max_count = *params->query.max_results;
344
345  HistoryService* hs = HistoryServiceFactory::GetForProfile(
346      GetProfile(), Profile::EXPLICIT_ACCESS);
347  hs->QueryHistory(search_text,
348                   options,
349                   base::Bind(&HistorySearchFunction::SearchComplete,
350                              base::Unretained(this)),
351                   &task_tracker_);
352
353  return true;
354}
355
356void HistorySearchFunction::SearchComplete(history::QueryResults* results) {
357  HistoryItemList history_item_vec;
358  if (results && !results->empty()) {
359    for (history::QueryResults::URLResultVector::const_iterator iterator =
360            results->begin();
361         iterator != results->end();
362        ++iterator) {
363      history_item_vec.push_back(make_linked_ptr(
364          GetHistoryItem(**iterator).release()));
365    }
366  }
367  results_ = Search::Results::Create(history_item_vec);
368  SendAsyncResponse();
369}
370
371bool HistoryAddUrlFunction::RunAsync() {
372  scoped_ptr<AddUrl::Params> params(AddUrl::Params::Create(*args_));
373  EXTENSION_FUNCTION_VALIDATE(params.get());
374
375  GURL url;
376  if (!ValidateUrl(params->details.url, &url))
377    return false;
378
379  HistoryService* hs = HistoryServiceFactory::GetForProfile(
380      GetProfile(), Profile::EXPLICIT_ACCESS);
381  hs->AddPage(url, base::Time::Now(), history::SOURCE_EXTENSION);
382
383  SendResponse(true);
384  return true;
385}
386
387bool HistoryDeleteUrlFunction::RunAsync() {
388  scoped_ptr<DeleteUrl::Params> params(DeleteUrl::Params::Create(*args_));
389  EXTENSION_FUNCTION_VALIDATE(params.get());
390
391  if (!VerifyDeleteAllowed())
392    return false;
393
394  GURL url;
395  if (!ValidateUrl(params->details.url, &url))
396    return false;
397
398  HistoryService* hs = HistoryServiceFactory::GetForProfile(
399      GetProfile(), Profile::EXPLICIT_ACCESS);
400  hs->DeleteURL(url);
401
402  // Also clean out from the activity log. If the activity log testing flag is
403  // set then don't clean so testers can see what potentially malicious
404  // extensions have been trying to clean from their logs.
405  if (!CommandLine::ForCurrentProcess()->HasSwitch(
406          switches::kEnableExtensionActivityLogTesting)) {
407    ActivityLog* activity_log = ActivityLog::GetInstance(GetProfile());
408    DCHECK(activity_log);
409    activity_log->RemoveURL(url);
410  }
411
412  SendResponse(true);
413  return true;
414}
415
416bool HistoryDeleteRangeFunction::RunAsyncImpl() {
417  scoped_ptr<DeleteRange::Params> params(DeleteRange::Params::Create(*args_));
418  EXTENSION_FUNCTION_VALIDATE(params.get());
419
420  if (!VerifyDeleteAllowed())
421    return false;
422
423  base::Time start_time = GetTime(params->range.start_time);
424  base::Time end_time = GetTime(params->range.end_time);
425
426  std::set<GURL> restrict_urls;
427  HistoryService* hs = HistoryServiceFactory::GetForProfile(
428      GetProfile(), Profile::EXPLICIT_ACCESS);
429  hs->ExpireHistoryBetween(
430      restrict_urls,
431      start_time,
432      end_time,
433      base::Bind(&HistoryDeleteRangeFunction::DeleteComplete,
434                 base::Unretained(this)),
435      &task_tracker_);
436
437  // Also clean from the activity log unless in testing mode.
438  if (!CommandLine::ForCurrentProcess()->HasSwitch(
439          switches::kEnableExtensionActivityLogTesting)) {
440    ActivityLog* activity_log = ActivityLog::GetInstance(GetProfile());
441    DCHECK(activity_log);
442    activity_log->RemoveURLs(restrict_urls);
443  }
444
445  return true;
446}
447
448void HistoryDeleteRangeFunction::DeleteComplete() {
449  SendAsyncResponse();
450}
451
452bool HistoryDeleteAllFunction::RunAsyncImpl() {
453  if (!VerifyDeleteAllowed())
454    return false;
455
456  std::set<GURL> restrict_urls;
457  HistoryService* hs = HistoryServiceFactory::GetForProfile(
458      GetProfile(), Profile::EXPLICIT_ACCESS);
459  hs->ExpireHistoryBetween(
460      restrict_urls,
461      base::Time(),      // Unbounded beginning...
462      base::Time(),      // ...and the end.
463      base::Bind(&HistoryDeleteAllFunction::DeleteComplete,
464                 base::Unretained(this)),
465      &task_tracker_);
466
467  // Also clean from the activity log unless in testing mode.
468  if (!CommandLine::ForCurrentProcess()->HasSwitch(
469          switches::kEnableExtensionActivityLogTesting)) {
470    ActivityLog* activity_log = ActivityLog::GetInstance(GetProfile());
471    DCHECK(activity_log);
472    activity_log->RemoveURLs(restrict_urls);
473  }
474
475  return true;
476}
477
478void HistoryDeleteAllFunction::DeleteComplete() {
479  SendAsyncResponse();
480}
481
482}  // namespace extensions
483