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