1 // Copyright 2013 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.
5 #include "chrome/browser/extensions/api/sessions/sessions_api.h"
7 #include "base/command_line.h"
8 #include "base/path_service.h"
9 #include "base/strings/pattern.h"
10 #include "base/strings/stringprintf.h"
11 #include "chrome/browser/extensions/api/tabs/tabs_api.h"
12 #include "chrome/browser/extensions/extension_apitest.h"
13 #include "chrome/browser/extensions/extension_function_test_utils.h"
14 #include "chrome/browser/profiles/profile_manager.h"
15 #include "chrome/browser/sync/profile_sync_components_factory_mock.h"
16 #include "chrome/browser/sync/profile_sync_service.h"
17 #include "chrome/browser/sync/profile_sync_service_factory.h"
18 #include "chrome/browser/sync/profile_sync_service_mock.h"
19 #include "chrome/common/chrome_paths.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/test/base/in_process_browser_test.h"
22 #include "chrome/test/base/test_switches.h"
23 #include "chrome/test/base/testing_browser_process.h"
24 #include "components/sync_driver/local_device_info_provider_mock.h"
25 #include "extensions/browser/api_test_utils.h"
26 #include "sync/api/attachments/attachment_id.h"
27 #include "sync/api/fake_sync_change_processor.h"
28 #include "sync/api/sync_error_factory_mock.h"
29 #include "sync/internal_api/public/attachments/attachment_service_proxy_for_test.h"
31 #if defined(OS_CHROMEOS)
32 #include "chromeos/chromeos_switches.h"
35 namespace utils
= extension_function_test_utils
;
37 namespace extensions
{
41 // Fake session tabs (used to construct arbitrary device info) and tab IDs
42 // (used to construct arbitrary tab info) to use in all tests.
43 const char* const kSessionTags
[] = {"tag0", "tag1", "tag2", "tag3", "tag4"};
44 const SessionID::id_type kTabIDs
[] = {5, 10, 13, 17};
46 void BuildSessionSpecifics(const std::string
& tag
,
47 sync_pb::SessionSpecifics
* meta
) {
48 meta
->set_session_tag(tag
);
49 sync_pb::SessionHeader
* header
= meta
->mutable_header();
50 header
->set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX
);
51 header
->set_client_name(tag
);
54 void BuildWindowSpecifics(int window_id
,
55 const std::vector
<int>& tab_list
,
56 sync_pb::SessionSpecifics
* meta
) {
57 sync_pb::SessionHeader
* header
= meta
->mutable_header();
58 sync_pb::SessionWindow
* window
= header
->add_window();
59 window
->set_window_id(window_id
);
60 window
->set_selected_tab_index(0);
61 window
->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED
);
62 for (std::vector
<int>::const_iterator iter
= tab_list
.begin();
63 iter
!= tab_list
.end(); ++iter
) {
64 window
->add_tab(*iter
);
68 void BuildTabSpecifics(const std::string
& tag
,
70 sync_pb::SessionSpecifics
* tab_base
) {
71 tab_base
->set_session_tag(tag
);
72 tab_base
->set_tab_node_id(0);
73 sync_pb::SessionTab
* tab
= tab_base
->mutable_tab();
74 tab
->set_tab_id(tab_id
);
75 tab
->set_tab_visual_index(1);
76 tab
->set_current_navigation_index(0);
77 tab
->set_pinned(true);
78 tab
->set_extension_app_id("app_id");
79 sync_pb::TabNavigation
* navigation
= tab
->add_navigation();
80 navigation
->set_virtual_url("http://foo/1");
81 navigation
->set_favicon_url("http://foo/favicon.ico");
82 navigation
->set_referrer("MyReferrer");
83 navigation
->set_title("MyTitle");
84 navigation
->set_page_transition(sync_pb::SyncEnums_PageTransition_TYPED
);
87 class ExtensionSessionsTest
: public InProcessBrowserTest
{
89 void SetUpCommandLine(base::CommandLine
* command_line
) override
;
90 void SetUpOnMainThread() override
;
93 static scoped_ptr
<KeyedService
> BuildProfileSyncService(
94 content::BrowserContext
* profile
);
96 void CreateTestProfileSyncService();
97 void CreateTestExtension();
98 void CreateSessionModels();
101 scoped_refptr
<T
> CreateFunction(bool has_callback
) {
102 scoped_refptr
<T
> fn(new T());
103 fn
->set_extension(extension_
.get());
104 fn
->set_has_callback(has_callback
);
109 scoped_refptr
<Extension
> extension_
;
112 void ExtensionSessionsTest::SetUpCommandLine(base::CommandLine
* command_line
) {
113 #if defined(OS_CHROMEOS)
114 command_line
->AppendSwitch(
115 chromeos::switches::kIgnoreUserProfileMappingForTests
);
119 void ExtensionSessionsTest::SetUpOnMainThread() {
120 CreateTestProfileSyncService();
121 CreateTestExtension();
124 scoped_ptr
<KeyedService
> ExtensionSessionsTest::BuildProfileSyncService(
125 content::BrowserContext
* context
) {
126 scoped_ptr
<ProfileSyncComponentsFactoryMock
> factory(
127 new ProfileSyncComponentsFactoryMock());
129 factory
->SetLocalDeviceInfoProvider(
130 scoped_ptr
<sync_driver::LocalDeviceInfoProvider
>(
131 new sync_driver::LocalDeviceInfoProviderMock(
136 sync_pb::SyncEnums_DeviceType_TYPE_LINUX
,
139 return make_scoped_ptr(new ProfileSyncServiceMock(
140 factory
.Pass(), static_cast<Profile
*>(context
)));
143 void ExtensionSessionsTest::CreateTestProfileSyncService() {
144 ProfileManager
* profile_manager
= g_browser_process
->profile_manager();
146 PathService::Get(chrome::DIR_USER_DATA
, &path
);
147 path
= path
.AppendASCII("test_profile");
148 if (!base::PathExists(path
))
149 CHECK(base::CreateDirectory(path
));
151 Profile::CreateProfile(path
, NULL
, Profile::CREATE_MODE_SYNCHRONOUS
);
152 profile_manager
->RegisterTestingProfile(profile
, true, false);
153 ProfileSyncServiceMock
* service
= static_cast<ProfileSyncServiceMock
*>(
154 ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
155 profile
, &ExtensionSessionsTest::BuildProfileSyncService
));
156 browser_
= new Browser(Browser::CreateParams(
157 profile
, chrome::HOST_DESKTOP_TYPE_NATIVE
));
159 syncer::ModelTypeSet preferred_types
;
160 preferred_types
.Put(syncer::SESSIONS
);
161 GoogleServiceAuthError
no_error(GoogleServiceAuthError::NONE
);
162 ON_CALL(*service
, IsDataTypeControllerRunning(syncer::SESSIONS
))
163 .WillByDefault(testing::Return(true));
164 ON_CALL(*service
, GetRegisteredDataTypes())
165 .WillByDefault(testing::Return(syncer::UserTypes()));
166 ON_CALL(*service
, GetPreferredDataTypes()).WillByDefault(
167 testing::Return(preferred_types
));
168 EXPECT_CALL(*service
, GetAuthError()).WillRepeatedly(
169 testing::ReturnRef(no_error
));
170 ON_CALL(*service
, GetActiveDataTypes()).WillByDefault(
171 testing::Return(preferred_types
));
173 EXPECT_CALL(*service
, AddObserver(testing::_
)).Times(testing::AnyNumber());
174 EXPECT_CALL(*service
, RemoveObserver(testing::_
)).Times(testing::AnyNumber());
176 service
->Initialize();
179 void ExtensionSessionsTest::CreateTestExtension() {
180 scoped_ptr
<base::DictionaryValue
> test_extension_value(
181 api_test_utils::ParseDictionary(
182 "{\"name\": \"Test\", \"version\": \"1.0\", "
183 "\"permissions\": [\"sessions\", \"tabs\"]}"));
184 extension_
= api_test_utils::CreateExtension(test_extension_value
.get());
187 void ExtensionSessionsTest::CreateSessionModels() {
188 syncer::SyncDataList initial_data
;
189 for (size_t index
= 0; index
< arraysize(kSessionTags
); ++index
) {
190 // Fill an instance of session specifics with a foreign session's data.
191 sync_pb::SessionSpecifics meta
;
192 BuildSessionSpecifics(kSessionTags
[index
], &meta
);
193 std::vector
<SessionID::id_type
> tab_list(kTabIDs
,
194 kTabIDs
+ arraysize(kTabIDs
));
195 BuildWindowSpecifics(index
, tab_list
, &meta
);
196 std::vector
<sync_pb::SessionSpecifics
> tabs(tab_list
.size());
197 for (size_t i
= 0; i
< tab_list
.size(); ++i
) {
198 BuildTabSpecifics(kSessionTags
[index
], tab_list
[i
], &tabs
[i
]);
201 sync_pb::EntitySpecifics entity
;
202 entity
.mutable_session()->CopyFrom(meta
);
203 initial_data
.push_back(syncer::SyncData::CreateRemoteData(
207 syncer::AttachmentIdList(),
208 syncer::AttachmentServiceProxyForTest::Create()));
209 for (size_t i
= 0; i
< tabs
.size(); i
++) {
210 sync_pb::EntitySpecifics entity
;
211 entity
.mutable_session()->CopyFrom(tabs
[i
]);
212 initial_data
.push_back(syncer::SyncData::CreateRemoteData(
216 syncer::AttachmentIdList(),
217 syncer::AttachmentServiceProxyForTest::Create()));
221 ProfileSyncServiceFactory::GetForProfile(browser_
->profile())->
222 GetSessionsSyncableService()->
223 MergeDataAndStartSyncing(syncer::SESSIONS
, initial_data
,
224 scoped_ptr
<syncer::SyncChangeProcessor
>(
225 new syncer::FakeSyncChangeProcessor()),
226 scoped_ptr
<syncer::SyncErrorFactory
>(
227 new syncer::SyncErrorFactoryMock()));
230 testing::AssertionResult
CheckSessionModels(const base::ListValue
& devices
,
231 size_t num_sessions
) {
232 EXPECT_EQ(5u, devices
.GetSize());
233 const base::DictionaryValue
* device
= NULL
;
234 const base::ListValue
* sessions
= NULL
;
235 for (size_t i
= 0; i
< devices
.GetSize(); ++i
) {
236 EXPECT_TRUE(devices
.GetDictionary(i
, &device
));
237 EXPECT_EQ(kSessionTags
[i
], api_test_utils::GetString(device
, "info"));
238 EXPECT_EQ(kSessionTags
[i
], api_test_utils::GetString(device
, "deviceName"));
239 EXPECT_TRUE(device
->GetList("sessions", &sessions
));
240 EXPECT_EQ(num_sessions
, sessions
->GetSize());
241 // Because this test is hurried, really there are only ever 0 or 1
242 // sessions, and if 1, that will be a Window. Grab it.
243 if (num_sessions
== 0)
245 const base::DictionaryValue
* session
= NULL
;
246 EXPECT_TRUE(sessions
->GetDictionary(0, &session
));
247 const base::DictionaryValue
* window
= NULL
;
248 EXPECT_TRUE(session
->GetDictionary("window", &window
));
249 // Only the tabs are interesting.
250 const base::ListValue
* tabs
= NULL
;
251 EXPECT_TRUE(window
->GetList("tabs", &tabs
));
252 EXPECT_EQ(arraysize(kTabIDs
), tabs
->GetSize());
253 for (size_t j
= 0; j
< tabs
->GetSize(); ++j
) {
254 const base::DictionaryValue
* tab
= NULL
;
255 EXPECT_TRUE(tabs
->GetDictionary(j
, &tab
));
256 EXPECT_FALSE(tab
->HasKey("id")); // sessions API does not give tab IDs
257 EXPECT_EQ(static_cast<int>(j
), api_test_utils::GetInteger(tab
, "index"));
258 EXPECT_EQ(0, api_test_utils::GetInteger(tab
, "windowId"));
259 // Test setup code always sets tab 0 to selected (which means active in
260 // extension terminology).
261 EXPECT_EQ(j
== 0, api_test_utils::GetBoolean(tab
, "active"));
262 // While selected/highlighted are different to active, and should always
264 EXPECT_FALSE(api_test_utils::GetBoolean(tab
, "selected"));
265 EXPECT_FALSE(api_test_utils::GetBoolean(tab
, "highlighted"));
266 EXPECT_FALSE(api_test_utils::GetBoolean(tab
, "incognito"));
267 EXPECT_TRUE(api_test_utils::GetBoolean(tab
, "pinned"));
268 EXPECT_EQ("http://foo/1", api_test_utils::GetString(tab
, "url"));
269 EXPECT_EQ("MyTitle", api_test_utils::GetString(tab
, "title"));
270 EXPECT_EQ("http://foo/favicon.ico",
271 api_test_utils::GetString(tab
, "favIconUrl"));
272 EXPECT_EQ(base::StringPrintf("%s.%d", kSessionTags
[i
], kTabIDs
[j
]),
273 api_test_utils::GetString(tab
, "sessionId"));
276 return testing::AssertionSuccess();
279 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetDevices
) {
280 CreateSessionModels();
281 scoped_ptr
<base::ListValue
> result(utils::ToList(
282 utils::RunFunctionAndReturnSingleResult(
283 CreateFunction
<SessionsGetDevicesFunction
>(true).get(),
284 "[{\"maxResults\": 0}]",
287 EXPECT_TRUE(CheckSessionModels(*result
, 0u));
290 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetDevicesMaxResults
) {
291 CreateSessionModels();
292 scoped_ptr
<base::ListValue
> result(utils::ToList(
293 utils::RunFunctionAndReturnSingleResult(
294 CreateFunction
<SessionsGetDevicesFunction
>(true).get(),
298 EXPECT_TRUE(CheckSessionModels(*result
, 1u));
301 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetDevicesListEmpty
) {
302 scoped_ptr
<base::ListValue
> result(utils::ToList(
303 utils::RunFunctionAndReturnSingleResult(
304 CreateFunction
<SessionsGetDevicesFunction
>(true).get(),
309 base::ListValue
* devices
= result
.get();
310 EXPECT_EQ(0u, devices
->GetSize());
313 // Flaky timeout: http://crbug.com/278372
314 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
,
315 DISABLED_RestoreForeignSessionWindow
) {
316 CreateSessionModels();
318 scoped_ptr
<base::DictionaryValue
> restored_window_session(utils::ToDictionary(
319 utils::RunFunctionAndReturnSingleResult(
320 CreateFunction
<SessionsRestoreFunction
>(true).get(),
323 utils::INCLUDE_INCOGNITO
)));
324 ASSERT_TRUE(restored_window_session
);
326 scoped_ptr
<base::ListValue
> result(utils::ToList(
327 utils::RunFunctionAndReturnSingleResult(
328 CreateFunction
<WindowsGetAllFunction
>(true).get(),
333 base::ListValue
* windows
= result
.get();
334 EXPECT_EQ(2u, windows
->GetSize());
335 base::DictionaryValue
* restored_window
= NULL
;
336 EXPECT_TRUE(restored_window_session
->GetDictionary("window",
338 base::DictionaryValue
* window
= NULL
;
339 int restored_id
= api_test_utils::GetInteger(restored_window
, "id");
340 for (size_t i
= 0; i
< windows
->GetSize(); ++i
) {
341 EXPECT_TRUE(windows
->GetDictionary(i
, &window
));
342 if (api_test_utils::GetInteger(window
, "id") == restored_id
)
345 EXPECT_EQ(restored_id
, api_test_utils::GetInteger(window
, "id"));
348 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, RestoreForeignSessionInvalidId
) {
349 CreateSessionModels();
351 EXPECT_TRUE(base::MatchPattern(utils::RunFunctionAndReturnError(
352 CreateFunction
<SessionsRestoreFunction
>(true).get(),
354 browser_
), "Invalid session id: \"tag3.0\"."));
357 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, RestoreInIncognito
) {
358 CreateSessionModels();
360 EXPECT_TRUE(base::MatchPattern(utils::RunFunctionAndReturnError(
361 CreateFunction
<SessionsRestoreFunction
>(true).get(),
363 CreateIncognitoBrowser()),
364 "Can not restore sessions in incognito mode."));
367 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetRecentlyClosedIncognito
) {
368 scoped_ptr
<base::ListValue
> result(utils::ToList(
369 utils::RunFunctionAndReturnSingleResult(
370 CreateFunction
<SessionsGetRecentlyClosedFunction
>(true).get(),
372 CreateIncognitoBrowser())));
374 base::ListValue
* sessions
= result
.get();
375 EXPECT_EQ(0u, sessions
->GetSize());
378 // http://crbug.com/251199
379 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, DISABLED_SessionsApis
) {
380 #if defined(OS_WIN) && defined(USE_ASH)
381 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
382 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
383 switches::kAshBrowserTests
))
387 ASSERT_TRUE(RunExtensionSubtest("sessions",
388 "sessions.html")) << message_
;
393 } // namespace extensions