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 "base/auto_reset.h"
6#include "base/files/scoped_temp_dir.h"
7#include "base/json/json_reader.h"
8#include "base/prefs/pref_service.h"
9#include "base/values.h"
10#include "chrome/browser/history/download_row.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/ui/browser.h"
13#include "chrome/browser/ui/webui/downloads_dom_handler.h"
14#include "chrome/common/pref_names.h"
15#include "chrome/test/base/in_process_browser_test.h"
16#include "chrome/test/base/ui_test_utils.h"
17#include "content/public/browser/web_contents.h"
18
19namespace {
20
21// Reads |right_json| into a ListValue |left_list|; returns true if all
22// key-value pairs in in all dictionaries in |right_list| are also in the
23// corresponding dictionary in |left_list|. Ignores keys in dictionaries in
24// |left_list| that are not in the corresponding dictionary in |right_list|.
25bool ListMatches(base::ListValue* left_list, const std::string& right_json) {
26  scoped_ptr<base::Value> right_value(base::JSONReader::Read(right_json));
27  base::ListValue* right_list = NULL;
28  CHECK(right_value->GetAsList(&right_list));
29  for (size_t i = 0; i < left_list->GetSize(); ++i) {
30    base::DictionaryValue* left_dict = NULL;
31    base::DictionaryValue* right_dict = NULL;
32    CHECK(left_list->GetDictionary(i, &left_dict));
33    CHECK(right_list->GetDictionary(i, &right_dict));
34    for (base::DictionaryValue::Iterator iter(*right_dict);
35          !iter.IsAtEnd(); iter.Advance()) {
36      base::Value* left_value = NULL;
37      if (left_dict->HasKey(iter.key()) &&
38          left_dict->Get(iter.key(), &left_value) &&
39          !iter.value().Equals(left_value)) {
40        LOG(WARNING) << iter.key();
41        return false;
42      }
43    }
44  }
45  return true;
46}
47
48// A |DownloadsDOMHandler| that doesn't use a real WebUI object, but is real in
49// all other respects.
50class MockDownloadsDOMHandler : public DownloadsDOMHandler {
51 public:
52  explicit MockDownloadsDOMHandler(content::DownloadManager* dlm)
53    : DownloadsDOMHandler(dlm),
54      waiting_list_(false),
55      waiting_updated_(false) {
56  }
57  virtual ~MockDownloadsDOMHandler() {}
58
59  base::ListValue* downloads_list() { return downloads_list_.get(); }
60  base::ListValue* download_updated() { return download_updated_.get(); }
61
62  void WaitForDownloadsList() {
63    if (downloads_list_.get())
64      return;
65    base::AutoReset<bool> reset_waiting(&waiting_list_, true);
66    content::RunMessageLoop();
67  }
68
69  void WaitForDownloadUpdated() {
70    if (download_updated_.get())
71      return;
72    base::AutoReset<bool> reset_waiting(&waiting_updated_, true);
73    content::RunMessageLoop();
74  }
75
76  void ForceSendCurrentDownloads() {
77    ScheduleSendCurrentDownloads();
78  }
79
80  void reset_downloads_list() { downloads_list_.reset(); }
81  void reset_download_updated() { download_updated_.reset(); }
82
83 protected:
84  virtual content::WebContents* GetWebUIWebContents() OVERRIDE {
85    return NULL;
86  }
87
88  virtual void CallDownloadsList(const base::ListValue& downloads) OVERRIDE {
89    downloads_list_.reset(downloads.DeepCopy());
90    if (waiting_list_) {
91      content::BrowserThread::PostTask(content::BrowserThread::UI,
92                                       FROM_HERE,
93                                       base::MessageLoop::QuitClosure());
94    }
95  }
96
97  virtual void CallDownloadUpdated(const base::ListValue& download) OVERRIDE {
98    download_updated_.reset(download.DeepCopy());
99    if (waiting_updated_) {
100      content::BrowserThread::PostTask(content::BrowserThread::UI,
101                                       FROM_HERE,
102                                       base::MessageLoop::QuitClosure());
103    }
104  }
105
106 private:
107  scoped_ptr<base::ListValue> downloads_list_;
108  scoped_ptr<base::ListValue> download_updated_;
109  bool waiting_list_;
110  bool waiting_updated_;
111
112  DISALLOW_COPY_AND_ASSIGN(MockDownloadsDOMHandler);
113};
114
115}  // namespace
116
117class DownloadsDOMHandlerTest : public InProcessBrowserTest {
118 public:
119  DownloadsDOMHandlerTest() {}
120
121  virtual ~DownloadsDOMHandlerTest() {}
122
123  virtual void SetUpOnMainThread() OVERRIDE {
124    mock_handler_.reset(new MockDownloadsDOMHandler(download_manager()));
125    CHECK(downloads_directory_.CreateUniqueTempDir());
126    browser()->profile()->GetPrefs()->SetFilePath(
127        prefs::kDownloadDefaultDirectory,
128        downloads_directory_.path());
129    CHECK(test_server()->Start());
130  }
131
132  content::DownloadManager* download_manager() {
133    return content::BrowserContext::GetDownloadManager(browser()->profile());
134  }
135
136  void DownloadAnItem() {
137    GURL url = test_server()->GetURL("files/downloads/image.jpg");
138    std::vector<GURL> url_chain;
139    url_chain.push_back(url);
140    base::Time current(base::Time::Now());
141    download_manager()->CreateDownloadItem(
142        1, // id
143        base::FilePath(FILE_PATH_LITERAL("/path/to/file")),
144        base::FilePath(FILE_PATH_LITERAL("/path/to/file")),
145        url_chain,
146        GURL(std::string()),
147        "application/octet-stream",
148        "application/octet-stream",
149        current,
150        current,
151        std::string(),
152        std::string(),
153        128,
154        128,
155        content::DownloadItem::COMPLETE,
156        content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
157        content::DOWNLOAD_INTERRUPT_REASON_NONE,
158        false);
159
160    mock_handler_->WaitForDownloadsList();
161    ASSERT_EQ(1, static_cast<int>(mock_handler_->downloads_list()->GetSize()));
162    EXPECT_TRUE(ListMatches(
163        mock_handler_->downloads_list(),
164        "[{\"file_externally_removed\": false,"
165        "  \"file_name\": \"file\","
166        "  \"id\": 1,"
167        "  \"otr\": false,"
168        "  \"since_string\": \"Today\","
169        "  \"state\": \"COMPLETE\","
170        "  \"total\": 128}]"));
171  }
172
173 protected:
174  scoped_ptr<MockDownloadsDOMHandler> mock_handler_;
175
176 private:
177  base::ScopedTempDir downloads_directory_;
178
179  DISALLOW_COPY_AND_ASSIGN(DownloadsDOMHandlerTest);
180};
181
182// Tests removing all items, both when prohibited and when allowed.
183IN_PROC_BROWSER_TEST_F(DownloadsDOMHandlerTest, RemoveAll) {
184  DownloadAnItem();
185
186  mock_handler_->reset_downloads_list();
187  browser()->profile()->GetPrefs()->SetBoolean(
188      prefs::kAllowDeletingBrowserHistory, false);
189  mock_handler_->HandleClearAll(NULL);
190  mock_handler_->WaitForDownloadsList();
191  ASSERT_EQ(1, static_cast<int>(mock_handler_->downloads_list()->GetSize()));
192
193  mock_handler_->reset_downloads_list();
194  browser()->profile()->GetPrefs()->SetBoolean(
195      prefs::kAllowDeletingBrowserHistory, true);
196  mock_handler_->HandleClearAll(NULL);
197  mock_handler_->WaitForDownloadsList();
198  EXPECT_EQ(0, static_cast<int>(mock_handler_->downloads_list()->GetSize()));
199}
200
201// Tests removing one item, both when prohibited and when allowed.
202IN_PROC_BROWSER_TEST_F(DownloadsDOMHandlerTest, RemoveOneItem) {
203  DownloadAnItem();
204  base::ListValue item;
205  item.AppendInteger(1);
206
207  mock_handler_->reset_downloads_list();
208  browser()->profile()->GetPrefs()->SetBoolean(
209      prefs::kAllowDeletingBrowserHistory, false);
210  mock_handler_->HandleRemove(&item);
211  // Removing an item only sends the new download list if anything was actually
212  // removed, so force it.
213  mock_handler_->ForceSendCurrentDownloads();
214  mock_handler_->WaitForDownloadsList();
215  ASSERT_EQ(1, static_cast<int>(mock_handler_->downloads_list()->GetSize()));
216
217  mock_handler_->reset_downloads_list();
218  browser()->profile()->GetPrefs()->SetBoolean(
219      prefs::kAllowDeletingBrowserHistory, true);
220  mock_handler_->HandleRemove(&item);
221  mock_handler_->WaitForDownloadsList();
222  EXPECT_EQ(0, static_cast<int>(mock_handler_->downloads_list()->GetSize()));
223}
224
225// Tests that DownloadsDOMHandler detects new downloads and relays them to the
226// renderer.
227// crbug.com/159390: This test fails when daylight savings time ends.
228IN_PROC_BROWSER_TEST_F(DownloadsDOMHandlerTest, DownloadsRelayed) {
229  DownloadAnItem();
230
231  mock_handler_->WaitForDownloadUpdated();
232  ASSERT_EQ(1, static_cast<int>(mock_handler_->download_updated()->GetSize()));
233  EXPECT_TRUE(ListMatches(
234      mock_handler_->download_updated(),
235      "[{\"file_externally_removed\": true,"
236      "  \"id\": 1}]"));
237
238  mock_handler_->reset_downloads_list();
239  browser()->profile()->GetPrefs()->SetBoolean(
240      prefs::kAllowDeletingBrowserHistory, true);
241  mock_handler_->HandleClearAll(NULL);
242  mock_handler_->WaitForDownloadsList();
243  EXPECT_EQ(0, static_cast<int>(mock_handler_->downloads_list()->GetSize()));
244}
245
246
247// TODO(benjhayden): Test the extension downloads filter for both
248// mock_handler_.downloads_list() and mock_handler_.download_updated().
249
250// TODO(benjhayden): Test incognito, both downloads_list() and that on-record
251// calls can't access off-record items.
252
253// TODO(benjhayden): Test that bad download ids incoming from the javascript are
254// dropped on the floor.
255
256// TODO(benjhayden): Test that IsTemporary() downloads are not shown.
257
258// TODO(benjhayden): Test that RemoveObserver is called on all download items,
259// including items that crossed IsTemporary() and back.
260