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. 4 5 #include "chrome/browser/extensions/api/sessions/sessions_api.h" 6 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_service.h" 16 #include "chrome/browser/sync/profile_sync_service_factory.h" 17 #include "chrome/browser/sync/profile_sync_service_mock.h" 18 #include "chrome/common/chrome_paths.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "chrome/test/base/in_process_browser_test.h" 21 #include "chrome/test/base/test_switches.h" 22 #include "chrome/test/base/testing_browser_process.h" 23 #include "sync/api/attachments/attachment_id.h" 24 #include "sync/api/attachments/attachment_service_proxy_for_test.h" 25 #include "sync/api/fake_sync_change_processor.h" 26 #include "sync/api/sync_error_factory_mock.h" 27 28 #if defined(OS_CHROMEOS) 29 #include "chromeos/chromeos_switches.h" 30 #endif 31 32 namespace utils = extension_function_test_utils; 33 34 namespace extensions { 35 36 namespace { 37 38 // If more sessions are added to session tags, num sessions should be updated. 39 const char* kSessionTags[] = {"tag0", "tag1", "tag2", "tag3", "tag4"}; 40 const size_t kNumSessions = 5; 41 42 void BuildSessionSpecifics(const std::string& tag, 43 sync_pb::SessionSpecifics* meta) { 44 meta->set_session_tag(tag); 45 sync_pb::SessionHeader* header = meta->mutable_header(); 46 header->set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX); 47 header->set_client_name(tag); 48 } 49 50 void BuildWindowSpecifics(int window_id, 51 const std::vector<int>& tab_list, 52 sync_pb::SessionSpecifics* meta) { 53 sync_pb::SessionHeader* header = meta->mutable_header(); 54 sync_pb::SessionWindow* window = header->add_window(); 55 window->set_window_id(window_id); 56 window->set_selected_tab_index(0); 57 window->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED); 58 for (std::vector<int>::const_iterator iter = tab_list.begin(); 59 iter != tab_list.end(); ++iter) { 60 window->add_tab(*iter); 61 } 62 } 63 64 void BuildTabSpecifics(const std::string& tag, int window_id, int tab_id, 65 sync_pb::SessionSpecifics* tab_base) { 66 tab_base->set_session_tag(tag); 67 tab_base->set_tab_node_id(0); 68 sync_pb::SessionTab* tab = tab_base->mutable_tab(); 69 tab->set_tab_id(tab_id); 70 tab->set_tab_visual_index(1); 71 tab->set_current_navigation_index(0); 72 tab->set_pinned(true); 73 tab->set_extension_app_id("app_id"); 74 sync_pb::TabNavigation* navigation = tab->add_navigation(); 75 navigation->set_virtual_url("http://foo/1"); 76 navigation->set_referrer("referrer"); 77 navigation->set_title("title"); 78 navigation->set_page_transition(sync_pb::SyncEnums_PageTransition_TYPED); 79 } 80 81 } // namespace 82 83 class ExtensionSessionsTest : public InProcessBrowserTest { 84 public: 85 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE; 86 virtual void SetUpOnMainThread() OVERRIDE; 87 protected: 88 void CreateTestProfileSyncService(); 89 void CreateTestExtension(); 90 void CreateSessionModels(); 91 92 template <class T> 93 scoped_refptr<T> CreateFunction(bool has_callback) { 94 scoped_refptr<T> fn(new T()); 95 fn->set_extension(extension_.get()); 96 fn->set_has_callback(has_callback); 97 return fn; 98 }; 99 100 Browser* browser_; 101 scoped_refptr<extensions::Extension> extension_; 102 }; 103 104 void ExtensionSessionsTest::SetUpCommandLine(CommandLine* command_line) { 105 #if defined(OS_CHROMEOS) 106 command_line->AppendSwitch( 107 chromeos::switches::kIgnoreUserProfileMappingForTests); 108 #endif 109 } 110 111 void ExtensionSessionsTest::SetUpOnMainThread() { 112 CreateTestProfileSyncService(); 113 CreateTestExtension(); 114 } 115 116 void ExtensionSessionsTest::CreateTestProfileSyncService() { 117 ProfileManager* profile_manager = g_browser_process->profile_manager(); 118 base::FilePath path; 119 PathService::Get(chrome::DIR_USER_DATA, &path); 120 path = path.AppendASCII("test_profile"); 121 if (!base::PathExists(path)) 122 CHECK(base::CreateDirectory(path)); 123 Profile* profile = 124 Profile::CreateProfile(path, NULL, Profile::CREATE_MODE_SYNCHRONOUS); 125 profile_manager->RegisterTestingProfile(profile, true, false); 126 ProfileSyncServiceMock* service = static_cast<ProfileSyncServiceMock*>( 127 ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse( 128 profile, &ProfileSyncServiceMock::BuildMockProfileSyncService)); 129 browser_ = new Browser(Browser::CreateParams( 130 profile, chrome::HOST_DESKTOP_TYPE_NATIVE)); 131 132 syncer::ModelTypeSet preferred_types; 133 preferred_types.Put(syncer::SESSIONS); 134 GoogleServiceAuthError no_error(GoogleServiceAuthError::NONE); 135 ON_CALL(*service, IsSessionsDataTypeControllerRunning()) 136 .WillByDefault(testing::Return(true)); 137 ON_CALL(*service, GetRegisteredDataTypes()) 138 .WillByDefault(testing::Return(syncer::UserTypes())); 139 ON_CALL(*service, GetPreferredDataTypes()).WillByDefault( 140 testing::Return(preferred_types)); 141 EXPECT_CALL(*service, GetAuthError()).WillRepeatedly( 142 testing::ReturnRef(no_error)); 143 ON_CALL(*service, GetActiveDataTypes()).WillByDefault( 144 testing::Return(preferred_types)); 145 ON_CALL(*service, GetLocalDeviceInfoMock()).WillByDefault( 146 testing::Return(new browser_sync::DeviceInfo( 147 std::string(kSessionTags[0]), 148 "machine name", 149 "Chromium 10k", 150 "Chrome 10k", 151 sync_pb::SyncEnums_DeviceType_TYPE_LINUX))); 152 ON_CALL(*service, GetLocalSyncCacheGUID()).WillByDefault( 153 testing::Return(std::string(kSessionTags[0]))); 154 EXPECT_CALL(*service, AddObserver(testing::_)).Times(testing::AnyNumber()); 155 EXPECT_CALL(*service, RemoveObserver(testing::_)).Times(testing::AnyNumber()); 156 157 service->Initialize(); 158 } 159 160 void ExtensionSessionsTest::CreateTestExtension() { 161 scoped_ptr<base::DictionaryValue> test_extension_value( 162 utils::ParseDictionary( 163 "{\"name\": \"Test\", \"version\": \"1.0\", " 164 "\"permissions\": [\"sessions\", \"tabs\"]}")); 165 extension_ = utils::CreateExtension(test_extension_value.get()); 166 } 167 168 void ExtensionSessionsTest::CreateSessionModels() { 169 syncer::SyncDataList initial_data; 170 for (size_t index = 0; index < kNumSessions; ++index) { 171 // Fill an instance of session specifics with a foreign session's data. 172 sync_pb::SessionSpecifics meta; 173 BuildSessionSpecifics(kSessionTags[index], &meta); 174 SessionID::id_type tab_nums1[] = {5, 10, 13, 17}; 175 std::vector<SessionID::id_type> tab_list1( 176 tab_nums1, tab_nums1 + arraysize(tab_nums1)); 177 BuildWindowSpecifics(index, tab_list1, &meta); 178 std::vector<sync_pb::SessionSpecifics> tabs1; 179 tabs1.resize(tab_list1.size()); 180 for (size_t i = 0; i < tab_list1.size(); ++i) { 181 BuildTabSpecifics(kSessionTags[index], 0, tab_list1[i], &tabs1[i]); 182 } 183 184 sync_pb::EntitySpecifics entity; 185 entity.mutable_session()->CopyFrom(meta); 186 initial_data.push_back(syncer::SyncData::CreateRemoteData( 187 1, 188 entity, 189 base::Time(), 190 syncer::AttachmentIdList(), 191 syncer::AttachmentServiceProxyForTest::Create())); 192 for (size_t i = 0; i < tabs1.size(); i++) { 193 sync_pb::EntitySpecifics entity; 194 entity.mutable_session()->CopyFrom(tabs1[i]); 195 initial_data.push_back(syncer::SyncData::CreateRemoteData( 196 i + 2, 197 entity, 198 base::Time(), 199 syncer::AttachmentIdList(), 200 syncer::AttachmentServiceProxyForTest::Create())); 201 } 202 } 203 204 ProfileSyncServiceFactory::GetForProfile(browser_->profile())-> 205 GetSessionsSyncableService()-> 206 MergeDataAndStartSyncing(syncer::SESSIONS, initial_data, 207 scoped_ptr<syncer::SyncChangeProcessor>( 208 new syncer::FakeSyncChangeProcessor()), 209 scoped_ptr<syncer::SyncErrorFactory>( 210 new syncer::SyncErrorFactoryMock())); 211 } 212 213 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetDevices) { 214 CreateSessionModels(); 215 216 scoped_ptr<base::ListValue> result(utils::ToList( 217 utils::RunFunctionAndReturnSingleResult( 218 CreateFunction<SessionsGetDevicesFunction>(true).get(), 219 "[{\"maxResults\": 0}]", 220 browser_))); 221 ASSERT_TRUE(result); 222 base::ListValue* devices = result.get(); 223 EXPECT_EQ(5u, devices->GetSize()); 224 base::DictionaryValue* device = NULL; 225 base::ListValue* sessions = NULL; 226 for (size_t i = 0; i < devices->GetSize(); ++i) { 227 EXPECT_TRUE(devices->GetDictionary(i, &device)); 228 EXPECT_EQ(kSessionTags[i], utils::GetString(device, "info")); 229 EXPECT_EQ(kSessionTags[i], utils::GetString(device, "deviceName")); 230 EXPECT_TRUE(device->GetList("sessions", &sessions)); 231 EXPECT_EQ(0u, sessions->GetSize()); 232 } 233 } 234 235 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetDevicesMaxResults) { 236 CreateSessionModels(); 237 238 scoped_ptr<base::ListValue> result(utils::ToList( 239 utils::RunFunctionAndReturnSingleResult( 240 CreateFunction<SessionsGetDevicesFunction>(true).get(), 241 "[]", 242 browser_))); 243 ASSERT_TRUE(result); 244 base::ListValue* devices = result.get(); 245 EXPECT_EQ(5u, devices->GetSize()); 246 base::DictionaryValue* device = NULL; 247 base::ListValue* sessions = NULL; 248 for (size_t i = 0; i < devices->GetSize(); ++i) { 249 EXPECT_TRUE(devices->GetDictionary(i, &device)); 250 EXPECT_EQ(kSessionTags[i], utils::GetString(device, "info")); 251 EXPECT_EQ(kSessionTags[i], utils::GetString(device, "deviceName")); 252 EXPECT_TRUE(device->GetList("sessions", &sessions)); 253 EXPECT_EQ(1u, sessions->GetSize()); 254 } 255 } 256 257 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetDevicesListEmpty) { 258 scoped_ptr<base::ListValue> result(utils::ToList( 259 utils::RunFunctionAndReturnSingleResult( 260 CreateFunction<SessionsGetDevicesFunction>(true).get(), 261 "[]", 262 browser_))); 263 264 ASSERT_TRUE(result); 265 base::ListValue* devices = result.get(); 266 EXPECT_EQ(0u, devices->GetSize()); 267 } 268 269 // Flaky timeout: http://crbug.com/278372 270 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, 271 DISABLED_RestoreForeignSessionWindow) { 272 CreateSessionModels(); 273 274 scoped_ptr<base::DictionaryValue> restored_window_session(utils::ToDictionary( 275 utils::RunFunctionAndReturnSingleResult( 276 CreateFunction<SessionsRestoreFunction>(true).get(), 277 "[\"tag3.3\"]", 278 browser_, 279 utils::INCLUDE_INCOGNITO))); 280 ASSERT_TRUE(restored_window_session); 281 282 scoped_ptr<base::ListValue> result(utils::ToList( 283 utils::RunFunctionAndReturnSingleResult( 284 CreateFunction<WindowsGetAllFunction>(true).get(), 285 "[]", 286 browser_))); 287 ASSERT_TRUE(result); 288 289 base::ListValue* windows = result.get(); 290 EXPECT_EQ(2u, windows->GetSize()); 291 base::DictionaryValue* restored_window = NULL; 292 EXPECT_TRUE(restored_window_session->GetDictionary("window", 293 &restored_window)); 294 base::DictionaryValue* window = NULL; 295 int restored_id = utils::GetInteger(restored_window, "id"); 296 for (size_t i = 0; i < windows->GetSize(); ++i) { 297 EXPECT_TRUE(windows->GetDictionary(i, &window)); 298 if (utils::GetInteger(window, "id") == restored_id) 299 break; 300 } 301 EXPECT_EQ(restored_id, utils::GetInteger(window, "id")); 302 } 303 304 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, RestoreForeignSessionInvalidId) { 305 CreateSessionModels(); 306 307 EXPECT_TRUE(MatchPattern(utils::RunFunctionAndReturnError( 308 CreateFunction<SessionsRestoreFunction>(true).get(), 309 "[\"tag3.0\"]", 310 browser_), "Invalid session id: \"tag3.0\".")); 311 } 312 313 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, RestoreInIncognito) { 314 CreateSessionModels(); 315 316 EXPECT_TRUE(MatchPattern(utils::RunFunctionAndReturnError( 317 CreateFunction<SessionsRestoreFunction>(true).get(), 318 "[\"1\"]", 319 CreateIncognitoBrowser()), 320 "Can not restore sessions in incognito mode.")); 321 } 322 323 IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetRecentlyClosedIncognito) { 324 scoped_ptr<base::ListValue> result(utils::ToList( 325 utils::RunFunctionAndReturnSingleResult( 326 CreateFunction<SessionsGetRecentlyClosedFunction>(true).get(), 327 "[]", 328 CreateIncognitoBrowser()))); 329 ASSERT_TRUE(result); 330 base::ListValue* sessions = result.get(); 331 EXPECT_EQ(0u, sessions->GetSize()); 332 } 333 334 // Flaky on ChromeOS, times out on OSX Debug http://crbug.com/251199 335 #if defined(OS_CHROMEOS) || (defined(OS_MACOSX) && !defined(NDEBUG)) 336 #define MAYBE_SessionsApis DISABLED_SessionsApis 337 #else 338 #define MAYBE_SessionsApis SessionsApis 339 #endif 340 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_SessionsApis) { 341 #if defined(OS_WIN) && defined(USE_ASH) 342 // Disable this test in Metro+Ash for now (http://crbug.com/262796). 343 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) 344 return; 345 #endif 346 347 ASSERT_TRUE(RunExtensionSubtest("sessions", 348 "sessions.html")) << message_; 349 } 350 351 } // namespace extensions 352