// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "apps/app_window.h" #include #include "apps/app_window_geometry_cache.h" #include "apps/app_window_registry.h" #include "apps/apps_client.h" #include "apps/size_constraints.h" #include "apps/ui/native_app_window.h" #include "apps/ui/web_contents_sizer.h" #include "base/command_line.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h" #include "chrome/browser/extensions/suggest_permission_util.h" #include "chrome/common/chrome_switches.h" #include "components/web_modal/web_contents_modal_dialog_manager.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/invalidate_type.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "content/public/common/media_stream_request.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/view_type_utils.h" #include "extensions/common/extension.h" #include "extensions/common/extension_messages.h" #include "extensions/common/manifest_handlers/icons_handler.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/screen.h" #if !defined(OS_MACOSX) #include "apps/pref_names.h" #include "base/prefs/pref_service.h" #endif using content::BrowserContext; using content::ConsoleMessageLevel; using content::WebContents; using extensions::APIPermission; using web_modal::WebContentsModalDialogHost; using web_modal::WebContentsModalDialogManager; namespace apps { namespace { const int kDefaultWidth = 512; const int kDefaultHeight = 384; bool IsFullscreen(int fullscreen_types) { return fullscreen_types != apps::AppWindow::FULLSCREEN_TYPE_NONE; } void SetConstraintProperty(const std::string& name, int value, base::DictionaryValue* bounds_properties) { if (value != SizeConstraints::kUnboundedSize) bounds_properties->SetInteger(name, value); else bounds_properties->Set(name, base::Value::CreateNullValue()); } void SetBoundsProperties(const gfx::Rect& bounds, const gfx::Size& min_size, const gfx::Size& max_size, const std::string& bounds_name, base::DictionaryValue* window_properties) { scoped_ptr bounds_properties( new base::DictionaryValue()); bounds_properties->SetInteger("left", bounds.x()); bounds_properties->SetInteger("top", bounds.y()); bounds_properties->SetInteger("width", bounds.width()); bounds_properties->SetInteger("height", bounds.height()); SetConstraintProperty("minWidth", min_size.width(), bounds_properties.get()); SetConstraintProperty( "minHeight", min_size.height(), bounds_properties.get()); SetConstraintProperty("maxWidth", max_size.width(), bounds_properties.get()); SetConstraintProperty( "maxHeight", max_size.height(), bounds_properties.get()); window_properties->Set(bounds_name, bounds_properties.release()); } // Combines the constraints of the content and window, and returns constraints // for the window. gfx::Size GetCombinedWindowConstraints(const gfx::Size& window_constraints, const gfx::Size& content_constraints, const gfx::Insets& frame_insets) { gfx::Size combined_constraints(window_constraints); if (content_constraints.width() > 0) { combined_constraints.set_width( content_constraints.width() + frame_insets.width()); } if (content_constraints.height() > 0) { combined_constraints.set_height( content_constraints.height() + frame_insets.height()); } return combined_constraints; } // Combines the constraints of the content and window, and returns constraints // for the content. gfx::Size GetCombinedContentConstraints(const gfx::Size& window_constraints, const gfx::Size& content_constraints, const gfx::Insets& frame_insets) { gfx::Size combined_constraints(content_constraints); if (window_constraints.width() > 0) { combined_constraints.set_width( std::max(0, window_constraints.width() - frame_insets.width())); } if (window_constraints.height() > 0) { combined_constraints.set_height( std::max(0, window_constraints.height() - frame_insets.height())); } return combined_constraints; } } // namespace // AppWindow::BoundsSpecification const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN; AppWindow::BoundsSpecification::BoundsSpecification() : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {} AppWindow::BoundsSpecification::~BoundsSpecification() {} void AppWindow::BoundsSpecification::ResetBounds() { bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0); } // AppWindow::CreateParams AppWindow::CreateParams::CreateParams() : window_type(AppWindow::WINDOW_TYPE_DEFAULT), frame(AppWindow::FRAME_CHROME), has_frame_color(false), transparent_background(false), creator_process_id(0), state(ui::SHOW_STATE_DEFAULT), hidden(false), resizable(true), focused(true), always_on_top(false) {} AppWindow::CreateParams::~CreateParams() {} gfx::Rect AppWindow::CreateParams::GetInitialWindowBounds( const gfx::Insets& frame_insets) const { // Combine into a single window bounds. gfx::Rect combined_bounds(window_spec.bounds); if (content_spec.bounds.x() != BoundsSpecification::kUnspecifiedPosition) combined_bounds.set_x(content_spec.bounds.x() - frame_insets.left()); if (content_spec.bounds.y() != BoundsSpecification::kUnspecifiedPosition) combined_bounds.set_y(content_spec.bounds.y() - frame_insets.top()); if (content_spec.bounds.width() > 0) { combined_bounds.set_width( content_spec.bounds.width() + frame_insets.width()); } if (content_spec.bounds.height() > 0) { combined_bounds.set_height( content_spec.bounds.height() + frame_insets.height()); } // Constrain the bounds. SizeConstraints constraints( GetCombinedWindowConstraints( window_spec.minimum_size, content_spec.minimum_size, frame_insets), GetCombinedWindowConstraints( window_spec.maximum_size, content_spec.maximum_size, frame_insets)); combined_bounds.set_size(constraints.ClampSize(combined_bounds.size())); return combined_bounds; } gfx::Size AppWindow::CreateParams::GetContentMinimumSize( const gfx::Insets& frame_insets) const { return GetCombinedContentConstraints(window_spec.minimum_size, content_spec.minimum_size, frame_insets); } gfx::Size AppWindow::CreateParams::GetContentMaximumSize( const gfx::Insets& frame_insets) const { return GetCombinedContentConstraints(window_spec.maximum_size, content_spec.maximum_size, frame_insets); } gfx::Size AppWindow::CreateParams::GetWindowMinimumSize( const gfx::Insets& frame_insets) const { return GetCombinedWindowConstraints(window_spec.minimum_size, content_spec.minimum_size, frame_insets); } gfx::Size AppWindow::CreateParams::GetWindowMaximumSize( const gfx::Insets& frame_insets) const { return GetCombinedWindowConstraints(window_spec.maximum_size, content_spec.maximum_size, frame_insets); } // AppWindow::Delegate AppWindow::Delegate::~Delegate() {} // AppWindow AppWindow::AppWindow(BrowserContext* context, Delegate* delegate, const extensions::Extension* extension) : browser_context_(context), extension_(extension), extension_id_(extension->id()), window_type_(WINDOW_TYPE_DEFAULT), delegate_(delegate), image_loader_ptr_factory_(this), fullscreen_types_(FULLSCREEN_TYPE_NONE), show_on_first_paint_(false), first_paint_complete_(false), cached_always_on_top_(false) { extensions::ExtensionsBrowserClient* client = extensions::ExtensionsBrowserClient::Get(); CHECK(!client->IsGuestSession(context) || context->IsOffTheRecord()) << "Only off the record window may be opened in the guest mode."; } void AppWindow::Init(const GURL& url, AppWindowContents* app_window_contents, const CreateParams& params) { // Initialize the render interface and web contents app_window_contents_.reset(app_window_contents); app_window_contents_->Initialize(browser_context(), url); WebContents* web_contents = app_window_contents_->GetWebContents(); if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableAppsShowOnFirstPaint)) { content::WebContentsObserver::Observe(web_contents); } delegate_->InitWebContents(web_contents); WebContentsModalDialogManager::CreateForWebContents(web_contents); // TODO(jamescook): Delegate out this creation. extensions::ChromeExtensionWebContentsObserver::CreateForWebContents( web_contents); web_contents->SetDelegate(this); WebContentsModalDialogManager::FromWebContents(web_contents) ->SetDelegate(this); extensions::SetViewType(web_contents, extensions::VIEW_TYPE_APP_WINDOW); // Initialize the window CreateParams new_params = LoadDefaults(params); window_type_ = new_params.window_type; window_key_ = new_params.window_key; // Windows cannot be always-on-top in fullscreen mode for security reasons. cached_always_on_top_ = new_params.always_on_top; if (new_params.state == ui::SHOW_STATE_FULLSCREEN) new_params.always_on_top = false; native_app_window_.reset(delegate_->CreateNativeAppWindow(this, new_params)); if (!new_params.hidden) { // Panels are not activated by default. Show(window_type_is_panel() || !new_params.focused ? SHOW_INACTIVE : SHOW_ACTIVE); } if (new_params.state == ui::SHOW_STATE_FULLSCREEN) Fullscreen(); else if (new_params.state == ui::SHOW_STATE_MAXIMIZED) Maximize(); else if (new_params.state == ui::SHOW_STATE_MINIMIZED) Minimize(); OnNativeWindowChanged(); // When the render view host is changed, the native window needs to know // about it in case it has any setup to do to make the renderer appear // properly. In particular, on Windows, the view's clickthrough region needs // to be set. extensions::ExtensionsBrowserClient* client = extensions::ExtensionsBrowserClient::Get(); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, content::Source( client->GetOriginalContext(browser_context_))); // Close when the browser process is exiting. registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, content::NotificationService::AllSources()); // Update the app menu if an ephemeral app becomes installed. registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, content::Source( client->GetOriginalContext(browser_context_))); app_window_contents_->LoadContents(new_params.creator_process_id); if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableAppsShowOnFirstPaint)) { // We want to show the window only when the content has been painted. For // that to happen, we need to define a size for the content, otherwise the // layout will happen in a 0x0 area. // Note: WebContents::GetView() is guaranteed to be non-null. gfx::Insets frame_insets = native_app_window_->GetFrameInsets(); gfx::Rect initial_bounds = new_params.GetInitialWindowBounds(frame_insets); initial_bounds.Inset(frame_insets); apps::ResizeWebContents(web_contents, initial_bounds.size()); } // Prevent the browser process from shutting down while this window is open. AppsClient::Get()->IncrementKeepAliveCount(); UpdateExtensionAppIcon(); AppWindowRegistry::Get(browser_context_)->AddAppWindow(this); } AppWindow::~AppWindow() { // Unregister now to prevent getting NOTIFICATION_APP_TERMINATING if we're the // last window open. registrar_.RemoveAll(); // Remove shutdown prevention. AppsClient::Get()->DecrementKeepAliveCount(); } void AppWindow::RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) { delegate_->RequestMediaAccessPermission( web_contents, request, callback, extension()); } WebContents* AppWindow::OpenURLFromTab(WebContents* source, const content::OpenURLParams& params) { // Don't allow the current tab to be navigated. It would be nice to map all // anchor tags (even those without target="_blank") to new tabs, but right // now we can't distinguish between those and refreshes or window.href // navigations, which we don't want to allow. // TOOD(mihaip): Can we check for user gestures instead? WindowOpenDisposition disposition = params.disposition; if (disposition == CURRENT_TAB) { AddMessageToDevToolsConsole( content::CONSOLE_MESSAGE_LEVEL_ERROR, base::StringPrintf( "Can't open same-window link to \"%s\"; try target=\"_blank\".", params.url.spec().c_str())); return NULL; } // These dispositions aren't really navigations. if (disposition == SUPPRESS_OPEN || disposition == SAVE_TO_DISK || disposition == IGNORE_ACTION) { return NULL; } WebContents* contents = delegate_->OpenURLFromTab(browser_context_, source, params); if (!contents) { AddMessageToDevToolsConsole( content::CONSOLE_MESSAGE_LEVEL_ERROR, base::StringPrintf( "Can't navigate to \"%s\"; apps do not support navigation.", params.url.spec().c_str())); } return contents; } void AppWindow::AddNewContents(WebContents* source, WebContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture, bool* was_blocked) { DCHECK(new_contents->GetBrowserContext() == browser_context_); delegate_->AddNewContents(browser_context_, new_contents, disposition, initial_pos, user_gesture, was_blocked); } bool AppWindow::PreHandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { // Here, we can handle a key event before the content gets it. When we are // fullscreen and it is not forced, we want to allow the user to leave // when ESC is pressed. // However, if the application has the "overrideEscFullscreen" permission, we // should let it override that behavior. // ::HandleKeyboardEvent() will only be called if the KeyEvent's default // action is not prevented. // Thus, we should handle the KeyEvent here only if the permission is not set. if (event.windowsKeyCode == ui::VKEY_ESCAPE && (fullscreen_types_ != FULLSCREEN_TYPE_NONE) && ((fullscreen_types_ & FULLSCREEN_TYPE_FORCED) == 0) && !extension_->HasAPIPermission(APIPermission::kOverrideEscFullscreen)) { Restore(); return true; } return false; } void AppWindow::HandleKeyboardEvent( WebContents* source, const content::NativeWebKeyboardEvent& event) { // If the window is currently fullscreen and not forced, ESC should leave // fullscreen. If this code is being called for ESC, that means that the // KeyEvent's default behavior was not prevented by the content. if (event.windowsKeyCode == ui::VKEY_ESCAPE && (fullscreen_types_ != FULLSCREEN_TYPE_NONE) && ((fullscreen_types_ & FULLSCREEN_TYPE_FORCED) == 0)) { Restore(); return; } native_app_window_->HandleKeyboardEvent(event); } void AppWindow::RequestToLockMouse(WebContents* web_contents, bool user_gesture, bool last_unlocked_by_target) { bool has_permission = IsExtensionWithPermissionOrSuggestInConsole( APIPermission::kPointerLock, extension_, web_contents->GetRenderViewHost()); web_contents->GotResponseToLockMouseRequest(has_permission); } bool AppWindow::PreHandleGestureEvent(WebContents* source, const blink::WebGestureEvent& event) { // Disable pinch zooming in app windows. return event.type == blink::WebGestureEvent::GesturePinchBegin || event.type == blink::WebGestureEvent::GesturePinchUpdate || event.type == blink::WebGestureEvent::GesturePinchEnd; } void AppWindow::DidFirstVisuallyNonEmptyPaint(int32 page_id) { first_paint_complete_ = true; if (show_on_first_paint_) { DCHECK(delayed_show_type_ == SHOW_ACTIVE || delayed_show_type_ == SHOW_INACTIVE); Show(delayed_show_type_); } } void AppWindow::OnNativeClose() { AppWindowRegistry::Get(browser_context_)->RemoveAppWindow(this); if (app_window_contents_) { WebContents* web_contents = app_window_contents_->GetWebContents(); WebContentsModalDialogManager::FromWebContents(web_contents) ->SetDelegate(NULL); app_window_contents_->NativeWindowClosed(); } delete this; } void AppWindow::OnNativeWindowChanged() { SaveWindowPosition(); #if defined(OS_WIN) if (native_app_window_ && cached_always_on_top_ && !IsFullscreen(fullscreen_types_) && !native_app_window_->IsMaximized() && !native_app_window_->IsMinimized()) { UpdateNativeAlwaysOnTop(); } #endif if (app_window_contents_ && native_app_window_) app_window_contents_->NativeWindowChanged(native_app_window_.get()); } void AppWindow::OnNativeWindowActivated() { AppWindowRegistry::Get(browser_context_)->AppWindowActivated(this); } content::WebContents* AppWindow::web_contents() const { return app_window_contents_->GetWebContents(); } NativeAppWindow* AppWindow::GetBaseWindow() { return native_app_window_.get(); } gfx::NativeWindow AppWindow::GetNativeWindow() { return GetBaseWindow()->GetNativeWindow(); } gfx::Rect AppWindow::GetClientBounds() const { gfx::Rect bounds = native_app_window_->GetBounds(); bounds.Inset(native_app_window_->GetFrameInsets()); return bounds; } base::string16 AppWindow::GetTitle() const { // WebContents::GetTitle() will return the page's URL if there's no // specified. However, we'd prefer to show the name of the extension in that // case, so we directly inspect the NavigationEntry's title. base::string16 title; if (!web_contents() || !web_contents()->GetController().GetActiveEntry() || web_contents()->GetController().GetActiveEntry()->GetTitle().empty()) { title = base::UTF8ToUTF16(extension()->name()); } else { title = web_contents()->GetTitle(); } const base::char16 kBadChars[] = {'\n', 0}; base::RemoveChars(title, kBadChars, &title); return title; } void AppWindow::SetAppIconUrl(const GURL& url) { // If the same url is being used for the badge, ignore it. if (url == badge_icon_url_) return; // Avoid using any previous icons that were being downloaded. image_loader_ptr_factory_.InvalidateWeakPtrs(); // Reset |app_icon_image_| to abort pending image load (if any). app_icon_image_.reset(); app_icon_url_ = url; web_contents()->DownloadImage( url, true, // is a favicon 0, // no maximum size base::Bind(&AppWindow::DidDownloadFavicon, image_loader_ptr_factory_.GetWeakPtr())); } void AppWindow::SetBadgeIconUrl(const GURL& url) { // Avoid using any previous icons that were being downloaded. image_loader_ptr_factory_.InvalidateWeakPtrs(); // Reset |app_icon_image_| to abort pending image load (if any). badge_icon_image_.reset(); badge_icon_url_ = url; web_contents()->DownloadImage( url, true, // is a favicon 0, // no maximum size base::Bind(&AppWindow::DidDownloadFavicon, image_loader_ptr_factory_.GetWeakPtr())); } void AppWindow::ClearBadge() { badge_icon_image_.reset(); badge_icon_url_ = GURL(); UpdateBadgeIcon(gfx::Image()); } void AppWindow::UpdateShape(scoped_ptr<SkRegion> region) { native_app_window_->UpdateShape(region.Pass()); } void AppWindow::UpdateDraggableRegions( const std::vector<extensions::DraggableRegion>& regions) { native_app_window_->UpdateDraggableRegions(regions); } void AppWindow::UpdateAppIcon(const gfx::Image& image) { if (image.IsEmpty()) return; app_icon_ = image; native_app_window_->UpdateWindowIcon(); AppWindowRegistry::Get(browser_context_)->AppWindowIconChanged(this); } void AppWindow::Fullscreen() { #if !defined(OS_MACOSX) // Do not enter fullscreen mode if disallowed by pref. PrefService* prefs = extensions::ExtensionsBrowserClient::Get()->GetPrefServiceForContext( browser_context()); if (!prefs->GetBoolean(prefs::kAppFullscreenAllowed)) return; #endif fullscreen_types_ |= FULLSCREEN_TYPE_WINDOW_API; SetNativeWindowFullscreen(); } void AppWindow::Maximize() { GetBaseWindow()->Maximize(); } void AppWindow::Minimize() { GetBaseWindow()->Minimize(); } void AppWindow::Restore() { if (IsFullscreen(fullscreen_types_)) { fullscreen_types_ = FULLSCREEN_TYPE_NONE; SetNativeWindowFullscreen(); } else { GetBaseWindow()->Restore(); } } void AppWindow::OSFullscreen() { #if !defined(OS_MACOSX) // Do not enter fullscreen mode if disallowed by pref. PrefService* prefs = extensions::ExtensionsBrowserClient::Get()->GetPrefServiceForContext( browser_context()); if (!prefs->GetBoolean(prefs::kAppFullscreenAllowed)) return; #endif fullscreen_types_ |= FULLSCREEN_TYPE_OS; SetNativeWindowFullscreen(); } void AppWindow::ForcedFullscreen() { fullscreen_types_ |= FULLSCREEN_TYPE_FORCED; SetNativeWindowFullscreen(); } void AppWindow::SetContentSizeConstraints(const gfx::Size& min_size, const gfx::Size& max_size) { SizeConstraints constraints(min_size, max_size); native_app_window_->SetContentSizeConstraints(constraints.GetMinimumSize(), constraints.GetMaximumSize()); gfx::Rect bounds = GetClientBounds(); gfx::Size constrained_size = constraints.ClampSize(bounds.size()); if (bounds.size() != constrained_size) { bounds.set_size(constrained_size); bounds.Inset(-native_app_window_->GetFrameInsets()); native_app_window_->SetBounds(bounds); } OnNativeWindowChanged(); } void AppWindow::Show(ShowType show_type) { if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableAppsShowOnFirstPaint)) { show_on_first_paint_ = true; if (!first_paint_complete_) { delayed_show_type_ = show_type; return; } } switch (show_type) { case SHOW_ACTIVE: GetBaseWindow()->Show(); break; case SHOW_INACTIVE: GetBaseWindow()->ShowInactive(); break; } } void AppWindow::Hide() { // This is there to prevent race conditions with Hide() being called before // there was a non-empty paint. It should have no effect in a non-racy // scenario where the application is hiding then showing a window: the second // show will not be delayed. show_on_first_paint_ = false; GetBaseWindow()->Hide(); } void AppWindow::SetAlwaysOnTop(bool always_on_top) { if (cached_always_on_top_ == always_on_top) return; cached_always_on_top_ = always_on_top; // As a security measure, do not allow fullscreen windows or windows that // overlap the taskbar to be on top. The property will be applied when the // window exits fullscreen and moves away from the taskbar. if (!IsFullscreen(fullscreen_types_) && !IntersectsWithTaskbar()) native_app_window_->SetAlwaysOnTop(always_on_top); OnNativeWindowChanged(); } bool AppWindow::IsAlwaysOnTop() const { return cached_always_on_top_; } void AppWindow::GetSerializedState(base::DictionaryValue* properties) const { DCHECK(properties); properties->SetBoolean("fullscreen", native_app_window_->IsFullscreenOrPending()); properties->SetBoolean("minimized", native_app_window_->IsMinimized()); properties->SetBoolean("maximized", native_app_window_->IsMaximized()); properties->SetBoolean("alwaysOnTop", IsAlwaysOnTop()); properties->SetBoolean("hasFrameColor", native_app_window_->HasFrameColor()); properties->SetInteger("frameColor", native_app_window_->FrameColor()); gfx::Rect content_bounds = GetClientBounds(); gfx::Size content_min_size = native_app_window_->GetContentMinimumSize(); gfx::Size content_max_size = native_app_window_->GetContentMaximumSize(); SetBoundsProperties(content_bounds, content_min_size, content_max_size, "innerBounds", properties); gfx::Insets frame_insets = native_app_window_->GetFrameInsets(); gfx::Rect frame_bounds = native_app_window_->GetBounds(); gfx::Size frame_min_size = SizeConstraints::AddFrameToConstraints(content_min_size, frame_insets); gfx::Size frame_max_size = SizeConstraints::AddFrameToConstraints(content_max_size, frame_insets); SetBoundsProperties(frame_bounds, frame_min_size, frame_max_size, "outerBounds", properties); } //------------------------------------------------------------------------------ // Private methods void AppWindow::UpdateBadgeIcon(const gfx::Image& image) { badge_icon_ = image; native_app_window_->UpdateBadgeIcon(); } void AppWindow::DidDownloadFavicon( int id, int http_status_code, const GURL& image_url, const std::vector<SkBitmap>& bitmaps, const std::vector<gfx::Size>& original_bitmap_sizes) { if ((image_url != app_icon_url_ && image_url != badge_icon_url_) || bitmaps.empty()) { return; } // Bitmaps are ordered largest to smallest. Choose the smallest bitmap // whose height >= the preferred size. int largest_index = 0; for (size_t i = 1; i < bitmaps.size(); ++i) { if (bitmaps[i].height() < delegate_->PreferredIconSize()) break; largest_index = i; } const SkBitmap& largest = bitmaps[largest_index]; if (image_url == app_icon_url_) { UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest)); return; } UpdateBadgeIcon(gfx::Image::CreateFrom1xBitmap(largest)); } void AppWindow::OnExtensionIconImageChanged(extensions::IconImage* image) { DCHECK_EQ(app_icon_image_.get(), image); UpdateAppIcon(gfx::Image(app_icon_image_->image_skia())); } void AppWindow::UpdateExtensionAppIcon() { // Avoid using any previous app icons were being downloaded. image_loader_ptr_factory_.InvalidateWeakPtrs(); app_icon_image_.reset( new extensions::IconImage(browser_context(), extension(), extensions::IconsInfo::GetIcons(extension()), delegate_->PreferredIconSize(), extensions::IconsInfo::GetDefaultAppIcon(), this)); // Triggers actual image loading with 1x resources. The 2x resource will // be handled by IconImage class when requested. app_icon_image_->image_skia().GetRepresentation(1.0f); } void AppWindow::SetNativeWindowFullscreen() { native_app_window_->SetFullscreen(fullscreen_types_); if (cached_always_on_top_) UpdateNativeAlwaysOnTop(); } bool AppWindow::IntersectsWithTaskbar() const { #if defined(OS_WIN) gfx::Screen* screen = gfx::Screen::GetNativeScreen(); gfx::Rect window_bounds = native_app_window_->GetRestoredBounds(); std::vector<gfx::Display> displays = screen->GetAllDisplays(); for (std::vector<gfx::Display>::const_iterator it = displays.begin(); it != displays.end(); ++it) { gfx::Rect taskbar_bounds = it->bounds(); taskbar_bounds.Subtract(it->work_area()); if (taskbar_bounds.IsEmpty()) continue; if (window_bounds.Intersects(taskbar_bounds)) return true; } #endif return false; } void AppWindow::UpdateNativeAlwaysOnTop() { DCHECK(cached_always_on_top_); bool is_on_top = native_app_window_->IsAlwaysOnTop(); bool fullscreen = IsFullscreen(fullscreen_types_); bool intersects_taskbar = IntersectsWithTaskbar(); if (is_on_top && (fullscreen || intersects_taskbar)) { // When entering fullscreen or overlapping the taskbar, ensure windows are // not always-on-top. native_app_window_->SetAlwaysOnTop(false); } else if (!is_on_top && !fullscreen && !intersects_taskbar) { // When exiting fullscreen and moving away from the taskbar, reinstate // always-on-top. native_app_window_->SetAlwaysOnTop(true); } } void AppWindow::CloseContents(WebContents* contents) { native_app_window_->Close(); } bool AppWindow::ShouldSuppressDialogs() { return true; } content::ColorChooser* AppWindow::OpenColorChooser( WebContents* web_contents, SkColor initial_color, const std::vector<content::ColorSuggestion>& suggestionss) { return delegate_->ShowColorChooser(web_contents, initial_color); } void AppWindow::RunFileChooser(WebContents* tab, const content::FileChooserParams& params) { if (window_type_is_panel()) { // Panels can't host a file dialog, abort. TODO(stevenjb): allow file // dialogs to be unhosted but still close with the owning web contents. // crbug.com/172502. LOG(WARNING) << "File dialog opened by panel."; return; } delegate_->RunFileChooser(tab, params); } bool AppWindow::IsPopupOrPanel(const WebContents* source) const { return true; } void AppWindow::MoveContents(WebContents* source, const gfx::Rect& pos) { native_app_window_->SetBounds(pos); } void AppWindow::NavigationStateChanged(const content::WebContents* source, unsigned changed_flags) { if (changed_flags & content::INVALIDATE_TYPE_TITLE) native_app_window_->UpdateWindowTitle(); else if (changed_flags & content::INVALIDATE_TYPE_TAB) native_app_window_->UpdateWindowIcon(); } void AppWindow::ToggleFullscreenModeForTab(content::WebContents* source, bool enter_fullscreen) { #if !defined(OS_MACOSX) // Do not enter fullscreen mode if disallowed by pref. // TODO(bartfab): Add a test once it becomes possible to simulate a user // gesture. http://crbug.com/174178 PrefService* prefs = extensions::ExtensionsBrowserClient::Get()->GetPrefServiceForContext( browser_context()); if (enter_fullscreen && !prefs->GetBoolean(prefs::kAppFullscreenAllowed)) { return; } #endif if (!IsExtensionWithPermissionOrSuggestInConsole( APIPermission::kFullscreen, extension_, source->GetRenderViewHost())) { return; } if (enter_fullscreen) fullscreen_types_ |= FULLSCREEN_TYPE_HTML_API; else fullscreen_types_ &= ~FULLSCREEN_TYPE_HTML_API; SetNativeWindowFullscreen(); } bool AppWindow::IsFullscreenForTabOrPending(const content::WebContents* source) const { return ((fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0); } void AppWindow::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { const extensions::Extension* unloaded_extension = content::Details<extensions::UnloadedExtensionInfo>(details) ->extension; if (extension_ == unloaded_extension) native_app_window_->Close(); break; } case chrome::NOTIFICATION_EXTENSION_INSTALLED: { const extensions::Extension* installed_extension = content::Details<const extensions::InstalledExtensionInfo>(details) ->extension; DCHECK(installed_extension); if (installed_extension->id() == extension_->id()) native_app_window_->UpdateShelfMenu(); break; } case chrome::NOTIFICATION_APP_TERMINATING: native_app_window_->Close(); break; default: NOTREACHED() << "Received unexpected notification"; } } void AppWindow::SetWebContentsBlocked(content::WebContents* web_contents, bool blocked) { delegate_->SetWebContentsBlocked(web_contents, blocked); } bool AppWindow::IsWebContentsVisible(content::WebContents* web_contents) { return delegate_->IsWebContentsVisible(web_contents); } WebContentsModalDialogHost* AppWindow::GetWebContentsModalDialogHost() { return native_app_window_.get(); } void AppWindow::AddMessageToDevToolsConsole(ConsoleMessageLevel level, const std::string& message) { content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); rvh->Send(new ExtensionMsg_AddMessageToConsole( rvh->GetRoutingID(), level, message)); } void AppWindow::SaveWindowPosition() { if (window_key_.empty()) return; if (!native_app_window_) return; AppWindowGeometryCache* cache = AppWindowGeometryCache::Get(browser_context()); gfx::Rect bounds = native_app_window_->GetRestoredBounds(); gfx::Rect screen_bounds = gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds).work_area(); ui::WindowShowState window_state = native_app_window_->GetRestoredState(); cache->SaveGeometry( extension()->id(), window_key_, bounds, screen_bounds, window_state); } void AppWindow::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) const { *bounds = cached_bounds; // Reposition and resize the bounds if the cached_screen_bounds is different // from the current screen bounds and the current screen bounds doesn't // completely contain the bounds. if (cached_screen_bounds != current_screen_bounds && !current_screen_bounds.Contains(cached_bounds)) { bounds->set_width( std::max(minimum_size.width(), std::min(bounds->width(), current_screen_bounds.width()))); bounds->set_height( std::max(minimum_size.height(), std::min(bounds->height(), current_screen_bounds.height()))); bounds->set_x( std::max(current_screen_bounds.x(), std::min(bounds->x(), current_screen_bounds.right() - bounds->width()))); bounds->set_y( std::max(current_screen_bounds.y(), std::min(bounds->y(), current_screen_bounds.bottom() - bounds->height()))); } } AppWindow::CreateParams AppWindow::LoadDefaults(CreateParams params) const { // Ensure width and height are specified. if (params.content_spec.bounds.width() == 0 && params.window_spec.bounds.width() == 0) { params.content_spec.bounds.set_width(kDefaultWidth); } if (params.content_spec.bounds.height() == 0 && params.window_spec.bounds.height() == 0) { params.content_spec.bounds.set_height(kDefaultHeight); } // If left and top are left undefined, the native app window will center // the window on the main screen in a platform-defined manner. // Load cached state if it exists. if (!params.window_key.empty()) { AppWindowGeometryCache* cache = AppWindowGeometryCache::Get(browser_context()); gfx::Rect cached_bounds; gfx::Rect cached_screen_bounds; ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT; if (cache->GetGeometry(extension()->id(), params.window_key, &cached_bounds, &cached_screen_bounds, &cached_state)) { // App window has cached screen bounds, make sure it fits on screen in // case the screen resolution changed. gfx::Screen* screen = gfx::Screen::GetNativeScreen(); gfx::Display display = screen->GetDisplayMatching(cached_bounds); gfx::Rect current_screen_bounds = display.work_area(); SizeConstraints constraints(params.GetWindowMinimumSize(gfx::Insets()), params.GetWindowMaximumSize(gfx::Insets())); AdjustBoundsToBeVisibleOnScreen(cached_bounds, cached_screen_bounds, current_screen_bounds, constraints.GetMinimumSize(), ¶ms.window_spec.bounds); params.state = cached_state; // Since we are restoring a cached state, reset the content bounds spec to // ensure it is not used. params.content_spec.ResetBounds(); } } return params; } // static SkRegion* AppWindow::RawDraggableRegionsToSkRegion( const std::vector<extensions::DraggableRegion>& regions) { SkRegion* sk_region = new SkRegion; for (std::vector<extensions::DraggableRegion>::const_iterator iter = regions.begin(); iter != regions.end(); ++iter) { const extensions::DraggableRegion& region = *iter; sk_region->op( region.bounds.x(), region.bounds.y(), region.bounds.right(), region.bounds.bottom(), region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); } return sk_region; } } // namespace apps