| // Copyright (c) 2015 The Chromium Embedded Framework 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 "tests/cefclient/browser/root_window_manager.h" |
| |
| #include <sstream> |
| |
| #include "include/base/cef_bind.h" |
| #include "include/base/cef_logging.h" |
| #include "include/wrapper/cef_helpers.h" |
| #include "tests/cefclient/browser/main_context.h" |
| #include "tests/cefclient/browser/test_runner.h" |
| #include "tests/shared/browser/extension_util.h" |
| #include "tests/shared/browser/file_util.h" |
| #include "tests/shared/browser/resource_util.h" |
| #include "tests/shared/common/client_switches.h" |
| |
| namespace client { |
| |
| namespace { |
| |
| class ClientRequestContextHandler : public CefRequestContextHandler, |
| public CefExtensionHandler { |
| public: |
| ClientRequestContextHandler() {} |
| |
| // CefRequestContextHandler methods: |
| bool OnBeforePluginLoad(const CefString& mime_type, |
| const CefString& plugin_url, |
| bool is_main_frame, |
| const CefString& top_origin_url, |
| CefRefPtr<CefWebPluginInfo> plugin_info, |
| PluginPolicy* plugin_policy) OVERRIDE { |
| // Always allow the PDF plugin to load. |
| if (*plugin_policy != PLUGIN_POLICY_ALLOW && |
| mime_type == "application/pdf") { |
| *plugin_policy = PLUGIN_POLICY_ALLOW; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void OnRequestContextInitialized( |
| CefRefPtr<CefRequestContext> request_context) OVERRIDE { |
| CEF_REQUIRE_UI_THREAD(); |
| |
| CefRefPtr<CefCommandLine> command_line = |
| CefCommandLine::GetGlobalCommandLine(); |
| if (command_line->HasSwitch(switches::kLoadExtension)) { |
| if (MainContext::Get() |
| ->GetRootWindowManager() |
| ->request_context_per_browser()) { |
| // The example extension loading implementation requires all browsers to |
| // share the same request context. |
| LOG(ERROR) |
| << "Cannot mix --load-extension and --request-context-per-browser"; |
| return; |
| } |
| |
| // Load one or more extension paths specified on the command-line and |
| // delimited with semicolon. |
| const std::string& extension_path = |
| command_line->GetSwitchValue(switches::kLoadExtension); |
| if (!extension_path.empty()) { |
| std::string part; |
| std::istringstream f(extension_path); |
| while (getline(f, part, ';')) { |
| if (!part.empty()) |
| extension_util::LoadExtension(request_context, part, this); |
| } |
| } |
| } |
| } |
| |
| // CefExtensionHandler methods: |
| void OnExtensionLoaded(CefRefPtr<CefExtension> extension) OVERRIDE { |
| CEF_REQUIRE_UI_THREAD(); |
| MainContext::Get()->GetRootWindowManager()->AddExtension(extension); |
| } |
| |
| CefRefPtr<CefBrowser> GetActiveBrowser(CefRefPtr<CefExtension> extension, |
| CefRefPtr<CefBrowser> browser, |
| bool include_incognito) OVERRIDE { |
| CEF_REQUIRE_UI_THREAD(); |
| |
| // Return the browser for the active/foreground window. |
| CefRefPtr<CefBrowser> active_browser = |
| MainContext::Get()->GetRootWindowManager()->GetActiveBrowser(); |
| if (!active_browser) { |
| LOG(WARNING) |
| << "No active browser available for extension " |
| << browser->GetHost()->GetExtension()->GetIdentifier().ToString(); |
| } else { |
| // The active browser should not be hosting an extension. |
| DCHECK(!active_browser->GetHost()->GetExtension()); |
| } |
| return active_browser; |
| } |
| |
| private: |
| IMPLEMENT_REFCOUNTING(ClientRequestContextHandler); |
| DISALLOW_COPY_AND_ASSIGN(ClientRequestContextHandler); |
| }; |
| |
| } // namespace |
| |
| RootWindowManager::RootWindowManager(bool terminate_when_all_windows_closed) |
| : terminate_when_all_windows_closed_(terminate_when_all_windows_closed) { |
| CefRefPtr<CefCommandLine> command_line = |
| CefCommandLine::GetGlobalCommandLine(); |
| DCHECK(command_line.get()); |
| request_context_per_browser_ = |
| command_line->HasSwitch(switches::kRequestContextPerBrowser); |
| request_context_shared_cache_ = |
| command_line->HasSwitch(switches::kRequestContextSharedCache); |
| } |
| |
| RootWindowManager::~RootWindowManager() { |
| // All root windows should already have been destroyed. |
| DCHECK(root_windows_.empty()); |
| } |
| |
| scoped_refptr<RootWindow> RootWindowManager::CreateRootWindow( |
| const RootWindowConfig& config) { |
| CefBrowserSettings settings; |
| MainContext::Get()->PopulateBrowserSettings(&settings); |
| |
| scoped_refptr<RootWindow> root_window = |
| RootWindow::Create(MainContext::Get()->UseViews()); |
| root_window->Init(this, config, settings); |
| |
| // Store a reference to the root window on the main thread. |
| OnRootWindowCreated(root_window); |
| |
| return root_window; |
| } |
| |
| scoped_refptr<RootWindow> RootWindowManager::CreateRootWindowAsPopup( |
| bool with_controls, |
| bool with_osr, |
| const CefPopupFeatures& popupFeatures, |
| CefWindowInfo& windowInfo, |
| CefRefPtr<CefClient>& client, |
| CefBrowserSettings& settings) { |
| CEF_REQUIRE_UI_THREAD(); |
| |
| if (!temp_window_) { |
| // TempWindow must be created on the UI thread. |
| temp_window_.reset(new TempWindow()); |
| } |
| |
| MainContext::Get()->PopulateBrowserSettings(&settings); |
| |
| scoped_refptr<RootWindow> root_window = |
| RootWindow::Create(MainContext::Get()->UseViews()); |
| root_window->InitAsPopup(this, with_controls, with_osr, popupFeatures, |
| windowInfo, client, settings); |
| |
| // Store a reference to the root window on the main thread. |
| OnRootWindowCreated(root_window); |
| |
| return root_window; |
| } |
| |
| scoped_refptr<RootWindow> RootWindowManager::CreateRootWindowAsExtension( |
| CefRefPtr<CefExtension> extension, |
| const CefRect& source_bounds, |
| CefRefPtr<CefWindow> parent_window, |
| const base::Closure& close_callback, |
| bool with_controls, |
| bool with_osr) { |
| const std::string& extension_url = extension_util::GetExtensionURL(extension); |
| if (extension_url.empty()) { |
| NOTREACHED() << "Extension cannot be loaded directly."; |
| return nullptr; |
| } |
| |
| // Create an initially hidden browser window that loads the extension URL. |
| // We'll show the window when the desired size becomes available via |
| // ClientHandler::OnAutoResize. |
| RootWindowConfig config; |
| config.with_controls = with_controls; |
| config.with_osr = with_osr; |
| config.with_extension = true; |
| config.initially_hidden = true; |
| config.source_bounds = source_bounds; |
| config.parent_window = parent_window; |
| config.close_callback = close_callback; |
| config.url = extension_url; |
| return CreateRootWindow(config); |
| } |
| |
| bool RootWindowManager::HasRootWindowAsExtension( |
| CefRefPtr<CefExtension> extension) { |
| REQUIRE_MAIN_THREAD(); |
| |
| RootWindowSet::const_iterator it = root_windows_.begin(); |
| for (; it != root_windows_.end(); ++it) { |
| const RootWindow* root_window = (*it); |
| if (!root_window->WithExtension()) |
| continue; |
| |
| CefRefPtr<CefBrowser> browser = root_window->GetBrowser(); |
| if (!browser) |
| continue; |
| |
| CefRefPtr<CefExtension> browser_extension = |
| browser->GetHost()->GetExtension(); |
| DCHECK(browser_extension); |
| if (browser_extension->GetIdentifier() == extension->GetIdentifier()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| scoped_refptr<RootWindow> RootWindowManager::GetWindowForBrowser( |
| int browser_id) const { |
| REQUIRE_MAIN_THREAD(); |
| |
| RootWindowSet::const_iterator it = root_windows_.begin(); |
| for (; it != root_windows_.end(); ++it) { |
| CefRefPtr<CefBrowser> browser = (*it)->GetBrowser(); |
| if (browser.get() && browser->GetIdentifier() == browser_id) |
| return *it; |
| } |
| return nullptr; |
| } |
| |
| scoped_refptr<RootWindow> RootWindowManager::GetActiveRootWindow() const { |
| REQUIRE_MAIN_THREAD(); |
| return active_root_window_; |
| } |
| |
| CefRefPtr<CefBrowser> RootWindowManager::GetActiveBrowser() const { |
| base::AutoLock lock_scope(active_browser_lock_); |
| return active_browser_; |
| } |
| |
| void RootWindowManager::CloseAllWindows(bool force) { |
| if (!CURRENTLY_ON_MAIN_THREAD()) { |
| // Execute this method on the main thread. |
| MAIN_POST_CLOSURE(base::Bind(&RootWindowManager::CloseAllWindows, |
| base::Unretained(this), force)); |
| return; |
| } |
| |
| if (root_windows_.empty()) |
| return; |
| |
| // Use a copy of |root_windows_| because the original set may be modified |
| // in OnRootWindowDestroyed while iterating. |
| RootWindowSet root_windows = root_windows_; |
| |
| RootWindowSet::const_iterator it = root_windows.begin(); |
| for (; it != root_windows.end(); ++it) |
| (*it)->Close(force); |
| } |
| |
| void RootWindowManager::AddExtension(CefRefPtr<CefExtension> extension) { |
| if (!CURRENTLY_ON_MAIN_THREAD()) { |
| // Execute this method on the main thread. |
| MAIN_POST_CLOSURE(base::Bind(&RootWindowManager::AddExtension, |
| base::Unretained(this), extension)); |
| return; |
| } |
| |
| // Don't track extensions that can't be loaded directly. |
| if (extension_util::GetExtensionURL(extension).empty()) |
| return; |
| |
| // Don't add the same extension multiple times. |
| ExtensionSet::const_iterator it = extensions_.begin(); |
| for (; it != extensions_.end(); ++it) { |
| if ((*it)->GetIdentifier() == extension->GetIdentifier()) |
| return; |
| } |
| |
| extensions_.insert(extension); |
| NotifyExtensionsChanged(); |
| } |
| |
| void RootWindowManager::OnRootWindowCreated( |
| scoped_refptr<RootWindow> root_window) { |
| if (!CURRENTLY_ON_MAIN_THREAD()) { |
| // Execute this method on the main thread. |
| MAIN_POST_CLOSURE(base::Bind(&RootWindowManager::OnRootWindowCreated, |
| base::Unretained(this), root_window)); |
| return; |
| } |
| |
| root_windows_.insert(root_window); |
| if (!root_window->WithExtension()) { |
| root_window->OnExtensionsChanged(extensions_); |
| |
| if (root_windows_.size() == 1U) { |
| // The first non-extension root window should be considered the active |
| // window. |
| OnRootWindowActivated(root_window); |
| } |
| } |
| } |
| |
| void RootWindowManager::NotifyExtensionsChanged() { |
| REQUIRE_MAIN_THREAD(); |
| |
| RootWindowSet::const_iterator it = root_windows_.begin(); |
| for (; it != root_windows_.end(); ++it) { |
| RootWindow* root_window = *it; |
| if (!root_window->WithExtension()) |
| root_window->OnExtensionsChanged(extensions_); |
| } |
| } |
| |
| CefRefPtr<CefRequestContext> RootWindowManager::GetRequestContext( |
| RootWindow* root_window) { |
| REQUIRE_MAIN_THREAD(); |
| |
| if (request_context_per_browser_) { |
| // Create a new request context for each browser. |
| CefRequestContextSettings settings; |
| |
| CefRefPtr<CefCommandLine> command_line = |
| CefCommandLine::GetGlobalCommandLine(); |
| if (command_line->HasSwitch(switches::kCachePath)) { |
| if (request_context_shared_cache_) { |
| // Give each browser the same cache path. The resulting context objects |
| // will share the same storage internally. |
| CefString(&settings.cache_path) = |
| command_line->GetSwitchValue(switches::kCachePath); |
| } else { |
| // Give each browser a unique cache path. This will create completely |
| // isolated context objects. |
| std::stringstream ss; |
| ss << command_line->GetSwitchValue(switches::kCachePath).ToString() |
| << file_util::kPathSep << time(NULL); |
| CefString(&settings.cache_path) = ss.str(); |
| } |
| } |
| |
| return CefRequestContext::CreateContext(settings, |
| new ClientRequestContextHandler); |
| } |
| |
| // All browsers will share the global request context. |
| if (!shared_request_context_.get()) { |
| shared_request_context_ = CefRequestContext::CreateContext( |
| CefRequestContext::GetGlobalContext(), new ClientRequestContextHandler); |
| } |
| return shared_request_context_; |
| } |
| |
| scoped_refptr<ImageCache> RootWindowManager::GetImageCache() { |
| CEF_REQUIRE_UI_THREAD(); |
| |
| if (!image_cache_) { |
| image_cache_ = new ImageCache; |
| } |
| return image_cache_; |
| } |
| |
| void RootWindowManager::OnTest(RootWindow* root_window, int test_id) { |
| REQUIRE_MAIN_THREAD(); |
| |
| test_runner::RunTest(root_window->GetBrowser(), test_id); |
| } |
| |
| void RootWindowManager::OnExit(RootWindow* root_window) { |
| REQUIRE_MAIN_THREAD(); |
| |
| CloseAllWindows(false); |
| } |
| |
| void RootWindowManager::OnRootWindowDestroyed(RootWindow* root_window) { |
| REQUIRE_MAIN_THREAD(); |
| |
| RootWindowSet::iterator it = root_windows_.find(root_window); |
| DCHECK(it != root_windows_.end()); |
| if (it != root_windows_.end()) |
| root_windows_.erase(it); |
| |
| if (root_window == active_root_window_) { |
| active_root_window_ = nullptr; |
| |
| base::AutoLock lock_scope(active_browser_lock_); |
| active_browser_ = nullptr; |
| } |
| |
| if (terminate_when_all_windows_closed_ && root_windows_.empty()) { |
| // All windows have closed. Clean up on the UI thread. |
| CefPostTask(TID_UI, base::Bind(&RootWindowManager::CleanupOnUIThread, |
| base::Unretained(this))); |
| } |
| } |
| |
| void RootWindowManager::OnRootWindowActivated(RootWindow* root_window) { |
| REQUIRE_MAIN_THREAD(); |
| |
| if (root_window->WithExtension()) { |
| // We don't want extension apps to become the active RootWindow. |
| return; |
| } |
| |
| if (root_window == active_root_window_) |
| return; |
| |
| active_root_window_ = root_window; |
| |
| { |
| base::AutoLock lock_scope(active_browser_lock_); |
| // May be NULL at this point, in which case we'll make the association in |
| // OnBrowserCreated. |
| active_browser_ = active_root_window_->GetBrowser(); |
| } |
| } |
| |
| void RootWindowManager::OnBrowserCreated(RootWindow* root_window, |
| CefRefPtr<CefBrowser> browser) { |
| REQUIRE_MAIN_THREAD(); |
| |
| if (root_window == active_root_window_) { |
| base::AutoLock lock_scope(active_browser_lock_); |
| active_browser_ = browser; |
| } |
| } |
| |
| void RootWindowManager::CreateExtensionWindow( |
| CefRefPtr<CefExtension> extension, |
| const CefRect& source_bounds, |
| CefRefPtr<CefWindow> parent_window, |
| const base::Closure& close_callback, |
| bool with_osr) { |
| REQUIRE_MAIN_THREAD(); |
| |
| if (!HasRootWindowAsExtension(extension)) { |
| CreateRootWindowAsExtension(extension, source_bounds, parent_window, |
| close_callback, false, with_osr); |
| } |
| } |
| |
| void RootWindowManager::CleanupOnUIThread() { |
| CEF_REQUIRE_UI_THREAD(); |
| |
| if (temp_window_) { |
| // TempWindow must be destroyed on the UI thread. |
| temp_window_.reset(nullptr); |
| } |
| |
| if (image_cache_) { |
| image_cache_ = nullptr; |
| } |
| |
| // Quit the main message loop. |
| MainMessageLoop::Get()->Quit(); |
| } |
| |
| } // namespace client |