1 // Copyright 2014 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 "extensions/browser/app_window/app_window.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "base/bind.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/task_runner.h"
19 #include "base/threading/thread_task_runner_handle.h"
20 #include "base/values.h"
21 #include "build/build_config.h"
22 #include "build/chromeos_buildflags.h"
23 #include "components/web_modal/web_contents_modal_dialog_manager.h"
24 #include "content/public/browser/browser_context.h"
25 #include "content/public/browser/file_select_listener.h"
26 #include "content/public/browser/invalidate_type.h"
27 #include "content/public/browser/keyboard_event_processing_result.h"
28 #include "content/public/browser/navigation_entry.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "content/public/browser/render_widget_host.h"
31 #include "content/public/browser/web_contents.h"
32 #include "extensions/browser/app_window/app_delegate.h"
33 #include "extensions/browser/app_window/app_web_contents_helper.h"
34 #include "extensions/browser/app_window/app_window_client.h"
35 #include "extensions/browser/app_window/app_window_geometry_cache.h"
36 #include "extensions/browser/app_window/app_window_registry.h"
37 #include "extensions/browser/app_window/native_app_window.h"
38 #include "extensions/browser/app_window/size_constraints.h"
39 #include "extensions/browser/extension_registry.h"
40 #include "extensions/browser/extension_system.h"
41 #include "extensions/browser/extension_web_contents_observer.h"
42 #include "extensions/browser/extensions_browser_client.h"
43 #include "extensions/browser/notification_types.h"
44 #include "extensions/browser/process_manager.h"
45 #include "extensions/browser/suggest_permission_util.h"
46 #include "extensions/browser/view_type_utils.h"
47 #include "extensions/common/draggable_region.h"
48 #include "extensions/common/extension.h"
49 #include "extensions/common/extension_messages.h"
50 #include "extensions/common/manifest_handlers/icons_handler.h"
51 #include "extensions/common/permissions/permissions_data.h"
52 #include "ipc/ipc_message_macros.h"
53 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
54 #include "third_party/skia/include/core/SkBitmap.h"
55 #include "third_party/skia/include/core/SkRegion.h"
56 #include "ui/display/display.h"
57 #include "ui/display/screen.h"
58 #include "ui/events/keycodes/keyboard_codes.h"
59
60 #if !defined(OS_MAC)
61 #include "components/prefs/pref_service.h"
62 #include "extensions/browser/pref_names.h"
63 #endif
64
65 using blink::mojom::ConsoleMessageLevel;
66 using content::BrowserContext;
67 using content::WebContents;
68 using web_modal::WebContentsModalDialogHost;
69 using web_modal::WebContentsModalDialogManager;
70
71 namespace extensions {
72
73 namespace {
74
75 const int kDefaultWidth = 512;
76 const int kDefaultHeight = 384;
77
SetConstraintProperty(const std::string & name,int value,base::DictionaryValue * bounds_properties)78 void SetConstraintProperty(const std::string& name,
79 int value,
80 base::DictionaryValue* bounds_properties) {
81 if (value != SizeConstraints::kUnboundedSize)
82 bounds_properties->SetInteger(name, value);
83 else
84 bounds_properties->Set(name, std::make_unique<base::Value>());
85 }
86
SetBoundsProperties(const gfx::Rect & bounds,const gfx::Size & min_size,const gfx::Size & max_size,const std::string & bounds_name,base::DictionaryValue * window_properties)87 void SetBoundsProperties(const gfx::Rect& bounds,
88 const gfx::Size& min_size,
89 const gfx::Size& max_size,
90 const std::string& bounds_name,
91 base::DictionaryValue* window_properties) {
92 std::unique_ptr<base::DictionaryValue> bounds_properties(
93 new base::DictionaryValue());
94
95 bounds_properties->SetInteger("left", bounds.x());
96 bounds_properties->SetInteger("top", bounds.y());
97 bounds_properties->SetInteger("width", bounds.width());
98 bounds_properties->SetInteger("height", bounds.height());
99
100 SetConstraintProperty("minWidth", min_size.width(), bounds_properties.get());
101 SetConstraintProperty(
102 "minHeight", min_size.height(), bounds_properties.get());
103 SetConstraintProperty("maxWidth", max_size.width(), bounds_properties.get());
104 SetConstraintProperty(
105 "maxHeight", max_size.height(), bounds_properties.get());
106
107 window_properties->Set(bounds_name, std::move(bounds_properties));
108 }
109
110 // Combines the constraints of the content and window, and returns constraints
111 // for the window.
GetCombinedWindowConstraints(const gfx::Size & window_constraints,const gfx::Size & content_constraints,const gfx::Insets & frame_insets)112 gfx::Size GetCombinedWindowConstraints(const gfx::Size& window_constraints,
113 const gfx::Size& content_constraints,
114 const gfx::Insets& frame_insets) {
115 gfx::Size combined_constraints(window_constraints);
116 if (content_constraints.width() > 0) {
117 combined_constraints.set_width(
118 content_constraints.width() + frame_insets.width());
119 }
120 if (content_constraints.height() > 0) {
121 combined_constraints.set_height(
122 content_constraints.height() + frame_insets.height());
123 }
124 return combined_constraints;
125 }
126
127 // Combines the constraints of the content and window, and returns constraints
128 // for the content.
GetCombinedContentConstraints(const gfx::Size & window_constraints,const gfx::Size & content_constraints,const gfx::Insets & frame_insets)129 gfx::Size GetCombinedContentConstraints(const gfx::Size& window_constraints,
130 const gfx::Size& content_constraints,
131 const gfx::Insets& frame_insets) {
132 gfx::Size combined_constraints(content_constraints);
133 if (window_constraints.width() > 0) {
134 combined_constraints.set_width(
135 std::max(0, window_constraints.width() - frame_insets.width()));
136 }
137 if (window_constraints.height() > 0) {
138 combined_constraints.set_height(
139 std::max(0, window_constraints.height() - frame_insets.height()));
140 }
141 return combined_constraints;
142 }
143
144 } // namespace
145
146 // AppWindow::BoundsSpecification
147
148 const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN;
149
BoundsSpecification()150 AppWindow::BoundsSpecification::BoundsSpecification()
151 : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {}
152
~BoundsSpecification()153 AppWindow::BoundsSpecification::~BoundsSpecification() {}
154
ResetBounds()155 void AppWindow::BoundsSpecification::ResetBounds() {
156 bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0);
157 }
158
159 // AppWindow::CreateParams
160
CreateParams()161 AppWindow::CreateParams::CreateParams()
162 : window_type(AppWindow::WINDOW_TYPE_DEFAULT),
163 frame(AppWindow::FRAME_CHROME),
164 has_frame_color(false),
165 active_frame_color(SK_ColorBLACK),
166 inactive_frame_color(SK_ColorBLACK),
167 alpha_enabled(false),
168 is_ime_window(false),
169 creator_process_id(0),
170 state(ui::SHOW_STATE_DEFAULT),
171 hidden(false),
172 resizable(true),
173 focused(true),
174 always_on_top(false),
175 visible_on_all_workspaces(false),
176 show_on_lock_screen(false),
177 show_in_shelf(false) {}
178
179 AppWindow::CreateParams::CreateParams(const CreateParams& other) = default;
180
~CreateParams()181 AppWindow::CreateParams::~CreateParams() {}
182
GetInitialWindowBounds(const gfx::Insets & frame_insets) const183 gfx::Rect AppWindow::CreateParams::GetInitialWindowBounds(
184 const gfx::Insets& frame_insets) const {
185 // Combine into a single window bounds.
186 gfx::Rect combined_bounds(window_spec.bounds);
187 if (content_spec.bounds.x() != BoundsSpecification::kUnspecifiedPosition)
188 combined_bounds.set_x(content_spec.bounds.x() - frame_insets.left());
189 if (content_spec.bounds.y() != BoundsSpecification::kUnspecifiedPosition)
190 combined_bounds.set_y(content_spec.bounds.y() - frame_insets.top());
191 if (content_spec.bounds.width() > 0) {
192 combined_bounds.set_width(
193 content_spec.bounds.width() + frame_insets.width());
194 }
195 if (content_spec.bounds.height() > 0) {
196 combined_bounds.set_height(
197 content_spec.bounds.height() + frame_insets.height());
198 }
199
200 // Constrain the bounds.
201 SizeConstraints constraints(
202 GetCombinedWindowConstraints(
203 window_spec.minimum_size, content_spec.minimum_size, frame_insets),
204 GetCombinedWindowConstraints(
205 window_spec.maximum_size, content_spec.maximum_size, frame_insets));
206 combined_bounds.set_size(constraints.ClampSize(combined_bounds.size()));
207
208 return combined_bounds;
209 }
210
GetContentMinimumSize(const gfx::Insets & frame_insets) const211 gfx::Size AppWindow::CreateParams::GetContentMinimumSize(
212 const gfx::Insets& frame_insets) const {
213 return GetCombinedContentConstraints(window_spec.minimum_size,
214 content_spec.minimum_size,
215 frame_insets);
216 }
217
GetContentMaximumSize(const gfx::Insets & frame_insets) const218 gfx::Size AppWindow::CreateParams::GetContentMaximumSize(
219 const gfx::Insets& frame_insets) const {
220 return GetCombinedContentConstraints(window_spec.maximum_size,
221 content_spec.maximum_size,
222 frame_insets);
223 }
224
GetWindowMinimumSize(const gfx::Insets & frame_insets) const225 gfx::Size AppWindow::CreateParams::GetWindowMinimumSize(
226 const gfx::Insets& frame_insets) const {
227 return GetCombinedWindowConstraints(window_spec.minimum_size,
228 content_spec.minimum_size,
229 frame_insets);
230 }
231
GetWindowMaximumSize(const gfx::Insets & frame_insets) const232 gfx::Size AppWindow::CreateParams::GetWindowMaximumSize(
233 const gfx::Insets& frame_insets) const {
234 return GetCombinedWindowConstraints(window_spec.maximum_size,
235 content_spec.maximum_size,
236 frame_insets);
237 }
238
239 // AppWindow
240
AppWindow(BrowserContext * context,AppDelegate * app_delegate,const Extension * extension)241 AppWindow::AppWindow(BrowserContext* context,
242 AppDelegate* app_delegate,
243 const Extension* extension)
244 : browser_context_(context),
245 extension_id_(extension->id()),
246 session_id_(SessionID::NewUnique()),
247 app_delegate_(app_delegate) {
248 ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
249 CHECK(!client->IsGuestSession(context) || context->IsOffTheRecord())
250 << "Only off the record window may be opened in the guest mode.";
251 }
252
Init(const GURL & url,AppWindowContents * app_window_contents,content::RenderFrameHost * creator_frame,const CreateParams & params)253 void AppWindow::Init(const GURL& url,
254 AppWindowContents* app_window_contents,
255 content::RenderFrameHost* creator_frame,
256 const CreateParams& params) {
257 // Initialize the render interface and web contents
258 app_window_contents_.reset(app_window_contents);
259 app_window_contents_->Initialize(browser_context(), creator_frame, url);
260
261 initial_url_ = url;
262
263 content::WebContentsObserver::Observe(web_contents());
264 SetViewType(web_contents(), VIEW_TYPE_APP_WINDOW);
265 app_delegate_->InitWebContents(web_contents());
266
267 ExtensionWebContentsObserver::GetForWebContents(web_contents())->
268 dispatcher()->set_delegate(this);
269
270 WebContentsModalDialogManager::CreateForWebContents(web_contents());
271
272 web_contents()->SetDelegate(this);
273 WebContentsModalDialogManager::FromWebContents(web_contents())
274 ->SetDelegate(this);
275
276 // Initialize the window
277 CreateParams new_params = LoadDefaults(params);
278 window_type_ = new_params.window_type;
279 window_key_ = new_params.window_key;
280
281 // Windows cannot be always-on-top in fullscreen mode for security reasons.
282 cached_always_on_top_ = new_params.always_on_top;
283 if (new_params.state == ui::SHOW_STATE_FULLSCREEN &&
284 !ExtensionsBrowserClient::Get()->IsScreensaverInDemoMode(
285 extension_id())) {
286 new_params.always_on_top = false;
287 }
288
289 requested_alpha_enabled_ = new_params.alpha_enabled;
290 is_ime_window_ = params.is_ime_window;
291 show_on_lock_screen_ = params.show_on_lock_screen;
292 show_in_shelf_ = params.show_in_shelf;
293
294 AppWindowClient* app_window_client = AppWindowClient::Get();
295 native_app_window_.reset(
296 app_window_client->CreateNativeAppWindow(this, &new_params));
297
298 helper_.reset(new AppWebContentsHelper(
299 browser_context_, extension_id_, web_contents(), app_delegate_.get()));
300
301 native_app_window_->UpdateWindowIcon();
302
303 if (params.window_icon_url.is_valid())
304 SetAppIconUrl(params.window_icon_url);
305
306 AppWindowRegistry::Get(browser_context_)->AddAppWindow(this);
307
308 if (new_params.hidden) {
309 // Although the window starts hidden by default, calling Hide() here
310 // notifies observers of the window being hidden.
311 Hide();
312 } else {
313 Show(SHOW_INACTIVE);
314
315 // These states may cause the window to show, so they are ignored if the
316 // window is initially hidden.
317 if (new_params.state == ui::SHOW_STATE_FULLSCREEN)
318 Fullscreen();
319 else if (new_params.state == ui::SHOW_STATE_MAXIMIZED)
320 Maximize();
321 else if (new_params.state == ui::SHOW_STATE_MINIMIZED)
322 Minimize();
323
324 Show(new_params.focused ? SHOW_ACTIVE : SHOW_INACTIVE);
325 }
326
327 OnNativeWindowChanged();
328
329 ExtensionRegistry::Get(browser_context_)->AddObserver(this);
330
331 // Close when the browser process is exiting.
332 app_delegate_->SetTerminatingCallback(base::BindOnce(
333 &NativeAppWindow::Close, base::Unretained(native_app_window_.get())));
334
335 app_window_contents_->LoadContents(new_params.creator_process_id);
336 }
337
~AppWindow()338 AppWindow::~AppWindow() {
339 ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
340 }
341
RequestMediaAccessPermission(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback)342 void AppWindow::RequestMediaAccessPermission(
343 content::WebContents* web_contents,
344 const content::MediaStreamRequest& request,
345 content::MediaResponseCallback callback) {
346 DCHECK_EQ(AppWindow::web_contents(), web_contents);
347 helper_->RequestMediaAccessPermission(request, std::move(callback));
348 }
349
CheckMediaAccessPermission(content::RenderFrameHost * render_frame_host,const GURL & security_origin,blink::mojom::MediaStreamType type)350 bool AppWindow::CheckMediaAccessPermission(
351 content::RenderFrameHost* render_frame_host,
352 const GURL& security_origin,
353 blink::mojom::MediaStreamType type) {
354 DCHECK_EQ(web_contents(),
355 content::WebContents::FromRenderFrameHost(render_frame_host)
356 ->GetOutermostWebContents());
357 return helper_->CheckMediaAccessPermission(render_frame_host, security_origin,
358 type);
359 }
360
OpenURLFromTab(WebContents * source,const content::OpenURLParams & params)361 WebContents* AppWindow::OpenURLFromTab(WebContents* source,
362 const content::OpenURLParams& params) {
363 DCHECK_EQ(web_contents(), source);
364 return helper_->OpenURLFromTab(params);
365 }
366
AddNewContents(WebContents * source,std::unique_ptr<WebContents> new_contents,const GURL & target_url,WindowOpenDisposition disposition,const gfx::Rect & initial_rect,bool user_gesture,bool * was_blocked)367 void AppWindow::AddNewContents(WebContents* source,
368 std::unique_ptr<WebContents> new_contents,
369 const GURL& target_url,
370 WindowOpenDisposition disposition,
371 const gfx::Rect& initial_rect,
372 bool user_gesture,
373 bool* was_blocked) {
374 DCHECK(new_contents->GetBrowserContext() == browser_context_);
375 app_delegate_->AddNewContents(browser_context_, std::move(new_contents),
376 target_url, disposition, initial_rect,
377 user_gesture);
378 }
379
PreHandleKeyboardEvent(content::WebContents * source,const content::NativeWebKeyboardEvent & event)380 content::KeyboardEventProcessingResult AppWindow::PreHandleKeyboardEvent(
381 content::WebContents* source,
382 const content::NativeWebKeyboardEvent& event) {
383 const Extension* extension = GetExtension();
384 if (!extension)
385 return content::KeyboardEventProcessingResult::NOT_HANDLED;
386
387 // Here, we can handle a key event before the content gets it. When we are
388 // fullscreen and it is not forced, we want to allow the user to leave
389 // when ESC is pressed.
390 // However, if the application has the "overrideEscFullscreen" permission, we
391 // should let it override that behavior.
392 // ::HandleKeyboardEvent() will only be called if the KeyEvent's default
393 // action is not prevented.
394 // Thus, we should handle the KeyEvent here only if the permission is not set.
395 if (event.windows_key_code == ui::VKEY_ESCAPE && IsFullscreen() &&
396 !IsForcedFullscreen() &&
397 !extension->permissions_data()->HasAPIPermission(
398 APIPermission::kOverrideEscFullscreen)) {
399 Restore();
400 return content::KeyboardEventProcessingResult::HANDLED;
401 }
402
403 return content::KeyboardEventProcessingResult::NOT_HANDLED;
404 }
405
HandleKeyboardEvent(WebContents * source,const content::NativeWebKeyboardEvent & event)406 bool AppWindow::HandleKeyboardEvent(
407 WebContents* source,
408 const content::NativeWebKeyboardEvent& event) {
409 // If the window is currently fullscreen and not forced, ESC should leave
410 // fullscreen. If this code is being called for ESC, that means that the
411 // KeyEvent's default behavior was not prevented by the content.
412 if (event.windows_key_code == ui::VKEY_ESCAPE && IsFullscreen() &&
413 !IsForcedFullscreen()) {
414 Restore();
415 return true;
416 }
417
418 return native_app_window_->HandleKeyboardEvent(event);
419 }
420
RequestToLockMouse(WebContents * web_contents,bool user_gesture,bool last_unlocked_by_target)421 void AppWindow::RequestToLockMouse(WebContents* web_contents,
422 bool user_gesture,
423 bool last_unlocked_by_target) {
424 DCHECK_EQ(AppWindow::web_contents(), web_contents);
425 helper_->RequestToLockMouse();
426 }
427
PreHandleGestureEvent(WebContents * source,const blink::WebGestureEvent & event)428 bool AppWindow::PreHandleGestureEvent(WebContents* source,
429 const blink::WebGestureEvent& event) {
430 return AppWebContentsHelper::ShouldSuppressGestureEvent(event);
431 }
432
TakeFocus(WebContents * source,bool reverse)433 bool AppWindow::TakeFocus(WebContents* source, bool reverse) {
434 return app_delegate_->TakeFocus(source, reverse);
435 }
436
EnterPictureInPicture(content::WebContents * web_contents,const viz::SurfaceId & surface_id,const gfx::Size & natural_size)437 content::PictureInPictureResult AppWindow::EnterPictureInPicture(
438 content::WebContents* web_contents,
439 const viz::SurfaceId& surface_id,
440 const gfx::Size& natural_size) {
441 return app_delegate_->EnterPictureInPicture(web_contents, surface_id,
442 natural_size);
443 }
444
ExitPictureInPicture()445 void AppWindow::ExitPictureInPicture() {
446 app_delegate_->ExitPictureInPicture();
447 }
448
ShouldShowStaleContentOnEviction(content::WebContents * source)449 bool AppWindow::ShouldShowStaleContentOnEviction(content::WebContents* source) {
450 #if BUILDFLAG(IS_CHROMEOS_ASH)
451 return true;
452 #else
453 return false;
454 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
455 }
456
OnMessageReceived(const IPC::Message & message,content::RenderFrameHost * render_frame_host)457 bool AppWindow::OnMessageReceived(const IPC::Message& message,
458 content::RenderFrameHost* render_frame_host) {
459 bool handled = true;
460 IPC_BEGIN_MESSAGE_MAP(AppWindow, message)
461 IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppWindowReady, OnAppWindowReady)
462 IPC_MESSAGE_UNHANDLED(handled = false)
463 IPC_END_MESSAGE_MAP()
464 return handled;
465 }
466
RenderViewCreated(content::RenderViewHost * render_view_host)467 void AppWindow::RenderViewCreated(content::RenderViewHost* render_view_host) {
468 app_delegate_->RenderViewCreated(render_view_host);
469 }
470
AddOnDidFinishFirstNavigationCallback(DidFinishFirstNavigationCallback callback)471 void AppWindow::AddOnDidFinishFirstNavigationCallback(
472 DidFinishFirstNavigationCallback callback) {
473 on_did_finish_first_navigation_callbacks_.push_back(std::move(callback));
474 }
475
OnDidFinishFirstNavigation()476 void AppWindow::OnDidFinishFirstNavigation() {
477 did_finish_first_navigation_ = true;
478 std::vector<DidFinishFirstNavigationCallback> callbacks;
479 std::swap(callbacks, on_did_finish_first_navigation_callbacks_);
480 for (auto&& callback : callbacks)
481 std::move(callback).Run(true /* did_finish */);
482 }
483
OnNativeClose()484 void AppWindow::OnNativeClose() {
485 AppWindowRegistry::Get(browser_context_)->RemoveAppWindow(this);
486
487 // Run pending |on_did_finish_first_navigation_callback_| so that
488 // AppWindowCreateFunction can respond with an error properly.
489 std::vector<DidFinishFirstNavigationCallback> callbacks;
490 std::swap(callbacks, on_did_finish_first_navigation_callbacks_);
491 // No "OnClosed" event on window creation error.
492 const bool send_onclosed = callbacks.empty();
493 for (auto&& callback : callbacks)
494 std::move(callback).Run(false /* did_finish */);
495
496 if (app_window_contents_) {
497 WebContentsModalDialogManager* modal_dialog_manager =
498 WebContentsModalDialogManager::FromWebContents(web_contents());
499 if (modal_dialog_manager) // May be null in unit tests.
500 modal_dialog_manager->SetDelegate(nullptr);
501 app_window_contents_->NativeWindowClosed(send_onclosed);
502 }
503
504 delete this;
505 }
506
OnNativeWindowChanged()507 void AppWindow::OnNativeWindowChanged() {
508 // This may be called during Init before |native_app_window_| is set.
509 if (!native_app_window_)
510 return;
511
512 #if defined(OS_MAC)
513 // On Mac the user can change the window's fullscreen state. If that has
514 // happened, update AppWindow's internal state.
515 if (native_app_window_->IsFullscreen()) {
516 if (!IsFullscreen())
517 fullscreen_types_ = FULLSCREEN_TYPE_OS;
518 } else {
519 fullscreen_types_ = FULLSCREEN_TYPE_NONE;
520 }
521
522 RestoreAlwaysOnTop(); // Same as in SetNativeWindowFullscreen.
523 #endif
524
525 SaveWindowPosition();
526
527 #if defined(OS_WIN)
528 if (cached_always_on_top_ && !IsFullscreen() &&
529 !native_app_window_->IsMaximized() &&
530 !native_app_window_->IsMinimized()) {
531 UpdateNativeAlwaysOnTop();
532 }
533 #endif
534
535 if (app_window_contents_)
536 app_window_contents_->NativeWindowChanged(native_app_window_.get());
537 }
538
OnNativeWindowActivated()539 void AppWindow::OnNativeWindowActivated() {
540 AppWindowRegistry::Get(browser_context_)->AppWindowActivated(this);
541 }
542
web_contents() const543 content::WebContents* AppWindow::web_contents() const {
544 if (app_window_contents_)
545 return app_window_contents_->GetWebContents();
546 return nullptr;
547 }
548
GetExtension() const549 const Extension* AppWindow::GetExtension() const {
550 return ExtensionRegistry::Get(browser_context_)
551 ->enabled_extensions()
552 .GetByID(extension_id_);
553 }
554
GetBaseWindow()555 NativeAppWindow* AppWindow::GetBaseWindow() { return native_app_window_.get(); }
556
GetNativeWindow()557 gfx::NativeWindow AppWindow::GetNativeWindow() {
558 return GetBaseWindow()->GetNativeWindow();
559 }
560
GetClientBounds() const561 gfx::Rect AppWindow::GetClientBounds() const {
562 gfx::Rect bounds = native_app_window_->GetBounds();
563 bounds.Inset(native_app_window_->GetFrameInsets());
564 return bounds;
565 }
566
GetTitle() const567 base::string16 AppWindow::GetTitle() const {
568 const Extension* extension = GetExtension();
569 if (!extension)
570 return base::string16();
571
572 // WebContents::GetTitle() will return the page's URL if there's no <title>
573 // specified. However, we'd prefer to show the name of the extension in that
574 // case, so we directly inspect the NavigationEntry's title.
575 base::string16 title;
576 content::NavigationEntry* entry = web_contents() ?
577 web_contents()->GetController().GetLastCommittedEntry() : nullptr;
578 if (!entry || entry->GetTitle().empty()) {
579 title = base::UTF8ToUTF16(extension->name());
580 } else {
581 title = web_contents()->GetTitle();
582 }
583 base::RemoveChars(title, base::ASCIIToUTF16("\n"), &title);
584 return title;
585 }
586
SetAppIconUrl(const GURL & url)587 void AppWindow::SetAppIconUrl(const GURL& url) {
588 app_icon_url_ = url;
589
590 // Don't start custom app icon loading in the case window is not ready yet.
591 // see crbug.com/788531.
592 if (!window_ready_)
593 return;
594
595 StartAppIconDownload();
596 }
597
UpdateShape(std::unique_ptr<ShapeRects> rects)598 void AppWindow::UpdateShape(std::unique_ptr<ShapeRects> rects) {
599 native_app_window_->UpdateShape(std::move(rects));
600 }
601
UpdateDraggableRegions(const std::vector<DraggableRegion> & regions)602 void AppWindow::UpdateDraggableRegions(
603 const std::vector<DraggableRegion>& regions) {
604 native_app_window_->UpdateDraggableRegions(regions);
605 }
606
UpdateAppIcon(const gfx::Image & image)607 void AppWindow::UpdateAppIcon(const gfx::Image& image) {
608 if (image.IsEmpty())
609 return;
610 custom_app_icon_ = image;
611 native_app_window_->UpdateWindowIcon();
612 }
613
SetFullscreen(FullscreenType type,bool enable)614 void AppWindow::SetFullscreen(FullscreenType type, bool enable) {
615 DCHECK_NE(FULLSCREEN_TYPE_NONE, type);
616
617 if (enable) {
618 #if !defined(OS_MAC)
619 // Do not enter fullscreen mode if disallowed by pref.
620 // TODO(bartfab): Add a test once it becomes possible to simulate a user
621 // gesture. http://crbug.com/174178
622 if (type != FULLSCREEN_TYPE_FORCED) {
623 PrefService* prefs =
624 ExtensionsBrowserClient::Get()->GetPrefServiceForContext(
625 browser_context());
626 if (!prefs->GetBoolean(pref_names::kAppFullscreenAllowed))
627 return;
628 }
629 #endif
630 fullscreen_types_ |= type;
631 } else {
632 fullscreen_types_ &= ~type;
633 }
634 SetNativeWindowFullscreen();
635 }
636
IsFullscreen() const637 bool AppWindow::IsFullscreen() const {
638 return fullscreen_types_ != FULLSCREEN_TYPE_NONE;
639 }
640
IsForcedFullscreen() const641 bool AppWindow::IsForcedFullscreen() const {
642 return (fullscreen_types_ & FULLSCREEN_TYPE_FORCED) != 0;
643 }
644
IsHtmlApiFullscreen() const645 bool AppWindow::IsHtmlApiFullscreen() const {
646 return (fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0;
647 }
648
IsOsFullscreen() const649 bool AppWindow::IsOsFullscreen() const {
650 return (fullscreen_types_ & FULLSCREEN_TYPE_OS) != 0;
651 }
652
Fullscreen()653 void AppWindow::Fullscreen() {
654 SetFullscreen(FULLSCREEN_TYPE_WINDOW_API, true);
655 }
656
Maximize()657 void AppWindow::Maximize() { GetBaseWindow()->Maximize(); }
658
Minimize()659 void AppWindow::Minimize() { GetBaseWindow()->Minimize(); }
660
Restore()661 void AppWindow::Restore() {
662 if (IsFullscreen()) {
663 fullscreen_types_ = FULLSCREEN_TYPE_NONE;
664 SetNativeWindowFullscreen();
665 } else {
666 GetBaseWindow()->Restore();
667 }
668 }
669
OSFullscreen()670 void AppWindow::OSFullscreen() {
671 SetFullscreen(FULLSCREEN_TYPE_OS, true);
672 }
673
ForcedFullscreen()674 void AppWindow::ForcedFullscreen() {
675 SetFullscreen(FULLSCREEN_TYPE_FORCED, true);
676 }
677
SetContentSizeConstraints(const gfx::Size & min_size,const gfx::Size & max_size)678 void AppWindow::SetContentSizeConstraints(const gfx::Size& min_size,
679 const gfx::Size& max_size) {
680 SizeConstraints constraints(min_size, max_size);
681 native_app_window_->SetContentSizeConstraints(constraints.GetMinimumSize(),
682 constraints.GetMaximumSize());
683
684 gfx::Rect bounds = GetClientBounds();
685 gfx::Size constrained_size = constraints.ClampSize(bounds.size());
686 if (bounds.size() != constrained_size) {
687 bounds.set_size(constrained_size);
688 bounds.Inset(-native_app_window_->GetFrameInsets());
689 native_app_window_->SetBounds(bounds);
690 }
691 OnNativeWindowChanged();
692 }
693
Show(ShowType show_type)694 void AppWindow::Show(ShowType show_type) {
695 app_delegate_->OnShow();
696 bool was_hidden = is_hidden_ || !has_been_shown_;
697 is_hidden_ = false;
698
699 switch (show_type) {
700 case SHOW_ACTIVE:
701 GetBaseWindow()->Show();
702 break;
703 case SHOW_INACTIVE:
704 GetBaseWindow()->ShowInactive();
705 break;
706 }
707 AppWindowRegistry::Get(browser_context_)->AppWindowShown(this, was_hidden);
708 has_been_shown_ = true;
709 }
710
Hide()711 void AppWindow::Hide() {
712 is_hidden_ = true;
713 GetBaseWindow()->Hide();
714 AppWindowRegistry::Get(browser_context_)->AppWindowHidden(this);
715 app_delegate_->OnHide();
716 }
717
SetAlwaysOnTop(bool always_on_top)718 void AppWindow::SetAlwaysOnTop(bool always_on_top) {
719 if (cached_always_on_top_ == always_on_top)
720 return;
721
722 cached_always_on_top_ = always_on_top;
723
724 // As a security measure, do not allow fullscreen windows or windows that
725 // overlap the taskbar to be on top. The property will be applied when the
726 // window exits fullscreen and moves away from the taskbar.
727 if ((!IsFullscreen() ||
728 ExtensionsBrowserClient::Get()->IsScreensaverInDemoMode(
729 extension_id())) &&
730 !IntersectsWithTaskbar()) {
731 native_app_window_->SetZOrderLevel(always_on_top
732 ? ui::ZOrderLevel::kFloatingWindow
733 : ui::ZOrderLevel::kNormal);
734 }
735
736 OnNativeWindowChanged();
737 }
738
IsAlwaysOnTop() const739 bool AppWindow::IsAlwaysOnTop() const { return cached_always_on_top_; }
740
RestoreAlwaysOnTop()741 void AppWindow::RestoreAlwaysOnTop() {
742 if (cached_always_on_top_)
743 UpdateNativeAlwaysOnTop();
744 }
745
GetSerializedState(base::DictionaryValue * properties) const746 void AppWindow::GetSerializedState(base::DictionaryValue* properties) const {
747 DCHECK(properties);
748
749 properties->SetBoolean("fullscreen",
750 native_app_window_->IsFullscreenOrPending());
751 properties->SetBoolean("minimized", native_app_window_->IsMinimized());
752 properties->SetBoolean("maximized", native_app_window_->IsMaximized());
753 properties->SetBoolean("alwaysOnTop", IsAlwaysOnTop());
754 properties->SetBoolean("hasFrameColor", native_app_window_->HasFrameColor());
755 properties->SetBoolean(
756 "alphaEnabled",
757 requested_alpha_enabled_ && native_app_window_->CanHaveAlphaEnabled());
758
759 // These properties are undocumented and are to enable testing. Alpha is
760 // removed to
761 // make the values easier to check.
762 SkColor transparent_white = ~SK_ColorBLACK;
763 properties->SetInteger(
764 "activeFrameColor",
765 native_app_window_->ActiveFrameColor() & transparent_white);
766 properties->SetInteger(
767 "inactiveFrameColor",
768 native_app_window_->InactiveFrameColor() & transparent_white);
769
770 gfx::Rect content_bounds = GetClientBounds();
771 gfx::Size content_min_size = native_app_window_->GetContentMinimumSize();
772 gfx::Size content_max_size = native_app_window_->GetContentMaximumSize();
773 SetBoundsProperties(content_bounds,
774 content_min_size,
775 content_max_size,
776 "innerBounds",
777 properties);
778
779 gfx::Insets frame_insets = native_app_window_->GetFrameInsets();
780 gfx::Rect frame_bounds = native_app_window_->GetBounds();
781 gfx::Size frame_min_size = SizeConstraints::AddFrameToConstraints(
782 content_min_size, frame_insets);
783 gfx::Size frame_max_size = SizeConstraints::AddFrameToConstraints(
784 content_max_size, frame_insets);
785 SetBoundsProperties(frame_bounds,
786 frame_min_size,
787 frame_max_size,
788 "outerBounds",
789 properties);
790 }
791
792 //------------------------------------------------------------------------------
793 // Private methods
StartAppIconDownload()794 void AppWindow::StartAppIconDownload() {
795 DCHECK(app_icon_url_.is_valid());
796
797 // Avoid using any previous icons that were being downloaded.
798 image_loader_ptr_factory_.InvalidateWeakPtrs();
799 web_contents()->DownloadImage(
800 app_icon_url_,
801 true, // is a favicon
802 app_delegate_->PreferredIconSize(),
803 0, // no maximum size
804 false, // normal cache policy
805 base::BindOnce(&AppWindow::DidDownloadFavicon,
806 image_loader_ptr_factory_.GetWeakPtr()));
807 }
808
DidDownloadFavicon(int id,int http_status_code,const GURL & image_url,const std::vector<SkBitmap> & bitmaps,const std::vector<gfx::Size> & original_bitmap_sizes)809 void AppWindow::DidDownloadFavicon(
810 int id,
811 int http_status_code,
812 const GURL& image_url,
813 const std::vector<SkBitmap>& bitmaps,
814 const std::vector<gfx::Size>& original_bitmap_sizes) {
815 if (image_url != app_icon_url_ || bitmaps.empty())
816 return;
817
818 // Bitmaps are ordered largest to smallest. Choose the smallest bitmap
819 // whose height >= the preferred size.
820 int largest_index = 0;
821 for (size_t i = 1; i < bitmaps.size(); ++i) {
822 if (bitmaps[i].height() < app_delegate_->PreferredIconSize())
823 break;
824 largest_index = i;
825 }
826 const SkBitmap& largest = bitmaps[largest_index];
827 UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest));
828 }
829
SetNativeWindowFullscreen()830 void AppWindow::SetNativeWindowFullscreen() {
831 native_app_window_->SetFullscreen(fullscreen_types_);
832
833 RestoreAlwaysOnTop();
834 }
835
IntersectsWithTaskbar() const836 bool AppWindow::IntersectsWithTaskbar() const {
837 #if defined(OS_WIN)
838 display::Screen* screen = display::Screen::GetScreen();
839 gfx::Rect window_bounds = native_app_window_->GetRestoredBounds();
840 std::vector<display::Display> displays = screen->GetAllDisplays();
841
842 for (std::vector<display::Display>::const_iterator it = displays.begin();
843 it != displays.end(); ++it) {
844 gfx::Rect taskbar_bounds = it->bounds();
845 taskbar_bounds.Subtract(it->work_area());
846 if (taskbar_bounds.IsEmpty())
847 continue;
848
849 if (window_bounds.Intersects(taskbar_bounds))
850 return true;
851 }
852 #endif
853
854 return false;
855 }
856
UpdateNativeAlwaysOnTop()857 void AppWindow::UpdateNativeAlwaysOnTop() {
858 DCHECK(cached_always_on_top_);
859 bool is_on_top =
860 native_app_window_->GetZOrderLevel() == ui::ZOrderLevel::kFloatingWindow;
861 bool fullscreen = IsFullscreen();
862 bool intersects_taskbar = IntersectsWithTaskbar();
863
864 if (is_on_top && (fullscreen || intersects_taskbar)) {
865 // When entering fullscreen or overlapping the taskbar, ensure windows are
866 // not always-on-top.
867 native_app_window_->SetZOrderLevel(ui::ZOrderLevel::kNormal);
868 } else if (!is_on_top && !fullscreen && !intersects_taskbar) {
869 // When exiting fullscreen and moving away from the taskbar, reinstate
870 // always-on-top.
871 native_app_window_->SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
872 }
873 }
874
ActivateContents(WebContents * contents)875 void AppWindow::ActivateContents(WebContents* contents) {
876 native_app_window_->Activate();
877 }
878
CloseContents(WebContents * contents)879 void AppWindow::CloseContents(WebContents* contents) {
880 native_app_window_->Close();
881 }
882
ShouldSuppressDialogs(WebContents * source)883 bool AppWindow::ShouldSuppressDialogs(WebContents* source) {
884 return true;
885 }
886
OpenColorChooser(WebContents * web_contents,SkColor initial_color,const std::vector<blink::mojom::ColorSuggestionPtr> & suggestions)887 content::ColorChooser* AppWindow::OpenColorChooser(
888 WebContents* web_contents,
889 SkColor initial_color,
890 const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
891 return app_delegate_->ShowColorChooser(web_contents, initial_color);
892 }
893
RunFileChooser(content::RenderFrameHost * render_frame_host,scoped_refptr<content::FileSelectListener> listener,const blink::mojom::FileChooserParams & params)894 void AppWindow::RunFileChooser(
895 content::RenderFrameHost* render_frame_host,
896 scoped_refptr<content::FileSelectListener> listener,
897 const blink::mojom::FileChooserParams& params) {
898 app_delegate_->RunFileChooser(render_frame_host, std::move(listener), params);
899 }
900
SetContentsBounds(WebContents * source,const gfx::Rect & bounds)901 void AppWindow::SetContentsBounds(WebContents* source,
902 const gfx::Rect& bounds) {
903 native_app_window_->SetBounds(bounds);
904 }
905
NavigationStateChanged(content::WebContents * source,content::InvalidateTypes changed_flags)906 void AppWindow::NavigationStateChanged(content::WebContents* source,
907 content::InvalidateTypes changed_flags) {
908 if (changed_flags & content::INVALIDATE_TYPE_TITLE)
909 native_app_window_->UpdateWindowTitle();
910 else if (changed_flags & content::INVALIDATE_TYPE_TAB)
911 native_app_window_->UpdateWindowIcon();
912 }
913
EnterFullscreenModeForTab(content::RenderFrameHost * requesting_frame,const blink::mojom::FullscreenOptions & options)914 void AppWindow::EnterFullscreenModeForTab(
915 content::RenderFrameHost* requesting_frame,
916 const blink::mojom::FullscreenOptions& options) {
917 ToggleFullscreenModeForTab(WebContents::FromRenderFrameHost(requesting_frame),
918 true);
919 }
920
ExitFullscreenModeForTab(content::WebContents * source)921 void AppWindow::ExitFullscreenModeForTab(content::WebContents* source) {
922 ToggleFullscreenModeForTab(source, false);
923 }
924
OnAppWindowReady()925 void AppWindow::OnAppWindowReady() {
926 window_ready_ = true;
927
928 if (app_icon_url_.is_valid())
929 StartAppIconDownload();
930 }
931
ToggleFullscreenModeForTab(content::WebContents * source,bool enter_fullscreen)932 void AppWindow::ToggleFullscreenModeForTab(content::WebContents* source,
933 bool enter_fullscreen) {
934 const Extension* extension = GetExtension();
935 if (!extension)
936 return;
937
938 if (!IsExtensionWithPermissionOrSuggestInConsole(
939 APIPermission::kFullscreen, extension, source->GetMainFrame())) {
940 return;
941 }
942
943 SetFullscreen(FULLSCREEN_TYPE_HTML_API, enter_fullscreen);
944 }
945
IsFullscreenForTabOrPending(const content::WebContents * source)946 bool AppWindow::IsFullscreenForTabOrPending(
947 const content::WebContents* source) {
948 return IsHtmlApiFullscreen();
949 }
950
GetDisplayMode(const content::WebContents * source)951 blink::mojom::DisplayMode AppWindow::GetDisplayMode(
952 const content::WebContents* source) {
953 return IsFullscreen() ? blink::mojom::DisplayMode::kFullscreen
954 : blink::mojom::DisplayMode::kStandalone;
955 }
956
GetExtensionWindowController() const957 WindowController* AppWindow::GetExtensionWindowController() const {
958 return app_window_contents_->GetWindowController();
959 }
960
GetAssociatedWebContents() const961 content::WebContents* AppWindow::GetAssociatedWebContents() const {
962 return web_contents();
963 }
964
OnExtensionUnloaded(BrowserContext * browser_context,const Extension * extension,UnloadedExtensionReason reason)965 void AppWindow::OnExtensionUnloaded(BrowserContext* browser_context,
966 const Extension* extension,
967 UnloadedExtensionReason reason) {
968 if (extension_id_ == extension->id())
969 native_app_window_->Close();
970 }
971
SetWebContentsBlocked(content::WebContents * web_contents,bool blocked)972 void AppWindow::SetWebContentsBlocked(content::WebContents* web_contents,
973 bool blocked) {
974 app_delegate_->SetWebContentsBlocked(web_contents, blocked);
975 }
976
IsWebContentsVisible(content::WebContents * web_contents)977 bool AppWindow::IsWebContentsVisible(content::WebContents* web_contents) {
978 return app_delegate_->IsWebContentsVisible(web_contents);
979 }
980
GetWebContentsModalDialogHost()981 WebContentsModalDialogHost* AppWindow::GetWebContentsModalDialogHost() {
982 return native_app_window_.get();
983 }
984
SaveWindowPosition()985 void AppWindow::SaveWindowPosition() {
986 DCHECK(native_app_window_);
987 if (window_key_.empty())
988 return;
989
990 AppWindowGeometryCache* cache =
991 AppWindowGeometryCache::Get(browser_context());
992
993 gfx::Rect bounds = native_app_window_->GetRestoredBounds();
994 gfx::Rect screen_bounds =
995 display::Screen::GetScreen()->GetDisplayMatching(bounds).work_area();
996 ui::WindowShowState window_state = native_app_window_->GetRestoredState();
997 cache->SaveGeometry(
998 extension_id(), window_key_, bounds, screen_bounds, window_state);
999 }
1000
AdjustBoundsToBeVisibleOnScreen(const gfx::Rect & cached_bounds,const gfx::Rect & cached_screen_bounds,const gfx::Rect & current_screen_bounds,const gfx::Size & minimum_size,gfx::Rect * bounds) const1001 void AppWindow::AdjustBoundsToBeVisibleOnScreen(
1002 const gfx::Rect& cached_bounds,
1003 const gfx::Rect& cached_screen_bounds,
1004 const gfx::Rect& current_screen_bounds,
1005 const gfx::Size& minimum_size,
1006 gfx::Rect* bounds) const {
1007 *bounds = cached_bounds;
1008
1009 // Reposition and resize the bounds if the cached_screen_bounds is different
1010 // from the current screen bounds and the current screen bounds doesn't
1011 // completely contain the bounds.
1012 if (cached_screen_bounds != current_screen_bounds &&
1013 !current_screen_bounds.Contains(cached_bounds)) {
1014 bounds->set_width(
1015 std::max(minimum_size.width(),
1016 std::min(bounds->width(), current_screen_bounds.width())));
1017 bounds->set_height(
1018 std::max(minimum_size.height(),
1019 std::min(bounds->height(), current_screen_bounds.height())));
1020 bounds->set_x(
1021 std::max(current_screen_bounds.x(),
1022 std::min(bounds->x(),
1023 current_screen_bounds.right() - bounds->width())));
1024 bounds->set_y(
1025 std::max(current_screen_bounds.y(),
1026 std::min(bounds->y(),
1027 current_screen_bounds.bottom() - bounds->height())));
1028 }
1029 }
1030
LoadDefaults(CreateParams params) const1031 AppWindow::CreateParams AppWindow::LoadDefaults(CreateParams params)
1032 const {
1033 // Ensure width and height are specified.
1034 if (params.content_spec.bounds.width() == 0 &&
1035 params.window_spec.bounds.width() == 0) {
1036 params.content_spec.bounds.set_width(kDefaultWidth);
1037 }
1038 if (params.content_spec.bounds.height() == 0 &&
1039 params.window_spec.bounds.height() == 0) {
1040 params.content_spec.bounds.set_height(kDefaultHeight);
1041 }
1042
1043 // If left and top are left undefined, the native app window will center
1044 // the window on the main screen in a platform-defined manner.
1045
1046 // Load cached state if it exists.
1047 if (!params.window_key.empty()) {
1048 AppWindowGeometryCache* cache =
1049 AppWindowGeometryCache::Get(browser_context());
1050
1051 gfx::Rect cached_bounds;
1052 gfx::Rect cached_screen_bounds;
1053 ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT;
1054 if (cache->GetGeometry(extension_id(),
1055 params.window_key,
1056 &cached_bounds,
1057 &cached_screen_bounds,
1058 &cached_state)) {
1059 // App window has cached screen bounds, make sure it fits on screen in
1060 // case the screen resolution changed.
1061 display::Screen* screen = display::Screen::GetScreen();
1062 display::Display display = screen->GetDisplayMatching(cached_bounds);
1063 gfx::Rect current_screen_bounds = display.work_area();
1064 SizeConstraints constraints(params.GetWindowMinimumSize(gfx::Insets()),
1065 params.GetWindowMaximumSize(gfx::Insets()));
1066 AdjustBoundsToBeVisibleOnScreen(cached_bounds,
1067 cached_screen_bounds,
1068 current_screen_bounds,
1069 constraints.GetMinimumSize(),
1070 ¶ms.window_spec.bounds);
1071 params.state = cached_state;
1072
1073 // Since we are restoring a cached state, reset the content bounds spec to
1074 // ensure it is not used.
1075 params.content_spec.ResetBounds();
1076 }
1077 }
1078
1079 return params;
1080 }
1081
1082 // static
RawDraggableRegionsToSkRegion(const std::vector<DraggableRegion> & regions)1083 SkRegion* AppWindow::RawDraggableRegionsToSkRegion(
1084 const std::vector<DraggableRegion>& regions) {
1085 SkRegion* sk_region = new SkRegion;
1086 for (auto iter = regions.cbegin(); iter != regions.cend(); ++iter) {
1087 const DraggableRegion& region = *iter;
1088 sk_region->op(
1089 SkIRect::MakeLTRB(region.bounds.x(), region.bounds.y(),
1090 region.bounds.right(), region.bounds.bottom()),
1091 region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
1092 }
1093 return sk_region;
1094 }
1095
1096 } // namespace extensions
1097