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 <string>
6
7#include "base/basictypes.h"
8#include "base/command_line.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/message_loop/message_loop.h"
11#include "base/prefs/pref_service.h"
12#include "base/prefs/scoped_user_pref_update.h"
13#include "base/run_loop.h"
14#include "base/strings/utf_string_conversions.h"
15#include "chrome/browser/background/background_contents_service.h"
16#include "chrome/browser/background/background_contents_service_factory.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/tab_contents/background_contents.h"
19#include "chrome/browser/ui/browser_list.h"
20#include "chrome/common/extensions/extension_test_util.h"
21#include "chrome/common/pref_names.h"
22#include "chrome/test/base/browser_with_test_window_test.h"
23#include "chrome/test/base/testing_browser_process.h"
24#include "chrome/test/base/testing_profile.h"
25#include "chrome/test/base/testing_profile_manager.h"
26#include "content/public/browser/notification_service.h"
27#include "content/public/test/test_browser_thread.h"
28#include "extensions/common/extension.h"
29#include "testing/gtest/include/gtest/gtest.h"
30#include "testing/platform_test.h"
31#include "url/gurl.h"
32
33#if defined(ENABLE_NOTIFICATIONS)
34#include "chrome/browser/notifications/message_center_notification_manager.h"
35#include "chrome/browser/notifications/notification.h"
36#include "ui/message_center/fake_message_center_tray_delegate.h"
37#include "ui/message_center/message_center.h"
38#include "ui/message_center/message_center_observer.h"
39#endif
40
41class BackgroundContentsServiceTest : public testing::Test {
42 public:
43  BackgroundContentsServiceTest() {}
44  virtual ~BackgroundContentsServiceTest() {}
45  virtual void SetUp() {
46    command_line_.reset(new CommandLine(CommandLine::NO_PROGRAM));
47  }
48
49  const base::DictionaryValue* GetPrefs(Profile* profile) {
50    return profile->GetPrefs()->GetDictionary(
51        prefs::kRegisteredBackgroundContents);
52  }
53
54  // Returns the stored pref URL for the passed app id.
55  std::string GetPrefURLForApp(Profile* profile, const base::string16& appid) {
56    const base::DictionaryValue* pref = GetPrefs(profile);
57    EXPECT_TRUE(pref->HasKey(base::UTF16ToUTF8(appid)));
58    const base::DictionaryValue* value;
59    pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid), &value);
60    std::string url;
61    value->GetString("url", &url);
62    return url;
63  }
64
65  scoped_ptr<CommandLine> command_line_;
66};
67
68class MockBackgroundContents : public BackgroundContents {
69 public:
70  explicit MockBackgroundContents(Profile* profile)
71      : appid_(base::ASCIIToUTF16("app_id")),
72        profile_(profile) {
73  }
74  MockBackgroundContents(Profile* profile, const std::string& id)
75      : appid_(base::ASCIIToUTF16(id)),
76        profile_(profile) {
77  }
78
79  void SendOpenedNotification(BackgroundContentsService* service) {
80    base::string16 frame_name = base::ASCIIToUTF16("background");
81    BackgroundContentsOpenedDetails details = {
82        this, frame_name, appid_ };
83    service->BackgroundContentsOpened(&details);
84  }
85
86  virtual void Navigate(GURL url) {
87    url_ = url;
88    content::NotificationService::current()->Notify(
89        chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
90        content::Source<Profile>(profile_),
91        content::Details<BackgroundContents>(this));
92  }
93  virtual const GURL& GetURL() const OVERRIDE { return url_; }
94
95  void MockClose(Profile* profile) {
96    content::NotificationService::current()->Notify(
97        chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
98        content::Source<Profile>(profile),
99        content::Details<BackgroundContents>(this));
100    delete this;
101  }
102
103  virtual ~MockBackgroundContents() {
104    content::NotificationService::current()->Notify(
105        chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
106        content::Source<Profile>(profile_),
107        content::Details<BackgroundContents>(this));
108  }
109
110  const base::string16& appid() { return appid_; }
111
112 private:
113  GURL url_;
114
115  // The ID of our parent application
116  base::string16 appid_;
117
118  // Parent profile
119  Profile* profile_;
120};
121
122#if defined(ENABLE_NOTIFICATIONS)
123// Wait for the notification created.
124class NotificationWaiter : public message_center::MessageCenterObserver {
125 public:
126  explicit NotificationWaiter(const std::string& target_id)
127      : target_id_(target_id) {}
128  virtual ~NotificationWaiter() {}
129
130  void WaitForNotificationAdded() {
131    DCHECK(!run_loop_.running());
132    message_center::MessageCenter* message_center =
133        message_center::MessageCenter::Get();
134
135    message_center->AddObserver(this);
136    run_loop_.Run();
137    message_center->RemoveObserver(this);
138  }
139
140 private:
141  // message_center::MessageCenterObserver overrides:
142  virtual void OnNotificationAdded(
143      const std::string& notification_id) OVERRIDE {
144    if (notification_id == target_id_)
145      run_loop_.Quit();
146  }
147
148  virtual void OnNotificationUpdated(
149      const std::string& notification_id) OVERRIDE {
150    if (notification_id == target_id_)
151      run_loop_.Quit();
152  }
153
154  std::string target_id_;
155  base::RunLoop run_loop_;
156
157  DISALLOW_COPY_AND_ASSIGN(NotificationWaiter);
158};
159
160class BackgroundContentsServiceNotificationTest
161    : public BrowserWithTestWindowTest {
162 public:
163  BackgroundContentsServiceNotificationTest() {}
164  virtual ~BackgroundContentsServiceNotificationTest() {}
165
166  // Overridden from testing::Test
167  virtual void SetUp() {
168    BrowserWithTestWindowTest::SetUp();
169    // In ChromeOS environment, BrowserWithTestWindowTest initializes
170    // MessageCenter.
171#if !defined(OS_CHROMEOS)
172    message_center::MessageCenter::Initialize();
173#endif
174    profile_manager_.reset(new TestingProfileManager(
175        TestingBrowserProcess::GetGlobal()));
176    ASSERT_TRUE(profile_manager_->SetUp());
177    MessageCenterNotificationManager* manager =
178        static_cast<MessageCenterNotificationManager*>(
179            g_browser_process->notification_ui_manager());
180    manager->SetMessageCenterTrayDelegateForTest(
181        new message_center::FakeMessageCenterTrayDelegate(
182            message_center::MessageCenter::Get(), base::Closure()));
183  }
184
185  virtual void TearDown() {
186    g_browser_process->notification_ui_manager()->CancelAll();
187    profile_manager_.reset();
188#if !defined(OS_CHROMEOS)
189    message_center::MessageCenter::Shutdown();
190#endif
191    BrowserWithTestWindowTest::TearDown();
192  }
193
194 protected:
195  // Creates crash notification for the specified extension and returns
196  // the created one.
197  const Notification* CreateCrashNotification(
198      scoped_refptr<extensions::Extension> extension) {
199    std::string notification_id =
200        BackgroundContentsService::GetNotificationIdForExtensionForTesting(
201            extension->id());
202    NotificationWaiter waiter(notification_id);
203    BackgroundContentsService::ShowBalloonForTesting(
204        extension.get(), profile());
205    waiter.WaitForNotificationAdded();
206
207    return g_browser_process->notification_ui_manager()->FindById(
208        notification_id);
209  }
210
211 private:
212  scoped_ptr<TestingProfileManager> profile_manager_;
213
214  DISALLOW_COPY_AND_ASSIGN(BackgroundContentsServiceNotificationTest);
215};
216#endif  // ENABLE_NOTIFICATIONS
217
218TEST_F(BackgroundContentsServiceTest, Create) {
219  // Check for creation and leaks.
220  TestingProfile profile;
221  BackgroundContentsService service(&profile, command_line_.get());
222}
223
224TEST_F(BackgroundContentsServiceTest, BackgroundContentsCreateDestroy) {
225  TestingProfile profile;
226  BackgroundContentsService service(&profile, command_line_.get());
227  MockBackgroundContents* contents = new MockBackgroundContents(&profile);
228  EXPECT_FALSE(service.IsTracked(contents));
229  contents->SendOpenedNotification(&service);
230  EXPECT_TRUE(service.IsTracked(contents));
231  delete contents;
232  EXPECT_FALSE(service.IsTracked(contents));
233}
234
235TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAdded) {
236  TestingProfile profile;
237  BackgroundContentsService service(&profile, command_line_.get());
238  BackgroundContentsServiceFactory::GetInstance()->
239      RegisterUserPrefsOnBrowserContextForTest(&profile);
240  GURL orig_url;
241  GURL url("http://a/");
242  GURL url2("http://a/");
243  {
244    scoped_ptr<MockBackgroundContents> contents(
245        new MockBackgroundContents(&profile));
246    EXPECT_EQ(0U, GetPrefs(&profile)->size());
247    contents->SendOpenedNotification(&service);
248
249    contents->Navigate(url);
250    EXPECT_EQ(1U, GetPrefs(&profile)->size());
251    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
252
253    // Navigate the contents to a new url, should not change url.
254    contents->Navigate(url2);
255    EXPECT_EQ(1U, GetPrefs(&profile)->size());
256    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
257  }
258  // Contents are deleted, url should persist.
259  EXPECT_EQ(1U, GetPrefs(&profile)->size());
260}
261
262TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAddedAndClosed) {
263  TestingProfile profile;
264  BackgroundContentsService service(&profile, command_line_.get());
265  BackgroundContentsServiceFactory::GetInstance()->
266      RegisterUserPrefsOnBrowserContextForTest(&profile);
267
268  GURL url("http://a/");
269  MockBackgroundContents* contents = new MockBackgroundContents(&profile);
270  EXPECT_EQ(0U, GetPrefs(&profile)->size());
271  contents->SendOpenedNotification(&service);
272  contents->Navigate(url);
273  EXPECT_EQ(1U, GetPrefs(&profile)->size());
274  EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
275
276  // Fake a window closed by script.
277  contents->MockClose(&profile);
278  EXPECT_EQ(0U, GetPrefs(&profile)->size());
279}
280
281// Test what happens if a BackgroundContents shuts down (say, due to a renderer
282// crash) then is restarted. Should not persist URL twice.
283TEST_F(BackgroundContentsServiceTest, RestartBackgroundContents) {
284  TestingProfile profile;
285  BackgroundContentsService service(&profile, command_line_.get());
286  BackgroundContentsServiceFactory::GetInstance()->
287      RegisterUserPrefsOnBrowserContextForTest(&profile);
288
289  GURL url("http://a/");
290  {
291    scoped_ptr<MockBackgroundContents> contents(new MockBackgroundContents(
292        &profile, "appid"));
293    contents->SendOpenedNotification(&service);
294    contents->Navigate(url);
295    EXPECT_EQ(1U, GetPrefs(&profile)->size());
296    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
297  }
298  // Contents deleted, url should be persisted.
299  EXPECT_EQ(1U, GetPrefs(&profile)->size());
300
301  {
302    // Reopen the BackgroundContents to the same URL, we should not register the
303    // URL again.
304    scoped_ptr<MockBackgroundContents> contents(new MockBackgroundContents(
305        &profile, "appid"));
306    contents->SendOpenedNotification(&service);
307    contents->Navigate(url);
308    EXPECT_EQ(1U, GetPrefs(&profile)->size());
309  }
310}
311
312// Ensures that BackgroundContentsService properly tracks the association
313// between a BackgroundContents and its parent extension, including
314// unregistering the BC when the extension is uninstalled.
315TEST_F(BackgroundContentsServiceTest, TestApplicationIDLinkage) {
316  TestingProfile profile;
317  BackgroundContentsService service(&profile, command_line_.get());
318  BackgroundContentsServiceFactory::GetInstance()->
319      RegisterUserPrefsOnBrowserContextForTest(&profile);
320
321  EXPECT_EQ(NULL,
322            service.GetAppBackgroundContents(base::ASCIIToUTF16("appid")));
323  MockBackgroundContents* contents = new MockBackgroundContents(&profile,
324                                                                "appid");
325  scoped_ptr<MockBackgroundContents> contents2(
326      new MockBackgroundContents(&profile, "appid2"));
327  contents->SendOpenedNotification(&service);
328  EXPECT_EQ(contents, service.GetAppBackgroundContents(contents->appid()));
329  contents2->SendOpenedNotification(&service);
330  EXPECT_EQ(contents2.get(), service.GetAppBackgroundContents(
331      contents2->appid()));
332  EXPECT_EQ(0U, GetPrefs(&profile)->size());
333
334  // Navigate the contents, then make sure the one associated with the extension
335  // is unregistered.
336  GURL url("http://a/");
337  GURL url2("http://b/");
338  contents->Navigate(url);
339  EXPECT_EQ(1U, GetPrefs(&profile)->size());
340  contents2->Navigate(url2);
341  EXPECT_EQ(2U, GetPrefs(&profile)->size());
342  service.ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16("appid"));
343  EXPECT_FALSE(service.IsTracked(contents));
344  EXPECT_EQ(NULL,
345            service.GetAppBackgroundContents(base::ASCIIToUTF16("appid")));
346  EXPECT_EQ(1U, GetPrefs(&profile)->size());
347  EXPECT_EQ(url2.spec(), GetPrefURLForApp(&profile, contents2->appid()));
348}
349
350#if defined(ENABLE_NOTIFICATIONS)
351TEST_F(BackgroundContentsServiceNotificationTest, TestShowBalloon) {
352  scoped_refptr<extensions::Extension> extension =
353      extension_test_util::LoadManifest("image_loading_tracker", "app.json");
354  ASSERT_TRUE(extension.get());
355  ASSERT_TRUE(extension->GetManifestData("icons"));
356
357  const Notification* notification = CreateCrashNotification(extension);
358  EXPECT_FALSE(notification->icon().IsEmpty());
359}
360
361// Verify if a test notification can show the default extension icon for
362// a crash notification for an extension without icon.
363TEST_F(BackgroundContentsServiceNotificationTest, TestShowBalloonNoIcon) {
364  // Extension manifest file with no 'icon' field.
365  scoped_refptr<extensions::Extension> extension =
366      extension_test_util::LoadManifest("app", "manifest.json");
367  ASSERT_TRUE(extension.get());
368  ASSERT_FALSE(extension->GetManifestData("icons"));
369
370  const Notification* notification = CreateCrashNotification(extension);
371  EXPECT_FALSE(notification->icon().IsEmpty());
372}
373
374TEST_F(BackgroundContentsServiceNotificationTest, TestShowTwoBalloons) {
375  TestingProfile profile;
376  scoped_refptr<extensions::Extension> extension =
377      extension_test_util::LoadManifest("app", "manifest.json");
378  ASSERT_TRUE(extension.get());
379  CreateCrashNotification(extension);
380  CreateCrashNotification(extension);
381
382  message_center::MessageCenter* message_center =
383      message_center::MessageCenter::Get();
384  message_center::NotificationList::Notifications notifications =
385      message_center->GetVisibleNotifications();
386  ASSERT_EQ(1u, notifications.size());
387}
388#endif
389