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