1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h" 6 7#include "base/command_line.h" 8#include "extensions/browser/app_window/app_window.h" 9#include "extensions/browser/app_window/app_window_client.h" 10#include "extensions/browser/app_window/app_window_registry.h" 11#include "extensions/browser/app_window/native_app_window.h" 12#include "extensions/browser/app_window/size_constraints.h" 13#include "extensions/common/api/app_current_window_internal.h" 14#include "extensions/common/features/simple_feature.h" 15#include "extensions/common/permissions/permissions_data.h" 16#include "extensions/common/switches.h" 17#include "third_party/skia/include/core/SkRegion.h" 18 19namespace app_current_window_internal = 20 extensions::core_api::app_current_window_internal; 21 22namespace Show = app_current_window_internal::Show; 23namespace SetBounds = app_current_window_internal::SetBounds; 24namespace SetSizeConstraints = app_current_window_internal::SetSizeConstraints; 25namespace SetIcon = app_current_window_internal::SetIcon; 26namespace SetBadgeIcon = app_current_window_internal::SetBadgeIcon; 27namespace SetShape = app_current_window_internal::SetShape; 28namespace SetAlwaysOnTop = app_current_window_internal::SetAlwaysOnTop; 29namespace SetVisibleOnAllWorkspaces = 30 app_current_window_internal::SetVisibleOnAllWorkspaces; 31 32using app_current_window_internal::Bounds; 33using app_current_window_internal::Region; 34using app_current_window_internal::RegionRect; 35using app_current_window_internal::SizeConstraints; 36 37namespace extensions { 38 39namespace { 40 41const char kNoAssociatedAppWindow[] = 42 "The context from which the function was called did not have an " 43 "associated app window."; 44 45const char kDevChannelOnly[] = 46 "This function is currently only available in the Dev channel."; 47 48const char kRequiresFramelessWindow[] = 49 "This function requires a frameless window (frame:none)."; 50 51const char kAlwaysOnTopPermission[] = 52 "The \"app.window.alwaysOnTop\" permission is required."; 53 54const char kInvalidParameters[] = "Invalid parameters."; 55 56const int kUnboundedSize = SizeConstraints::kUnboundedSize; 57 58void GetBoundsFields(const Bounds& bounds_spec, gfx::Rect* bounds) { 59 if (bounds_spec.left) 60 bounds->set_x(*bounds_spec.left); 61 if (bounds_spec.top) 62 bounds->set_y(*bounds_spec.top); 63 if (bounds_spec.width) 64 bounds->set_width(*bounds_spec.width); 65 if (bounds_spec.height) 66 bounds->set_height(*bounds_spec.height); 67} 68 69// Copy the constraint value from the API to our internal representation of 70// content size constraints. A value of zero resets the constraints. The insets 71// are used to transform window constraints to content constraints. 72void GetConstraintWidth(const scoped_ptr<int>& width, 73 const gfx::Insets& insets, 74 gfx::Size* size) { 75 if (!width.get()) 76 return; 77 78 size->set_width(*width > 0 ? std::max(0, *width - insets.width()) 79 : kUnboundedSize); 80} 81 82void GetConstraintHeight(const scoped_ptr<int>& height, 83 const gfx::Insets& insets, 84 gfx::Size* size) { 85 if (!height.get()) 86 return; 87 88 size->set_height(*height > 0 ? std::max(0, *height - insets.height()) 89 : kUnboundedSize); 90} 91 92} // namespace 93 94namespace bounds { 95 96enum BoundsType { 97 INNER_BOUNDS, 98 OUTER_BOUNDS, 99 DEPRECATED_BOUNDS, 100 INVALID_TYPE 101}; 102 103const char kInnerBoundsType[] = "innerBounds"; 104const char kOuterBoundsType[] = "outerBounds"; 105const char kDeprecatedBoundsType[] = "bounds"; 106 107BoundsType GetBoundsType(const std::string& type_as_string) { 108 if (type_as_string == kInnerBoundsType) 109 return INNER_BOUNDS; 110 else if (type_as_string == kOuterBoundsType) 111 return OUTER_BOUNDS; 112 else if (type_as_string == kDeprecatedBoundsType) 113 return DEPRECATED_BOUNDS; 114 else 115 return INVALID_TYPE; 116} 117 118} // namespace bounds 119 120bool AppCurrentWindowInternalExtensionFunction::RunSync() { 121 AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context()); 122 DCHECK(registry); 123 content::RenderViewHost* rvh = render_view_host(); 124 if (!rvh) 125 // No need to set an error, since we won't return to the caller anyway if 126 // there's no RVH. 127 return false; 128 AppWindow* window = registry->GetAppWindowForRenderViewHost(rvh); 129 if (!window) { 130 error_ = kNoAssociatedAppWindow; 131 return false; 132 } 133 return RunWithWindow(window); 134} 135 136bool AppCurrentWindowInternalFocusFunction::RunWithWindow(AppWindow* window) { 137 window->GetBaseWindow()->Activate(); 138 return true; 139} 140 141bool AppCurrentWindowInternalFullscreenFunction::RunWithWindow( 142 AppWindow* window) { 143 window->Fullscreen(); 144 return true; 145} 146 147bool AppCurrentWindowInternalMaximizeFunction::RunWithWindow( 148 AppWindow* window) { 149 window->Maximize(); 150 return true; 151} 152 153bool AppCurrentWindowInternalMinimizeFunction::RunWithWindow( 154 AppWindow* window) { 155 window->Minimize(); 156 return true; 157} 158 159bool AppCurrentWindowInternalRestoreFunction::RunWithWindow(AppWindow* window) { 160 window->Restore(); 161 return true; 162} 163 164bool AppCurrentWindowInternalDrawAttentionFunction::RunWithWindow( 165 AppWindow* window) { 166 window->GetBaseWindow()->FlashFrame(true); 167 return true; 168} 169 170bool AppCurrentWindowInternalClearAttentionFunction::RunWithWindow( 171 AppWindow* window) { 172 window->GetBaseWindow()->FlashFrame(false); 173 return true; 174} 175 176bool AppCurrentWindowInternalShowFunction::RunWithWindow(AppWindow* window) { 177 scoped_ptr<Show::Params> params(Show::Params::Create(*args_)); 178 CHECK(params.get()); 179 if (params->focused && !*params->focused) 180 window->Show(AppWindow::SHOW_INACTIVE); 181 else 182 window->Show(AppWindow::SHOW_ACTIVE); 183 return true; 184} 185 186bool AppCurrentWindowInternalHideFunction::RunWithWindow(AppWindow* window) { 187 window->Hide(); 188 return true; 189} 190 191bool AppCurrentWindowInternalSetBoundsFunction::RunWithWindow( 192 AppWindow* window) { 193 scoped_ptr<SetBounds::Params> params(SetBounds::Params::Create(*args_)); 194 CHECK(params.get()); 195 196 bounds::BoundsType bounds_type = bounds::GetBoundsType(params->bounds_type); 197 if (bounds_type == bounds::INVALID_TYPE) { 198 NOTREACHED(); 199 error_ = kInvalidParameters; 200 return false; 201 } 202 203 // Start with the current bounds, and change any values that are specified in 204 // the incoming parameters. 205 gfx::Rect original_window_bounds = window->GetBaseWindow()->GetBounds(); 206 gfx::Rect window_bounds = original_window_bounds; 207 gfx::Insets frame_insets = window->GetBaseWindow()->GetFrameInsets(); 208 const Bounds& bounds_spec = params->bounds; 209 210 switch (bounds_type) { 211 case bounds::DEPRECATED_BOUNDS: { 212 // We need to maintain backcompatibility with a bug on Windows and 213 // ChromeOS, which sets the position of the window but the size of the 214 // content. 215 if (bounds_spec.left) 216 window_bounds.set_x(*bounds_spec.left); 217 if (bounds_spec.top) 218 window_bounds.set_y(*bounds_spec.top); 219 if (bounds_spec.width) 220 window_bounds.set_width(*bounds_spec.width + frame_insets.width()); 221 if (bounds_spec.height) 222 window_bounds.set_height(*bounds_spec.height + frame_insets.height()); 223 break; 224 } 225 case bounds::OUTER_BOUNDS: { 226 GetBoundsFields(bounds_spec, &window_bounds); 227 break; 228 } 229 case bounds::INNER_BOUNDS: { 230 window_bounds.Inset(frame_insets); 231 GetBoundsFields(bounds_spec, &window_bounds); 232 window_bounds.Inset(-frame_insets); 233 break; 234 } 235 default: 236 NOTREACHED(); 237 } 238 239 if (original_window_bounds != window_bounds) { 240 if (original_window_bounds.size() != window_bounds.size()) { 241 SizeConstraints constraints( 242 SizeConstraints::AddFrameToConstraints( 243 window->GetBaseWindow()->GetContentMinimumSize(), frame_insets), 244 SizeConstraints::AddFrameToConstraints( 245 window->GetBaseWindow()->GetContentMaximumSize(), frame_insets)); 246 247 window_bounds.set_size(constraints.ClampSize(window_bounds.size())); 248 } 249 250 window->GetBaseWindow()->SetBounds(window_bounds); 251 } 252 253 return true; 254} 255 256bool AppCurrentWindowInternalSetSizeConstraintsFunction::RunWithWindow( 257 AppWindow* window) { 258 scoped_ptr<SetSizeConstraints::Params> params( 259 SetSizeConstraints::Params::Create(*args_)); 260 CHECK(params.get()); 261 262 bounds::BoundsType bounds_type = bounds::GetBoundsType(params->bounds_type); 263 if (bounds_type != bounds::INNER_BOUNDS && 264 bounds_type != bounds::OUTER_BOUNDS) { 265 NOTREACHED(); 266 error_ = kInvalidParameters; 267 return false; 268 } 269 270 gfx::Size original_min_size = 271 window->GetBaseWindow()->GetContentMinimumSize(); 272 gfx::Size original_max_size = 273 window->GetBaseWindow()->GetContentMaximumSize(); 274 gfx::Size min_size = original_min_size; 275 gfx::Size max_size = original_max_size; 276 const app_current_window_internal::SizeConstraints& constraints = 277 params->constraints; 278 279 // Use the frame insets to convert window size constraints to content size 280 // constraints. 281 gfx::Insets insets; 282 if (bounds_type == bounds::OUTER_BOUNDS) 283 insets = window->GetBaseWindow()->GetFrameInsets(); 284 285 GetConstraintWidth(constraints.min_width, insets, &min_size); 286 GetConstraintWidth(constraints.max_width, insets, &max_size); 287 GetConstraintHeight(constraints.min_height, insets, &min_size); 288 GetConstraintHeight(constraints.max_height, insets, &max_size); 289 290 if (min_size != original_min_size || max_size != original_max_size) 291 window->SetContentSizeConstraints(min_size, max_size); 292 293 return true; 294} 295 296bool AppCurrentWindowInternalSetIconFunction::RunWithWindow(AppWindow* window) { 297 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() && 298 extension()->location() != extensions::Manifest::COMPONENT) { 299 error_ = kDevChannelOnly; 300 return false; 301 } 302 303 scoped_ptr<SetIcon::Params> params(SetIcon::Params::Create(*args_)); 304 CHECK(params.get()); 305 // The |icon_url| parameter may be a blob url (e.g. an image fetched with an 306 // XMLHttpRequest) or a resource url. 307 GURL url(params->icon_url); 308 if (!url.is_valid()) 309 url = extension()->GetResourceURL(params->icon_url); 310 311 window->SetAppIconUrl(url); 312 return true; 313} 314 315bool AppCurrentWindowInternalSetBadgeIconFunction::RunWithWindow( 316 AppWindow* window) { 317 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) { 318 error_ = kDevChannelOnly; 319 return false; 320 } 321 322 scoped_ptr<SetBadgeIcon::Params> params(SetBadgeIcon::Params::Create(*args_)); 323 CHECK(params.get()); 324 // The |icon_url| parameter may be a blob url (e.g. an image fetched with an 325 // XMLHttpRequest) or a resource url. 326 GURL url(params->icon_url); 327 if (!url.is_valid() && !params->icon_url.empty()) 328 url = extension()->GetResourceURL(params->icon_url); 329 330 window->SetBadgeIconUrl(url); 331 return true; 332} 333 334bool AppCurrentWindowInternalClearBadgeFunction::RunWithWindow( 335 AppWindow* window) { 336 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) { 337 error_ = kDevChannelOnly; 338 return false; 339 } 340 341 window->ClearBadge(); 342 return true; 343} 344 345bool AppCurrentWindowInternalSetShapeFunction::RunWithWindow( 346 AppWindow* window) { 347 348 if (!window->GetBaseWindow()->IsFrameless()) { 349 error_ = kRequiresFramelessWindow; 350 return false; 351 } 352 353 scoped_ptr<SetShape::Params> params( 354 SetShape::Params::Create(*args_)); 355 const Region& shape = params->region; 356 357 // Build a region from the supplied list of rects. 358 // If |rects| is missing, then the input region is removed. This clears the 359 // input region so that the entire window accepts input events. 360 // To specify an empty input region (so the window ignores all input), 361 // |rects| should be an empty list. 362 scoped_ptr<SkRegion> region(new SkRegion); 363 if (shape.rects) { 364 for (std::vector<linked_ptr<RegionRect> >::const_iterator i = 365 shape.rects->begin(); 366 i != shape.rects->end(); 367 ++i) { 368 const RegionRect& inputRect = **i; 369 int32_t x = inputRect.left; 370 int32_t y = inputRect.top; 371 int32_t width = inputRect.width; 372 int32_t height = inputRect.height; 373 374 SkIRect rect = SkIRect::MakeXYWH(x, y, width, height); 375 region->op(rect, SkRegion::kUnion_Op); 376 } 377 } else { 378 region.reset(NULL); 379 } 380 381 window->UpdateShape(region.Pass()); 382 383 return true; 384} 385 386bool AppCurrentWindowInternalSetAlwaysOnTopFunction::RunWithWindow( 387 AppWindow* window) { 388 if (!extension()->permissions_data()->HasAPIPermission( 389 extensions::APIPermission::kAlwaysOnTopWindows)) { 390 error_ = kAlwaysOnTopPermission; 391 return false; 392 } 393 394 scoped_ptr<SetAlwaysOnTop::Params> params( 395 SetAlwaysOnTop::Params::Create(*args_)); 396 CHECK(params.get()); 397 window->SetAlwaysOnTop(params->always_on_top); 398 return true; 399} 400 401bool AppCurrentWindowInternalSetVisibleOnAllWorkspacesFunction::RunWithWindow( 402 AppWindow* window) { 403 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) { 404 error_ = kDevChannelOnly; 405 return false; 406 } 407 408 scoped_ptr<SetVisibleOnAllWorkspaces::Params> params( 409 SetVisibleOnAllWorkspaces::Params::Create(*args_)); 410 CHECK(params.get()); 411 window->GetBaseWindow()->SetVisibleOnAllWorkspaces(params->always_visible); 412 return true; 413} 414 415} // namespace extensions 416