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