1 // Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "tests/cefclient/browser/root_window_manager.h"
6
7 #include <sstream>
8
9 #include "include/base/cef_callback.h"
10 #include "include/base/cef_logging.h"
11 #include "include/wrapper/cef_helpers.h"
12 #include "tests/cefclient/browser/main_context.h"
13 #include "tests/cefclient/browser/test_runner.h"
14 #include "tests/shared/browser/extension_util.h"
15 #include "tests/shared/browser/file_util.h"
16 #include "tests/shared/browser/resource_util.h"
17 #include "tests/shared/common/client_switches.h"
18
19 namespace client {
20
21 namespace {
22
23 class ClientRequestContextHandler : public CefRequestContextHandler,
24 public CefExtensionHandler {
25 public:
ClientRequestContextHandler()26 ClientRequestContextHandler() {}
27
28 // CefRequestContextHandler methods:
OnRequestContextInitialized(CefRefPtr<CefRequestContext> request_context)29 void OnRequestContextInitialized(
30 CefRefPtr<CefRequestContext> request_context) override {
31 CEF_REQUIRE_UI_THREAD();
32
33 CefRefPtr<CefCommandLine> command_line =
34 CefCommandLine::GetGlobalCommandLine();
35 if (command_line->HasSwitch(switches::kLoadExtension)) {
36 if (MainContext::Get()
37 ->GetRootWindowManager()
38 ->request_context_per_browser()) {
39 // The example extension loading implementation requires all browsers to
40 // share the same request context.
41 LOG(ERROR)
42 << "Cannot mix --load-extension and --request-context-per-browser";
43 return;
44 }
45
46 // Load one or more extension paths specified on the command-line and
47 // delimited with semicolon.
48 const std::string& extension_path =
49 command_line->GetSwitchValue(switches::kLoadExtension);
50 if (!extension_path.empty()) {
51 std::string part;
52 std::istringstream f(extension_path);
53 while (getline(f, part, ';')) {
54 if (!part.empty())
55 extension_util::LoadExtension(request_context, part, this);
56 }
57 }
58 }
59 }
60
61 // CefExtensionHandler methods:
OnExtensionLoaded(CefRefPtr<CefExtension> extension)62 void OnExtensionLoaded(CefRefPtr<CefExtension> extension) override {
63 CEF_REQUIRE_UI_THREAD();
64 MainContext::Get()->GetRootWindowManager()->AddExtension(extension);
65 }
66
GetActiveBrowser(CefRefPtr<CefExtension> extension,CefRefPtr<CefBrowser> browser,bool include_incognito)67 CefRefPtr<CefBrowser> GetActiveBrowser(CefRefPtr<CefExtension> extension,
68 CefRefPtr<CefBrowser> browser,
69 bool include_incognito) override {
70 CEF_REQUIRE_UI_THREAD();
71
72 // Return the browser for the active/foreground window.
73 CefRefPtr<CefBrowser> active_browser =
74 MainContext::Get()->GetRootWindowManager()->GetActiveBrowser();
75 if (!active_browser) {
76 LOG(WARNING)
77 << "No active browser available for extension "
78 << browser->GetHost()->GetExtension()->GetIdentifier().ToString();
79 } else {
80 // The active browser should not be hosting an extension.
81 DCHECK(!active_browser->GetHost()->GetExtension());
82 }
83 return active_browser;
84 }
85
86 private:
87 IMPLEMENT_REFCOUNTING(ClientRequestContextHandler);
88 DISALLOW_COPY_AND_ASSIGN(ClientRequestContextHandler);
89 };
90
91 } // namespace
92
RootWindowManager(bool terminate_when_all_windows_closed)93 RootWindowManager::RootWindowManager(bool terminate_when_all_windows_closed)
94 : terminate_when_all_windows_closed_(terminate_when_all_windows_closed) {
95 CefRefPtr<CefCommandLine> command_line =
96 CefCommandLine::GetGlobalCommandLine();
97 DCHECK(command_line.get());
98 request_context_per_browser_ =
99 command_line->HasSwitch(switches::kRequestContextPerBrowser);
100 request_context_shared_cache_ =
101 command_line->HasSwitch(switches::kRequestContextSharedCache);
102 }
103
~RootWindowManager()104 RootWindowManager::~RootWindowManager() {
105 // All root windows should already have been destroyed.
106 DCHECK(root_windows_.empty());
107 }
108
CreateRootWindow(std::unique_ptr<RootWindowConfig> config)109 scoped_refptr<RootWindow> RootWindowManager::CreateRootWindow(
110 std::unique_ptr<RootWindowConfig> config) {
111 CefBrowserSettings settings;
112 MainContext::Get()->PopulateBrowserSettings(&settings);
113
114 scoped_refptr<RootWindow> root_window =
115 RootWindow::Create(MainContext::Get()->UseViews());
116 root_window->Init(this, std::move(config), settings);
117
118 // Store a reference to the root window on the main thread.
119 OnRootWindowCreated(root_window);
120
121 return root_window;
122 }
123
CreateRootWindowAsPopup(bool with_controls,bool with_osr,const CefPopupFeatures & popupFeatures,CefWindowInfo & windowInfo,CefRefPtr<CefClient> & client,CefBrowserSettings & settings)124 scoped_refptr<RootWindow> RootWindowManager::CreateRootWindowAsPopup(
125 bool with_controls,
126 bool with_osr,
127 const CefPopupFeatures& popupFeatures,
128 CefWindowInfo& windowInfo,
129 CefRefPtr<CefClient>& client,
130 CefBrowserSettings& settings) {
131 CEF_REQUIRE_UI_THREAD();
132
133 if (!temp_window_) {
134 // TempWindow must be created on the UI thread.
135 temp_window_.reset(new TempWindow());
136 }
137
138 MainContext::Get()->PopulateBrowserSettings(&settings);
139
140 scoped_refptr<RootWindow> root_window =
141 RootWindow::Create(MainContext::Get()->UseViews());
142 root_window->InitAsPopup(this, with_controls, with_osr, popupFeatures,
143 windowInfo, client, settings);
144
145 // Store a reference to the root window on the main thread.
146 OnRootWindowCreated(root_window);
147
148 return root_window;
149 }
150
CreateRootWindowAsExtension(CefRefPtr<CefExtension> extension,const CefRect & source_bounds,CefRefPtr<CefWindow> parent_window,base::OnceClosure close_callback,bool with_controls,bool with_osr)151 scoped_refptr<RootWindow> RootWindowManager::CreateRootWindowAsExtension(
152 CefRefPtr<CefExtension> extension,
153 const CefRect& source_bounds,
154 CefRefPtr<CefWindow> parent_window,
155 base::OnceClosure close_callback,
156 bool with_controls,
157 bool with_osr) {
158 const std::string& extension_url = extension_util::GetExtensionURL(extension);
159 if (extension_url.empty()) {
160 NOTREACHED() << "Extension cannot be loaded directly.";
161 return nullptr;
162 }
163
164 // Create an initially hidden browser window that loads the extension URL.
165 // We'll show the window when the desired size becomes available via
166 // ClientHandler::OnAutoResize.
167 auto config = std::make_unique<RootWindowConfig>();
168 config->with_controls = with_controls;
169 config->with_osr = with_osr;
170 config->with_extension = true;
171 config->initially_hidden = true;
172 config->source_bounds = source_bounds;
173 config->parent_window = parent_window;
174 config->close_callback = std::move(close_callback);
175 config->url = extension_url;
176 return CreateRootWindow(std::move(config));
177 }
178
HasRootWindowAsExtension(CefRefPtr<CefExtension> extension)179 bool RootWindowManager::HasRootWindowAsExtension(
180 CefRefPtr<CefExtension> extension) {
181 REQUIRE_MAIN_THREAD();
182
183 for (auto root_window : root_windows_) {
184 if (!root_window->WithExtension())
185 continue;
186
187 CefRefPtr<CefBrowser> browser = root_window->GetBrowser();
188 if (!browser)
189 continue;
190
191 CefRefPtr<CefExtension> browser_extension =
192 browser->GetHost()->GetExtension();
193 DCHECK(browser_extension);
194 if (browser_extension->GetIdentifier() == extension->GetIdentifier())
195 return true;
196 }
197
198 return false;
199 }
200
GetWindowForBrowser(int browser_id) const201 scoped_refptr<RootWindow> RootWindowManager::GetWindowForBrowser(
202 int browser_id) const {
203 REQUIRE_MAIN_THREAD();
204
205 for (auto root_window : root_windows_) {
206 CefRefPtr<CefBrowser> browser = root_window->GetBrowser();
207 if (browser.get() && browser->GetIdentifier() == browser_id)
208 return root_window;
209 }
210 return nullptr;
211 }
212
GetActiveRootWindow() const213 scoped_refptr<RootWindow> RootWindowManager::GetActiveRootWindow() const {
214 REQUIRE_MAIN_THREAD();
215 return active_root_window_;
216 }
217
GetActiveBrowser() const218 CefRefPtr<CefBrowser> RootWindowManager::GetActiveBrowser() const {
219 base::AutoLock lock_scope(active_browser_lock_);
220 return active_browser_;
221 }
222
CloseAllWindows(bool force)223 void RootWindowManager::CloseAllWindows(bool force) {
224 if (!CURRENTLY_ON_MAIN_THREAD()) {
225 // Execute this method on the main thread.
226 MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::CloseAllWindows,
227 base::Unretained(this), force));
228 return;
229 }
230
231 if (root_windows_.empty())
232 return;
233
234 // Use a copy of |root_windows_| because the original set may be modified
235 // in OnRootWindowDestroyed while iterating.
236 RootWindowSet root_windows = root_windows_;
237
238 for (auto root_window : root_windows) {
239 root_window->Close(force);
240 }
241 }
242
AddExtension(CefRefPtr<CefExtension> extension)243 void RootWindowManager::AddExtension(CefRefPtr<CefExtension> extension) {
244 if (!CURRENTLY_ON_MAIN_THREAD()) {
245 // Execute this method on the main thread.
246 MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::AddExtension,
247 base::Unretained(this), extension));
248 return;
249 }
250
251 // Don't track extensions that can't be loaded directly.
252 if (extension_util::GetExtensionURL(extension).empty())
253 return;
254
255 // Don't add the same extension multiple times.
256 ExtensionSet::const_iterator it = extensions_.begin();
257 for (; it != extensions_.end(); ++it) {
258 if ((*it)->GetIdentifier() == extension->GetIdentifier())
259 return;
260 }
261
262 extensions_.insert(extension);
263 NotifyExtensionsChanged();
264 }
265
OnRootWindowCreated(scoped_refptr<RootWindow> root_window)266 void RootWindowManager::OnRootWindowCreated(
267 scoped_refptr<RootWindow> root_window) {
268 if (!CURRENTLY_ON_MAIN_THREAD()) {
269 // Execute this method on the main thread.
270 MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::OnRootWindowCreated,
271 base::Unretained(this), root_window));
272 return;
273 }
274
275 root_windows_.insert(root_window);
276 if (!root_window->WithExtension()) {
277 root_window->OnExtensionsChanged(extensions_);
278
279 if (root_windows_.size() == 1U) {
280 // The first non-extension root window should be considered the active
281 // window.
282 OnRootWindowActivated(root_window.get());
283 }
284 }
285 }
286
NotifyExtensionsChanged()287 void RootWindowManager::NotifyExtensionsChanged() {
288 REQUIRE_MAIN_THREAD();
289
290 for (auto root_window : root_windows_) {
291 if (!root_window->WithExtension())
292 root_window->OnExtensionsChanged(extensions_);
293 }
294 }
295
GetRequestContext(RootWindow * root_window)296 CefRefPtr<CefRequestContext> RootWindowManager::GetRequestContext(
297 RootWindow* root_window) {
298 REQUIRE_MAIN_THREAD();
299
300 if (request_context_per_browser_) {
301 // Create a new request context for each browser.
302 CefRequestContextSettings settings;
303
304 CefRefPtr<CefCommandLine> command_line =
305 CefCommandLine::GetGlobalCommandLine();
306 if (command_line->HasSwitch(switches::kCachePath)) {
307 if (request_context_shared_cache_) {
308 // Give each browser the same cache path. The resulting context objects
309 // will share the same storage internally.
310 CefString(&settings.cache_path) =
311 command_line->GetSwitchValue(switches::kCachePath);
312 } else {
313 // Give each browser a unique cache path. This will create completely
314 // isolated context objects.
315 std::stringstream ss;
316 ss << command_line->GetSwitchValue(switches::kCachePath).ToString()
317 << file_util::kPathSep << time(nullptr);
318 CefString(&settings.cache_path) = ss.str();
319 }
320 }
321
322 return CefRequestContext::CreateContext(settings,
323 new ClientRequestContextHandler);
324 }
325
326 // All browsers will share the global request context.
327 if (!shared_request_context_.get()) {
328 shared_request_context_ = CefRequestContext::CreateContext(
329 CefRequestContext::GetGlobalContext(), new ClientRequestContextHandler);
330 }
331 return shared_request_context_;
332 }
333
GetImageCache()334 scoped_refptr<ImageCache> RootWindowManager::GetImageCache() {
335 CEF_REQUIRE_UI_THREAD();
336
337 if (!image_cache_) {
338 image_cache_ = new ImageCache;
339 }
340 return image_cache_;
341 }
342
OnTest(RootWindow * root_window,int test_id)343 void RootWindowManager::OnTest(RootWindow* root_window, int test_id) {
344 REQUIRE_MAIN_THREAD();
345
346 test_runner::RunTest(root_window->GetBrowser(), test_id);
347 }
348
OnExit(RootWindow * root_window)349 void RootWindowManager::OnExit(RootWindow* root_window) {
350 REQUIRE_MAIN_THREAD();
351
352 CloseAllWindows(false);
353 }
354
OnRootWindowDestroyed(RootWindow * root_window)355 void RootWindowManager::OnRootWindowDestroyed(RootWindow* root_window) {
356 REQUIRE_MAIN_THREAD();
357
358 RootWindowSet::iterator it = root_windows_.find(root_window);
359 DCHECK(it != root_windows_.end());
360 if (it != root_windows_.end())
361 root_windows_.erase(it);
362
363 if (root_window == active_root_window_) {
364 active_root_window_ = nullptr;
365
366 base::AutoLock lock_scope(active_browser_lock_);
367 active_browser_ = nullptr;
368 }
369
370 if (terminate_when_all_windows_closed_ && root_windows_.empty()) {
371 // All windows have closed. Clean up on the UI thread.
372 CefPostTask(TID_UI, base::BindOnce(&RootWindowManager::CleanupOnUIThread,
373 base::Unretained(this)));
374 }
375 }
376
OnRootWindowActivated(RootWindow * root_window)377 void RootWindowManager::OnRootWindowActivated(RootWindow* root_window) {
378 REQUIRE_MAIN_THREAD();
379
380 if (root_window->WithExtension()) {
381 // We don't want extension apps to become the active RootWindow.
382 return;
383 }
384
385 if (root_window == active_root_window_)
386 return;
387
388 active_root_window_ = root_window;
389
390 {
391 base::AutoLock lock_scope(active_browser_lock_);
392 // May be nullptr at this point, in which case we'll make the association in
393 // OnBrowserCreated.
394 active_browser_ = active_root_window_->GetBrowser();
395 }
396 }
397
OnBrowserCreated(RootWindow * root_window,CefRefPtr<CefBrowser> browser)398 void RootWindowManager::OnBrowserCreated(RootWindow* root_window,
399 CefRefPtr<CefBrowser> browser) {
400 REQUIRE_MAIN_THREAD();
401
402 if (root_window == active_root_window_) {
403 base::AutoLock lock_scope(active_browser_lock_);
404 active_browser_ = browser;
405 }
406 }
407
CreateExtensionWindow(CefRefPtr<CefExtension> extension,const CefRect & source_bounds,CefRefPtr<CefWindow> parent_window,base::OnceClosure close_callback,bool with_osr)408 void RootWindowManager::CreateExtensionWindow(
409 CefRefPtr<CefExtension> extension,
410 const CefRect& source_bounds,
411 CefRefPtr<CefWindow> parent_window,
412 base::OnceClosure close_callback,
413 bool with_osr) {
414 REQUIRE_MAIN_THREAD();
415
416 if (!HasRootWindowAsExtension(extension)) {
417 CreateRootWindowAsExtension(extension, source_bounds, parent_window,
418 std::move(close_callback), false, with_osr);
419 }
420 }
421
CleanupOnUIThread()422 void RootWindowManager::CleanupOnUIThread() {
423 CEF_REQUIRE_UI_THREAD();
424
425 if (temp_window_) {
426 // TempWindow must be destroyed on the UI thread.
427 temp_window_.reset(nullptr);
428 }
429
430 if (image_cache_) {
431 image_cache_ = nullptr;
432 }
433
434 // Quit the main message loop.
435 MainMessageLoop::Get()->Quit();
436 }
437
438 } // namespace client
439