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