background_contents_service_unittest.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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) && !defined(TOOLKIT_GTK)
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    if (message_center->HasNotification(target_id_))
135      return;
136
137    message_center->AddObserver(this);
138    run_loop_.Run();
139    message_center->RemoveObserver(this);
140  }
141
142 private:
143  // message_center::MessageCenterObserver overrides:
144  virtual void OnNotificationAdded(
145      const std::string& notification_id) OVERRIDE {
146    if (notification_id == target_id_)
147      run_loop_.Quit();
148  }
149
150  std::string target_id_;
151  base::RunLoop run_loop_;
152
153  DISALLOW_COPY_AND_ASSIGN(NotificationWaiter);
154};
155
156class BackgroundContentsServiceNotificationTest
157    : public BrowserWithTestWindowTest {
158 public:
159  BackgroundContentsServiceNotificationTest() {}
160  virtual ~BackgroundContentsServiceNotificationTest() {}
161
162  // Overridden from testing::Test
163  virtual void SetUp() {
164    BrowserWithTestWindowTest::SetUp();
165    if (!NotificationUIManager::DelegatesToMessageCenter())
166      return;
167
168    // In ChromeOS environment, BrowserWithTestWindowTest initializes
169    // MessageCenter.
170#if !defined(OS_CHROMEOS)
171    message_center::MessageCenter::Initialize();
172#endif
173    profile_manager_.reset(new TestingProfileManager(
174        TestingBrowserProcess::GetGlobal()));
175    ASSERT_TRUE(profile_manager_->SetUp());
176    MessageCenterNotificationManager* manager =
177        static_cast<MessageCenterNotificationManager*>(
178            g_browser_process->notification_ui_manager());
179    manager->SetMessageCenterTrayDelegateForTest(
180        new message_center::FakeMessageCenterTrayDelegate(
181            message_center::MessageCenter::Get(), base::Closure()));
182  }
183
184  virtual void TearDown() {
185    g_browser_process->notification_ui_manager()->CancelAll();
186    profile_manager_.reset();
187#if !defined(OS_CHROMEOS)
188    message_center::MessageCenter::Shutdown();
189#endif
190    BrowserWithTestWindowTest::TearDown();
191  }
192
193 protected:
194  // Creates crash notification for the specified extension and returns
195  // the created one.
196  const Notification* CreateCrashNotification(
197      scoped_refptr<extensions::Extension> extension) {
198    std::string notification_id =
199        BackgroundContentsService::GetNotificationIdForExtensionForTesting(
200            extension->id());
201    NotificationWaiter waiter(notification_id);
202    BackgroundContentsService::ShowBalloonForTesting(
203        extension.get(), profile());
204    waiter.WaitForNotificationAdded();
205
206    return g_browser_process->notification_ui_manager()->FindById(
207        notification_id);
208  }
209
210 private:
211  scoped_ptr<TestingProfileManager> profile_manager_;
212
213  DISALLOW_COPY_AND_ASSIGN(BackgroundContentsServiceNotificationTest);
214};
215#endif  // ENABLE_NOTIFICATIONS
216
217TEST_F(BackgroundContentsServiceTest, Create) {
218  // Check for creation and leaks.
219  TestingProfile profile;
220  BackgroundContentsService service(&profile, command_line_.get());
221}
222
223TEST_F(BackgroundContentsServiceTest, BackgroundContentsCreateDestroy) {
224  TestingProfile profile;
225  BackgroundContentsService service(&profile, command_line_.get());
226  MockBackgroundContents* contents = new MockBackgroundContents(&profile);
227  EXPECT_FALSE(service.IsTracked(contents));
228  contents->SendOpenedNotification(&service);
229  EXPECT_TRUE(service.IsTracked(contents));
230  delete contents;
231  EXPECT_FALSE(service.IsTracked(contents));
232}
233
234TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAdded) {
235  TestingProfile profile;
236  BackgroundContentsService service(&profile, command_line_.get());
237  BackgroundContentsServiceFactory::GetInstance()->
238      RegisterUserPrefsOnBrowserContextForTest(&profile);
239  GURL orig_url;
240  GURL url("http://a/");
241  GURL url2("http://a/");
242  {
243    scoped_ptr<MockBackgroundContents> contents(
244        new MockBackgroundContents(&profile));
245    EXPECT_EQ(0U, GetPrefs(&profile)->size());
246    contents->SendOpenedNotification(&service);
247
248    contents->Navigate(url);
249    EXPECT_EQ(1U, GetPrefs(&profile)->size());
250    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
251
252    // Navigate the contents to a new url, should not change url.
253    contents->Navigate(url2);
254    EXPECT_EQ(1U, GetPrefs(&profile)->size());
255    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
256  }
257  // Contents are deleted, url should persist.
258  EXPECT_EQ(1U, GetPrefs(&profile)->size());
259}
260
261TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAddedAndClosed) {
262  TestingProfile profile;
263  BackgroundContentsService service(&profile, command_line_.get());
264  BackgroundContentsServiceFactory::GetInstance()->
265      RegisterUserPrefsOnBrowserContextForTest(&profile);
266
267  GURL url("http://a/");
268  MockBackgroundContents* contents = new MockBackgroundContents(&profile);
269  EXPECT_EQ(0U, GetPrefs(&profile)->size());
270  contents->SendOpenedNotification(&service);
271  contents->Navigate(url);
272  EXPECT_EQ(1U, GetPrefs(&profile)->size());
273  EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
274
275  // Fake a window closed by script.
276  contents->MockClose(&profile);
277  EXPECT_EQ(0U, GetPrefs(&profile)->size());
278}
279
280// Test what happens if a BackgroundContents shuts down (say, due to a renderer
281// crash) then is restarted. Should not persist URL twice.
282TEST_F(BackgroundContentsServiceTest, RestartBackgroundContents) {
283  TestingProfile profile;
284  BackgroundContentsService service(&profile, command_line_.get());
285  BackgroundContentsServiceFactory::GetInstance()->
286      RegisterUserPrefsOnBrowserContextForTest(&profile);
287
288  GURL url("http://a/");
289  {
290    scoped_ptr<MockBackgroundContents> contents(new MockBackgroundContents(
291        &profile, "appid"));
292    contents->SendOpenedNotification(&service);
293    contents->Navigate(url);
294    EXPECT_EQ(1U, GetPrefs(&profile)->size());
295    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
296  }
297  // Contents deleted, url should be persisted.
298  EXPECT_EQ(1U, GetPrefs(&profile)->size());
299
300  {
301    // Reopen the BackgroundContents to the same URL, we should not register the
302    // URL again.
303    scoped_ptr<MockBackgroundContents> contents(new MockBackgroundContents(
304        &profile, "appid"));
305    contents->SendOpenedNotification(&service);
306    contents->Navigate(url);
307    EXPECT_EQ(1U, GetPrefs(&profile)->size());
308  }
309}
310
311// Ensures that BackgroundContentsService properly tracks the association
312// between a BackgroundContents and its parent extension, including
313// unregistering the BC when the extension is uninstalled.
314TEST_F(BackgroundContentsServiceTest, TestApplicationIDLinkage) {
315  TestingProfile profile;
316  BackgroundContentsService service(&profile, command_line_.get());
317  BackgroundContentsServiceFactory::GetInstance()->
318      RegisterUserPrefsOnBrowserContextForTest(&profile);
319
320  EXPECT_EQ(NULL,
321            service.GetAppBackgroundContents(base::ASCIIToUTF16("appid")));
322  MockBackgroundContents* contents = new MockBackgroundContents(&profile,
323                                                                "appid");
324  scoped_ptr<MockBackgroundContents> contents2(
325      new MockBackgroundContents(&profile, "appid2"));
326  contents->SendOpenedNotification(&service);
327  EXPECT_EQ(contents, service.GetAppBackgroundContents(contents->appid()));
328  contents2->SendOpenedNotification(&service);
329  EXPECT_EQ(contents2.get(), service.GetAppBackgroundContents(
330      contents2->appid()));
331  EXPECT_EQ(0U, GetPrefs(&profile)->size());
332
333  // Navigate the contents, then make sure the one associated with the extension
334  // is unregistered.
335  GURL url("http://a/");
336  GURL url2("http://b/");
337  contents->Navigate(url);
338  EXPECT_EQ(1U, GetPrefs(&profile)->size());
339  contents2->Navigate(url2);
340  EXPECT_EQ(2U, GetPrefs(&profile)->size());
341  service.ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16("appid"));
342  EXPECT_FALSE(service.IsTracked(contents));
343  EXPECT_EQ(NULL,
344            service.GetAppBackgroundContents(base::ASCIIToUTF16("appid")));
345  EXPECT_EQ(1U, GetPrefs(&profile)->size());
346  EXPECT_EQ(url2.spec(), GetPrefURLForApp(&profile, contents2->appid()));
347}
348
349#if defined(ENABLE_NOTIFICATIONS) && !defined(TOOLKIT_GTK)
350TEST_F(BackgroundContentsServiceNotificationTest, TestShowBalloon) {
351  if (!NotificationUIManager::DelegatesToMessageCenter())
352    return;
353
354  scoped_refptr<extensions::Extension> extension =
355      extension_test_util::LoadManifest("image_loading_tracker", "app.json");
356  ASSERT_TRUE(extension.get());
357  ASSERT_TRUE(extension->GetManifestData("icons"));
358
359  const Notification* notification = CreateCrashNotification(extension);
360  EXPECT_FALSE(notification->icon().IsEmpty());
361}
362
363// Verify if a test notification can show the default extension icon for
364// a crash notification for an extension without icon.
365TEST_F(BackgroundContentsServiceNotificationTest, TestShowBalloonNoIcon) {
366  if (!NotificationUIManager::DelegatesToMessageCenter())
367    return;
368
369  // Extension manifest file with no 'icon' field.
370  scoped_refptr<extensions::Extension> extension =
371      extension_test_util::LoadManifest("app", "manifest.json");
372  ASSERT_TRUE(extension.get());
373  ASSERT_FALSE(extension->GetManifestData("icons"));
374
375  const Notification* notification = CreateCrashNotification(extension);
376  EXPECT_FALSE(notification->icon().IsEmpty());
377}
378#endif
379