// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/sessions/sessions_api.h" #include #include "base/i18n/rtl.h" #include "base/lazy_instance.h" #include "base/prefs/pref_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "chrome/browser/extensions/api/sessions/session_id.h" #include "chrome/browser/extensions/api/tabs/windows_util.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/window_controller.h" #include "chrome/browser/extensions/window_controller_list.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search/search.h" #include "chrome/browser/sessions/session_restore.h" #include "chrome/browser/sessions/tab_restore_service_delegate.h" #include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/host_desktop.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/pref_names.h" #include "components/sync_driver/glue/synced_session.h" #include "components/sync_driver/open_tabs_ui_delegate.h" #include "components/url_formatter/url_formatter.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_function_dispatcher.h" #include "extensions/browser/extension_function_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/common/error_utils.h" #include "ui/base/layout.h" namespace extensions { namespace GetRecentlyClosed = api::sessions::GetRecentlyClosed; namespace GetDevices = api::sessions::GetDevices; namespace Restore = api::sessions::Restore; namespace tabs = api::tabs; namespace windows = api::windows; const char kNoRecentlyClosedSessionsError[] = "There are no recently closed sessions."; const char kInvalidSessionIdError[] = "Invalid session id: \"*\"."; const char kNoBrowserToRestoreSession[] = "There are no browser windows to restore the session."; const char kSessionSyncError[] = "Synced sessions are not available."; const char kRestoreInIncognitoError[] = "Can not restore sessions in incognito mode."; // Comparator function for use with std::sort that will sort sessions by // descending modified_time (i.e., most recent first). bool SortSessionsByRecency(const sync_driver::SyncedSession* s1, const sync_driver::SyncedSession* s2) { return s1->modified_time > s2->modified_time; } // Comparator function for use with std::sort that will sort tabs in a window // by descending timestamp (i.e., most recent first). bool SortTabsByRecency(const sessions::SessionTab* t1, const sessions::SessionTab* t2) { return t1->timestamp > t2->timestamp; } scoped_ptr CreateTabModelHelper( Profile* profile, const sessions::SerializedNavigationEntry& current_navigation, const std::string& session_id, int index, bool pinned, int selected_index, const Extension* extension) { scoped_ptr tab_struct(new tabs::Tab); const GURL& url = current_navigation.virtual_url(); std::string title = base::UTF16ToUTF8(current_navigation.title()); tab_struct->session_id.reset(new std::string(session_id)); tab_struct->url.reset(new std::string(url.spec())); tab_struct->fav_icon_url.reset( new std::string(current_navigation.favicon_url().spec())); if (!title.empty()) { tab_struct->title.reset(new std::string(title)); } else { const std::string languages = profile->GetPrefs()->GetString(prefs::kAcceptLanguages); tab_struct->title.reset(new std::string( base::UTF16ToUTF8(url_formatter::FormatUrl(url, languages)))); } tab_struct->index = index; tab_struct->pinned = pinned; // Note: |selected_index| from the sync sessions model is what we call // "active" in extensions terminology. "selected" is deprecated because it's // not clear whether it means "active" (user can see) or "highlighted" (user // has highlighted, since you can select tabs without bringing them into the // foreground). tab_struct->active = index == selected_index; ExtensionTabUtil::ScrubTabForExtension(extension, tab_struct.get()); return tab_struct.Pass(); } scoped_ptr CreateWindowModelHelper( scoped_ptr>> tabs, const std::string& session_id, const windows::WindowType& type, const windows::WindowState& state) { scoped_ptr window_struct(new windows::Window); window_struct->tabs = tabs.Pass(); window_struct->session_id.reset(new std::string(session_id)); window_struct->incognito = false; window_struct->always_on_top = false; window_struct->focused = false; window_struct->type = type; window_struct->state = state; return window_struct.Pass(); } scoped_ptr CreateSessionModelHelper( int last_modified, scoped_ptr tab, scoped_ptr window) { scoped_ptr session_struct(new api::sessions::Session); session_struct->last_modified = last_modified; if (tab) session_struct->tab = tab.Pass(); else if (window) session_struct->window = window.Pass(); else NOTREACHED(); return session_struct.Pass(); } bool is_tab_entry(const TabRestoreService::Entry* entry) { return entry->type == TabRestoreService::TAB; } bool is_window_entry(const TabRestoreService::Entry* entry) { return entry->type == TabRestoreService::WINDOW; } scoped_ptr SessionsGetRecentlyClosedFunction::CreateTabModel( const TabRestoreService::Tab& tab, int session_id, int selected_index) { return CreateTabModelHelper(GetProfile(), tab.navigations[tab.current_navigation_index], base::IntToString(session_id), tab.tabstrip_index, tab.pinned, selected_index, extension()); } scoped_ptr SessionsGetRecentlyClosedFunction::CreateWindowModel( const TabRestoreService::Window& window, int session_id) { DCHECK(!window.tabs.empty()); scoped_ptr > > tabs( new std::vector >); for (size_t i = 0; i < window.tabs.size(); ++i) { tabs->push_back(make_linked_ptr( CreateTabModel(window.tabs[i], window.tabs[i].id, window.selected_tab_index).release())); } return CreateWindowModelHelper(tabs.Pass(), base::IntToString(session_id), windows::WINDOW_TYPE_NORMAL, windows::WINDOW_STATE_NORMAL); } scoped_ptr SessionsGetRecentlyClosedFunction::CreateSessionModel( const TabRestoreService::Entry* entry) { scoped_ptr tab; scoped_ptr window; switch (entry->type) { case TabRestoreService::TAB: tab = CreateTabModel( *static_cast(entry), entry->id, -1); break; case TabRestoreService::WINDOW: window = CreateWindowModel( *static_cast(entry), entry->id); break; default: NOTREACHED(); } return CreateSessionModelHelper(entry->timestamp.ToTimeT(), tab.Pass(), window.Pass()); } bool SessionsGetRecentlyClosedFunction::RunSync() { scoped_ptr params( GetRecentlyClosed::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params); int max_results = api::sessions::MAX_SESSION_RESULTS; if (params->filter && params->filter->max_results) max_results = *params->filter->max_results; EXTENSION_FUNCTION_VALIDATE(max_results >= 0 && max_results <= api::sessions::MAX_SESSION_RESULTS); std::vector > result; TabRestoreService* tab_restore_service = TabRestoreServiceFactory::GetForProfile(GetProfile()); // TabRestoreServiceFactory::GetForProfile() can return NULL (i.e., when in // incognito mode) if (!tab_restore_service) { DCHECK_NE(GetProfile(), GetProfile()->GetOriginalProfile()) << "TabRestoreService expected for normal profiles"; results_ = GetRecentlyClosed::Results::Create( std::vector >()); return true; } // List of entries. They are ordered from most to least recent. // We prune the list to contain max 25 entries at any time and removes // uninteresting entries. TabRestoreService::Entries entries = tab_restore_service->entries(); for (TabRestoreService::Entries::const_iterator it = entries.begin(); it != entries.end() && static_cast(result.size()) < max_results; ++it) { TabRestoreService::Entry* entry = *it; result.push_back(make_linked_ptr(CreateSessionModel(entry).release())); } results_ = GetRecentlyClosed::Results::Create(result); return true; } scoped_ptr SessionsGetDevicesFunction::CreateTabModel( const std::string& session_tag, const sessions::SessionTab& tab, int tab_index, int selected_index) { std::string session_id = SessionId(session_tag, tab.tab_id.id()).ToString(); return CreateTabModelHelper( GetProfile(), tab.navigations[tab.normalized_navigation_index()], session_id, tab_index, tab.pinned, selected_index, extension()); } scoped_ptr SessionsGetDevicesFunction::CreateWindowModel( const sessions::SessionWindow& window, const std::string& session_tag) { DCHECK(!window.tabs.empty()); // Prune tabs that are not syncable or are NewTabPage. Then, sort the tabs // from most recent to least recent. std::vector tabs_in_window; for (size_t i = 0; i < window.tabs.size(); ++i) { const sessions::SessionTab* tab = window.tabs[i]; if (tab->navigations.empty()) continue; const sessions::SerializedNavigationEntry& current_navigation = tab->navigations.at(tab->normalized_navigation_index()); if (search::IsNTPURL(current_navigation.virtual_url(), GetProfile())) { continue; } tabs_in_window.push_back(tab); } if (tabs_in_window.empty()) return scoped_ptr(); std::sort(tabs_in_window.begin(), tabs_in_window.end(), SortTabsByRecency); scoped_ptr > > tabs( new std::vector >); for (size_t i = 0; i < tabs_in_window.size(); ++i) { tabs->push_back(make_linked_ptr( CreateTabModel(session_tag, *tabs_in_window[i], i, window.selected_tab_index).release())); } std::string session_id = SessionId(session_tag, window.window_id.id()).ToString(); windows::WindowType type = windows::WINDOW_TYPE_NONE; switch (window.type) { case sessions::SessionWindow::TYPE_TABBED: type = windows::WINDOW_TYPE_NORMAL; break; case sessions::SessionWindow::TYPE_POPUP: type = windows::WINDOW_TYPE_POPUP; break; } windows::WindowState state = windows::WINDOW_STATE_NONE; switch (window.show_state) { case ui::SHOW_STATE_NORMAL: case ui::SHOW_STATE_DOCKED: state = windows::WINDOW_STATE_NORMAL; break; case ui::SHOW_STATE_MINIMIZED: state = windows::WINDOW_STATE_MINIMIZED; break; case ui::SHOW_STATE_MAXIMIZED: state = windows::WINDOW_STATE_MAXIMIZED; break; case ui::SHOW_STATE_FULLSCREEN: state = windows::WINDOW_STATE_FULLSCREEN; break; case ui::SHOW_STATE_DEFAULT: case ui::SHOW_STATE_INACTIVE: case ui::SHOW_STATE_END: break; } scoped_ptr window_struct( CreateWindowModelHelper(tabs.Pass(), session_id, type, state)); // TODO(dwankri): Dig deeper to resolve bounds not being optional, so closed // windows in GetRecentlyClosed can have set values in Window helper. window_struct->left.reset(new int(window.bounds.x())); window_struct->top.reset(new int(window.bounds.y())); window_struct->width.reset(new int(window.bounds.width())); window_struct->height.reset(new int(window.bounds.height())); return window_struct.Pass(); } scoped_ptr SessionsGetDevicesFunction::CreateSessionModel( const sessions::SessionWindow& window, const std::string& session_tag) { scoped_ptr window_model( CreateWindowModel(window, session_tag)); // There is a chance that after pruning uninteresting tabs the window will be // empty. return !window_model ? scoped_ptr() : CreateSessionModelHelper(window.timestamp.ToTimeT(), scoped_ptr(), window_model.Pass()); } scoped_ptr SessionsGetDevicesFunction::CreateDeviceModel( const sync_driver::SyncedSession* session) { int max_results = api::sessions::MAX_SESSION_RESULTS; // Already validated in RunAsync(). scoped_ptr params(GetDevices::Params::Create(*args_)); if (params->filter && params->filter->max_results) max_results = *params->filter->max_results; scoped_ptr device_struct(new api::sessions::Device); device_struct->info = session->session_name; device_struct->device_name = session->session_name; for (sync_driver::SyncedSession::SyncedWindowMap::const_iterator it = session->windows.begin(); it != session->windows.end() && static_cast(device_struct->sessions.size()) < max_results; ++it) { scoped_ptr session_model(CreateSessionModel( *it->second, session->session_tag)); if (session_model) device_struct->sessions.push_back(make_linked_ptr( session_model.release())); } return device_struct.Pass(); } bool SessionsGetDevicesFunction::RunSync() { ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->GetForProfile(GetProfile()); if (!(service && service->GetPreferredDataTypes().Has(syncer::SESSIONS))) { // Sync not enabled. results_ = GetDevices::Results::Create( std::vector >()); return true; } sync_driver::OpenTabsUIDelegate* open_tabs = service->GetOpenTabsUIDelegate(); std::vector sessions; if (!(open_tabs && open_tabs->GetAllForeignSessions(&sessions))) { results_ = GetDevices::Results::Create( std::vector >()); return true; } scoped_ptr params(GetDevices::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params); if (params->filter && params->filter->max_results) { EXTENSION_FUNCTION_VALIDATE(*params->filter->max_results >= 0 && *params->filter->max_results <= api::sessions::MAX_SESSION_RESULTS); } std::vector > result; // Sort sessions from most recent to least recent. std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency); for (size_t i = 0; i < sessions.size(); ++i) { result.push_back(make_linked_ptr(CreateDeviceModel(sessions[i]).release())); } results_ = GetDevices::Results::Create(result); return true; } void SessionsRestoreFunction::SetInvalidIdError(const std::string& invalid_id) { SetError(ErrorUtils::FormatErrorMessage(kInvalidSessionIdError, invalid_id)); } void SessionsRestoreFunction::SetResultRestoredTab( content::WebContents* contents) { scoped_ptr tab_value( ExtensionTabUtil::CreateTabValue(contents, extension())); scoped_ptr tab(tabs::Tab::FromValue(*tab_value)); scoped_ptr restored_session(CreateSessionModelHelper( base::Time::Now().ToTimeT(), tab.Pass(), scoped_ptr())); results_ = Restore::Results::Create(*restored_session); } bool SessionsRestoreFunction::SetResultRestoredWindow(int window_id) { WindowController* controller = NULL; if (!windows_util::GetWindowFromWindowID(this, window_id, &controller)) { // error_ is set by GetWindowFromWindowId function call. return false; } scoped_ptr window_value( controller->CreateWindowValueWithTabs(extension())); scoped_ptr window(windows::Window::FromValue( *window_value)); results_ = Restore::Results::Create(*CreateSessionModelHelper( base::Time::Now().ToTimeT(), scoped_ptr(), window.Pass())); return true; } bool SessionsRestoreFunction::RestoreMostRecentlyClosed(Browser* browser) { TabRestoreService* tab_restore_service = TabRestoreServiceFactory::GetForProfile(GetProfile()); chrome::HostDesktopType host_desktop_type = browser->host_desktop_type(); TabRestoreService::Entries entries = tab_restore_service->entries(); if (entries.empty()) { SetError(kNoRecentlyClosedSessionsError); return false; } bool is_window = is_window_entry(entries.front()); TabRestoreServiceDelegate* delegate = TabRestoreServiceDelegate::FindDelegateForWebContents( browser->tab_strip_model()->GetActiveWebContents()); std::vector contents = tab_restore_service->RestoreMostRecentEntry(delegate, host_desktop_type); DCHECK(contents.size()); if (is_window) { return SetResultRestoredWindow( ExtensionTabUtil::GetWindowIdOfTab(contents[0])); } SetResultRestoredTab(contents[0]); return true; } bool SessionsRestoreFunction::RestoreLocalSession(const SessionId& session_id, Browser* browser) { TabRestoreService* tab_restore_service = TabRestoreServiceFactory::GetForProfile(GetProfile()); chrome::HostDesktopType host_desktop_type = browser->host_desktop_type(); TabRestoreService::Entries entries = tab_restore_service->entries(); if (entries.empty()) { SetInvalidIdError(session_id.ToString()); return false; } // Check if the recently closed list contains an entry with the provided id. bool is_window = false; for (TabRestoreService::Entries::iterator it = entries.begin(); it != entries.end(); ++it) { if ((*it)->id == session_id.id()) { // The only time a full window is being restored is if the entry ID // matches the provided ID and the entry type is Window. is_window = is_window_entry(*it); break; } } TabRestoreServiceDelegate* delegate = TabRestoreServiceDelegate::FindDelegateForWebContents( browser->tab_strip_model()->GetActiveWebContents()); std::vector contents = tab_restore_service->RestoreEntryById(delegate, session_id.id(), host_desktop_type, UNKNOWN); // If the ID is invalid, contents will be empty. if (!contents.size()) { SetInvalidIdError(session_id.ToString()); return false; } // Retrieve the window through any of the tabs in contents. if (is_window) { return SetResultRestoredWindow( ExtensionTabUtil::GetWindowIdOfTab(contents[0])); } SetResultRestoredTab(contents[0]); return true; } bool SessionsRestoreFunction::RestoreForeignSession(const SessionId& session_id, Browser* browser) { ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->GetForProfile(GetProfile()); if (!(service && service->GetPreferredDataTypes().Has(syncer::SESSIONS))) { SetError(kSessionSyncError); return false; } sync_driver::OpenTabsUIDelegate* open_tabs = service->GetOpenTabsUIDelegate(); if (!open_tabs) { SetError(kSessionSyncError); return false; } const sessions::SessionTab* tab = NULL; if (open_tabs->GetForeignTab(session_id.session_tag(), session_id.id(), &tab)) { TabStripModel* tab_strip = browser->tab_strip_model(); content::WebContents* contents = tab_strip->GetActiveWebContents(); content::WebContents* tab_contents = SessionRestore::RestoreForeignSessionTab(contents, *tab, NEW_FOREGROUND_TAB); SetResultRestoredTab(tab_contents); return true; } // Restoring a full window. std::vector windows; if (!open_tabs->GetForeignSession(session_id.session_tag(), &windows)) { SetInvalidIdError(session_id.ToString()); return false; } std::vector::const_iterator window = windows.begin(); while (window != windows.end() && (*window)->window_id.id() != session_id.id()) { ++window; } if (window == windows.end()) { SetInvalidIdError(session_id.ToString()); return false; } chrome::HostDesktopType host_desktop_type = browser->host_desktop_type(); // Only restore one window at a time. std::vector browsers = SessionRestore::RestoreForeignSessionWindows( GetProfile(), host_desktop_type, window, window + 1); // Will always create one browser because we only restore one window per call. DCHECK_EQ(1u, browsers.size()); return SetResultRestoredWindow(ExtensionTabUtil::GetWindowId(browsers[0])); } bool SessionsRestoreFunction::RunSync() { scoped_ptr params(Restore::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params); Browser* browser = chrome::FindBrowserWithProfile( GetProfile(), chrome::HOST_DESKTOP_TYPE_NATIVE); if (!browser) { SetError(kNoBrowserToRestoreSession); return false; } if (GetProfile() != GetProfile()->GetOriginalProfile()) { SetError(kRestoreInIncognitoError); return false; } if (!params->session_id) return RestoreMostRecentlyClosed(browser); scoped_ptr session_id(SessionId::Parse(*params->session_id)); if (!session_id) { SetInvalidIdError(*params->session_id); return false; } return session_id->IsForeign() ? RestoreForeignSession(*session_id, browser) : RestoreLocalSession(*session_id, browser); } SessionsEventRouter::SessionsEventRouter(Profile* profile) : profile_(profile), tab_restore_service_(TabRestoreServiceFactory::GetForProfile(profile)) { // TabRestoreServiceFactory::GetForProfile() can return NULL (i.e., when in // incognito mode) if (tab_restore_service_) { tab_restore_service_->LoadTabsFromLastSession(); tab_restore_service_->AddObserver(this); } } SessionsEventRouter::~SessionsEventRouter() { if (tab_restore_service_) tab_restore_service_->RemoveObserver(this); } void SessionsEventRouter::TabRestoreServiceChanged( TabRestoreService* service) { scoped_ptr args(new base::ListValue()); EventRouter::Get(profile_)->BroadcastEvent(make_scoped_ptr( new Event(events::SESSIONS_ON_CHANGED, api::sessions::OnChanged::kEventName, args.Pass()))); } void SessionsEventRouter::TabRestoreServiceDestroyed( TabRestoreService* service) { tab_restore_service_ = NULL; } SessionsAPI::SessionsAPI(content::BrowserContext* context) : browser_context_(context) { EventRouter::Get(browser_context_)->RegisterObserver(this, api::sessions::OnChanged::kEventName); } SessionsAPI::~SessionsAPI() { } void SessionsAPI::Shutdown() { EventRouter::Get(browser_context_)->UnregisterObserver(this); } static base::LazyInstance > g_factory = LAZY_INSTANCE_INITIALIZER; BrowserContextKeyedAPIFactory* SessionsAPI::GetFactoryInstance() { return g_factory.Pointer(); } void SessionsAPI::OnListenerAdded(const EventListenerInfo& details) { sessions_event_router_.reset( new SessionsEventRouter(Profile::FromBrowserContext(browser_context_))); EventRouter::Get(browser_context_)->UnregisterObserver(this); } } // namespace extensions