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/stringprintf.h"
10 #include "chrome/browser/extensions/api/tabs/tabs_api.h"
11 #include "chrome/browser/extensions/extension_apitest.h"
12 #include "chrome/browser/extensions/extension_function_test_utils.h"
13 #include "chrome/browser/profiles/profile_manager.h"
14 #include "chrome/browser/sync/open_tabs_ui_delegate.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 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 KeyedService
* ExtensionSessionsTest::BuildProfileSyncService(
125 content::BrowserContext
* profile
) {
127 ProfileSyncComponentsFactoryMock
* factory
=
128 new ProfileSyncComponentsFactoryMock();
130 factory
->SetLocalDeviceInfoProvider(
131 scoped_ptr
<sync_driver::LocalDeviceInfoProvider
>(
132 new sync_driver::LocalDeviceInfoProviderMock(
137 sync_pb::SyncEnums_DeviceType_TYPE_LINUX
,
140 return new ProfileSyncServiceMock(
141 scoped_ptr
<ProfileSyncComponentsFactory
>(factory
),
142 static_cast<Profile
*>(profile
));
145 void ExtensionSessionsTest::CreateTestProfileSyncService() {
146 ProfileManager
* profile_manager
= g_browser_process
->profile_manager();
148 PathService::Get(chrome::DIR_USER_DATA
, &path
);
149 path
= path
.AppendASCII("test_profile");
150 if (!base::PathExists(path
))
151 CHECK(base::CreateDirectory(path
));
153 Profile::CreateProfile(path
, NULL
, Profile::CREATE_MODE_SYNCHRONOUS
);
154 profile_manager
->RegisterTestingProfile(profile
, true, false);
155 ProfileSyncServiceMock
* service
= static_cast<ProfileSyncServiceMock
*>(
156 ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
157 profile
, &ExtensionSessionsTest::BuildProfileSyncService
));
158 browser_
= new Browser(Browser::CreateParams(
159 profile
, chrome::HOST_DESKTOP_TYPE_NATIVE
));
161 syncer::ModelTypeSet preferred_types
;
162 preferred_types
.Put(syncer::SESSIONS
);
163 GoogleServiceAuthError
no_error(GoogleServiceAuthError::NONE
);
164 ON_CALL(*service
, IsDataTypeControllerRunning(syncer::SESSIONS
))
165 .WillByDefault(testing::Return(true));
166 ON_CALL(*service
, GetRegisteredDataTypes())
167 .WillByDefault(testing::Return(syncer::UserTypes()));
168 ON_CALL(*service
, GetPreferredDataTypes()).WillByDefault(
169 testing::Return(preferred_types
));
170 EXPECT_CALL(*service
, GetAuthError()).WillRepeatedly(
171 testing::ReturnRef(no_error
));
172 ON_CALL(*service
, GetActiveDataTypes()).WillByDefault(
173 testing::Return(preferred_types
));
175 EXPECT_CALL(*service
, AddObserver(testing::_
)).Times(testing::AnyNumber());
176 EXPECT_CALL(*service
, RemoveObserver(testing::_
)).Times(testing::AnyNumber());
178 service
->Initialize();
181 void ExtensionSessionsTest::CreateTestExtension() {
182 scoped_ptr
<base::DictionaryValue
> test_extension_value(
183 api_test_utils::ParseDictionary(
184 "{\"name\": \"Test\", \"version\": \"1.0\", "
185 "\"permissions\": [\"sessions\", \"tabs\"]}"));
186 extension_
= api_test_utils::CreateExtension(test_extension_value
.get());
189 void ExtensionSessionsTest::CreateSessionModels() {
190 syncer::SyncDataList initial_data
;
191 for (size_t index
= 0; index
< arraysize(kSessionTags
); ++index
) {
192 // Fill an instance of session specifics with a foreign session's data.
193 sync_pb::SessionSpecifics meta
;
194 BuildSessionSpecifics(kSessionTags
[index
], &meta
);
195 std::vector
<SessionID::id_type
> tab_list(kTabIDs
,
196 kTabIDs
+ arraysize(kTabIDs
));
197 BuildWindowSpecifics(index
, tab_list
, &meta
);
198 std::vector
<sync_pb::SessionSpecifics
> tabs(tab_list
.size());
199 for (size_t i
= 0; i
< tab_list
.size(); ++i
) {
200 BuildTabSpecifics(kSessionTags
[index
], tab_list
[i
], &tabs
[i
]);
203 sync_pb::EntitySpecifics entity
;
204 entity
.mutable_session()->CopyFrom(meta
);
205 initial_data
.push_back(syncer::SyncData::CreateRemoteData(
209 syncer::AttachmentIdList(),
210 syncer::AttachmentServiceProxyForTest::Create()));
211 for (size_t i
= 0; i
< tabs
.size(); i
++) {
212 sync_pb::EntitySpecifics entity
;
213 entity
.mutable_session()->CopyFrom(tabs
[i
]);
214 initial_data
.push_back(syncer::SyncData::CreateRemoteData(
218 syncer::AttachmentIdList(),
219 syncer::AttachmentServiceProxyForTest::Create()));
223 ProfileSyncServiceFactory::GetForProfile(browser_
->profile())->
224 GetSessionsSyncableService()->
225 MergeDataAndStartSyncing(syncer::SESSIONS
, initial_data
,
226 scoped_ptr
<syncer::SyncChangeProcessor
>(
227 new syncer::FakeSyncChangeProcessor()),
228 scoped_ptr
<syncer::SyncErrorFactory
>(
229 new syncer::SyncErrorFactoryMock()));
232 testing::AssertionResult
CheckSessionModels(const base::ListValue
& devices
,
233 size_t num_sessions
) {
234 EXPECT_EQ(5u, devices
.GetSize());
235 const base::DictionaryValue
* device
= NULL
;
236 const base::ListValue
* sessions
= NULL
;
237 for (size_t i
= 0; i
< devices
.GetSize(); ++i
) {
238 EXPECT_TRUE(devices
.GetDictionary(i
, &device
));
239 EXPECT_EQ(kSessionTags
[i
], api_test_utils::GetString(device
, "info"));
240 EXPECT_EQ(kSessionTags
[i
], api_test_utils::GetString(device
, "deviceName"));
241 EXPECT_TRUE(device
->GetList("sessions", &sessions
));
242 EXPECT_EQ(num_sessions
, sessions
->GetSize());
243 // Because this test is hurried, really there are only ever 0 or 1
244 // sessions, and if 1, that will be a Window. Grab it.
245 if (num_sessions
== 0)
247 const base::DictionaryValue
* session
= NULL
;
248 EXPECT_TRUE(sessions
->GetDictionary(0, &session
));
249 const base::DictionaryValue
* window
= NULL
;
250 EXPECT_TRUE(session
->GetDictionary("window", &window
));
251 // Only the tabs are interesting.
252 const base::ListValue
* tabs
= NULL
;
253 EXPECT_TRUE(window
->GetList("tabs", &tabs
));
254 EXPECT_EQ(arraysize(kTabIDs
), tabs
->GetSize());
255 for (size_t j
= 0; j
< tabs
->GetSize(); ++j
) {
256 const base::DictionaryValue
* tab
= NULL
;
257 EXPECT_TRUE(tabs
->GetDictionary(j
, &tab
));
258 EXPECT_FALSE(tab
->HasKey("id")); // sessions API does not give tab IDs
259 EXPECT_EQ(static_cast<int>(j
), api_test_utils::GetInteger(tab
, "index"));
260 EXPECT_EQ(0, api_test_utils::GetInteger(tab
, "windowId"));
261 // Test setup code always sets tab 0 to selected (which means active in
262 // extension terminology).
263 EXPECT_EQ(j
== 0, api_test_utils::GetBoolean(tab
, "active"));
264 // While selected/highlighted are different to active, and should always
266 EXPECT_FALSE(api_test_utils::GetBoolean(tab
, "selected"));
267 EXPECT_FALSE(api_test_utils::GetBoolean(tab
, "highlighted"));
268 EXPECT_FALSE(api_test_utils::GetBoolean(tab
, "incognito"));
269 EXPECT_TRUE(api_test_utils::GetBoolean(tab
, "pinned"));
270 EXPECT_EQ("http://foo/1", api_test_utils::GetString(tab
, "url"));
271 EXPECT_EQ("MyTitle", api_test_utils::GetString(tab
, "title"));
272 EXPECT_EQ("http://foo/favicon.ico",
273 api_test_utils::GetString(tab
, "favIconUrl"));
274 EXPECT_EQ(base::StringPrintf("%s.%d", kSessionTags
[i
], kTabIDs
[j
]),
275 api_test_utils::GetString(tab
, "sessionId"));
278 return testing::AssertionSuccess();
281 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetDevices
) {
282 CreateSessionModels();
283 scoped_ptr
<base::ListValue
> result(utils::ToList(
284 utils::RunFunctionAndReturnSingleResult(
285 CreateFunction
<SessionsGetDevicesFunction
>(true).get(),
286 "[{\"maxResults\": 0}]",
289 EXPECT_TRUE(CheckSessionModels(*result
, 0u));
292 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetDevicesMaxResults
) {
293 CreateSessionModels();
294 scoped_ptr
<base::ListValue
> result(utils::ToList(
295 utils::RunFunctionAndReturnSingleResult(
296 CreateFunction
<SessionsGetDevicesFunction
>(true).get(),
300 EXPECT_TRUE(CheckSessionModels(*result
, 1u));
303 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetDevicesListEmpty
) {
304 scoped_ptr
<base::ListValue
> result(utils::ToList(
305 utils::RunFunctionAndReturnSingleResult(
306 CreateFunction
<SessionsGetDevicesFunction
>(true).get(),
311 base::ListValue
* devices
= result
.get();
312 EXPECT_EQ(0u, devices
->GetSize());
315 // Flaky timeout: http://crbug.com/278372
316 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
,
317 DISABLED_RestoreForeignSessionWindow
) {
318 CreateSessionModels();
320 scoped_ptr
<base::DictionaryValue
> restored_window_session(utils::ToDictionary(
321 utils::RunFunctionAndReturnSingleResult(
322 CreateFunction
<SessionsRestoreFunction
>(true).get(),
325 utils::INCLUDE_INCOGNITO
)));
326 ASSERT_TRUE(restored_window_session
);
328 scoped_ptr
<base::ListValue
> result(utils::ToList(
329 utils::RunFunctionAndReturnSingleResult(
330 CreateFunction
<WindowsGetAllFunction
>(true).get(),
335 base::ListValue
* windows
= result
.get();
336 EXPECT_EQ(2u, windows
->GetSize());
337 base::DictionaryValue
* restored_window
= NULL
;
338 EXPECT_TRUE(restored_window_session
->GetDictionary("window",
340 base::DictionaryValue
* window
= NULL
;
341 int restored_id
= api_test_utils::GetInteger(restored_window
, "id");
342 for (size_t i
= 0; i
< windows
->GetSize(); ++i
) {
343 EXPECT_TRUE(windows
->GetDictionary(i
, &window
));
344 if (api_test_utils::GetInteger(window
, "id") == restored_id
)
347 EXPECT_EQ(restored_id
, api_test_utils::GetInteger(window
, "id"));
350 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, RestoreForeignSessionInvalidId
) {
351 CreateSessionModels();
353 EXPECT_TRUE(MatchPattern(utils::RunFunctionAndReturnError(
354 CreateFunction
<SessionsRestoreFunction
>(true).get(),
356 browser_
), "Invalid session id: \"tag3.0\"."));
359 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, RestoreInIncognito
) {
360 CreateSessionModels();
362 EXPECT_TRUE(MatchPattern(utils::RunFunctionAndReturnError(
363 CreateFunction
<SessionsRestoreFunction
>(true).get(),
365 CreateIncognitoBrowser()),
366 "Can not restore sessions in incognito mode."));
369 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest
, GetRecentlyClosedIncognito
) {
370 scoped_ptr
<base::ListValue
> result(utils::ToList(
371 utils::RunFunctionAndReturnSingleResult(
372 CreateFunction
<SessionsGetRecentlyClosedFunction
>(true).get(),
374 CreateIncognitoBrowser())));
376 base::ListValue
* sessions
= result
.get();
377 EXPECT_EQ(0u, sessions
->GetSize());
380 // Flaky on ChromeOS, times out on OSX Debug http://crbug.com/251199
381 #if defined(OS_CHROMEOS) || (defined(OS_MACOSX) && !defined(NDEBUG))
382 #define MAYBE_SessionsApis DISABLED_SessionsApis
384 #define MAYBE_SessionsApis SessionsApis
386 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, MAYBE_SessionsApis
) {
387 #if defined(OS_WIN) && defined(USE_ASH)
388 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
389 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
390 switches::kAshBrowserTests
))
394 ASSERT_TRUE(RunExtensionSubtest("sessions",
395 "sessions.html")) << message_
;
400 } // namespace extensions